/* ══════════════════════════════════════════════════════════════════════
   Chippy landing, v2 prototype.

   Fonts are vendored into fonts/ and served from this origin, the page
   makes no third-party requests. IBM Plex Sans ships as a variable font,
   so one file covers every weight used here; Plex Mono as two statics.
   Both families are SIL Open Font License 1.1.

   TYPE RULE (chippy:714, Z 2026-09-11 14:02: "We should have a good
   typography system and stick to it. I also don't really want any serif
   fonts."):
     IBM Plex Sans       every word, the product's and the spoken ones;
                         600 is the wordmark's weight and every heading's.
     IBM Plex Mono       data, key chords, sources, labels, eyebrows.
     Spoken words are told apart by the highlight, never by a face; the
     serif that used to mark them is gone, the face and its file.
   The steps are the tokens under "Type" in :root; type_test.go fails any
   font-size or font-family written outside them.

   COLOUR RULE: the two real states of text in this product,
     selected (before)  ->  spoken (during, and after),
   and both are ONE theme colour (--theme, chippy:585): a wash of it while
   the text is selected, the full colour on the word being spoken. The page
   is one dark desk at night; the theme colour is the only bright thing on
   it, because on this page that colour means a voice is happening.

   SHADOW RULE: cards sit ON the desk. Every card carries a long, soft,
   two-part shadow, contact shadow plus cast shadow, and a rounded
   corner. That is the whole of "artsy" here: depth and radius, not craft.
   ══════════════════════════════════════════════════════════════════════ */

/* chippy:644. Z, 2026-09-10, watching the account page load: "why the fuck does the font loading
   change the width??" The fallback sans painted first, then Plex Sans swapped in with wider metrics
   and the field and the button moved. Two things end it: each page PRELOADS its fonts in <head> so
   they are in hand before first paint, and font-display is OPTIONAL, which means a font that is not
   ready at first paint is not swapped in later at all for that visit (it is cached for the next one).
   Nothing on the page moves after it is first drawn. */
@font-face {
  font-family: "IBM Plex Sans";
  src: url("fonts/plex-sans-var.woff2") format("woff2");
  font-weight: 400 600;
  font-display: optional; /* chippy:644: never swap after first paint */
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("fonts/plex-mono-400.woff2") format("woff2");
  font-weight: 400;
  font-display: optional; /* chippy:644: never swap after first paint */
}
@font-face {
  font-family: "IBM Plex Mono";
  src: url("fonts/plex-mono-500.woff2") format("woff2");
  font-weight: 500;
  font-display: optional; /* chippy:644: never swap after first paint */
}

/* ── THE LADDER ──────────────────────────────────────────────────────
   chippy:909. Z, 2026-09-15 15:2x, a phone screenshot of the open Menu standing under the player's
   card: "you gotta get these z indexes under control". chippy:906 had already built the wall that
   ended the symptom — isolation: isolate on .desk, so the desk sorts its own windows inside itself
   and the whole desk stays under the bar. What it did not do is make the ladder legible: this sheet
   carried 21 raw z-index numbers and not one name, and public/desk.js counted a window's lift up
   from 10 with no ceiling (measured on the served page at 390 and at 1280: 22 presses took the
   player's inline z-index to 32). A number nobody can name is a number nobody can keep.

   So every rung is a token, and the tokens are grouped by the FOUR stacking contexts this page
   actually has. A token from one group never orders against a token from another; that is the whole
   discipline, and it is why the groups are written apart:

     the page — the root context. .nav is the sticky bar at 3 and the only furniture the page itself
       stacks; .skip is the overlay at 40, the first thing a keyboard reaches, over everything. The
       desk is NOT here: it opens its own context at z-index auto (chippy:906), so nothing inside it
       has a number the page can see.

     inside the nav — the disclosures and the phone Menu open inside .nav and ride its context, so
       --z-menu only has to beat the bar's own contents, not the page: .nav__drop and .nav__panel.

     inside the desk — the desk's own furniture, floor to ceiling. --z-desk-ground is the painting
       and the lamp, --z-desk-stage the four stages and the dock at the same plane (a window that
       reaches the floor floats over the dock, in DOM order), then the windows at rest: --z-win-side
       set back, --z-win-main in front. --z-win-lift..--z-win-lift-top is the BAND desk.js hands a
       dragged window: above both resting ranks, below --z-chrome-bar, because a Mac's menu bar is
       never covered and .chrome's own note below promises exactly that. --z-player is Chippy's
       window, above every window and needing no lift of its own; --z-cursor and --z-keycap are the
       pointer and the tap, which are drawn over the whole scene.

     a part's own layers — the four rungs a COMPONENT uses inside a context it already owns: the
       wordmark behind the footer's columns, the selection band under its words, the lit word over
       the karaoke mark, a tab over its thumb. These repeat by design; they never leave their part.

   The guard is ladder_909_test.go: it reads the SERVED stylesheet, fails on any raw z-index number
   outside a comment, on a missing or renumbered token, on a rule that stops reading its token, and
   on an order that stops climbing. */
:root {
  /* the page */
  --z-nav: 3;
  --z-overlay: 40;
  /* inside the nav */
  --z-menu: 4;
  /* inside the desk (isolation: isolate, chippy:906 — nothing here can reach the bar) */
  --z-desk-ground: 0;
  --z-desk-stage: 1;
  --z-win-side: 2;
  --z-win-main: 3;
  --z-win-lift: 4;
  --z-win-lift-top: 7;
  --z-chrome-bar: 8;
  --z-player: 9;
  --z-cursor: 10;
  --z-keycap: 11;
  /* a part's own layers, inside a component that already has a context of its own */
  --z-part-under: -1;
  --z-part-back: 0;
  --z-part-mid: 1;
  --z-part-front: 2;
}

:root {
  /* The desk. chippy:734, finding 15. Z, 06:33: "this has like a slate gray background, like a blue, and I've
     never liked that. If anything, it should blend with the current color, that would be really cool, and if
     that looks stupid for some reason, we should use a neutral black ... and then in light, maybe it's just
     white mode." The ground is a near-black step of the theme hue itself, 26% chroma at 10% lightness, so the
     page tints with the pick; the hairline, and the menu's ground, are the same hue higher and lower.
     (chippy:790 dropped the deeper bands: --desk-deep is the Products menu's ground alone.) The light
     theme's ground is white (below). Every page's head carries the same rule inline for the first frame.

     chippy:804. Z, on the Products menu: "the dropdown color is horribly mismatching how the site colors
     theming/blending works." The menu was already wired to these three tokens; THESE TOKENS were the
     defect. Measured on the running page, turning the dial 216 -> 280 moved --desk-deep six units of red
     and the light theme not at all, because 28% chroma at 6% lightness is nearly black whatever the hue
     and the light values were two hard-coded hexes that never said --theme-hue. Both are hue steps now,
     in BOTH themes, and the chroma is set so the swing beats the ground's own: --desk 26%/10% moves 9
     units of red across that arc and the page is agreed to visibly tint, so 9 is the floor. These give
     12 (deep) and 23 (edge) on the dark theme and 14 and 19 on the light one. The LIGHTNESS of all four
     is held within a point of what shipped, so the swing is bought with chroma and no surface that uses
     them changes weight. */
  --desk:       hsl(var(--theme-hue) 26% 10%);
  /* chippy:868, part 2. Z, 2026-09-14 16:3x, on the open menu: "not sure why the dropdown background color
     is off". Measured on the running page before this line, light theme, default hue: the panel was
     rgb(232,240,252) standing on a rgb(255,255,255) page, a lavender chip 23 units of blue off a white ground
     that carries no other tinted fill anywhere. chippy:804 was right that the token had to carry the hue and
     wrong about how much of it: 80% chroma at 95% lightness is a colour, not a surface. The menu is a floating
     panel, so it is given THE SURFACE EVERY OTHER PANEL ON THIS SITE DRAWS -- .cowboy, .confirm and
     .signup__input are all the desk plus a 3% white lift inside a --desk-edge hairline -- made opaque here,
     because those three sit on the page and a menu stands over it and cannot let the page read through. On
     the light theme --desk is white and the lift on white is white, which is exactly right and is how the
     panel separates on that theme: the hairline and --lift-2, the same two parts, never a fill. The hue
     survives at the strength the page itself tints, because --desk carries it: chippy:804's floor was 9
     units of red across the 216 -> 280 arc and --desk moves those same 9, which is the arc the page is
     agreed to show. --desk-deep has one consumer, .nav__drop, and this is its whole definition. */
  --desk-deep:  color-mix(in srgb, #fff 3%, var(--desk));
  /* chippy:834. Measured on the running page at 1400px, dark theme: the one hairline the site draws was
     rgb(34,48,68) on rgb(19,24,32) at the default hue, 1.34:1, and rgb(57,34,68) on rgb(28,19,32) at hue 280,
     1.28:1. The light theme's same line is 1.38:1 at that hue, which a tester read clearly across 1400px while
     calling the dark one close to invisible at a glance -- so the ratio is not the instrument. This close to
     black WCAG's 0.05 flare term flattens it, and the honest measure is the step in CIE L*: the light hairline
     sits 12.5 L* off its white ground at hue 280, and a pale line on a near-black ground needs a WIDER step
     than a dark line on white to read at all. 20% -> 27% puts every hue on the dial at 14.5 L* or better (the
     default hue 18.6, hue 280 17.0). chippy:804's swing survives and grows: 32 units of red across the
     216 -> 280 arc against its floor of 9. This is the dated line that supersedes 804's "lightness held within
     a point of what shipped" for THIS token on the DARK theme alone; the light theme's 89%, the chroma of all
     four tokens, and the 1px weight every consumer draws are untouched. */
  --desk-edge:  hsl(var(--theme-hue) 34% 27%);

  /* Paper, every card, every window */
  --paper:      #fbf8f2;
  --paper-2:    #f0ece2;
  --ink:        #14161f;
  --ink-soft:   #4e5468;
  --ink-faint:  #636b81;  /* chippy:843 second cut: was #878ea3, 3.27:1 on white, 3.08 on --paper, 2.77 on --paper-2; now 5.32, 5.01 and 4.51. It could only move once the player stopped drawing paper ink on its dark strip, below. */

  /* Text on the desk itself */
  --lit:        #f2f0ea;
  --lit-soft:   #a7adc4;
  --lit-faint:  #7f859d;  /* chippy:843: was #6f7691, 3.97:1 on the desk and 3.68:1 on the cards; the smallest lift that clears AA on both (4.87 and 4.53) */
  /* chippy:978. A THIRD GROUND 843 DID NOT MEASURE: a tile that lifts its own ground with
     rgba(255,255,255,.03) — the two keys on /voice — composites to rgb(33,33,33), where --lit-faint is
     4.40:1 and misses AA by a tenth. Nobody sees a tenth; the check does, which is the argument for it.
     This is the same smallest-lift answer on that ground: 4.58:1 on the tile, 4.75 on the cards, 4.95 on
     the desk, three points per channel and indistinguishable by eye. Faint ink on a lifted tile uses it. */
  --lit-faint-tile: #8288a0;

  /* ── THE THEME COLOUR (chippy:585) ──────────────────────────────────
     Z, verifying 0.2.9:
       "karaoke sort of looks right the words it already spoke have a yellow
        tint and i think it should be blue not yellow for karaoke?"
       "you keep using yellow for branding i think it should be blue but i
        really think the color can change to highlight the app has deep
        personalization and theming."
       "i like this typography (again yellow should follow theme color) ..."
     Z, 2026-09-09, finding 14:
       "conceptually yes but really chippy is meant to have a few sliders;
        like a hue slider; color intensity slider; and then blur slider and
        maybe warmth i haven't decided yet"

     So: ONE colour, and it is a knob. --theme-hue and --theme-sat are the
     sliders (hue, intensity); --theme is the resolved colour every rule
     below reads; --theme-past is its 30% wash (the words already spoken,
     the selection); --theme-deep is the same hue ten points darker, for
     bars on paper. Turn the hue and the karaoke tint, the wordmark bars,
     the eyebrow type, the buttons, the focus rings and the lamp all turn
     together. The favicon in index.html and account.html cannot read a
     CSS variable, so it carries the resolved hex of the default and a Go
     test (theme_test.go) holds the two equal.

     Default hue 216, 100%, 72% = #70a9ff. Why this blue: on the desk
     (#151827) it is 7.4:1 as text, so the eyebrow and the wordmark read;
     under --theme-ink on a solid button it is 7.8:1, so the button's dark
     ink still contrasts; and at 72% lightness it sits where the amber
     highlighter did, a tint you can read black type through, not a
     saturated accent that would swallow the karaoke word. */
  --theme-hue:  216;
  --theme-sat:  100%;
  /* chippy:736, finding 20. Z, 09:00 at lime: "this green is too light ... On a dark background, we can
     get away with anything. On a light background, we might need to use some trick to darken it." The
     accent's lightness is --theme-l: 72% on the dark theme; on the light theme it is the button's
     stepped-down lightness (theme.js writes --theme-button-l per hue), so yellow and lime darken most
     and blue and purple least, and every accent on a white ground clears 4.5:1 by that construction. */
  --theme-l:    72%;
  --theme:      hsl(var(--theme-hue) var(--theme-sat) var(--theme-l));
  --theme-past: hsl(var(--theme-hue) var(--theme-sat) var(--theme-l) / .30);
  --theme-deep: hsl(var(--theme-hue) var(--theme-sat) calc(var(--theme-l) - 10%));
  /* chippy:681. Z: "the buttons need to be blue and i think the text needs to be white." The solid
     button's fills are deeper steps of the same hue, so white reads on every state. Measured (WCAG,
     white on the fill): 5.2:1 at rest, 4.6:1 lit under the pointer, 5.9:1 pressed. The theme accent
     above stays for links, the pill and the spoken word. */
  /* chippy:716. Z, 14:07: "I don't understand why the call-to-action buttons don't have white text; this is
     ugly." The ink is white and nothing writes it; contrast is met by the fill alone: main.js steps the
     fill's lightness down from the accent's 72% until white clears 4.5:1 lit under the pointer (so at
     rest and pressed too), hue and saturation untouched, and writes it here as --theme-button-l. 48% is
     the blue default before any script runs. */
  --theme-button-l:       48%;
  --theme-button:         hsl(var(--theme-hue) var(--theme-sat) var(--theme-button-l));
  --theme-button-lit:     hsl(var(--theme-hue) var(--theme-sat) calc(var(--theme-button-l) + 4%));
  --theme-button-pressed: hsl(var(--theme-hue) var(--theme-sat) calc(var(--theme-button-l) - 4%));
  --theme-ink:  #0c1220;
  /* chippy:737, finding 39. Z, 22:04 at hue 245: "the dark purple, it's hard to read it with the black
     text, and so I think that you would actually want the text to be white ... white on yellow isn't
     gonna look good. So there should be an algorithm that does this for you." The lit word's chip:
     main.js measures the accent at 72% and writes these two. A light hue (yellow, lime, cyan, the
     default blue, the app's own look) keeps the 72% fill and takes the page's ground as ink; a dark
     hue (purple, red) takes the button's stepped-down fill, where white clears 4.5:1, and white ink.
     The blue defaults are here for the first paint before any script runs. */
  --chip-l:     72%;
  --chip-dark-ink: var(--desk); /* the dark ink a light chip takes: the desk on the dark theme, the ink on the light one */
  --chip-ink:   var(--chip-dark-ink);
  /* chippy:736, finding 1 at 24:30: "the OS doesn't change, but the Chippy representation has a light
     mode, dark mode variant." The mock's Chippy window (the player) reads these; the OS windows keep
     their paper. Dark theme, a dark window; light theme, the paper one. */
  --chippy-bg:   #1d2133;
  --chippy-bg-2: #171a2a;
  --chippy-ink:  var(--lit);
  /* chippy:843: the strip under the player is --chippy-bg-2, dark here and paper in the light theme, so its
     quiet text needs ink of its own; it was drawing --ink-soft and --ink-faint, which are for paper, at 2.29:1
     and 5.28:1 on this ground. Soft stays a step above faint, as the strip already drew them. */
  --chippy-ink-soft:  #8b93ab;  /* 5.63:1 on #171a2a */
  --chippy-ink-faint: #7a829b;  /* 4.51:1 on #171a2a */
  --chippy-edge: rgba(255,255,255,.08);
  /* chippy:737, finding 38. Z, 21:48: "this error state, this orange is kind of bugging me. I feel like
     it should be more of a red." One red, fixed, never derived from the hue: 5.8:1 as text on the desk.
     --bad-light is the same red for a white ground (6.1:1), for the light theme chippy:736 lands. */
  --bad:        hsl(4 100% 66%);
  --bad-light:  hsl(4 75% 42%);
  /* chippy:881. THE THREE STATES THE SITE DID NOT HAVE. --bad above is the site's own. Success was the
     theme blue (the old .signup__msg.is-good below), which is the same blue as every link and the focus
     ring, and that is 812's premise: Z read a success back as an error. These are
     prototypes/status-icons/v1's kernel unchanged, a green, an amber and the accent itself, each with a
     step for the white ground the way --bad has --bad-light. status_callout_881_test.go sweeps every one
     of the 360 hues the dial can set, over both desks, and holds their floors.
     --bulb-radius and --callout-tint are prototypes/faq-folds/v2's bulbous panel, 28px and 14% of the
     colour on the dark desk, 12% on the light one, named here so the callout and that panel are one shape. */
  --good:       hsl(145 62% 56%);
  --good-light: hsl(145 70% 30%);
  --warn:       hsl(40 100% 64%);
  --warn-light: hsl(36 95% 34%);
  --info:       var(--theme);
  --bulb-radius:   28px;
  --callout-tint:  14%;
  /* Aliases, so every rule written against the old names keeps working. */
  --speak:      var(--theme);
  --speak-past: var(--theme-past);
  --speak-deep: var(--theme-deep);
  /* chippy:724. Z: "color is not meant to change os application windows,
     it's more that it's for how it looks in chippy." The selection inside
     a mocked app window is that app's own system selection, fixed, and
     never reads the theme: opaque tints so adjacent selected words can
     overlap without a darker seam. One on paper, one on the dark terminal.
     The reading cursor in the small Chippy windows is fixed too, Chippy's
     default blue, so a swatch never boxes a word of the mock in green. */
  --select:      hsl(212 100% 85%);
  --select-dark: hsl(212 30% 34%);
  --reading:      hsl(216 100% 72%);
  --reading-past: hsl(216 100% 72% / .30);

  /* ── Type (chippy:714) ──────────────────────────────────────────────
     Two faces and eight steps. Every text style on the site is one step
     (size, weight, line height, tracking) on one face; nothing sets a size
     of its own. The wordmark's face and weight, Plex Sans 600, is the
     display face (Z: "I also really like the Chippy logo font, it feels
     kind of futuristic"). The mocks inside the desk have their own five
     steps in the desk's unit, under .desk. */
  --sans:  "IBM Plex Sans", -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif;
  --mono:  "IBM Plex Mono", ui-monospace, SFMono-Regular, Menlo, Consolas, monospace;
  --chord: -apple-system, BlinkMacSystemFont, "Segoe UI", system-ui, sans-serif; /* the option-key glyph only; Plex has none */
  /* THE CAP LOCK (chippy:876). Every size below is a CAP HEIGHT, not an em box. `font-size-adjust:
     cap-height 1` on html (THE LOCK, under the base type) makes a declared font-size mean the height of
     a capital letter, which is what 1naf's app/styles/text.sass and dc's app/app/app.css declare and
     what Z asked for on 872. Each number here is the em-box number it replaced times the face's own cap
     ratio, 0.698 for IBM Plex Sans and IBM Plex Mono, measured on this build at weight 400 and 600
     alike, so the ink on the page is unchanged to the pixel: 40 -> 27.92, 19 -> 13.262, 16 -> 11.168,
     14 -> 9.772, 12 -> 8.376. The three fluid steps stay clamps, each of their three arguments scaled,
     so they still move with the viewport.
     A LINE HEIGHT STAYS A RATIO, divided by the same 0.698. A ratio inherits as a ratio and re-resolves
     against each element's own size; an absolute px leading inherits as a fixed length and silently
     re-parents everything that takes its leading from an ancestor of another size (measured in the
     spike: span.nav__name 30.39 -> 25.59 high the moment --body-lh became 25.6px). 1naf's body ratio is
     2.25 against a cap size and dc's --text-lines 1.75 against --text-cap for the same reason.
     A TRACKING IS DIVIDED TOO. em resolves against the DECLARED size, which the lock does not change,
     so every em length on a locked element is divided by 0.698 (here, and the handful further down). The
     division is left for the browser to do, calc(1.6 / 0.698) and not 2.292264, because Blink lays out in
     64ths of a pixel and a ratio rounded at the sixth decimal tips a line box into the next 64th: at 2.292264
     span.nav__name stood 30.4063 where it had stood 30.3906, and a page of text moved a device pixel.
     The cap unit, the ch unit and line-height: normal all track the lock and are left alone; --code-size
     is an em on both parent and child, so its proportion survives untouched. */
  /* display: the hero's one sentence */
  --display-size: clamp(1.3262rem, 2.5826vw, 1.745rem); --display-weight: 600; --display-lh: calc(1.05 / 0.698); --display-track: calc(-0.036em / 0.698);
  /* heading: a section's title on the home page, the page title on every other */
  --heading-size: clamp(1.047rem, 2.1638vw, 1.5007rem); --heading-weight: 600; --heading-lh: calc(1.12 / 0.698); --heading-track: calc(-0.03em / 0.698);
  /* title: a heading inside a section, a story's name, a box's head, the wordmark */
  --title-size: 13.262px; --title-weight: 600; --title-lh: calc(1.3 / 0.698); --title-track: calc(-0.015em / 0.698);
  /* lead: the letter, read slowly */
  --lead-size: clamp(0.8027rem, 1.4658vw, 1.047rem); --lead-weight: 500; /* chippy:735, finding 29: "plus one hundred" */ --lead-lh: calc(1.5 / 0.698); --lead-track: 0;
  /* body: every paragraph, field, button and link in the flow */
  --body-size: 11.168px; --body-weight: 400; --body-lh: calc(1.6 / 0.698); --body-track: 0;
  /* small: a note, an aside, a status line, the nav's links, a signature */
  --small-size: 9.772px; --small-weight: 400; --small-lh: calc(1.6 / 0.698); --small-track: 0;
  /* caption: the mono meta line: the footer's tagline, a date, a card's transport, the tiny links */
  --caption-size: 8.376px; --caption-weight: 400; --caption-lh: calc(1.6 / 0.698); --caption-track: 0;
  /* wrap: how a block breaks its lines. Every heading and the letter balance their lines; every paragraph
     avoids an orphan (Z, 2026-09-11 15:2x on chippy:714: "text-wrap: balance; we should use that for all headings on the site"). */
  --wrap-heading: balance; --wrap-body: pretty;
  /* measure: none of its own. chippy:735, finding 32 (Z, 16:00, o outline on /sign-up: "I don't know why the
     description isn't full width"): a block of text runs to the edge of its column, and the column is a span
     of the grid below; the three ch measures and the heading's own are gone. */
  /* eyebrow: the mono label over a field (EMAIL ADDRESS) and the mocks' small caps; upper case, spaced. The
     small-caps eyebrows over sections are gone (chippy:735, finding 18: "these small caps headings don't really
     serve a purpose"). */
  --eyebrow-size: 8.376px; --eyebrow-weight: 500; --eyebrow-lh: calc(1.6 / 0.698); --eyebrow-track: calc(.13em / 0.698);
  /* code: monospace inside running text, a quarter smaller than the words around it. chippy:735, finding 37
     (Z, 20:14: "I want to treat monospace as like a twenty-five percent reduction ... code would be a zero
     point eight seven five ... em unit actually, since it's inheriting"). One em rule, never a pixel size. */
  --code-size: 0.875em;
  /* link: there is no --link-line token any more (chippy:791). It held the underline's colour so that
     /privacy could show the accent and /terms the text colour and Z could pick; he picked the text
     colour, so the underline is currentColor everywhere and a token that can only hold one value is a
     name that can lie. The three kinds of link and the one rule that draws them are at THE THREE KINDS
     below, next to .link--bright. */

  /* One shadow vocabulary, three depths. */
  --lift-1: 0 1px 2px rgba(6, 8, 18, .40), 0 6px 14px rgba(6, 8, 18, .34);
  --lift-2: 0 2px 4px rgba(6, 8, 18, .44), 0 14px 30px rgba(6, 8, 18, .46);
  --lift-3: 0 3px 6px rgba(6, 8, 18, .48), 0 26px 56px rgba(6, 8, 18, .58);

  /* ── Spacing (chippy:735, finding 25; folds 711, 726, 710, 733's twelve) ────────────────────────
     Z, 2026-09-12 11:39: "the typography system was very locked in ... you wanna think about this as like
     a half space or a full space, a two x space, a four x space"; 17:53: "three pixels, six pixels, twelve
     pixels, twenty-four pixels, forty-eight pixels, ninety-six pixels." 1naf's ladder, read from its source
     (public/styles/tokens.css: one 24 px seed and its doublings and halvings, named for the arithmetic so
     a name cannot lie about its value; the note on the row): six steps, named by their value. THE RULE, 1naf's:
     every space is margin-top on the element that FOLLOWS (a paragraph after its heading, a heading after a
     paragraph, a block after a block); no bottom margins; gaps only on a grid or a flex row. spacing_test.go
     holds both and the s overlay (726) paints only what is off the ladder. The mocks inside the desk keep their
     own unit (--u), a picture; the logo's bar gaps are the logo's own geometry. */
  --space-3: 3px;     /* a hairline's breath: nothing on the site yet takes it; the rung is kept so the ladder is whole */
  --space-6: 6px;     /* inside a control: the mark and its word, an icon and its word, a small line under an address */
  --space-12: 12px;   /* half a space: a heading to its own paragraph, a label to its field, a field to its status line, inside a pill */
  --space-24: 24px;   /* one space: paragraph to paragraph, a title to its lede, a feature's word to its sentence, a card's picture to its title, the grid's gap, the page's gutter */
  --space-48: 48px;   /* two spaces: a block after a block, a section head to its grid, a question to the next question, the nav to the hero (710), the letter's paragraphs and its signature (877) */
  --space-96: 96px;   /* four spaces: a band's padding top and bottom (48 under 720) */
  /* chippy:914 AMENDS THE LADDER, and it is the first rung that is not 1naf's. Z, 2026-09-15, ruling on
     the row: the seam between two sections takes a rung above 96 so a section head sits with its own row.
     The ladder is doublings and stops at 96, and 192 is a doubling nobody asked for: at 1280 it would put
     twice the air between two sections as under their own headings. 144 is 96 plus a full space, the one
     step the ladder can take without becoming a second seam, and it is the ONLY place it is used.
     Under 720 it is not used at all: there the head keeps 48 and the seam keeps 96, which is already the
     next step above it, so the phone reads 1:2 where the desk reads 96:144. */
  --space-144: 144px; /* six spaces: the seam between two sections of the same band, the desk only (chippy:914) */

  /* ── The grid (chippy:735, finding 22) ───────────────────────────────────────────────────────────
     Z, 2026-09-12 14:35: "The grid column should be like sixty-four pixels, and I think the gap should
     either be sixteen or twenty-four, whatever makes the math easier. I'm used to using twenty-four."
     Twelve 64 px tracks with the 24 px space between them: 12 x 64 + 11 x 24 = 1032, the one content
     width on every page (.wrap, .nav__inner, .hero). A narrow page is not a second width: it is the same
     twelve tracks with the content on four to nine (.wrap--narrow), the way he drew it: "inset off by one,
     two, three, and then start four, five, six, seven, eight, nine, leaving one, two, three at the end."
     The spans below are the widths a block may take; nothing else in this file names a width in pixels. */
  --grid-col: 64px;
  --grid-gap: var(--space-24);
  --grid: calc(var(--grid-col) * 12 + var(--grid-gap) * 11);   /* 1032 */
  --span-2: calc(var(--grid-col) * 2 + var(--grid-gap) * 1);   /* 152 */
  --span-3: calc(var(--grid-col) * 3 + var(--grid-gap) * 2);   /* 240: a feature column */
  --span-4: calc(var(--grid-col) * 4 + var(--grid-gap) * 3);   /* 328: a card, a story */
  --span-6: calc(var(--grid-col) * 6 + var(--grid-gap) * 5);   /* 504: tracks four to nine, the narrow content */
  --span-8: calc(var(--grid-col) * 8 + var(--grid-gap) * 7);   /* 680: the hero's sentence */
  --gut: var(--space-24);                                      /* the page's side padding, the seed, as 1naf's */
  --page: calc(var(--grid) + var(--gut) * 2);                  /* the container: the grid plus a gutter each side */
}

/* chippy:736, finding 1. Z, 00:18: "put like a cute light mode, dark mode icon so that you get theming in
   light mode, dark mode." The light theme is the same tokens turned: a white ground, the ink where the
   lit text was, the accent at its darkened step (finding 20), the red for a white ground (737), and the
   Chippy window on paper. Set on <html> by the head's script before first paint and by theme.js. */
:root[data-theme="light"] {
  --desk:       #ffffff;
  /* chippy:804: hue steps, not the #f3f4f8 / #dfe2ec that ignored the dial. The ground stays pure white.
     chippy:790 then took --desk-deep off the bands: on the light theme it now paints the Products menu
     and nothing else, and --desk-edge is the one tinted line the page draws against that white.
     chippy:868 took the light theme's own step off it too: --desk-deep is the panel surface in both themes
     now, defined once above as the desk plus the site's 3% panel lift, so this theme does not need a value
     of its own. See its note in the dark :root for the measurement that moved it. */
  --desk-edge:  hsl(var(--theme-hue) 50% 89%);
  --lit:        #14161f;
  --lit-soft:   #4e5468;
  --lit-faint:  #636b81;  /* chippy:843: was #878ea3, 3.27:1 on white; the smallest darkening that clears AA on the page ground (5.32) and on the panels (5.09, 5.01) */
  --theme-l:    var(--theme-button-l);
  --bad:        var(--bad-light);
  --good:       var(--good-light);   /* chippy:881 */
  --warn:       var(--warn-light);   /* chippy:881 */
  --callout-tint: 12%;               /* chippy:881: faq-folds v2's light step */
  --chip-dark-ink: var(--ink);
  --chippy-bg:   var(--paper);
  --chippy-bg-2: var(--paper-2);
  --chippy-ink:  var(--ink);
  --chippy-ink-soft:  var(--ink-soft);   /* 6.38:1 on --paper-2, the strip in this theme */
  --chippy-ink-faint: var(--ink-faint);  /* 4.51:1 on --paper-2 with --ink-faint at #636b81 */
  --chippy-edge: rgba(20,22,31,.08);
}

/* chippy:894. WARMTH IS A SWITCH, AND OFF IS THE DEFAULT. Z, 2026-09-15 01:1x: "it's not the color wheel
   that bothers me, it's the warm background. It's only for dark mode. It causes too many problems and it makes
   the site feel a little unprofessional ... I think we just need to pick a dark color and stick with it ...
   have warmth and it's a boolean, and it's only valid for dark mode, but at least that way I can play with it
   on and off." The warmth was --desk and --desk-edge above, both hue steps: measured on 7979, the ground was
   rgb(19,24,32) at hue 216 and rgb(28,19,32) at 280. The picked dark is the neutral at the same lightness,
   Z's own fallback in 734 ("a neutral black"), and it holds whatever the dial says: rgb(26,26,26), with the
   hairline at rgb(69,69,69), a 19.4 L* step against chippy:834's 14.5 floor. --desk-deep derives from --desk
   and follows. The head of every page paints the same neutral before this file arrives, and sets
   data-warmth="on" from the kept or handed pick (chippy-warmth, ?warmth=on) so the on state never flashes.
   THE SELECTOR SAYS LIGHT IS EXEMPT ITSELF: the light ground is white and the switch is inert there, which is
   the row's rule written into the cascade and not left to a script; it stands after the light block so the
   suite's second reading of --desk (colour_dot_882_test.go, status_callout_881_test.go) is still the light one. */
:root:not([data-warmth="on"]):not([data-theme="light"]) {
  --desk:       hsl(0 0% 10%);
  --desk-edge:  hsl(0 0% 27%);
}

*, *::before, *::after { box-sizing: border-box; }

/* chippy:649. One white frame between documents on every navigation: the canvas had no colour of its
   own until this stylesheet arrived, and the browser paints a blank canvas white. Every page also
   carries this rule inline in its head, before the stylesheet link, so the first frame is the desk. */
html { -webkit-text-size-adjust: 100%; scroll-behavior: smooth; background: var(--desk); color-scheme: dark; }
/* THE LOCK (chippy:876), the other half of 1naf's and dc's system and the half 872 was waiting on. It makes
   a declared font-size mean the height of a capital letter for every element on the page, so the eight steps
   in :root are optical sizes that survive a face swap, and with the trim below a one-line block is exactly
   its own declared size tall, cap line to baseline. It is inherited, so it is declared once. NEVER set type
   with the `font:` shorthand anywhere in this file: the shorthand silently resets font-size-adjust and drops
   that element out of the system (dc's warning, app/app/app.css). */
html { font-size-adjust: cap-height 1; }
/* A control does not reliably inherit the lock, because a UA stylesheet sets its font with that shorthand,
   so re-arm it (dc does the same for its login field). */
input, button, textarea, select { font-size-adjust: cap-height 1; }
/* NOT TEXT INSIDE AN SVG. A font-size on an svg <text> is a length in that drawing's own user units, a
   coordinate like any other in its viewBox, not a step of the type system; the lock read the footer's giant
   wordmark (download.go footWordmark, font-size 100 in a 301 x 131 box) as 100 of CAP and drew it 1/0.698
   too wide, the word doubled against itself across the whole footer. The svg ELEMENT stays locked, because
   the icon rule below sizes every icon box in cap units off its own font-size (chippy:736) and a cap unit
   only tracks the type while the lock is on it. */
svg text { font-size-adjust: none; }
/* NOT THE MOCKS, for 872's reason one line down: a window on the desk is a picture of the app, drawn to the
   app's own numbers in the desk's own five steps, which are container units and not the site's tokens. The
   lock would reinterpret those five as caps and grow every window by 1/0.698 while the site around it held
   still (measured: span.keycap 127.25 -> 166.49 wide with its font-size unchanged). The mocks are already
   out of the trim for the same reason, and dc opts its centrepiece out the same way. */
.desk, .desk *, .chippy, .chippy * { font-size-adjust: none; }
/* and the leading with it: --body-lh is a ratio against a CAP size now, so a mock that takes its leading from
   the page would draw 1/0.698 too much of it (measured: the menu bar's own row moved 0.26px and the windows'
   text with it). 1.6 is the ratio the desk inherited before the lock, --body-lh times 0.698. */
.chippy, .desk { line-height: 1.6; } /* .chippy is named first here on purpose: round2_layout_test.go reads the first rule in this file whose selector is the window alone, and this is not that rule */
@media (prefers-reduced-motion: reduce) { html { scroll-behavior: auto; } }

body {
  margin: 0;
  background: var(--desk);
  color: var(--lit);
  font-family: var(--sans);
  font-size: var(--body-size);
  line-height: var(--body-lh);
  -webkit-font-smoothing: antialiased;
  overflow-x: hidden;
}

p, h1, h2, h3, figure, blockquote { margin: 0; }
h1, h2, h3 { text-wrap: var(--wrap-heading); }
.hero__title { text-wrap: initial; } /* chippy:735, finding 32: the hero's three lines are hand breaks (Z, 16:15: "in this case, I kinda like the arbitrary new lines") */
/* chippy:735, finding 27: a heading that is an anchor lands with two spaces above it, never flush with the top. */
h1, h2, h3, .anchor, #join { scroll-margin-top: calc(var(--nav-h) + var(--space-48)); } /* chippy:868: the ladder's space above the heading, now clear of the sticky bar (--nav-h, the Nav section) — at 48 alone a heading landed 6 px under it */
p { text-wrap: var(--wrap-body); }
ul { margin: 0; padding: 0; list-style: none; }
/* chippy:890. The browser's own 1em margin on ol and pre is a spacing decision nobody made, and the
   lint could not see it because spacing_test.go reads the stylesheet and a UA default is not in it.
   Measured with the s overlay on 7922 and 7979, at 1280 and 390: one red band, "16px x1
   (ol.tut__steps)", on /tutorials/feedback, /tutorials/history and /tutorials/playback-speed, and
   three, "16px x3 (pre.post__code, pre.post__code.post__term)", on /blog/the-layout. Both tags are
   flex or grid items here, so the margin did not collapse into the next block's: ink to ink under
   ol.tut__steps ran 40 to p.tut__status and 64 to nav.cards--next, and under pre.post__code 40 to the
   next paragraph and 64 to the next h2. Every ol and pre the site serves declares its own top margin
   from the ladder (.tut__steps 24, .onb__steps 24, .post__code 24), so taking the default away leaves
   the space to the element that follows (chippy:735, finding 25) and those gaps land on 24 and 48. */
ol, pre { margin: 0; }

/* chippy:872. THE BOX IS THE INK, on every text block on the site and not only .longform's (the same
   declaration further down, chippy:814). Z, reviewing the nightly with cap lines on, 2026-09-14 16:37, of
   p.shape__body in the features band, 240 x 76.8 with a 12px top margin, IBM Plex Sans 16/25.6: "you guys
   are not really using the line cap typography trick correctly the block size of text is supposed to be the
   line cap; for the fifth time this is implemented correctly in 1naf and dc and not here". text-box takes
   the OUTER half-leadings off a block, so its box runs first cap line to last baseline and a margin between
   two blocks is ink to ink; the interior lines keep the role's full leading, so a paragraph reads exactly as
   it did and only the box tightens. 1naf's rule is app/styles/text.sass (.c-text, the one composition every
   text role wears) and dc's is app/app/app.css. Measured on the nightly at 1280, before -> after:
   p.shape__body 76.78 -> 62.36 (Z's number), h1.hero__title 126 -> 111.92, p.hero__sub 51.19 -> 36.77,
   p.letter__text 216 -> 196.75, h2.h2 38.53 -> 24.02, p.lede 76.78 -> 62.36, label.signup__label
   19.19 -> 8.38, a.foot__link 21.19 -> 10.38, a.nav__link 23.39 -> 10.77.
   THE OTHER HALF OF 1NAF'S SYSTEM IS HERE NOW (chippy:876): font-size-adjust: cap-height 1, THE LOCK, above
   the base type, with every size token re-declared as its cap so the lock changed nothing a person sees.
   The warning that used to stand here was right while the tokens were em boxes: the lock on its own would
   have grown the type by 1/0.698, about 43 percent, p.shape__body 62.36 -> 92.78 and a.nav__link 74.11 ->
   103.34 wide. It grows nothing now, and the numbers below are still the ones this trim draws. chippy:877
   re-derived the vertical margins as ink-to-ink numbers on top of it.
   NOT THE FIELDS. input and textarea are left on the line box on purpose: the trim leaves their border box
   46px exactly as it was but moves the text inside it off the centre the UA gives it (measured on /sign-up,
   the #email crop differs pixel for pixel with nothing else changed). A control is not a text block.
   NOT THE MOCKS. A window on the desk is a picture of the app, drawn to the app's own numbers, so trimming
   its paragraphs would move the app's layout and not the site's; chippy:861 said the same when .longform's
   trim reached .chippy__text inside a use case's desk. dc opts out the same way, text-box: normal
   (app/app/components/centerpiece/centerpiece.css). A browser without text-box keeps the line box
   everywhere and every margin still stands; nothing else changes for it. */
h1, h2, h3, h4, h5, h6, p, li, dt, dd, figcaption, blockquote, label, button, a { text-box: trim-both cap alphabetic; }
.desk :is(h1, h2, h3, h4, h5, h6, p, li, dt, dd, figcaption, blockquote, label, button, a) { text-box: normal; }

/* chippy:877. The ink reaches the last text blocks 872's rule could not. text-box trims the first and last
   LINE BOX of a block container, and these words are flex items (their parent is a flex container, which has
   no line box of its own: .shape__key, .card) or a tag 872's list did not name (footer). Measured on 7922 at
   1280, the leading each one still carried after 872: span.shape__word 17.13 (9.18 above the cap line, 7.95
   under the last baseline), span.card__title 11.42, span.card__meta 12.63, span.card__line 12.63,
   footer.letter__by 8.73 — so a 12 declared under a feature's word drew 19.95 of ink and a 6 inside a card
   drew 17.42. The margins below them are re-derived in this row. NOT a word in a lockup (.nav__name,
   .foot__name: the mark sits on that word's baseline, chippy:798) and NOT a control's own label (.btn span),
   for chippy:872's reason for leaving input and textarea on the line box: a control is not a text block. */
.shape__word, .card__title, .card__meta, .card__line { text-box: trim-both cap alphabetic; }
/* .letter__by carries the same declaration inside its own rule below, not here: type_test.go's
   TestEveryTextStyleIsAStepOfTheTypeSystem reads the first .letter__by selector in this file (this line
   must not write that selector with its brace, or the guard reads the comment) and must meet
   the type rule, not a trim list (fleet, 2026-09-14 20:5x). */

:focus-visible {
  outline: 2px solid var(--speak);
  outline-offset: 3px;
  border-radius: 4px;
}

.skip {
  position: absolute; left: -9999px; top: 0;
  background: var(--speak); color: var(--theme-ink);
  padding: var(--space-6) var(--space-12); z-index: var(--z-overlay); font-family: var(--mono); font-size: var(--caption-size);
}
.skip:focus { left: 8px; top: 8px; }

/* chippy:652. Z, of /account on the film: "content literally has three different widths". body.plain
   is a column flexbox, and a flex item with auto side margins shrinks to its content, so the narrow
   column was as wide as its longest line and re-centred in every state. width: 100% makes it the
   max-width every time. */
.wrap { width: 100%; max-width: var(--page); margin: 0 auto; padding: 0 var(--gut); }
/* chippy:735, finding 22. Z, 14:38, on /sign-up: "the content width changed. So the grid here is different
   than the grid width, which is kind of concerning." No second width: the narrow page is the same twelve
   tracks, and every block in it sits on tracks four to nine. Under 1000 it takes two to eleven, under 720
   the twelve. */
.wrap--narrow { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--grid-gap); }
.wrap--narrow > * { grid-column: 4 / 10; }

/* ── The main content has room to breathe ────────────────────────
   chippy:807. Z, 2026-09-14 00:4x, going down the subpages: "notice how the main content is super
   short? we need to do something like min height 100dvh or 90dvh ... the main content needs room to
   breathe; this goes for all pages". body.plain already filled the viewport, but it filled it with the
   footer: nothing gave main a height, so on /video main was 399px of a 900px window and the 562px
   footer opened at 459, halfway up the fold. Every main is at least 90dvh now, so a short page carries
   the fold and the footer's top edge is the only thing under it, which is the hint that there is more.
   The narrow pages are a grid, and a grid with room to spare stretches its auto rows, which would
   spread the blocks apart instead of giving them room; align-content: start leaves every block where
   it already was, measured. */
main { min-height: 90vh; min-height: 90dvh; } /* vh first for a browser that has no dvh */
main.wrap--narrow { align-content: start; }

/* ── Logomark: three bars, the smallest honest picture of a voice ────── */
/* chippy:692. Z, 2026-09-10 9:03 PM: "let's make the logo bigger, i actually like it a bit, and if
   you hover it it animates the waveform for a moment; it should do that in the footer as well." The
   nav mark is 20px tall with 4px bars (was 15 and 3), the word 20px; the footer mark (.mark--foot)
   is 13px with 3px bars. The mocks' tiny marks (.mark--sm) are the mocks' own and do not change.
   On hover the three bars play once like a live waveform, about a second, and settle; CSS only.
   Then Z, 9:3x PM: "they animate on hover which is cute but i want it to look like it's speaking,
   not just dancing." So not one wave: three tracks with different lengths (.9s, 1.1s, 1.0s) and
   different shapes, the middle bar leading, each peak reached fast and left slowly (the timing
   function on the rest frames is a quick ease-out, on the peaks a slow ease-in), settling to rest
   inside about a second, like the player's bars during a read. They are never in step. */
.mark { display: inline-flex; align-items: flex-end; gap: 3px; height: 20px; }
.mark i { display: block; width: 4px; background: var(--speak); border-radius: 2px; }
.mark i:nth-child(1) { height: 55%; }
.mark i:nth-child(2) { height: 100%; }
.mark i:nth-child(3) { height: 30%; }
.mark--sm { height: 9px; gap: 2px; }
.mark--sm i { width: 2px; border-radius: 1px; }
.mark--foot { height: 16px; gap: 3px; } /* chippy:735, finding 24: the footer's wordmark is the body size in the wordmark's weight, and the mark beside it grows with it (was 13) */
.mark--foot i { width: 3px; border-radius: 1.5px; }
/* chippy:737, finding 3. Z, 02:06: "I like the animation 'cause it's subtle, but it's actually distorting
   the border radius. it's kind of stretching it out and it's not keeping the corners as smooth, rounded
   edges." The bars used to scaleY, which scales the rounded ends into ovals. Now the HEIGHT moves and
   the radius is 2px in every frame; the same amplitudes (55% bar × 1.55 = 85.25%, and so on), the same
   curves, the same second. */
@keyframes mark-voice-a {
  0%   { height: 55%;    animation-timing-function: cubic-bezier(.2,.9,.3,1); }
  14%  { height: 85.25%; animation-timing-function: cubic-bezier(.6,0,.8,.5); }
  36%  { height: 38.5%;  animation-timing-function: cubic-bezier(.2,.9,.3,1); }
  46%  { height: 74.25%; animation-timing-function: cubic-bezier(.6,0,.8,.5); }
  70%  { height: 46.75%; animation-timing-function: cubic-bezier(.2,.9,.3,1); }
  78%  { height: 63.25%; animation-timing-function: cubic-bezier(.6,0,.8,.5); }
  100% { height: 55%; }
}
@keyframes mark-voice-b {
  0%   { height: 100%; animation-timing-function: cubic-bezier(.2,.9,.3,1); }
  8%   { height: 62%;  animation-timing-function: cubic-bezier(.2,.9,.3,1); }
  18%  { height: 120%; animation-timing-function: cubic-bezier(.6,0,.8,.5); }
  40%  { height: 50%;  animation-timing-function: cubic-bezier(.2,.9,.3,1); }
  48%  { height: 110%; animation-timing-function: cubic-bezier(.6,0,.8,.5); }
  74%  { height: 72%;  animation-timing-function: cubic-bezier(.2,.9,.3,1); }
  84%  { height: 105%; animation-timing-function: cubic-bezier(.6,0,.8,.5); }
  100% { height: 100%; }
}
@keyframes mark-voice-c {
  0%   { height: 30%; animation-timing-function: cubic-bezier(.2,.9,.3,1); }
  20%  { height: 69%; animation-timing-function: cubic-bezier(.6,0,.8,.5); }
  44%  { height: 36%; animation-timing-function: cubic-bezier(.2,.9,.3,1); }
  54%  { height: 81%; animation-timing-function: cubic-bezier(.6,0,.8,.5); }
  82%  { height: 42%; animation-timing-function: cubic-bezier(.2,.9,.3,1); }
  90%  { height: 51%; animation-timing-function: cubic-bezier(.6,0,.8,.5); }
  100% { height: 30%; }
}
/* chippy:717. Z: "the animation for the waveform is only really meant for when you hover the waveform and not the
   logo." The hover target is the mark's own box, in the nav and the footer; the word and the tagline leave the bars still. */
.nav__brand .mark:hover i:nth-child(1), .foot__meta .mark:hover i:nth-child(1) { animation: mark-voice-a .9s 1 both .05s; }
.nav__brand .mark:hover i:nth-child(2), .foot__meta .mark:hover i:nth-child(2) { animation: mark-voice-b 1.1s 1 both; }
.nav__brand .mark:hover i:nth-child(3), .foot__meta .mark:hover i:nth-child(3) { animation: mark-voice-c 1s 1 both .09s; }

/* ── Nav ──────────────────────────────────────────────────────────────
   A full-width bar with ONE content-width container inside it, the same
   pattern the footer uses (.foot bar + .wrap inner). The width lives on
   .nav__inner so both pages read it from one rule: home's body is a normal
   block, /account's is a flex column (body.plain), and a self-centering
   .nav shrank to its content in that flex column. chippy:555. ────────── */
/* chippy:868, part 4. Z, 2026-09-14 16:46: "let's make this sticky or fixed or whatever however 1naf works"
   ... "meaning we also need a hairline under it". 1naf's bar (app/components/ui/nav-desktop.tsx) is STICKY,
   not fixed, at inset-block-start 0, on the page's own background, and its hairline is a BOX SHADOW, not a
   border: "Hairline as shadow — it never enters layout math". Both halves of that are the reason there is no
   jump on load. Sticky leaves the bar in flow, so the page's first band already starts below it and nothing
   has to be padded down to make room; a shadow has no thickness, so the line under the bar cannot add the one
   pixel a border would and shift every page by it. The pattern, not the bytes: the ground is --desk, the line
   is --desk-edge, the site's one hairline token (chippy:834), drawn 1px as every other consumer draws it.
   z-index 3 is what the bar already carried and it still clears the page: the disclosures and the phone menu
   open inside this element, so they ride its stacking context (.nav__drop's own z-index 4 orders them within
   it) and stand over whatever the page has scrolled under the bar. chippy:909: those two numbers are --z-nav
   and --z-menu now, the ladder at the top of this file. --nav-h is the bar measured on this build,
   54.39 px at 1280 and at 390, rounded up to the 8 grid; the only thing that reads it is the landing space
   under an anchor, below, which has to clear a bar that no longer scrolls away. */
:root { --nav-h: 56px; }
.nav {
  position: sticky; top: 0; z-index: var(--z-nav);
  background: var(--desk);
  box-shadow: 0 1px 0 var(--desk-edge);
}
.nav__inner {
  display: flex; align-items: center; justify-content: space-between; gap: var(--space-12);
  max-width: var(--page); margin: 0 auto;
  padding: var(--space-12) var(--gut);
}
/* chippy:798. Z, 2026-09-14 00:1x: "we need to make the waveforms aligned to the baseline of Chippy
   it's slightly off right now and that needs to be consistent everywhere we show the logo". THE LOCKUP
   RULE, and it is one word: the mark and the word are aligned on the BASELINE, never centred. The mark is
   a flex box whose bars have no baseline of their own, so its own baseline is synthesised at the bottom of
   its box and the bars come to rest exactly on the word's baseline, at any size, with no number to keep in
   step. Centring put the bars 3.19 px low in the nav and 2.80 px low in the footer (measured, 7979). */
.nav__brand { display: inline-flex; align-items: baseline; gap: var(--space-6); text-decoration: none; color: var(--lit); }
.nav__name { font-weight: var(--title-weight); letter-spacing: var(--title-track); font-size: var(--title-size); } /* chippy:692: a step up with the mark; 714: the title step */
.nav__right { display: flex; align-items: center; gap: var(--space-24); } /* chippy:688: text links need air between them */
/* chippy:604: the nav's Sign in and Account ship with the `hidden` attribute and account.js reveals
   one. A `.btn` sets display:flex, which beats the browser's own [hidden]{display:none}, so a hidden
   button was still on screen (qa-factory's first film caught it). hidden means hidden, everywhere. */
[hidden] { display: none !important; }

/* ── Buttons ────────────────────────────────────────────────────────── */
.btn {
  display: inline-flex; align-items: center; justify-content: center;
  font-family: var(--sans); font-size: var(--body-size); font-weight: 500;
  text-decoration: none; cursor: pointer; white-space: nowrap;
  border-radius: 10px; border: 1px solid transparent; padding: var(--space-12) var(--space-24);
  /* chippy:809. Z, 2026-09-14 00:4x: "notice how the button animates with a delay it's weird should be instant
     like everything else?" This rule eased background-color, border-color, color and transform over 160ms, so
     while the hue slider moved the page's fills changed in one frame (no transition on any themed fill or
     border, the rule below) and every call to action crawled after them for a sixth of a second; measured on
     the nightly, the fill was still mid-way at 103ms. No transition: the button is the theme in the same frame
     as everything else, hover and press included. */
}
.btn--ghost { color: var(--lit); border-color: var(--desk-edge); background: rgba(255,255,255,.03); }
.btn--ghost:hover { border-color: var(--speak); color: var(--speak); }
/* chippy:811. Z, 2026-09-14 00:4x, reviewing the subpages: "not a huge fan of the shadow for the call
   to action i think we need to drop it and make it look a little more flat". This rule read
   `box-shadow: var(--lift-1);` and that one line was the elevation under EVERY call to action the site
   draws — the home band's Join the waitlist, the same button a step smaller in the top bar (.nav__cta),
   Send on chippy:740's contact form at the foot of every page, and every form's submit on /sign-up and
   /sign-in. The elevation is gone and nothing replaced it: no border,
   no keyline, no ring, no gradient. The button is a deep blue fill with white type and it stands on the
   page flat, at rest and under the pointer alike. --lift-1 itself stays; the mini window frame below
   still wears it (.chippy, and round2_layout_test.go pins its reach clear of the caption). */
.btn--solid {
  background: var(--theme-button); color: #fff; font-weight: 600;
}
/* chippy:653. Z: "i see yellow". This hover was the one amber literal 585 missed: the solid button
   lit up the old brand yellow under the pointer. It is the theme, a step lighter, now. */
/* chippy:681. Z, 2026-09-10 23:0x: "the buttons need to be blue and i think the text needs to be
   white." White on the theme accent was 2.4:1, so the button took deeper fills of the same hue
   (--theme-button and its lit and pressed steps, measured 5.2:1, 4.6:1 and 5.9:1 under white).
   chippy:682, Z: "the button being gray then blue seems weird": there is no greyed state at all; the
   button is the same blue always, and while a press is in flight it only stops being a pointer.
   chippy:680: no state changes the button's size or place, so nothing beside it moves. */
.btn--solid:hover { background: var(--theme-button-lit); }
.btn--solid:active { background: var(--theme-button-pressed); }
.btn--solid:disabled { cursor: default; }

/* ══ HERO ═══════════════════════════════════════════════════════════════ */
.hero {
  position: relative;
  max-width: var(--page); margin: 0 auto;
  padding: var(--space-96) var(--gut) var(--space-96); /* chippy:710, Z: "spacing here is too tight between nav and content"; a band's worth below. chippy:913, Z 2026-09-15 15:2x: "it needs more space above it it's cramped under the nav" — the nav-to-h1 space takes the next rung of the ladder, 48 to 96, the same four spaces the page already puts below the hero */
}
.hero__head { max-width: var(--span-8); }
.hero__title {
  font-family: var(--sans); font-weight: var(--display-weight);
  font-size: var(--display-size);
  line-height: var(--display-lh); letter-spacing: var(--display-track);
  /* chippy:891. Z, 2026-09-15 01:4x, of the new headline: "bold". The @font-face above declares
     `font-weight: 400 600`, so a CSS weight over 600 is CLAMPED to 600 and font-weight: 700 here
     would draw nothing new; the file itself carries wght 100..700 (fvar). Widening that face was
     refused because .chrome__app b asks for 650 and every bare <b> asks for 700, all of them held
     at 600 today, and they would all thicken at once. The axis is set here instead, on this one
     heading. Nothing moves: sCapHeight is 698/1000 at 400, 600 and 700 alike, so the cap line and
     the baseline the cap-line rule puts this heading on are exactly where they were and only the
     stems thicken. Computed font-weight stays 600, which is what type-measured.json records. */
  /* chippy:913. Z, 2026-09-15 15:2x, of the same headline: "heading bold is too bold". 700 comes back
     down to the 600 every other heading on the site takes (--heading-weight), so the h1 is the type
     system's weight again and nothing on the page is heavier than the rest. The axis does carry 650 and
     everything between (measured on 7979: "The best speech to text" advances 397.77 px at 600, 400.23 at
     650, 402.69 at 700), so a half step was available; 600 reads as a display weight at 27.92 px without
     it and a weight the system does not already name would be a second rule to keep. */
  font-variation-settings: "wght" 600;
}
.hero__sub {
  color: var(--lit-soft); margin-top: var(--space-24);
  font-size: var(--body-size); line-height: var(--body-lh);
}

/* ── The scene ──────────────────────────────────────────────────────── */
.scene { position: relative; margin-top: var(--space-48); }

/* chippy:592: the colour sandbox, top right of the hero beside the title. Eight swatches in the
   theme's own lightness and one slider across every hue; the pick lands on --theme-hue at :root
   (main.js) and everything that reads the theme follows. Under 720px it sits under the title. */
/* chippy:736: the hue slider moved into the nav; the swatches and YOUR COLOUR are gone. chippy:882 then
   retired the slider in the header too: the hue lives on the colour dot's panel now (.nav__panel-hue, at
   THE COLOUR DOT at the end of this file), and .nav__hue is not a class the site has any more.
   Z, 00:05: "This is stupid, having the individual colors. I don't really think it solves a problem." */

/* One aesthetic risk, and the only ambient effect on the page: the desk is
   lit by the voice. The lamp brightens while Chippy is speaking and dims
   when it stops, because on this page the theme colour IS the voice. */
.scene__lamp {
  position: absolute; inset: 0; z-index: var(--z-desk-ground);
  background: radial-gradient(46% 78% at 76% 82%,
    hsl(var(--theme-hue) var(--theme-sat) 72% / .26) 0%,
    hsl(var(--theme-hue) var(--theme-sat) 72% / .10) 40%, transparent 74%);
  opacity: .22; pointer-events: none; /* chippy:734: main.js sets it per frame; no transition */
}

.desk {
  position: relative;
  /* chippy:906. The player's bar was painting over the sticky nav: .desk was position:relative at z-index
     auto, which opens no stacking context, so every window inside the desk sorted in the ROOT context against
     .nav's z-index 3 — .player at 9, 11 once desk.js:17 lifts it, and .win--main at 3, which ties and wins on
     DOM order. overflow:hidden below clips them to the desk's BOX; it never contained their paint order.
     isolate is the wall: the desk sorts its own windows inside itself and the whole desk stays under the bar,
     and desk.js can go on incrementing topZ forever without reaching it — which is why this is not a bigger
     number on .nav. Measured on 7979 at 1280 and 390: elementFromPoint in the overlap answered chippy__bar.
     chippy:909: every number named above is a token now (--z-nav, --z-player, --z-win-main), and desk.js no
     longer increments anything — a press reassigns the --z-win-lift band, which stays under --z-chrome-bar. */
  isolation: isolate;
  container-type: inline-size;
  /* chippy:734, finding 40. Z, 23:37: "This is kind of too widescreen, it's just the wrong aspect ratio
     basically ... their aspect ratio feels more like a desktop"; 24:14: "as long as it's the same as the window,
     then it will look cute." A desktop's ratio, 16:10, the Mac's own; the desk's corners are the windows'
     corners, one token. */
  aspect-ratio: 16 / 10;
  --win-radius: calc(var(--u) * 1.5 * var(--k));
  /* The desk cannot read its own container unit, so its radius is the same sum from the width it has: the
     grid's, or the window less the gutters, whichever is narrower (the wrap's own rule). */
  --desk-radius: calc(min(var(--grid), 100vw - var(--gut) * 2) * .015 * var(--k));
  border-radius: var(--desk-radius);
  border: 1px solid var(--desk-edge);
  /* chippy:593. Z, 9:3x PM: "remove the shadow around the cards like the painting card thing, it's
     heavy (not anything inside the painting card)." No drop shadow on the desk; the windows keep theirs. */
  box-shadow: inset 0 1px 0 rgba(255,255,255,.05);
  overflow: hidden;

  /* --k scales every interior size. One number moves the whole scene. */
  --k: 1.24;
  --u: 1cqw;
  /* Mock type (chippy:714): the windows are pictures of the app in the desk's unit; five steps. */
  --m-caption: calc(var(--u) * .85 * var(--k));  /* rail heads, document heads, sources, the transport */
  --m-small:   calc(var(--u) * .95 * var(--k));  /* window titles, list rows, the player's name */
  --m-body:    calc(var(--u) * 1.05 * var(--k)); /* a mail's sender, the terminal */
  --m-read:    calc(var(--u) * 1.25 * var(--k)); /* the document being read, the keycap */
  --m-speak:   calc(var(--u) * 1.5 * var(--k));  /* the player's spoken line */
}
/* chippy:689. Z, 2026-09-10: a painterly background behind the window mock; then, of a first try in
   gradients, "why didn't it use the painterly background from the harness? it looks like it's in a
   nightclub." So it is the harness's own painting, the sea under a sky that qa-factory's stage draws
   behind the browser frame (credit in art/desk-sea-v1.txt beside the file), drawn the way the stage
   draws it: cover, centred, on a flat fallback. The windows above keep their own shadows; nothing is
   drawn over the painting itself (chippy:866, below).
   chippy:593. Z, 9:23 PM, of his own four paintings: "i would choose an environment per thing." One
   ground per desktop, all four in the desk at z-index 0 (--z-desk-ground, chippy:909), the one for the desktop on top by opacity,
   crossfading in .4s with no reflow: Writing an alpine lake at dawn, Mail a summer field, Agent a
   winter forest at dusk, Reading the open sea (fleet's mapping; Z may swap). The Unsplash sea has
   left the hero. Credit in art/wallpapers.txt. */
.desk { background: #1c2742; }
.ground {
  position: absolute; inset: 0; z-index: var(--z-desk-ground); pointer-events: none;
  background: #1c2742 center / cover no-repeat;
  opacity: 0; transition: opacity .4s ease;
}
.ground.is-on { opacity: 1; }
/* chippy:736, finding 28. Z, 13:44: "we used to have a painting from Unsplash, and I think I prefer it
   more. The problem with the AI-generated assets that we have here is that they're-- it, it's too big,
   and it makes the desktop proportions feel wrong." The one painting, the sea (art/desk-sea-v1.jpg,
   Birmingham Museums Trust on Unsplash, credit in desk-sea-v1.txt), 2880 wide, cover and centred, under
   every desktop; 593's four stay on disk and nothing draws them. */
.ground { background-image: url("art/desk-sea-v1.jpg"); }
/* chippy:866. Z, reviewing the nightly 2026-09-14 16:2x, on the scene over the sea: "is it just me or
   does the background image have a heavy inset shadow? it feels like it has a darkening effect i don't
   like it (like on the image/background)". It did, and .desk::after was the whole of it: a radial
   vignette to rgba(6, 8, 18, .55) at the corners, over an inset 0 0 60px rgba(6, 8, 18, .45). Measured
   on the desk's own pixels, 2.5 percent in from the frame, /use-cases/write at 1280 (luminance of 255):
   top left 123.59 with the pair, 153.40 with the shadow alone off, 130.46 with the gradient alone off,
   162.03 with both gone; bottom left 44.01 to 62.72; the centre never moved, 174.01 throughout. The
   shadow was the heavier half and the gradient the softer one, and both are gone. The painting is at
   its own brightness edge to edge, the windows above keep their own shadows, the desk still carries
   none (chippy:593), and the panel's corners no longer fade. Nothing replaced it. */

/* ── Stages: one dedicated desktop per use case ──────────────────────
   All four stages occupy the same box. Exactly one carries .is-on. The
   pointer, the key and Chippy live outside the stages, because they are
   the same pointer, the same key and the same Chippy on every desktop. */
.stage {
  position: absolute; inset: 0; z-index: var(--z-desk-stage);
  opacity: 0; visibility: hidden; /* chippy:734, finding 14: main.js draws the crossfade from the scene clock; no transition */
}

/* ── Windows ────────────────────────────────────────────────────────
   Two per desktop at most: the one you are working in (--main, lit and
   in front) and the one that gives it a reason to exist (--side, set
   back). Never more. Clutter was the whole note on v1. */
/* chippy:694. Z, 2026-09-10 9:24 PM: "i want none of the text selectable by the user but the windows
   should be able to be moved around like mac os." Nothing inside the desk selects; a window drags by
   its title bar (main.js, pointer events, so touch too), stays inside the desk, and comes to the
   front on press. The scene's own pointer keeps working because it reads word positions live. */
.desk, .desk * { -webkit-user-select: none; user-select: none; }
.win__bar { cursor: grab; touch-action: none; }
.win.is-held { cursor: grabbing; }
.win.is-held .win__bar { cursor: grabbing; }
.win {
  position: absolute;
  background: var(--paper);
  border-radius: var(--win-radius);
  overflow: hidden;
  transform-origin: 50% 50%;
}
/* chippy:734, finding 4. Z, 02:44: "I don't know why this window is dim ... if I select it, then, you know, I
   would want the traffic lights to change and sort of look like real macOS." No window is dimmed. The one in
   front (.is-front, set by main.js on press or drag) has coloured lights; the others grey, as macOS draws an
   inactive window. */
.win--main { box-shadow: var(--lift-3); z-index: var(--z-win-main); }
.win--side { box-shadow: var(--lift-2); z-index: var(--z-win-side); }
.win.is-front .win__lights i:nth-child(1) { background: #ff5f57; }
.win.is-front .win__lights i:nth-child(2) { background: #febc2e; }
.win.is-front .win__lights i:nth-child(3) { background: #28c840; }

/* Desktop 1 · Writing, the draft big and central, the list beside it. */
.stage--write .win--main { left: 3%;  top: 5%;  width: 58%; }
.stage--write .win--side { left: 64%; top: 14%; width: 33%; }

/* Desktop 2 · Mail, one window, wide, with its own rail. Nothing else
   belongs on this desktop; a second window would just be noise. */
.stage--mail .win--main { left: 5%;  top: 5%;  width: 90%; }

/* Desktop 3 · Agent, the terminal wide and low, the diff it produced
   sitting above and behind it. */
.stage--agent .win--main { left: 3%;  top: 20%; width: 60%; }
.stage--agent .win--side { left: 59%; top: 3%;  width: 38%; }

/* Desktop 4 · Reading, the long document, and the pile it came from. */
.stage--read .win--main { left: 3%;  top: 4%;  width: 60%; }
.stage--read .win--side { left: 66%; top: 12%; width: 31%; }

.win__bar {
  display: flex; align-items: center; gap: calc(var(--u) * .9 * var(--k));
  padding: calc(var(--u) * .75 * var(--k)) calc(var(--u) * 1.1 * var(--k));
  background: var(--paper-2);
  border-bottom: 1px solid rgba(20,22,31,.09);
}
.win__bar--dark { background: #262b3c; border-bottom-color: rgba(255,255,255,.08); }
.win__lights { display: inline-flex; gap: calc(var(--u) * .42 * var(--k)); }
.win__lights i {
  width: calc(var(--u) * .72 * var(--k)); height: calc(var(--u) * .72 * var(--k));
  border-radius: 50%; background: #cfcabd; display: block;
}
.win__bar--dark .win__lights i { background: #4a5168; }
.win__title {
  font-family: var(--mono); font-size: var(--m-small);
  color: var(--ink-faint); letter-spacing: .01em;
  white-space: nowrap; overflow: hidden; text-overflow: ellipsis;
}
.win__bar--dark .win__title { color: #98a0bb; }

.win__body { padding: calc(var(--u) * 1.5 * var(--k)); color: var(--ink); }
.win__body--doc, .win__body--mail, .win__body--read { font-family: var(--sans); } /* 714: the serif is gone; the highlight marks spoken words */

/* Mail keeps its message list beside the message, the way mail does. */
.win__split { display: flex; align-items: stretch; }
.win__rail {
  flex: 0 0 32%; border-right: 1px solid rgba(20,22,31,.09);
  background: #f6f3ec; padding: calc(var(--u) * .6 * var(--k)) 0;
}
.win__split .win__body { flex: 1 1 auto; min-width: 0; }

.row {
  font-family: var(--sans); font-size: var(--m-small);
  line-height: 1.45; color: var(--ink-soft);
  padding: calc(var(--u) * .55 * var(--k)) calc(var(--u) * 1 * var(--k));
}
.row b { display: block; font-weight: 600; color: var(--ink); font-size: var(--m-small); }
.row span { display: block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.row--sel { background: var(--select); }
.row--sel b, .row--sel span { color: #10131c; }
.row--head {
  font-family: var(--mono); font-size: var(--m-caption);
  letter-spacing: .1em; text-transform: uppercase; color: var(--ink-faint);
}
.win__body--list { padding: calc(var(--u) * .8 * var(--k)) 0; }

.doc__h, .read__h {
  font-family: var(--mono); font-size: var(--m-caption);
  color: var(--ink-faint); letter-spacing: .1em; text-transform: uppercase;
  margin-bottom: calc(var(--u) * .9 * var(--k));
}
.doc__p, .mail__p {
  font-size: var(--m-read); line-height: 1.5;
  margin-bottom: calc(var(--u) * .8 * var(--k));
}
.mail__from {
  font-family: var(--sans); font-weight: 600;
  font-size: var(--m-body); margin-bottom: calc(var(--u) * .25 * var(--k));
}
.mail__subj {
  font-family: var(--sans); font-size: var(--m-small);
  color: var(--ink-soft); margin-bottom: calc(var(--u) * .95 * var(--k));
}
.win__body--term {
  background: #1a1e2d; font-family: var(--mono);
  font-size: var(--m-body); line-height: 1.62; color: #dfe3f0;
}
.term__l { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.term__l--dim { color: #7e87a5; }
.term__p { white-space: normal; margin-top: calc(var(--u) * .6 * var(--k)); margin-bottom: calc(var(--u) * .6 * var(--k)); } /* chippy:734, finding 5: the agent's whole message, wrapped */
.term__pr { color: var(--speak); margin-right: .5em; }

.win__body--diff { font-family: var(--mono); font-size: var(--m-caption); line-height: 1.75; }
.diff { white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.diff--add { color: #1c7a3e; }
.diff--del { color: #a8232c; }

/* Selection: the same wash macOS puts under selected text. chippy:584, Z: "highlighting is obviously broken
   you can just see it"; chippy:734, finding 6, Z at 04:00: "it's also selecting by word and it's not selecting
   by character, which isn't how selection works." Both were answered on the word spans: a wash on .is-sel, a
   gradient over the fraction of .is-part under the I-beam, and a pad into the spaces either side so the words
   in a line ran together.
   chippy:874 (1). Z, 2026-09-14 16:39: "selected text on sibling lines needs to not have awkward gaps; that's
   not really how selection works it's like you start top left and drag bottom right, right now it does
   something completely different." A wash carried by the words can only ever be as tall as the words and as
   wide as the last one: measured on 7979 at 1280, the write desktop mid sweep, 4.00px of bare paper between
   every pair of sibling lines and line boxes from 152.99 to 563.11px. So no word carries the wash any more.
   main.js draws ONE BAND PER LINE under the text (.say__band, absolutely placed in the say block, so it
   scrolls with it on a phone): each band is the LINE BOX tall, which is what makes sibling lines touch, and
   the line the I-beam is on ends exactly under the I-beam, so the selection still grows by the character. The
   ends are square, because a selection the Mac paints has square ends; the rounded ends were an artefact of
   each word being its own box. The words keep only their selected INK. */
.say__p .w { border-radius: 0; }
.say__p .w.is-sel { color: #10131c; }
[data-say] { position: relative; }
.say__band { position: absolute; z-index: var(--z-part-back); pointer-events: none; background: var(--select); }
.say__p { position: relative; z-index: var(--z-part-mid); }
.win__body--term .say__p .w.is-sel { color: #eef2ff; }
.win__body--term .say__band { background: var(--select-dark); }

/* ── Cursor ─────────────────────────────────────────────────────────── */
/* chippy:734, finding 6. Z, 04:00: "if I make a selection, I'm pretty much just like dead center on the text,
   and then you can see the insertion carrot." The pointer is an I-beam the height of the line it is on,
   centred on that line, black with a white edge as the Mac's is over text. main.js places it every frame
   from the scene clock (no transition), so it never crawls or lunges (chippy:584). */
.cursor {
  position: absolute; z-index: var(--z-cursor);
  height: calc(var(--u) * 1.6 * var(--k)); aspect-ratio: 8 / 18;
  left: 12%; top: 30%;
  transform: translate(-50%, -50%);
  opacity: 0; pointer-events: none;
  filter: drop-shadow(0 1px 1px rgba(0,0,0,.35));
}
.cursor svg { display: block; overflow: visible; }
.cursor__edge { fill: none; stroke: #fff; stroke-width: 3.4; stroke-linecap: round; stroke-linejoin: round; }
.cursor__ink { fill: none; stroke: #0d0f1c; stroke-width: 1.5; stroke-linecap: round; stroke-linejoin: round; }

/* ── Keycap: the tap ────────────────────────────────────────────────── */
.keycap {
  position: absolute; z-index: var(--z-keycap);
  left: 50%; top: 50%;
  transform: translate(-50%, -50%) scale(.86);
  font-family: var(--mono); font-size: var(--m-read);
  letter-spacing: .02em;
  padding: calc(var(--u) * .7 * var(--k)) calc(var(--u) * 1.25 * var(--k));
  border-radius: calc(var(--u) * .9 * var(--k));
  background: var(--paper); color: var(--ink);
  border-bottom: calc(var(--u) * .3 * var(--k)) solid #c8c2b3;
  box-shadow: var(--lift-2);
  opacity: 0; pointer-events: none; /* chippy:734, finding 14: main.js pops it from the scene clock; no transition */
  white-space: nowrap;
}
.keycap b {
  font-weight: 500; color: var(--ink-faint); margin-right: .45em;
  font-family: var(--mono);
}
/* chippy:734, finding 9. Z, 04:35: "tap right, I can't fucking read it because it's in purple ... either it
   should be white or it should be like a dark mode thing." The hit key was white type on the button's fill,
   the step of the hue where white clears 4.5:1 (chippy:716's construction, theme.js), never the accent on
   the accent.
   chippy:874 (2). Z, 2026-09-14 16:39, watching the scene: "this needs to be like dark mode or light mode and
   it needs to linger longer so users understand a shortcut was pressed; the blue looks so stupid for the
   shortcut". 734 had left him both doors and he has taken the other one. The fill was measured on 7922 at 1280
   as rgb(0, 98, 245), the accent's own button step, which turns with the hue picker: a shortcut is not the
   theme. The chip is Chippy's own surface now, the one pair that already carries a light and a dark face
   (chippy:736, finding 1): --chippy-bg under --chippy-ink, a dark key on the dark theme and the paper key on
   the light one, with the strip's step as the edge it presses onto. Nothing in it reads the accent. The key
   still presses (the bottom edge collapses on the hit) and main.js now holds the chip from the tap until the
   first word is spoken, 820ms against the 420 it had. */
.keycap.is-hit {
  border-bottom-width: calc(var(--u) * .12 * var(--k));
  background: var(--chippy-bg); color: var(--chippy-ink);
  border-bottom-color: var(--chippy-bg-2);
}
.keycap.is-hit b { color: var(--chippy-ink-soft); opacity: 1; }

/* ── Chippy's window: one component, everywhere it appears ─────────────
   chippy:734, finding 16. Z, 07:15: "this should be basically the same UI as ... when it loads. I think it
   should be the same UI, roughly." The hero's player and the windows on /use-cases are one markup
   (.chippy, chippy.js) and one rule set, sized by --cu, the window's own unit: the desk sets it from its
   container unit, /use-cases from a fixed step (chippy:800 dropped the reel that was the third). The title bar is the
   mark and the word Chippy and nothing else (finding 7: "I don't really know why it says draft at the top
   right"); the controls are play, the clock and the speed (finding 11: the waveform "just looks stupid";
   finding 12: the right option chip "confusing and disorienting"). The text box holds three lines and
   scrolls under the lit word (finding 5); one word is lit at a time and a spoken word returns to plain
   text (finding 8: "It should only be highlighting the current word"). */
.chippy {
  --cu: 12.8px;
  /* Its type, in its own unit (714's mock steps): the name and the transport small, the spoken line the speak step. */
  --m-caption: calc(var(--cu) * .85);
  --m-small:   calc(var(--cu) * .95);
  --m-speak:   calc(var(--cu) * 1.5);
  position: relative;
  background: var(--chippy-bg); color: var(--chippy-ink); /* chippy:736: Chippy's own window has a light and a dark variant */
  border-radius: calc(var(--cu) * 1.7);
  box-shadow: 0 0 0 1px rgba(20,22,31,.08), var(--lift-1); /* chippy:735, finding 17: the short shadow, so no caption is ever under it */
  overflow: hidden;
}
.chippy__bar {
  display: flex; align-items: center; gap: calc(var(--cu) * .7);
  padding: calc(var(--cu) * .8) calc(var(--cu) * 1.2);
  border-bottom: 1px solid var(--chippy-edge);
}
.chippy__name {
  font-family: var(--sans); font-weight: 600;
  font-size: var(--m-small); color: var(--chippy-ink);
}
.chippy__text {
  font-family: var(--sans); color: var(--chippy-ink);
  font-size: var(--m-speak); line-height: 1.48;
  /* chippy:918. Z, 2026-09-15 16:1x, of his own recording of the hero: the top line 'scrolls half under the
     title bar'. It did, and the fade could not save it: overflow clips at the padding edge, so the fade had to
     reach zero within the padding, and the padding was 1.3cu against a line box of 1.48 * 1.5cu. Measured on
     7979 at 1280: the ramp was 16.6036px and a line box 28.3538px, 58.6 percent of it, so at a settled scroll
     the top line was cut 11.41px by the edge with its remaining 13.6px still up to 82 percent opaque: half a
     line, legible, under the bar. The fade region is one LINE BOX now, so the line leaving is inside it whole
     and is at zero before the edge reaches it, and the window grows by the difference at either end (about 24px
     at 1280). Three lines are still read; the two in the fade are the line going and the line coming. */
  --fade-line: calc(var(--m-speak) * 1.48);
  padding: var(--fade-line) calc(var(--cu) * 1.2);
  height: calc(var(--cu) * 1.5 * 1.48 * 3 + var(--fade-line) * 2); /* three lines and the fade at either end */
  overflow: hidden;
  /* chippy:734 tester, 1: overflow clips at the padding edge, so a fourth line showed half-cut inside the bottom
     padding and a scrolled line inside the top. The padding is a fade instead: a line coming into view fades in. */
  -webkit-mask-image: linear-gradient(to bottom, transparent 0, #000 var(--fade-line), #000 calc(100% - var(--fade-line)), transparent 100%);
  mask-image: linear-gradient(to bottom, transparent 0, #000 var(--fade-line), #000 calc(100% - var(--fade-line)), transparent 100%);
}
.chippy__tx {
  display: flex; align-items: center; gap: calc(var(--cu) * .9);
  padding: calc(var(--cu) * .75) calc(var(--cu) * 1.2);
  border-top: 1px solid var(--chippy-edge); background: var(--chippy-bg-2);
  font-family: var(--mono); font-size: var(--m-caption);
  color: var(--chippy-ink-soft);
}
/* chippy:587: the play button on the window. A real read in another voice; the highlight follows it. */
.chippy__play {
  pointer-events: auto; /* on the desk the player itself takes no pointer (it is the scene's); the button must */
  flex: none; display: inline-flex; align-items: center; justify-content: center; padding: 0; border: 0;
  width: calc(var(--cu) * 2.3); height: calc(var(--cu) * 2.3); border-radius: 999px;
  background: var(--theme-button); color: #fff; cursor: pointer;
}
.chippy__play:hover { background: var(--theme-button-lit); }
.chippy__play:active { background: var(--theme-button-pressed); }
.chippy__ico { width: 48%; height: 48%; }
.chippy__ico--stop { display: none; }
.chippy__play.is-playing .chippy__ico--play { display: none; }
.chippy__play.is-playing .chippy__ico--stop { display: block; }
.tx-time { font-variant-numeric: tabular-nums; }
.tx-slash { color: var(--chippy-ink-faint); margin: 0 .35em; }
.tx-rate { color: var(--chippy-ink-faint); }

/* The karaoke. chippy:737, finding 39. Z, 22:20: "Chippy needs to be a little bit bulbous and rounded, and so I
   think you wanna make the active word highlight a little bit more rounded 'cause it's kinda sharp here": the
   radius is half the chip's line (.5em). And 23:07: no transition on any themed fill or border. chippy:734,
   finding 8: only the word being spoken carries the chip; there is no trail. */
.w { border-radius: .5em; }
.chippy__text .w { padding: 0 .1em; margin: 0 -.1em; } /* a breath of chip past the glyphs; the mocks keep their own */
.chippy__text .w.is-now { background: hsl(var(--theme-hue) var(--theme-sat) var(--chip-l)); color: var(--chip-ink); }

/* chippy:874 (3). Z, 2026-09-14 16:39, of the hero's demo: "we need to show sentence highlighting and the word
   animating just like we already worked out in the i18n demo." The rule above is the whole of the karaoke
   everywhere else on the site: a chip that appears on one word and disappears from it. In the hero's player it
   is now prototypes/i18n-languages/v4's mechanism, which Z settled there: the same chip, but drawn as a MARK
   that glides from word to word under the text, and a BAND under the sentence being spoken. Both are placed by
   main.js in the text's own scrolling content, so they follow the read down the window; both take one radius,
   v4's .32rem at its 24px type, which is .213 of the step the line is set in; and neither is a transition, so
   a frame drawn twice is the same frame. The mark keeps chippy:737's chip exactly (--chip-l under --chip-ink),
   so the lit word is the colour Z already passed; the band is the accent at 17 percent, v2's number that v4
   kept, written the way --theme-past is written. The words sit above both.
   The hook is is-karaoke, which main.js writes on the window it has put layers into, and NOT .player: a use
   case's Chippy window is the hero's markup and carries .player too (usecases.go cuts it out of index.html),
   so a rule written against .player took the chip off the lit word on /use-cases/<case> where no mark exists
   to replace it. Measured on 7922 before this line: the lit word's background was rgba(0, 0, 0, 0) against
   rgb(112, 169, 255) on the nightly, with the chip's dark ink still on it. These three rules belong to the
   window that has the layers, and to no other. */
.is-karaoke .chippy__text { position: relative; --karaoke-r: calc(var(--m-speak) * .213); }
.is-karaoke .chippy__text .w { position: relative; z-index: var(--z-part-front); }
.is-karaoke .chippy__text .w.is-now { background: none; } /* the chip is the mark below it now, and the mark moves */
.chippy__band { position: absolute; inset: 0; z-index: var(--z-part-back); pointer-events: none; }
.chippy__band div { position: absolute; border-radius: var(--karaoke-r); background: hsl(var(--theme-hue) var(--theme-sat) var(--theme-l) / .17); }
.chippy__mark {
  position: absolute; left: 0; top: 0; z-index: var(--z-part-mid); pointer-events: none;
  border-radius: var(--karaoke-r);
  background: hsl(var(--theme-hue) var(--theme-sat) var(--chip-l));
  opacity: 0;
  will-change: transform, width;
}

/* chippy:806. The 404 page reads itself: Chippy's read (chippy.js) with the window taken off. Z, 2026-09-14: "just
   the text with a playback button and it reads itself karaoke style but without the player that would be enough."
   No box, no bar, no transport; the play button leads the paragraph, centred on its first line, and the paragraph
   is the page's own prose (.legal p sets its size and colour), whole: no three-line window and no fade. The lit
   word is the window's. */
.chippy--bare {
  display: flex; align-items: flex-start; gap: var(--space-12);
  background: none; color: inherit; border-radius: 0; box-shadow: none; overflow: visible;
  margin-top: var(--space-24);
}
.chippy--bare .chippy__play { margin-top: calc((var(--body-size) * var(--body-lh) - var(--cu) * 2.3) / 2); }
.legal .chippy--bare .chippy__text {
  flex: 1 1 auto; min-width: 0; height: auto; overflow: visible; padding: 0; margin-top: 0;
  -webkit-mask-image: none; mask-image: none;
}

/* The player: Chippy's window on the desk. main.js sets its opacity and transform from the scene clock
   (finding 14), lifts it to the front when it comes up and drags it by its bar (finding 13: "Chippy, I
   should be able to move around the screen just like I can move these"). */
.player {
  position: absolute; z-index: var(--z-player);
  right: 3.5%; bottom: 6%; width: 40%;
  --cu: calc(var(--u) * var(--k));
  box-shadow: var(--lift-3); /* on the desk it floats over the windows, as a window does */
  opacity: 0;
  pointer-events: none;
}
.player .chippy__bar { pointer-events: auto; cursor: grab; touch-action: none; }
.player.is-held .chippy__bar { cursor: grabbing; }

/* ── The desktop's own furniture: a menu bar and a dock ──────────────
   chippy:865. Z, reviewing the nightly 2026-09-14 16:2x, with tryklack.com's hero for the reference:
   "on the home page we should show an illustrative dock and menu bar ... but on non-home page we don't
   need to show the dock or menu bar". Drawn in the site's own hand out of the scene's own units, so it
   scales with --k like everything else in the desk: no Apple mark (the bar opens on Chippy's mark, where
   macOS puts the logo), no real app icon (Chippy's tile and, since chippy:893, the desktops' own apps drawn from
   boxes), nothing photographed.
   The markup is index.html's alone, so a use case's desk (usecases.go builds it) has none of it.
   It is furniture, not weather: no transition and no timer touches it, and nothing here darkens the
   painting (chippy:866) — the bar and the dock are light on the sea, the way a Mac's own chrome is. */
/* .chrome sets no z-index of its own, so it makes no stacking context and its two pieces sort against
   the desk itself: the bar above every window (a Mac's menu bar is never covered), the dock at the
   stages' own level but before them in the markup, so a window that reaches the floor floats over it. */
.chrome { position: absolute; inset: 0; pointer-events: none; }
.chrome__bar {
  position: absolute; top: 0; left: 0; right: 0; z-index: var(--z-chrome-bar);
  display: flex; align-items: center;
  height: calc(var(--u) * 1.9 * var(--k));
  padding: 0 calc(var(--u) * 1.2 * var(--k));
  gap: calc(var(--u) * 1.5 * var(--k));
  background: rgba(244, 246, 252, .14);
  border-bottom: 1px solid rgba(255, 255, 255, .10);
  font-size: var(--m-caption); color: rgba(255, 255, 255, .92);
}
.chrome__app { display: inline-flex; align-items: center; gap: calc(var(--u) * .6 * var(--k)); }
.chrome__app b { font-weight: 650; }
.chrome__app .mark { height: calc(var(--u) * .95 * var(--k)); gap: calc(var(--u) * .2 * var(--k)); }
.chrome__app .mark i { width: calc(var(--u) * .22 * var(--k)); border-radius: 999px; }
.chrome__menus { display: inline-flex; gap: calc(var(--u) * 1.1 * var(--k)); color: rgba(255, 255, 255, .76); }
.chrome__status { margin-left: auto; display: inline-flex; align-items: center; gap: calc(var(--u) * 1 * var(--k)); }
/* Two generic status glyphs, three arcs and a pair of switches: the shapes a person reads as a menu
   bar without being any one company's marks. Drawn from boxes, like the mark itself. */
.chrome__wifi { display: inline-flex; align-items: flex-end; gap: calc(var(--u) * .16 * var(--k)); }
.chrome__wifi i {
  display: block; width: calc(var(--u) * .2 * var(--k)); border-radius: 999px;
  background: rgba(255, 255, 255, .86);
}
.chrome__wifi i:nth-child(1) { height: calc(var(--u) * .34 * var(--k)); }
.chrome__wifi i:nth-child(2) { height: calc(var(--u) * .62 * var(--k)); }
.chrome__wifi i:nth-child(3) { height: calc(var(--u) * .9 * var(--k)); }
.chrome__toggles { display: inline-flex; flex-direction: column; gap: calc(var(--u) * .22 * var(--k)); }
.chrome__toggles i {
  display: block; width: calc(var(--u) * .95 * var(--k)); height: calc(var(--u) * .34 * var(--k));
  border-radius: 999px; background: rgba(255, 255, 255, .86);
}
.chrome__clock { font-variant-numeric: tabular-nums; }
/* The dock: Chippy's tile first, in the theme, then the three desktops' own apps (chippy:893), every one with
   the running dot under it. It sits below the windows in the same z-plane, in DOM order, so a window that
   reaches the floor of the desk floats over it, as one does on a Mac.
   chippy:938: that is not a courtesy, it is the tie. .chrome opens no stacking context of its own, so the rung
   entering the sort is the dock's, tying with the stage's own; a tie is settled by DOM order, and .chrome stands
   before the stages in index.html, so the stages — and the window riding one — paint last. Nothing in this sheet
   can see that order: dock_order_938_test.go is what holds it. */
.chrome__dock {
  position: absolute; left: 50%; bottom: calc(var(--u) * .7 * var(--k)); z-index: var(--z-desk-stage);
  transform: translateX(-50%);
  display: flex; align-items: flex-end; gap: calc(var(--u) * .5 * var(--k));
  padding: calc(var(--u) * .45 * var(--k));
  border-radius: calc(var(--u) * 1 * var(--k));
  background: rgba(244, 246, 252, .18);
  border: 1px solid rgba(255, 255, 255, .14);
}
.chrome__tile {
  position: relative;
  display: inline-flex; align-items: center; justify-content: center;
  width: calc(var(--u) * 3.2 * var(--k)); height: calc(var(--u) * 3.2 * var(--k));
  border-radius: calc(var(--u) * .78 * var(--k));
  background: rgba(255, 255, 255, .30);
  border: 1px solid rgba(255, 255, 255, .16);
  box-shadow: inset 0 1px 0 rgba(255, 255, 255, .24);
}
/* chippy:893. Z, 2026-09-15 01:1x: "the dock we need to show with the apps that are open so that it's all sort of
   contextualized." The dot a Mac draws under a running app, under every tile: each is an app with a window open on
   one of the three desktops, and a Mac's dock is one dock across every Space, so nothing here changes with the
   crossfade. The three that were blank placeholders, stepped back by position, are the apps now, all one shade. */
.chrome__tile::after {
  content: ""; position: absolute; left: 50%; bottom: calc(var(--u) * -.42 * var(--k));
  width: calc(var(--u) * .3 * var(--k)); height: calc(var(--u) * .3 * var(--k));
  margin-left: calc(var(--u) * -.15 * var(--k));
  border-radius: 999px; background: rgba(255, 255, 255, .72);
}
.chrome__tile--chippy { background: var(--speak); border-color: transparent; box-shadow: none; }
.chrome__tile--chippy .mark { height: calc(var(--u) * 1.45 * var(--k)); gap: calc(var(--u) * .28 * var(--k)); }
.chrome__tile--chippy .mark i { width: calc(var(--u) * .34 * var(--k)); border-radius: 999px; background: var(--theme-ink); }
/* The apps' glyphs, drawn from boxes like the wifi arcs above, in the scene's units so they scale with the tile:
   the draft is three lines of a page with the last one short, the inbox an envelope (the box and the two halves
   of its flap meeting in the middle), the terminal a prompt, a chevron and a cursor on a dark tile the way the
   agent's own window is dark. No company's marks. */
.chrome__glyph { position: relative; display: block; width: calc(var(--u) * 1.6 * var(--k)); height: calc(var(--u) * 1.6 * var(--k)); }
.chrome__glyph i { position: absolute; display: block; border-radius: 999px; background: rgba(255, 255, 255, .92); }
.chrome__glyph--draft i { left: 0; height: calc(var(--u) * .24 * var(--k)); }
.chrome__glyph--draft i:nth-child(1) { top: 14%; width: 100%; }
.chrome__glyph--draft i:nth-child(2) { top: 43%; width: 100%; }
.chrome__glyph--draft i:nth-child(3) { top: 72%; width: 60%; }
.chrome__glyph--mail {
  height: calc(var(--u) * 1.2 * var(--k)); overflow: hidden;
  border: calc(var(--u) * .2 * var(--k)) solid rgba(255, 255, 255, .92);
  border-radius: calc(var(--u) * .24 * var(--k));
}
.chrome__glyph--mail i { top: 0; width: 66%; height: calc(var(--u) * .2 * var(--k)); border-radius: 0; }
.chrome__glyph--mail i:nth-child(1) { left: 0; transform-origin: left top; transform: rotate(35deg); }
.chrome__glyph--mail i:nth-child(2) { right: 0; transform-origin: right top; transform: rotate(-35deg); }
.chrome__tile--term { background: rgba(24, 28, 40, .78); border-color: rgba(255, 255, 255, .22); }
.chrome__glyph--term i { height: calc(var(--u) * .22 * var(--k)); }
.chrome__glyph--term i:nth-child(1) { left: 8%; top: 22%; width: 40%; transform-origin: left center; transform: rotate(45deg); }
.chrome__glyph--term i:nth-child(2) { left: 8%; top: 78%; width: 40%; transform-origin: left center; transform: rotate(-45deg); }
.chrome__glyph--term i:nth-child(3) { right: 6%; bottom: 10%; width: 40%; }

/* ── Scene footer ───────────────────────────────────────────────────── */
.scene__foot {
  display: flex; align-items: baseline; justify-content: flex-end;
  gap: var(--space-12); flex-wrap: wrap; margin-top: var(--space-12);
}
.scene__dots { display: flex; gap: var(--space-6); }
.dot {
  font-family: var(--mono); font-weight: var(--eyebrow-weight); font-size: var(--eyebrow-size); letter-spacing: var(--eyebrow-track);
  text-transform: uppercase; cursor: pointer;
  background: transparent; color: var(--lit-faint);
  border: 1px solid var(--desk-edge); border-radius: 999px;
  padding: var(--space-6) var(--space-12);
  /* chippy:737: no transition; the selected pill's fill and border are the theme and change with it in one frame. */
}
.dot { display: inline-flex; align-items: center; gap: var(--space-6); } /* chippy:736, finding 10: an icon before the word, by the oversize rule, no taller */
.dot .c-icon { margin-right: 0; }
.dot:hover { color: var(--lit); border-color: var(--lit-faint); }
.dot[aria-selected="true"] { color: var(--theme-ink); background: var(--speak); border-color: var(--speak); }

/* chippy:875. Z, reviewing the nightly 2026-09-14 16:42, of the control above: "maybe there should be a link
   under this where you learn about use cases or something". The link is .link--go, so this rule sets nothing
   about its ink; it places the line and nothing else. It is a block of .scene, not a child of .scene__foot,
   because that row turns column-reverse under 720 and a child would rise above the pills on a phone. The gap
   is --space-12, the foot's own margin and its own gap, so the control and the line under it keep one rhythm;
   the text is right-aligned to sit under the pills, which .scene__foot justifies to flex-end, and goes left
   under 720 where the pills stretch from the left edge. */
.scene__more { margin-top: var(--space-12); text-align: right; }

/* ══ Bands ══════════════════════════════════════════════════════════════ */
/* chippy:790. Z, on the home page, 2026-09-14 00:1x: "i don't like the alternating background colors
   it feels cheap stick to one background color and the hairline dividers." Two of the five bands sat
   on --desk-deep, so the page read ground -> tinted -> ground -> tinted -> ground down its length; a
   tester tonight called it candy-striped. chippy:804 made it louder, not quieter, by putting the theme
   hue into that token for the Products menu's sake: what had been a near-invisible slab became a band
   of colour. The two backgrounds are gone. ONE ground, --desk, from the header to the foot, and every
   section boundary is a single --desk-edge hairline and nothing else -- that token, which carries the
   hue, is the site's only separator. --desk-deep is kept and is now the Products menu's alone: the
   menu is a panel ABOVE the page and must have a ground of its own (.nav__drop, chippy:804).
   Each boundary the shaded bands used to announce still has its rule: shape top, letter top, cta top,
   foot top. The one seam with no rule is shape -> stories, which is deliberate (735, below) -- those
   two are one section.
   chippy:800 dropped the cases band, so the hero now meets the four things directly; the rule that
   was .band--cases's top moved to .band--shape and the boundary it drew is the same one, in the same
   place. No two hairlines are adjacent and no band lost its separation. */
.band { padding: var(--space-96) 0; }
.band--shape { border-top: 1px solid var(--desk-edge); }
.band--letter { border-top: 1px solid var(--desk-edge); }
.band--cta { border-top: 1px solid var(--desk-edge); }
/* chippy:735's rule for the shape -> stories seam (the second band's top padding went) left with the stories band:
   chippy:801 folded the three stories into the shape band's second row, so there is no seam to rule. */

/* chippy:735, finding 18. Z, 07:45: "these small caps headings don't really serve a purpose ... drop the
   lines." The .eyebrow rule and the three section eyebrows are gone; the mono treatment survives on the form
   label alone (.signup__label, chippy:589's shipped 500 face). */
.h2 {
  font-family: var(--sans); font-weight: var(--heading-weight);
  font-size: var(--heading-size);
  line-height: var(--heading-lh); letter-spacing: var(--heading-track);
}
.lede { color: var(--lit-soft); margin-top: var(--space-24); font-size: var(--body-size); }

/* chippy:800. Z, 2026-09-14 00:1x-00:3x, of the band headed Everything you have heard is still here:
   "i think we can drop this section since it's redundant with the hero." It was the hero's own window
   playing the hero's own three paragraphs behind the hero's own three pills, so the reel, its beat and
   its waving mark are gone with it. .case__cap stays: /use-cases draws it. */

/* chippy:496. /use-cases: three of Z's scenarios, each with one card that plays a real read. The
   card is the cases band's photograph, set straight; the words take the player's highlight. */
.usecases .uc { margin-top: var(--space-48); }
.usecases .uc h2 { margin-top: 0; }
.uc__card { margin-top: var(--space-24); } /* chippy:713: the window, no card around it; 735: it fills its column; 734: it is Chippy's window, .chippy */
.uc__card .case__cap { margin-top: var(--space-24); }
.usecases .uc__back { margin-top: var(--space-48); color: var(--lit-soft); }
/* ── The use case's desktop (chippy:823) ─────────────────────────────────────────────────────
   Z, 2026-09-14 00:4x-01:0x: "each time you see chippy it should be in context on a desktop so it looks real; just the
   player is ok but it's kind of dry and i want to be able to interact with it; this is basically how we market chippy,
   windows into real world use cases in a simulated desktop to tell a story." The desk on /use-cases/<slug> is the
   hero's: the same .desk, .ground, .scene__lamp, .stage and .win rules above, and the stage's markup is cut out of
   index.html at request time (usecases.go), so the site has one desktop. Chippy's window is the hero's .player. The
   desk takes the twelve tracks so its unit is the hero's and the windows read at the hero's size; the caption sits
   under it on the column. The hero's clock hides a stage and the player until their beat; here the stage is simply
   on and Chippy is up, live, and draggable (use-cases.js). */
.usecases .uc__desk { grid-column: 1 / -1; }
.usecases .uc__desk .scene { margin-top: 0; } /* the wrapper carries the ladder's one space (.uc__card) */
.usecases .uc__desk + .case__cap { margin-top: var(--space-24); }
.desk--case .stage { opacity: 1; visibility: visible; }
.desk--case .player { opacity: 1; pointer-events: auto; }

/* The caption on the photograph's bottom lip, the way a caption sits on a
   polaroid. The body step (714: the serif that named spoken words is gone). */
.case__cap {
  font-family: var(--sans); font-size: var(--body-size); line-height: var(--body-lh);
  color: var(--lit-soft); margin-top: var(--space-24); /* chippy:713: on the band now, not on a card */
}

/* ── The shape ──────────────────────────────────────────────────────── */
.shape {
  display: grid; grid-template-columns: repeat(4, 1fr);
  gap: var(--grid-gap); margin-top: var(--space-96); /* chippy:914, Z 2026-09-15 15:2x: "spacing under headings is tight here too" — the section head to its icon row takes the ladder's next rung. The brush under the accent is absolutely positioned and hangs below the heading's box (measured on 7979: 14.50 px at 1280, 10.84 px at 390), so 48 of box read as 33.50 and 37.16 of ink. */
}
/* chippy:735, finding 18. Z, 07:57: "These lines don't make any sense to me ... If anything, you would have lines
   in between the columns, not above them." The hairline above each column is gone and none runs between them.
   09:56: "I don't know what to do with the small caps." The word under the icon is a plain heading at the title
   step now; the other cut, icon and body with no word, is a frame on the row for him to pick. */
.shape__key {
  font-family: var(--sans); font-weight: var(--title-weight); font-size: var(--title-size); letter-spacing: var(--title-track);
  color: var(--lit);
}
/* chippy:736, finding 19. Z, 08:04: "these icons are actually pretty important ... I think the icons need
   to be huge, like really big ... these are features, and they don't feel like features." The icon
   stands above its word at --feature-icon (40px; 32px is the other size on the row for him to pick).
   chippy:801: solid, Phosphor's Fill weight in the heading's ink (solid.go), one fill and no stroke; Z, 2026-09-14:
   "i am over the duotone icon thing i just want solid punchy icons." The duotone svg (lucideIcon) still draws
   every control glyph on the site; only the eight on this grid are solid.
   chippy:871. Z, of the nightly 2026-09-14 16:36: "i think these icons need to be even bigger use the given
   theme color". 56px, not 48: measured on 7922 against the heading step and the column at 1280, 720 and 390,
   48 is 1.40 times the 34.4px heading and 20 percent of the 240px column, a step too small to read as an
   answer to even bigger; 56 is 1.63 times the heading and 23 percent of the column at 1280, and at 390 it is
   16 percent of a 342px column, still a mark standing over its word and not a picture. The colour is --theme,
   the one the picker drives and .accent's marker uses, set on the glyph alone: the svg draws in currentColor
   (solid.go) so the word under it keeps --lit and only the mark takes the accent, in both themes. */
.shape__key { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-12); }
.shape__key .c-icon { --feature-icon: 56px; inline-size: var(--feature-icon); block-size: var(--feature-icon); margin-right: 0; color: var(--theme); }
/* chippy:736. 1naf's icon rule, verbatim in its three properties (the note on the row): the box is 1cap
   tall, baseline to cap line, so the row never grows; 1cap times --oversize-scale wide, real layout, so
   the gap to the word holds; overflow visible with preserveAspectRatio slice in the markup, so the
   drawing spills past the box on the y-axis without taking that space. Z, 02:28: "oversized probably by
   a one point two five magnifier." Duotone: the same nodes filled soft under the stroke.
   margin-right is the icon's own trailing gap, and after chippy:799 every glyph the site still draws
   zeroes it (.shape__key, .dot, .nav__theme, .nav__menu-btn .c-icon--chev, .link--go): the glyphs that
   wanted it were the nav and footer link glyphs, and those are gone. It stays as the default an inline
   glyph before a word would take, and because .link--go's override is 792's rule about which gap draws
   its arrow (link_go_test.go holds that line). Removing it is a one-line change and nothing moves. */
.c-icon {
  --oversize-scale: 1.25;
  flex: none; overflow: visible; vertical-align: baseline; margin-right: var(--space-6);
  inline-size: round(1cap * var(--oversize-scale), 1px); block-size: round(1cap, 1px);
}
.c-icon__tone { opacity: .22; }
.ico { width: 14px; height: 14px; flex: none; fill: currentColor; } /* chippy:551's rule; no page draws it since 736 */
/* chippy:877: one space, not half. The word above it is ink now (the rule under the base type), so this
   number is the whole gap: at 12 it drew 12 of white under a sentence whose own lines are 14.43 apart, and
   the title read as a line of the paragraph. Measured at 1280, ink to ink: 19.95 before (12 declared plus
   7.95 of the word's leading), 24 after. The icon keeps its 12 to the word (.shape__key): a graphic binding
   to its label, not one text block after another. */
.shape__body { color: var(--lit-soft); font-size: var(--body-size); line-height: var(--body-lh); margin-top: var(--space-24); }
/* chippy:801: the band is two rows of four on the one grid, features then benefits (Z: "a features and benefits
   section where we show off the features and then we describe the benefits using the same grid icon thing"). The
   second heading sits four spaces under the first grid, a band's worth, so the rows read as two and the grid's own
   two spaces under each heading still hold. The stories band (.stories, .story__who, .story__body, chippy:711) is
   gone: its three sentences are the second row. */
.shape__again { margin-top: var(--space-144); } /* chippy:914: the seam stands a rung above the 96 under each head, so the head belongs to the row under it and not to the section above (Z ruled this after the first cut made the two gaps equal) */
/* The accent. Z, of Klack's grid: "the accents are kind of cute": a marker stroke in the theme colour under one
   or two words of the heading, drawn once as a mask so it takes --theme in either theme and re-colours with the
   picker. The span is in the flow; the stroke is a pseudo-element under its baseline and adds no height.
   chippy:870. Z, of the nightly 2026-09-14 16:33: "logo regression; it was better before". This rule landed on the
   BARE .mark, which the bars glyph already owned (:418 above), so the stroke drew under the nav brand, the footer
   and the use-case windows too: measured on 7922, theme blue 3 device px below the nav bars' baseline at 1280 and
   390. The accent is its own class now and .mark is the bars alone again; the heading accent itself is unchanged. */
.accent { position: relative; white-space: nowrap; }
.accent::after {
  content: ""; position: absolute; left: calc(-0.05em / 0.698); right: calc(-0.05em / 0.698); bottom: calc(-0.16em / 0.698); height: calc(0.3em / 0.698); /* chippy:876: the four ems of the accent, divided by 0.698 so they draw the pixels they drew before the lock */
  background: var(--theme); pointer-events: none;
  mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 24' preserveAspectRatio='none'%3E%3Cpath d='M3 15 C 40 9, 90 8, 128 11 S 190 15, 197 9 C 170 15, 110 18, 60 17 S 8 19, 3 15 Z' fill='%23000'/%3E%3C/svg%3E") no-repeat center / 100% 100%;
  -webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 24' preserveAspectRatio='none'%3E%3Cpath d='M3 15 C 40 9, 90 8, 128 11 S 190 15, 197 9 C 170 15, 110 18, 60 17 S 8 19, 3 15 Z' fill='%23000'/%3E%3C/svg%3E") no-repeat center / 100% 100%;
}

/* ── Letter ─────────────────────────────────────────────────────────── */
/* chippy:714. Z, 14:02, of the letter set in the serif with THE PERSON WHO MADE IT under it: "can we
   make this better? I also don't really want any serif fonts." The lead step of the sans, in the
   narrow column (about 46 characters a line at 1280, measured before and after on the row). 14:08:
   "I don't like the signature here; it should be more like the personal emails, like 'Z, the maker
   of Chippy'." The signature is the small step, sentence case, as docs/VOICE.md signs. */
.letter__text {
  font-family: var(--sans); font-weight: var(--lead-weight); font-size: var(--lead-size);
  line-height: var(--lead-lh); letter-spacing: var(--lead-track); color: var(--lit);
  text-wrap: var(--wrap-heading); /* the pull quote balances like a heading (chippy:714, Z 15:2x) */
}
/* chippy:879: Z's letter is two paragraphs now (his slash is the break), and p is margin: 0 for the whole
   site, so the second one needs the one space the site gives a paragraph to the next. Measured on 7924:
   0px between the boxes before this line at 1280 and 390, 24px after. chippy:877 (margins, ink to ink)
   re-derives this number later. */
/* chippy:877 re-derives it here, and it is two spaces. The letter is the lead step: its own lines stand
   19.25 apart at 1280, so a 24 between the paragraphs was a break a quarter wider than a line break and the
   two paragraphs read as one. Measured at 1280, ink to ink: 24 before, 48 after (2.5 lines of white against
   1.25). At 390 the step is smaller, 14.76 between lines, and 48 reads as the same beat. */
.letter__text + .letter__text { margin-top: var(--space-48); }
/* chippy:735, finding 29. Z, 14:14: "the signature is awfully small, which doesn't really make a lot of sense ...
   this is important and it's big ... It's basically invisible." The title step in the text colour, sentence
   case as docs/VOICE.md signs; the quote above it is a weight heavier (--lead-weight 500). */
/* chippy:764: the line under the letter that leads to the whole of it */
/* chippy:791: the muted colour sits on the line, not on the anchor, so the link is currentColor like every
   other muted link and the sentence and the link cannot drift to two colours. */
.letter__more { margin-top: var(--space-12); font-size: var(--body-size); line-height: var(--body-lh); color: var(--lit-soft); }
/* chippy:877: the signature is ink too (the trim list under the base type could not carry it, see there),
   and it stands two spaces under the letter, not one. Measured at 1280, ink to ink: 32.73 before (24
   declared plus 8.73 of its own leading), 48 after. At 24 it drew less white than the letter leaves
   between its own lines (19.25), so the sign-off read as a last line of the last paragraph. */
.letter__by {
  font-family: var(--sans); font-size: var(--title-size); font-weight: var(--title-weight); letter-spacing: var(--title-track); color: var(--lit);
  margin-top: var(--space-48); text-box: trim-both cap alphabetic;
}
/* chippy:793. Z, 2026-09-14 00:1x, of the letter on the home page: "this should probably be in quotes
   like 1naf but the quotes need to not be hand and the quotes themselves need to be fairly dim so as to
   not distract." Tonight, ruling on how: "i want quotes that do not cause reflow." So the marks are the
   typographic pair of the letter's own face (--sans, inherited, no declaration here, so they are never
   the hand), --lit-faint, the dimmest step of the text colour (#636b81 on paper, #7f859d on the desk,
   against --lit at #14161f / #f2f0ea), and NEITHER OF THEM TAKES ROOM IN A LINE.
   hanging-punctuation is not the road: measured in the Chromium the testers drive (151.0.7922.34),
   CSS.supports('hanging-punctuation','first last'), 'first' and 'last' are all false and the property
   is not even in the style declaration, so it would draw nothing. Instead the opening mark is out of
   flow, hung to the left of the column, and the closing mark is a zero-width inline-block whose glyph
   overflows to the right of the last full stop. Every word of the quote was measured before and after
   at 1280, 720 and 390: same x, same y, to the thousandth of a pixel. */
.letter__text { position: relative; }
.letter__mark { color: var(--lit-faint); font-style: normal; }
/* right: 100% puts the mark's own right edge on the column's left edge, and the padding is the optical
   gap between the mark and the C. text-box is why top: 0 lands: :455 trims every p to its cap line, and
   text-box does not inherit, so an out-of-flow box beside a trimmed paragraph keeps its half-leading and
   hangs LOW. Measured at 1280 before this line: the mark's ink began 9.00 device-independent px under the
   C's (106.00 against 97.00), 6.50 under at 390. With the same trim on the mark, cap sits on cap. */
.letter__mark--open { position: absolute; right: 100%; top: 0; padding-right: .1em; text-box: trim-both cap alphabetic; }
/* width 0 is the whole of it: the box adds no advance, so the line cannot rewrap and text-wrap: balance
   sees the same content it saw; the glyph draws past the box into the white after the stop. */
.letter__mark--close { display: inline-block; width: 0; overflow: visible; white-space: nowrap; }

/* ── Waitlist ───────────────────────────────────────────────────────── */
.signup { margin-top: var(--space-24); }
/* chippy:695. Z, 9:33 PM: the grey EMAIL ADDRESS label "looks inactive"; "it can look like it is on
   the homepage". One label token for every form on the site: the .eyebrow rule above, verbatim, in
   the theme blue, with the label's own spacing. */
/* chippy:733, finding 34. Z, 17:41, o outline on /sign-up: "this gap, you know, it's a little bit thin,
   maybe use twelve pixels, maybe use twelve pixels here, you know, twelve pixels here." ONE FORM RULE
   for every form on the site: label to field and field to status line are the same step, 12, on the
   element that follows (chippy:735 put 12 on the ladder). */
.signup__label {
  display: block; font-family: var(--mono); font-weight: var(--eyebrow-weight); font-size: var(--eyebrow-size); letter-spacing: var(--eyebrow-track);
  text-transform: uppercase; color: var(--theme);
}
/* chippy:795 REVERSES chippy:735 finding 23. Z, 2026-09-14 00:1x, on the home page: "we have keyline
   alignment to the text inside the input and it looks stupid in practice i think we need to stop doing
   this everywhere." The row no longer hangs left by the field's padding: the CONTROL'S EDGE sits on the
   keyline the label and the paragraph share, and the text inside the field is inset by the field's own
   padding, which is what the footer's contact form already did. One rule for every form on the site. */
.signup__row { display: flex; gap: var(--space-12); flex-wrap: wrap; margin-top: var(--space-12); }
.signup__input {
  flex: 1 1 260px; min-width: 0;
  font-family: var(--sans); font-size: var(--body-size); color: var(--lit);
  background: rgba(255,255,255,.04);
  border: 1px solid var(--desk-edge); border-radius: 10px;
  padding: var(--space-12) var(--space-24);
  /* chippy:856: an address wider than the field says it was cut. Measured on the band before this
     line, at 1280 and 390: a 42-character address is 352.56 px of text in a content box of 273 and
     292, so 79.56 and 60.56 px sat outside it and the last letter was sliced at the edge with
     nothing to read it by. The field cannot grow (the band's row is 504 px and the button is beside
     it; at 390 the button has already wrapped and the field is the whole row), and scrolling to the
     end would hide the local part, which is the half the line under the field points at when it
     says "that address". The engine draws this ellipsis only while the field is not focused, which
     is where a press leaves a person (854 keeps the value; focus goes to the button), and the whole
     address is still there to click into, select and scroll. One declaration on the one form rule,
     so the band, /sign-up, /sign-in and /contact are the same shape. long_address_test.go holds it. */
  text-overflow: ellipsis;
  transition: border-color .16s ease, background-color .16s ease;
}
.signup__input::placeholder { color: var(--lit-faint); }
.signup__input:focus { outline: none; border-color: var(--speak); background: rgba(255,255,255,.06); }
.signup__input.is-bad { border-color: var(--bad); }
/* chippy:808 REVERSES chippy:680's reserve. Z, 2026-09-14 00:4x, on the subpages: "the awkward gaps
   under the sign in exist because of the error text but it's strange; the error can cause reflow so
   let's just rip the spacer, consistently across forms." No box is held for a sentence that is not
   there: an empty status line is 0 high on every form, and the answer to a press reflows what is
   under it. The integer line height stays, so the sentence itself sits on the ladder. */
.signup__msg { line-height: 22px; margin-top: var(--space-12); font-size: var(--small-size); color: var(--lit-soft); }
/* chippy:881. THE STATUS CALLOUT. Z's pick off prototypes/status-icons/v1, 2026-09-14 16:52: "i think
   the bulbous status thing from gpb is closer to how i want it to work; i like Inline glyph but Tinted
   panel options; but i would want it to be cuter like gpb". The two cuts he named are merged and the
   panel takes the shape of the callout on his PropaneBuddy page: one full-width bulbous panel, a pale
   tint of the STATE'S OWN colour as its ground, a small solid glyph in the full colour on the left and
   vertically centred, a bold title and one line in the reading ink beside it, generous padding, no
   border and no shadow. Radius and tint strength are prototypes/faq-folds/v2's bulbous panel
   (--bulb-radius 28px, --callout-tint 14% dark and 12% light), so the two shapes agree.
   The ink is --lit, not the state's colour: the colour is carried by the tint and the glyph, which is
   what lets one callout serve four states without a sentence ever dropping under 4.5:1. The sweep that
   holds that over all 360 hues is status_callout_881_test.go.
   chippy:808 still holds, an empty line is 0 high, and IN FLIGHT IS NOT A PANEL: a press still in the
   air has no glyph in any of 812's cuts and nothing has happened yet, so it stays the bare line it is
   today and the panel arrives with the answer. */
.signup__msg.is-good, .signup__msg.is-bad, .signup__msg.is-warn, .signup__msg.is-info {
  --callout-mark: var(--speak);
  display: flex; align-items: center; gap: var(--space-12);
  padding: var(--space-24);
  border-radius: var(--bulb-radius);
  background: color-mix(in srgb, var(--callout-mark) var(--callout-tint), transparent);
  color: var(--lit);
}
.signup__msg.is-good { --callout-mark: var(--good); }
.signup__msg.is-bad { --callout-mark: var(--bad); }
.signup__msg.is-warn { --callout-mark: var(--warn); }
.signup__msg.is-info { --callout-mark: var(--info); }
/* The glyph is the site's own solid set (solid.go), handed to the page's script in
   <template id="status-glyphs">; .c-icon sizes itself from the cap height, and inside the callout it is
   a fixed 20px box instead so the four glyphs are one size whatever the sentence does. */
.msg__ico { flex: none; display: block; inline-size: 20px; block-size: 20px; color: var(--callout-mark); }
.msg__ico .c-icon { margin-right: 0; inline-size: 100%; block-size: 100%; }
.msg__text { min-width: 0; }
.msg__title { display: block; font-weight: 600; color: var(--lit); }
.msg__line { display: block; }
/* chippy:733: the development build's line under the status, where the mail went; production never fills it. */
.signup__dev { margin-top: var(--space-6); font-size: var(--small-size); color: var(--lit-faint); }

/* chippy:747. Continue with Google, under the form row on /sign-in and /sign-up (site/google.go
   fills it; with no client configured the block is not in the page at all). The email link is the
   page's one blue press, so the second door is the ghost button, and the G keeps Google's own four
   colours because that mark may not be recoloured; nothing here reads a theme token. */
.google { margin-top: var(--space-24); }
.btn--google { gap: var(--space-12); line-height: normal; } /* an <a> inherits the body's line-height; the <button> beside it gets normal, and the two must be one height */
.google__g { flex: none; width: calc(1.125em / 0.698); height: calc(1.125em / 0.698); } /* chippy:876: 1.125em / 0.698, still 18px */
.google__note { margin-top: var(--space-12); color: var(--lit-faint); font-size: var(--small-size); line-height: var(--body-lh); }

/* chippy:522. The cowboy door: shown only after a waitlist success on an instance whose build is an
   alpha, under the form, in the same quiet box the sign-in note uses. */
.cowboy { margin-top: var(--space-24); padding: var(--space-24); border: 1px solid var(--desk-edge); border-radius: 12px; background: rgba(255,255,255,.03); }
.cowboy__head { font-family: var(--sans); font-weight: var(--title-weight); font-size: var(--title-size); letter-spacing: var(--title-track); color: var(--lit); }
.cowboy__body { margin-top: var(--space-12); font-size: var(--body-size); line-height: var(--body-lh); color: var(--lit-soft); }
.cowboy .btn { margin-top: var(--space-24); }
.cowboy__msg { line-height: 22px; margin-top: var(--space-12); font-size: var(--small-size); color: var(--lit-soft); }

/* ── Foot ───────────────────────────────────────────────────────────── */
/* chippy:687. Z, 9:3x PM: "shouldn't these links be right aligned or something, they seem weird being
   on the left" and "these link styles are too strong; they need to be subtle like the nav link items
   but not the same size." Two ends: the wordmark and the tagline left, the two links on the right
   edge of the same .wrap the nav uses, in the nav link's treatment (muted colour, thin underline
   below on hover) at 13px. Under 720px they stack, links under the tagline. */
/* chippy:910. Z, 2026-09-15 15:2x, over a screenshot of the page's end: "why is copyright not closer to the
   bottom?" Measured on 7979 at 1280 and 390, on /, /pricing, /faq, /blog and /privacy: the line's box ended
   48.0px above the end of the document on every one of them, which is this padding, two spaces on the scale,
   while .foot__end gives the same line one space (24) above it. So it sat with twice as much room under it as
   over it, and read as stranded. The bottom is one space now, the copyright is within one step of the end, and
   the 48 stays at the top, where the footer meets the page above it (a block after a block). */
.foot { border-top: 1px solid var(--desk-edge); padding: var(--space-48) 0 var(--space-24); }
/* chippy:867: the footer is the giant wordmark's frame. isolation makes .foot its own stacking context, so
   .foot__word at z-index -1 (--z-part-under, chippy:909) sits above the footer's own ground and under every column; overflow hides the
   tails the word runs off the bottom and the sides. Neither changes a box already on the page. */
.foot { position: relative; isolation: isolate; overflow: hidden; }
/* chippy:740: 1naf's footer, four columns on the grid (4, 2, 2, 4 tracks); the columns go two by two under 1000
   and one under another under 720 (below). Rows inside a column are the ladder's 12 on the follower. */
.foot__grid { display: grid; grid-template-columns: repeat(12, minmax(0, 1fr)); column-gap: var(--grid-gap); row-gap: var(--space-48); }
/* chippy:867: four columns of three tracks, which is 1naf's own footer (SPAN_3 on all four), now that the
   call to action sits under the brand lines instead of holding a column of its own. */
.foot__col { grid-column: span 3; min-width: 0; }
.foot__join { margin-top: var(--space-48); } /* the call to action under the brand block, a band's space below it */
/* chippy:735, finding 24. Z, 11:10, of the footer: "Chippy, okay, this is too small. This needs to be bigger.
   Not quite as big as the logo, but it needs to be bigger." The line is the text face at the body size, the
   word Chippy in the wordmark's weight (the nav's is the title step, 19; this is 16), the mark grown with it;
   the tagline keeps its place after the interpunct (dropping it below is his other pick). */
.foot__meta {
  display: flex; align-items: baseline; gap: var(--space-6); margin: 0; /* chippy:798: the lockup rule, the mark on the word's baseline */
  /* chippy:867: the lockup is one line (735, finding 24) and it needs 250.1px, measured, where a three-track
     column on the 1080 page is 240. So it leans 10.1px of its last word into the 24px gutter, which leaves
     14px of clear ground before the Product column, and the columns stay 1naf's four equal thirds. Below
     1081 they are two by two and the question does not arise: that breakpoint moved from 1000 for this. */
  white-space: nowrap;
  font-family: var(--sans); font-size: var(--body-size); color: var(--lit-soft);
}
.foot__name { font-weight: var(--title-weight); letter-spacing: var(--title-track); color: var(--lit); }
.foot__blurb { margin: 0; margin-top: var(--space-12); font-family: var(--sans); font-size: var(--caption-size); line-height: calc(1.5 / 0.698); color: var(--lit-soft); } /* chippy:876: 1.5 / 0.698, still 18px */
/* the column heading is the form label token (chippy:695, one label rule for every form), so the four
   columns and the form's own labels read as one family */
.foot__head { margin: 0; font-family: var(--mono); font-weight: var(--eyebrow-weight); font-size: var(--eyebrow-size); letter-spacing: var(--eyebrow-track); text-transform: uppercase; color: var(--theme); }
.foot__links { display: flex; flex-direction: column; align-items: flex-start; gap: var(--space-12); margin: 0; margin-top: var(--space-12); }
.foot__link {
  color: var(--lit-soft); text-decoration: none; font-family: var(--sans); font-size: var(--caption-size);
  border-bottom: 1px solid transparent; padding-bottom: 1px; transition: color .15s ease, border-color .15s ease;
}
.foot__link:hover { color: var(--speak); border-color: currentColor; }
/* the contact form: the sign-up form's field, label and status tokens, stacked; label to field 12, field to
   the next label 24, the button 24 under the message, the status line 12 under the button */
.contact { margin-top: var(--space-12); }
.contact__label { margin-top: var(--space-24); }
.contact__label:first-child { margin-top: 0; }
.contact__input { display: block; width: 100%; margin-top: var(--space-12); padding: var(--space-12); font-size: var(--caption-size); }
.contact__text { resize: vertical; min-height: calc(6em / 0.698); line-height: calc(1.5 / 0.698); } /* chippy:876: 6em and 1.5 / 0.698, still 72px and 18px */
.contact__send { margin-top: var(--space-24); }
.contact__msg { margin-top: var(--space-12); }
.foot__col--contact .signup__dev { margin-top: var(--space-6); font-size: var(--caption-size); } /* the smaller line, as the index says */

/* chippy:867. Z, of the footer, 2026-09-14 16:3x: "we should probably have some copywright thing at the
   bottom". 1naf's is the same move: the columns, then one line on its own under them.
   chippy:897. Z, 2026-09-15 01:1x: "the line above the copyright looks stupid." It was a 1px --desk-edge rule
   here, and measured at 1280 it cut straight across the giant wordmark at ten times the word's own contrast
   (rgb 45 64 92 on the dark ground is a delta of 60; the word's ink is 6) and ran the .wrap's box, 100 to
   1180, which matched neither the page-wide hairline over the footer nor the columns' text at 124 to 1156. The
   footer's one hairline is the one on .foot; the wordmark alone stands behind the copyright. The 48 and the 24
   stay so the line is the only thing that went: the copyright rose the 1px the rule occupied and nothing else moved. */
.foot__end { margin-top: var(--space-48); padding-top: var(--space-24); }
.foot__copy { margin: 0; font-family: var(--sans); font-size: var(--caption-size); color: var(--lit-soft); text-align: center; }

/* chippy:867's note: the giant wordmark behind the footer, between the two cuts Z picked from
   prototypes/site-footer/v1 and quieter than either. The colour is the theme, not the ink, so the hue slider
   turns it (the duotone's idea); the opacities are under the monochrome fade's .15/.05 and well under the
   duotone's .30/.12, which is the "more subtle" he asked for. The box is the word's measured ink box
   (download.go footWordmark), so no ascender and neither i dot is cut at the top; translateY drops a fifth of
   it past the footer's bottom edge, where .foot's overflow takes it, and the word runs under the columns'
   text, which he ruled is the point ("it can leak behind the text ... it's just artsy"). */
.foot__word {
  position: absolute; z-index: var(--z-part-under); left: 0; right: 0; bottom: 0;
  display: flex; justify-content: center; padding: 0 var(--gut);
  color: var(--theme); transform: translateY(20%);
}
.foot__word svg { display: block; width: 100%; max-width: var(--page); height: auto; }
.foot__word-a { stop-color: currentColor; stop-opacity: var(--word-a); }
.foot__word-b { stop-color: currentColor; stop-opacity: var(--word-b); }
.foot__word-c { stop-color: currentColor; stop-opacity: 0; }
:root { --word-a: .10; --word-b: .04; }
/* the light theme's blue is a darker step on a white ground, so the same alpha reads twice as strongly;
   these are the measured pair that lands it at the same quietness (the numbers are on chippy:867). */
:root[data-theme="light"] { --word-a: .06; --word-b: .025; }

/* ══ Responsive ═════════════════════════════════════════════════════════
   The scene does not reflow, it re-stages. Below 720px the desk shows one
   window at a time, full width, with the others held behind it, and every
   interior size doubles via --k. Container query units do the rest. ════ */

/* chippy:867: the footer's own breakpoint, 80px above the page's. Over 1080 the .wrap is at its cap and a
   three-track column is exactly 240px; between 1001 and 1080 it would shrink to 220 and the brand lockup's
   10px lean would become a 30px collision with the Product column. Two by two from 1080 down instead. */
@media (max-width: 1080px) {
  .foot__col { grid-column: span 6; } /* chippy:740: two by two; 867: one rule, every column is three tracks now */
}

@media (max-width: 1000px) {
  .shape { grid-template-columns: 1fr 1fr; }
  .wrap--narrow > * { grid-column: 2 / 12; } /* chippy:735: the inset widens as the tracks narrow */
}

@media (max-width: 720px) {
  /* Below 720px the interior stops scaling with the container and switches
     to fixed sizes (--u: 1px), because a percentage-scaled desktop keeps
     shrinking the text past the point where a phone can read it. The window
     still fills the width; only the type holds still. */
  .desk { aspect-ratio: 4 / 4.6; --u: 1px; --k: 12.5; --desk-radius: calc(1.5px * var(--k)); }

  /* One window per desktop on a phone. The side window is context for a
     mouse, not for a thumb, it goes, rather than being shrunk to
     illegibility. The rest scales through --k and container units. */
  .stage .win--main { left: 4%; right: 4%; width: auto; top: 5%; }
  .stage .win--side { display: none; }
  .win__rail { display: none; }
  .term__l--dim:nth-of-type(n+3) { display: none; }
  /* chippy:734, finding 5: the whole paragraph is the selection on a phone too; the window's body is clipped
     at a height and main.js scrolls it under the I-beam as the selection runs down. */
  .win__body[data-say] { max-height: calc(var(--u) * 21 * var(--k)); overflow: hidden; }

  .player { left: 4%; right: 4%; width: auto; bottom: calc(var(--u) * 4.8 * var(--k)); }

  /* chippy:865, the phone's dock. Measured at 390 with the scene paused on the read: the player is
     312.8 x 188 on a 342 x 418.9 desk and stands 17.7px off the floor, so a floor-centred dock of
     176.8 x 49.1 lost all but a 4px sliver of itself behind it. The dock stays, smaller, and the
     player stands on it instead of through it: the shelf is the dock's own height plus the gap it
     already had. It costs the read window 31px of the strip the player did not already cover. */
  .chrome__dock { gap: calc(var(--u) * .4 * var(--k)); padding: calc(var(--u) * .38 * var(--k)); }
  .chrome__tile { width: calc(var(--u) * 2.6 * var(--k)); height: calc(var(--u) * 2.6 * var(--k)); }

  .scene__foot { flex-direction: column-reverse; align-items: stretch; gap: var(--space-12); }
  .foot__col { grid-column: span 12; } /* chippy:740: one column under another; 867: one rule */
  .scene__dots { flex-wrap: wrap; }
  .scene__more { text-align: left; } /* chippy:875: the pills stretch from the left edge here, so the line under them does too */

  .shape { grid-template-columns: 1fr; margin-top: var(--space-48); } /* chippy:914: the section head's four spaces halve under 720, the way a band's padding does below. Measured at 390: the column's own items sit 24 apart, so at 96 the head stood four times further from the row it heads than that row's items stand from each other, and it read stranded between the two sections. */
  .shape__again { margin-top: var(--space-96); } /* chippy:914: with the head at 48 here, 96 is already the rung above it, so the phone reads 48 under the head and 96 above it; 144 is the desk's number and is not used at this width */
  .wrap--narrow > * { grid-column: 1 / -1; }
  .band { padding: var(--space-48) 0; } /* chippy:735: a band's padding halves under 720, as 1naf's does */
  .hero { padding-bottom: var(--space-48); }
}

@media (max-width: 420px) {
  .desk { aspect-ratio: 4 / 4.9; --k: 11.5; }
}

/* ══ Reduced motion ═════════════════════════════════════════════════════
   No timers run (main.js checks the same query and renders the finished
   state instead). Kill the transitions so nothing eases into place. ═══ */
@media (prefers-reduced-motion: reduce) {
  .stage, .win, .cursor, .keycap, .player, .scene__lamp, .btn, .w {
    transition: none !important;
  }
  .nav__brand .mark:hover i, .foot__meta .mark:hover i { animation: none !important; } /* chippy:692, 717 */
  .ground { transition: none !important; } /* chippy:593: the painting swaps, no crossfade */
  /* chippy:773: !important, like every other line in this block. Without it the plain .slide__track rule
     further down this file outranks this one by source order (a media query adds no specificity), so a
     person who asked for no motion still watched the /voice copy slide for 450 ms behind the address. */
  .slide__track, .seg__thumb, .key, .nav__menu-btn .c-icon--chev { transition: none !important; } /* chippy:746 */
  .nav__switch-knob { transition: none !important; } /* chippy:894: the warmth switch's knob slides; not for a person who asked for no motion */
}

/* ══ chippy:488 / chippy:489: the gate, the account, and the plain pages ══

   TWO TRUE STATES, ONE PAGE. `<body data-build>` is substituted per request
   by site/api.go from buildWord(): "ready" when there is a build to hand
   over, "none" when chippy:494 has not landed. It is the SAME word /health
   answers with, so the page and the service never say two different things
   about one fact (chippy:556/558). The page carries BOTH sets of words and
   this is what shows the true one, so the page is honest with JavaScript
   off, and a button never turns into "sorry, not yet" after it is pressed.
   If the token ever went out unsubstituted, both show; that is loud, which
   is the right way for that to fail.                                    ═══ */

body[data-build="ready"] .only-none { display: none; }
body[data-build="none"]  .only-ready { display: none; }
body[data-build="alpha"] .only-ready { display: none; } /* chippy:522: an alpha is not a public build; the door is under the form */

/* An anchor kept where a heading used to be, so old links still land. */
.anchor { display: block; height: 0; }

.nav__link {
  color: var(--lit-soft); text-decoration: none; font-size: var(--small-size);
  border-bottom: 1px solid transparent; transition: color .15s ease, border-color .15s ease;
}
.nav__link:hover { color: var(--speak); border-bottom-color: var(--speak); }
/* chippy:907. THE CURRENT PAGE WEARS THE BAR, AND IT IS THE ONLY THING THAT DOES. Z, 2026-09-15 15:2x, two
   screenshots of the bar: "why are the navlinks inconsistent??" — on one page Join the waitlist is the accent
   with a line under it and nothing is under Pages; on the next, Pages carries the line and the waitlist item is
   grey. MEASURED on 7979 before this rule, every page at 1280 and 390: every nav anchor and both summaries rest
   at var(--lit-soft) with a 1px TRANSPARENT bottom border and no text-decoration, on every page, including the
   one item that carries aria-current="page" — the nav drew NOTHING from aria-current (the note at THE THREE
   KINDS OF LINK says so in as many words). So there were not two current-page rules; there were none, and the
   only bar the site could draw was the hover above. The line Z saw move was his own pointer, which is why it
   was under a different word in each shot.
   THE BAR IS THE MARKER AND THE COLOUR IS NOT. Every item keeps the one rest colour — a current item that also
   changed colour would be a second mark, and the accent is spoken for by the pointer. currentColor, so the bar
   is the item's own ink in either theme and at any hue.
   ONE MARK PER PAGE, AND IT IS ALWAYS IN THE VISIBLE ROW. Three of the six pages a person can be on mark
   themselves inside a closed disclosure (Pages holds /use-cases, /tutorials, /blog, /about, /faq, /contact;
   the Products switcher held / and /video until chippy:912 dropped it, so on the home page and on /video the
   desk row now wears no bar at all and the phone Menu still wears one), so the summary wears the bar on its
   rows' behalf; the rows keep aria-current
   for a screen reader and are not drawn, so the mark is never in two places at once. Under 720 the row's links
   and both desktop summaries are display: none and the phone Menu is the one thing left, so the same :has()
   puts the bar there. The transparent border is already in .nav__link above: this colours it and moves nothing.
   :not(:hover) so the pointer still wins — a marker that ate the hover would make the current item the one word
   in the row that does not answer the mouse. */
.nav__link[aria-current="page"]:not(:hover),
.nav__menu:has([aria-current="page"]) > .nav__menu-btn:not(:hover) { border-bottom-color: currentColor; }
/* chippy:882 RETIRED THE PAIR. The row carried a 96 px gradient <input type="range"> (.nav__hue, 64 px under
   720) and a sun/moon <button> (.nav__theme) side by side; both are gone from siteNav and both rules go with
   them, because a rule nothing wears is a rule that lies about the header. What stands in their place is one
   dot and the panel under it, at THE COLOUR DOT at the end of this file. The old 720 rule kept the row's gaps
   and let it wrap; the gaps stay here, and the wrap goes with the pair that made the row too long.
   chippy:736's finding 1 and chippy:802's "more subtle" are both carried into the dot rather than undone. */
@media (max-width: 720px) {
  .nav__right { gap: var(--space-12); flex-wrap: wrap; justify-content: flex-end; }
}
/* chippy:851. Two testers on 811, at 390: the header "wraps to three lines ... looks unfinished/cluttered".
   Measured on 7979 at 390: the bar 82.8 px tall, the row on two lines, 160 px left after the wordmark and
   Products for five items and two controls. The bar does what 1naf's does: under 720 the Products menu and
   the row's links go, and one Menu disclosure at the right edge (download.go, sitePhoneMenu; the same
   details/summary pattern and the same panel as Products) lists the products and the account items. The
   slider and the toggle stay in the bar. Above 720 the phone menu does not exist. The panel hangs from the
   right edge, because a panel hung from the left of a control at x 300 would leave the page. */
.nav__menu--phone { display: none; }
/* The phone row is the products row (.nav__drop-item above) declaration for declaration; its own rule, because
   round2_layout_test pins `.nav__drop-item {` as the one quiet-link rule the menu wears. */
.nav__phone-item {
  display: inline-flex; align-items: center;
  padding: var(--space-6) var(--space-6); border-radius: 8px;
  color: var(--lit-soft); text-decoration: none; font-size: var(--small-size); white-space: nowrap;
}
.nav__phone-item:hover, .nav__phone-item:focus-visible { color: var(--speak); background: color-mix(in srgb, var(--desk-edge) 45%, transparent); }
@media (max-width: 720px) {
  .nav__menu:not(.nav__menu--phone), .nav__right > .nav__link { display: none; }
  .nav__menu.nav__menu--phone { display: block; margin-right: 0; } /* two classes: .nav__menu sets margin-right: auto later in this file */
  .nav__drop.nav__drop--phone { left: auto; right: 0; } /* two classes: .nav__drop sets left: 0 later in this file */
}

/* chippy:688. Z: "make them text links, subtle." Every nav item is .nav__link above, the style
   production had for Account; no box and no pill (651's item rules are gone). */

.signup__note { margin-top: var(--space-24); color: var(--lit-faint); font-size: var(--small-size); }

/* ══ THE THREE KINDS OF LINK ════════════════════════════════════════════════════════════════════
   chippy:791. Z, 2026-09-14 00:1x-00:3x, counting the link styles on the home page:

     "why on earth are there so many link styles? there should really only be like three kinds; (a)
      navlinks/footer links which are subtle until hover (this is good); (b) bright links like this for
      things that need attention; (c) subtle links that do not draw attention, same color as text and the
      underline color is the same as the text color, so these are like muted links"

   Three, named by him, and every anchor the site serves is one of them:

     (a) THE QUIET NAVIGATION LINK — .nav__link, .nav__drop-item, .foot__link and the breadcrumbs.
         The muted text colour, no underline, and the accent under the pointer. Each keeps its own
         rule where it is drawn; he said of these "this is good", so none of them moved.
     (b) THE BRIGHT LINK — .link--bright, below. The accent, ink and underline alike. It is for the one
         thing a page is asking a person to do, in the middle of a sentence, and it is rare on purpose:
         two anchors on the whole site wear it, both of them "Join the waitlist".
     (c) THE MUTED LINK — this rule. The sentence's own colour, ink and underline alike, so it takes the
         colour of whatever paragraph it sits in and never brightens it. Under the pointer it lights to
         the accent, which is to say it becomes (b) for as long as the pointer is on it.

   (c) IS THE DEFAULT, AND IT IS THE DEFAULT FOR EVERY ANCHOR, not for a list of blocks. What stood here
   was a hand-kept allowlist (.legal a, .signup__note a, .signup__dev a, .account__aside a, .note__p a,
   .uc__back a, .confirm__keep) and it is how `<a href="/about">that is why it exists</a>` in a story on the
   home page came to be drawn by nobody at all, in the browser's own lavender: the block it sits in was
   never added to the list. A list like that goes stale the next time anyone writes a paragraph. So the
   rule is written on the anchor itself, inside :where(), which gives it no specificity of its own: every
   named thing below that is not running text — the nav, the footer, the wordmark, the buttons, the cards,
   the segment tabs, the headings that are anchors, the crumbs, the skip link — already sets its own colour
   and text-decoration in a rule with a class in it, and a class beats nothing every time. Nothing about
   those had to change to opt out, and nothing new has to be added when a paragraph gains a link. */
:where(a) {
  color: currentColor; text-decoration: underline; text-decoration-thickness: 1px;
  text-underline-offset: calc(.15em / 0.698); text-decoration-color: currentColor; /* chippy:876: .15em / 0.698 */
}
:where(a:hover) { color: var(--theme); text-decoration-color: currentColor; }
/* (b). The accent, ink and underline together, so it reads as one bright word and not as text with a
   coloured rule under it. Under the pointer it has nowhere brighter to go, so the line thickens instead;
   a thickness never reflows a line. */
.link--bright { color: var(--theme); text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: calc(.15em / 0.698); text-decoration-color: currentColor; }
.link--bright:hover { color: var(--theme); text-decoration-thickness: 2px; }

/* (a), APPLIED IN A NEW PLACE — NOT A FOURTH KIND. chippy:792. Z, 2026-09-14 00:1x-00:3x, of the line under
   the letter on the home page: "this link makes sense but we should do it like 1naf where we show an icon so
   the text is subtle probably like navlinks but there's an icon indicating you can click it and hover shows
   the bright style."
   "probably like navlinks" is (a) above, and this rule is (a)'s three properties and nothing else: the muted
   colour, no underline, the accent under the pointer — the breadcrumbs' shape (.blog .crumbs a) exactly. What
   it adds is the icon 1naf puts AFTER the words
   (~/GitHub/zaydek/1nightatfordstheatre.com/app/components/sections/what-people-are-saying.tsx:128-135, read
   on the row: <span>{q.source}</span> then the arrow).
   WHERE IT GOES: a link that stands on its own line, OUTSIDE a sentence, whose whole job is "go to this other
   page" — the line under the letter, and every "Back to the …". A link inside running prose stays (c): it is
   part of the sentence and must take the sentence's colour, so it never wears this.
   THE GLYPH SAYS WHERE IT GOES, and both halves are the rule:
     arrow-right     the link stays on this site. Every .link--go today is one.
     arrow-up-right  the link LEAVES this site. 1naf's is up-right because it points at an outside review; an
                     up-right arrow is the near-universal mark for "this opens somewhere else", so putting it
                     on an internal same-tab link says something untrue in a glyph. No served page has an
                     off-site anchor today (swept on chippy:792); the first one that gets one gets up-right.
   The colour is set once, on the anchor. .c-icon draws in currentColor, so the words and the arrow light
   together under the pointer rather than the words first and the arrow after. The icon's own margin-right was
   the trailing gap for a glyph that OPENED a link, in the nav and the footer; chippy:799 took those glyphs
   away, so this arrow is the last icon on the site that sits beside words — it closes a link rather than
   opening one, and its gap is the flex gap above, not that margin. */
.link--go {
  display: inline-flex; align-items: center; gap: var(--space-6);
  color: var(--lit-soft); text-decoration: none; transition: color .15s ease;
}
.link--go .c-icon { margin-right: 0; }
.link--go:hover { color: var(--speak); text-decoration: none; }

/* chippy:735, finding 27. Z, 13:11: "these need to be headings so that, you know, every page that has headings
   or subheadings, I should be able to click it, have it load into the URL." Every h2 on the FAQ, the legal
   pages, the blog and the tutorials, and Join the waitlist on the home page, is an anchor to itself; it reads
   as a heading at rest and underlines under the pointer.
   chippy:791: THIS IS THE ONE THING ON THE SITE THAT IS NOT ONE OF THE THREE, and it is kept on purpose. It is
   not a link style, it is a heading that happens to be clickable: a heading drawn in any of the three would
   read as a link in the middle of a document, which is what finding 18 and finding 26 were both about. The
   four rules that drew it separately (.h2 a, .legal h2 a, .blog .post h2 a, .tutorials .tut h2 a) were three
   different hovers between them; they are one rule now so they cannot drift apart again. */
.h2 a, .legal h2 a, .blog .post h2 a, .tutorials .tut h2 a { color: inherit; text-decoration: none; }
.h2 a:hover, .legal h2 a:hover, .blog .post h2 a:hover, .tutorials .tut h2 a:hover {
  text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: calc(.15em / 0.698); text-decoration-color: currentColor;
}

/* ── The plain pages ───────────────────────────────────────────────────
   /account, and the small pages a link lands on when it is dead or when it
   is handing a session to the Mac app. Same desk, no scene. ─────────── */

body.plain { min-height: 100vh; display: flex; flex-direction: column; }
body.plain .foot { margin-top: auto; }
/* chippy:677, 687. Z, 9:3x PM: "bro this is not a real privacy policy, and this is not a real terms of
   service." Two complete documents with headed sections, in the plain voice. */
.legal h2 { font-family: var(--sans); font-weight: var(--title-weight); font-size: var(--title-size); letter-spacing: var(--title-track); margin-top: var(--space-48); color: var(--lit); }
/* chippy:711, Z 15:2x with outline mode on: "why is the block stopping early before the end? makes no sense". One
   measure: the column (.wrap--narrow) sets the width; headings, paragraphs and the date fill it, no per-element
   max-width, so every box in a section shares one right edge. */
.legal .h2, .legal .lede, .legal p { max-width: none; }
.legal p:not(.desk p) { color: var(--lit-soft); font-size: var(--body-size); line-height: var(--body-lh); margin-top: var(--space-24); } /* chippy:849: not the mocks' paragraphs inside a desk (chippy:823 put the hero's desk on a use case, whose main is .legal): .row, .doc__p, .mail__p and .term__l are (0,1,0) and this rule was (0,1,1), so every window on a use case took the page's prose size and a 24px top margin, Notes 441px tall against the hero's 248; :not() leaves the desk to its own rules */
.legal h2 + p { margin-top: var(--space-12); } /* chippy:735, finding 25: a heading to its own paragraph is half a space; a paragraph to the next heading two */
.legal p.lede { color: var(--lit); }
/* chippy:735, finding 37. Z, 19:27: the date twice "is a little bit weird"; it is once now, this line at the foot,
   and the Changes paragraph names it. 20:14: monospace a quarter smaller, as an em of the paragraph it is in. */
.legal .legal__date { color: var(--lit-faint); font-family: var(--mono); font-size: var(--code-size); margin-top: var(--space-24); }

/* ── Long-form pages: eight tracks, the box is the caplines, the serious register ──────────────
   chippy:814, with 821 folded in. Z, 2026-09-14 00:4x-01:0x, going down the subpages: "make privacy and
   terms more serious, smaller fonts and small caps titles like qa factory; content is incredibly thin
   spanning 6 bars, go to 8; forms can be 6 including the new contact page but privacy, terms, blogs,
   longform content needs 8"; on a post: "way too fucking narrow, needs to be 8 columns for sure"; on the
   prose: "spacing for prose is a little loose i would tighten it; something is wrong: the bounding box
   for text is actually supposed to be the caplines, that's the whole point of this system; dearchitra
   and 1naf implemented this correctly".
   .longform is on the main of /privacy, /terms, /blog and /tutorials (the templates); a form page
   (/sign-up, /sign-in, /contact, /account) never carries it and keeps tracks four to nine. Eight tracks
   are three to eleven, 680px on the 1032 grid, the --span-8 width; under 1000 the inset is two to twelve
   as every narrow page's, under 720 the twelve, so a phone sees the same column as before. The rule is
   more specific than .wrap--narrow > *, so it repeats the two breakpoints itself.
   The box is the caplines, 1naf's way (app/styles/text.sass: text-box trims the block to cap line and
   baseline, so a space on the ladder is ink to ink): every heading, paragraph and list item on a
   long-form page is trimmed, so --space-24 between paragraphs is 24px from the last baseline to the
   next cap line, not 24px plus two half-leadings; that is the tightening, the ladder's values stand.
   A browser without text-box keeps the line box and the same margins; nothing else changes for it.
   The serious register is /privacy and /terms (.longform--serious): the paragraph at the small step,
   the lede at the body step still, and the section title in the site's one small-caps treatment, the
   eyebrow tokens (mono, upper case, spaced), in the text colour, never a new font; qa-factory's index
   draws its section heads the same way. The FAQ keeps I am Z and its own register (docs/VOICE.md). */
.wrap--narrow.longform > * { grid-column: 3 / 11; }
@media (max-width: 1000px) { .wrap--narrow.longform > * { grid-column: 2 / 12; } }
@media (max-width: 720px) { .wrap--narrow.longform > * { grid-column: 1 / -1; } }
/* chippy:861. Z, on the nightly with the layout grid on, 2026-09-14 16:2x: "for any and all long form content i wanted
   8 columns used not 6 for grid". /use-cases, its three case pages and /faq are prose and sat on four to nine; they take
   the same three to eleven as .longform above, by their own main class and not by .longform, whose capline trim would
   reach the mocks' paragraphs (.chippy__text, usecases.go) inside a use-case desk. This rule is (0,2,0) and later than
   .usecases .uc__desk (chippy:823), so the desk's twelve tracks are said again at (0,3,0) right under it. /contact is
   a form page and stays on six (Z, 814: "forms can be 6 including the new contact page"). longform_test.go holds it. */
.wrap--narrow.usecases > *, .wrap--narrow.faq > * { grid-column: 3 / 11; }
@media (max-width: 1000px) { .wrap--narrow.usecases > *, .wrap--narrow.faq > * { grid-column: 2 / 12; } }
@media (max-width: 720px) { .wrap--narrow.usecases > *, .wrap--narrow.faq > * { grid-column: 1 / -1; } }
.wrap--narrow.usecases > .uc__desk { grid-column: 1 / -1; }
.longform h1, .longform h2, .longform h3, .longform p, .longform li, .longform .legal__date, .longform .post__date, .longform .tut__status { text-box: trim-both cap alphabetic; }
.longform--serious p { font-size: var(--small-size); line-height: var(--small-lh); }
.longform--serious p.lede { font-size: var(--body-size); line-height: var(--body-lh); }
.longform--serious h2 { font-family: var(--mono); font-weight: var(--eyebrow-weight); font-size: var(--eyebrow-size); line-height: var(--eyebrow-lh); letter-spacing: var(--eyebrow-track); text-transform: uppercase; color: var(--lit); }

.note { max-width: calc(var(--span-6) + var(--gut) * 2); margin: 0 auto; padding: var(--space-96) var(--gut); } /* chippy:735: the six middle tracks, centred, the width the narrow pages take */
.note__h {
  font-weight: var(--heading-weight); font-size: var(--heading-size); line-height: var(--heading-lh); letter-spacing: var(--heading-track);
  color: var(--lit);
}
.note__p { color: var(--lit-soft); font-size: var(--body-size); line-height: var(--body-lh); margin-top: var(--space-24); }
.note__p--quiet { color: var(--lit-faint); font-size: var(--small-size); }

.account { padding-top: var(--space-96); padding-bottom: var(--space-96); } /* chippy:735: the sides keep the gutter, so the twelve tracks are the same width as the home page's */
.account__h2 { margin-top: var(--space-48); }
.account__row { display: flex; flex-wrap: wrap; gap: var(--space-12); margin-top: var(--space-24); align-items: center; }
/* chippy:558: the no-build sentence takes its own line above Sign out, so the one honest control
   reads as a sentence rather than a button crowded next to another one. */
.account__build { flex-basis: 100%; margin-top: var(--space-6); color: var(--lit-faint); font-size: var(--small-size); line-height: var(--body-lh); } /* chippy:638: the build named beside the button */
.account__nobuild { flex-basis: 100%; margin: 0; color: var(--lit-soft); font-size: var(--body-size); line-height: var(--body-lh); }
/* chippy:662: the since line under the address; the date never breaks. */
.account__since { margin-top: var(--space-6); }
.nowrap { white-space: nowrap; }
.account__aside { margin-top: var(--space-24); color: var(--lit-faint); font-size: var(--small-size); line-height: var(--body-lh); }
.account__dot { margin: 0 var(--space-6); color: var(--lit-faint); }

/* chippy:639. The one confirmation, in words, in the same quiet box the cowboy door uses (522). */
.confirm { margin-top: var(--space-24); padding: var(--space-24); border: 1px solid var(--desk-edge); border-radius: 12px; background: rgba(255,255,255,.03); }
.confirm__head { font-family: var(--sans); font-weight: var(--title-weight); font-size: var(--title-size); letter-spacing: var(--title-track); color: var(--lit); }
.confirm__body { margin-top: var(--space-12); font-size: var(--body-size); line-height: var(--body-lh); color: var(--lit-soft); }
.confirm__row { display: flex; align-items: center; gap: var(--space-12); margin-top: var(--space-24); }
.confirm__keep { font-size: var(--small-size); }
.confirm__msg { margin-top: var(--space-12); } /* chippy:808: no form on the site reserves a box for its status line */
/* chippy:733, finding 30. Z, 15:33: "the text inside the join the waitlist feels kinda thin. And then
   here, this looks right. This is like bold". This rule read `font: inherit`, which reset the solid
   button's 600 to the page's 400 on /sign-up, /sign-in and /account alone, so the same button was
   bold in the home band and thin on every form page. The weight comes from .btn and .btn--solid,
   once; a button never takes its font from its page. */
.account button.btn { cursor: pointer; }


/* ── chippy:746: the products menu, the voice shell and the product pages ─────────────────────── */

/* The dropdown right after the wordmark (fleet, Z 20:5x). A details element; the summary is a nav link with a
   small chevron; the panel hangs under it, one icon and one name a row, the same muted link style as the nav.

   chippy:804. Z, 2026-09-14 00:4x: "the dropdown color is horribly mismatching how the site colors
   theming/blending works." It was, and here is why. The panel painted itself with --chippy-bg and
   --chippy-edge, which are the MOCK APP WINDOW's chrome, not a surface of this site: --chippy-bg is a fixed
   slate (#1d2133) that does not follow --theme-hue, so turning the colour dial moved the whole page ground
   and left the menu sitting on it as a blue-grey card that belonged to nothing; and on the light theme
   --chippy-bg becomes --paper, a warm cream, which is why this block had to hard-code #fff and two rgba
   values on top of it to stop the menu reading as a sticky note. Three literals covering for a token that
   was never meant to be here.

   The panel takes the site's own raised-surface set (chippy:790 has since made it the only thing on it):
   --desk-deep for the ground, --desk-edge for the keyline, --lift-2 for the elevation. That move was right
   and it was not enough: a tester swept 216 -> 280 and the menu barely moved on the dark theme and did not
   move AT ALL on the light one, because the tokens themselves did not carry the hue. They do now, in both
   themes (see their note in :root), so the menu blends at every hue with NO override of its own -- there is
   no menu-only colour here, which is the whole point: a second borrowed surface would be the same mistake
   in a new place. The row's own hover ground is --desk-edge at a tenth, the same token again. */
.nav__menu { position: relative; margin-right: auto; }
/* chippy:819 held both disclosures in one box at the left, .nav__menus; chippy:868 left it holding the
   switcher alone when Pages went to the account row. chippy:912 drops the switcher (Z: "drop this and we'll
   just focus on chippy voice implicitly"), so the box holds nothing and both its rules go with it — a rule
   nothing wears is a rule that lies about the header. Nothing takes their place: .nav__inner is already
   space-between, so the wordmark sits at the left edge and the account row at the right with no auto margin
   to carry, measured on 7922 at 1280 (the row's items did not move) and at 390 (the group was display: none
   there since 819, so the phone bar is untouched). */
.nav__menu-btn { list-style: none; cursor: pointer; display: inline-flex; align-items: center; gap: var(--space-6); }
.nav__menu-btn::-webkit-details-marker { display: none; }
.nav__menu-btn .c-icon--chev { margin-right: 0; transition: transform .2s ease; }
/* chippy:868's select mark (lucide chevrons-up-down) was the switcher's glyph and nothing else on the site
   drew it; chippy:912 takes the switcher, so the rule and the icon both go (lucide.go). The carets below are
   Pages' and the phone Menu's, and they still turn over when their panel opens. */
.nav__menu[open] .nav__menu-btn .c-icon--chev { transform: rotate(180deg); }
.nav__drop {
  position: absolute; left: 0; top: calc(100% + var(--space-6)); z-index: var(--z-menu);
  display: flex; flex-direction: column; gap: var(--space-6);
  min-width: 200px; padding: var(--space-12);
  background: var(--desk-deep); border: 1px solid var(--desk-edge); border-radius: 12px;
  box-shadow: var(--lift-2);
}
/* chippy:799 dropped the glyph from these rows (Z: "we drop icons in the navlink and footer from the
   links"), so the gap that sat between the glyph and the word went with it — a gap kept for something
   that is gone is how a row ends up with a hole beside its word. The row is still the flex box: the
   padding and the hover ground are its own, not the glyph's. */
.nav__drop-item {
  display: inline-flex; align-items: center;
  padding: var(--space-6) var(--space-6); border-radius: 8px;
  color: var(--lit-soft); text-decoration: none; font-size: var(--small-size); white-space: nowrap;
}
.nav__drop-item:hover, .nav__drop-item:focus-visible { color: var(--speak); background: color-mix(in srgb, var(--desk-edge) 45%, transparent); }
/* Nothing here is drawn from the current-page mark: chippy:688 holds that this stylesheet never styles
   one nav item differently from another, and nav_set_test.go greps for it. The mark is for the reader
   a screen reader speaks to, not a highlight. */

/* One sentence of status per page, set like the legal date: mono, faint, honest. */
.product .status { color: var(--lit-faint); font-family: var(--mono); font-size: var(--code-size); margin-top: var(--space-24); }
:root[data-theme="light"] .product .status { color: var(--ink-faint); }
.product .said { margin: 0; margin-top: var(--space-48); padding-left: var(--space-24); border-left: 2px solid var(--speak); }
.product .said p { margin-top: 0; color: var(--lit); }
.product .said__who { margin-top: var(--space-12); color: var(--lit-faint); font-family: var(--mono); font-size: var(--code-size); }
:root[data-theme="light"] .product .said p { color: var(--ink); }
:root[data-theme="light"] .product .said__who { color: var(--ink-faint); }

/* The two keys: the lit one is the side the segment control shows. */
.keys { margin: 0; margin-top: var(--space-24); display: flex; gap: var(--space-12); }
.key {
  flex: 1 1 0; min-width: 0; display: flex; flex-direction: column; align-items: center; gap: var(--space-6);
  padding: var(--space-12); border-radius: 12px; border: 1px solid var(--chippy-edge); background: rgba(255,255,255,.03);
  color: var(--lit-faint-tile); transition: color .3s ease, border-color .3s ease; /* chippy:978: the tile lifts its own ground, so the unlit key's ink is the tile's faint, not the desk's */
}
.key kbd { font-family: var(--chord); font-size: var(--title-size); line-height: calc(1 / 0.698); color: inherit; } /* chippy:876: 1 / 0.698, so the key keeps its 19px box. The glyph is --chord, whose cap ratio is 0.7045781 and not Plex's 0.698, and the option sign itself comes from a fallback face, so under the lock it draws 18.219 px of ink where it drew 18.094, measured on /voice at 1280 on both ports. The box does not move; the sign is 0.125 px wider. */
.key__name { font-size: var(--small-size); }
.key__does { font-family: var(--mono); font-size: var(--code-size); }
[data-seg="listen"] .key--listen, [data-seg="talk"] .key--talk { color: var(--lit); border-color: var(--speak); }
:root[data-theme="light"] .key { border-color: rgba(0,0,0,.1); background: rgba(0,0,0,.02); color: var(--ink-faint); }
:root[data-theme="light"] [data-seg="listen"] .key--listen, :root[data-theme="light"] [data-seg="talk"] .key--talk { color: var(--ink); border-color: var(--speak); }

/* The segment control (Z: "a segment control at the top and it slides the content between the two marketing
   pages"). Two tabs on one pill, a thumb that slides under the chosen one, the same control the app will have. */
.seg {
  position: relative; display: grid; grid-template-columns: 1fr 1fr; margin-top: var(--space-24);
  padding: var(--space-6); border-radius: 999px; border: 1px solid var(--chippy-edge); background: rgba(255,255,255,.04);
}
.seg__tab {
  position: relative; z-index: var(--z-part-mid); text-align: center; padding: var(--space-6) var(--space-12); border-radius: 999px;
  color: var(--lit-soft); text-decoration: none; font-size: var(--small-size); font-weight: var(--title-weight);
}
.seg__tab[aria-selected="true"] { color: var(--theme-ink); }
.seg__tab:focus-visible { outline: 2px solid var(--theme); outline-offset: 2px; }
.seg__thumb {
  position: absolute; top: var(--space-6); bottom: var(--space-6); left: var(--space-6); width: calc(50% - var(--space-6)); border-radius: 999px;
  background: var(--theme); transition: transform .35s ease;
}
[data-seg="talk"] .seg__thumb { transform: translateX(100%); }
:root[data-theme="light"] .seg { border-color: rgba(0,0,0,.1); background: rgba(0,0,0,.03); }
:root[data-theme="light"] .seg__tab { color: var(--ink-soft); }
:root[data-theme="light"] .seg__tab[aria-selected="true"] { color: #fff; }
/* chippy:791. The tab a person is not on lights to the accent under the pointer. It did that on the dark
   theme only, and by accident: the underline rule for links in running text reached in and .legal a:hover
   painted it, while on the light theme the rule above out-specified that and the hover did nothing at all.
   Two selectors because the light rule is (0,2,1) and has to be answered at its own weight. */
.seg__tab:not([aria-selected="true"]):hover,
:root[data-theme="light"] .seg__tab:not([aria-selected="true"]):hover { color: var(--speak); }
:root[data-theme="light"] .seg__thumb { background: var(--theme-button); }

/* The slide: both panels on one track twice the width; the chosen side is in view, the other is off it. */
.slide { overflow: hidden; margin-top: var(--space-24); }
.slide__track { display: flex; width: 200%; transition: transform .45s ease; }
/* chippy:903: the panel carried padding-right 24 and nothing sat to its right. The two panels never show
   together (the one off the track is visibility:hidden and .slide clips), so the only thing it did was stop
   the prose 24 short of the right edge while the tablist above it ran the full column: measured on 7921,
   /voice #listen box 504 content 480 at 1280 and box 342 content 318 at 390, against 504 and 342 after. */
.slide__panel { flex: 0 0 50%; min-width: 0; box-sizing: border-box; }
.slide__panel > :first-child { margin-top: 0; }
[data-seg="talk"] .slide__track { transform: translateX(-50%); }
.slide__panel[aria-hidden="true"] { visibility: hidden; }

/* ── chippy:750: the blog ─────────────────────────────────────────────────────────────────────── */
.blog .post { margin-top: var(--space-48); }
/* chippy:759: a fenced block in a post, a contract shown as the listing it reads like; the code face, scrolls sideways, never wraps */
.blog .post__code { margin-top: var(--space-24); padding: var(--space-12); border-radius: 8px; overflow-x: auto; background: color-mix(in srgb, var(--lit) 6%, transparent); }
.blog .post__code code { font-family: var(--mono); font-size: var(--code-size); line-height: var(--body-lh); color: var(--lit); white-space: pre; }
/* ── chippy:816: a terminal capture, in the tool's own colour ──────────────────────────────────── */
/* Z on the layout post: layout "has its own pretty cli, it does color coding and all that and will look
   flashy in a blog post." blog.go turns the tool's own SGR codes into these classes over escaped text, so
   the page paints what the tool printed and nothing else. The colours are measured on the capture's own
   ground (the 6% wash over the page, rgb(32,37,44) dark and rgb(241,241,242) light), not on the page's:
   the red is the file's one red (chippy:737), 5.09:1 dark and 5.48:1 light; the green is built the same
   way, one fixed pair, because Z's rule at chippy:736 finding 20 is that a green has to darken on a white
   ground, and it lands at 6.56:1 dark and 5.45:1 light. Dim is --lit-soft, 6.91:1 and 6.66:1, not
   --lit-faint, which is 2.89:1 on the light ground: the post's own section is The comment column is the
   point, so the comments stay readable while the glyphs still carry the eye. No capture on the site draws
   a warn row yet; the amber is here so the day one does, it is not unstyled. */
:root { --term-ok: hsl(146 50% 50%); --term-warn: hsl(38 90% 58%); --term-dim: var(--lit-soft); }
:root[data-theme="light"] { --term-ok: hsl(146 55% 28%); --term-warn: hsl(32 85% 32%); }
.blog .term__ok { color: var(--term-ok); }
.blog .term__err { color: var(--bad); }
.blog .term__warn { color: var(--term-warn); }
.blog .term__dim { color: var(--term-dim); }
.blog .term__bold { font-weight: 600; }
/* The drawn hint, chippy:816. The fade that used to live here is gone: a pixel scan of the block at
   390 found no glyph in the last 24px on any of its 332 scanlines, because the tool pads the path
   field to column 44 and the visible window always ends inside that gutter, so there was nothing at
   the edge to fade and three cold readers each described a finished-looking box. What reads instead
   is a mark. It is the site's own arrow, the one every go link wears, in the dim ink the comment
   column already uses, and it sits in a strip of padding reserved under the last row, so it is never
   over a character even when the block is scrolled to its far end. Only under 820px, the width the
   capture measurably starts clipping at; above that nothing is hidden and nothing is drawn. It is
   persistent: telling it to disappear at the end of the scroll needs animation-timeline, which this
   site's own readers on Safari would not get, and a hint that works on one browser is not a hint. */
/* The wrapper is a flex row for one reason, measured: as a plain block it carried a 16px line strut under
   the pre, and the mark, pinned to the wrapper, landed below the block instead of inside it. As a flex row
   the wrapper's bottom edge is the block's bottom edge, which is what the mark is anchored to. The mark is
   drawn nowhere by default: above 820px nothing is hidden, and a block in flow there put a stray arrow
   under every capture, which is what the measurement caught before a reader did. */
.blog .post__termwrap { position: relative; display: flex; }
.blog .post__termwrap .post__term { flex: 1 1 auto; min-width: 0; }
.blog .post__termmore { display: none; }
@media (max-width: 820px) {
  .blog .post__termwrap .post__term { padding-bottom: var(--space-24); }
  .blog .post__termmore {
    /* The 1em is the margin a pre carries by default. The site never declares it and spacing_test.go will
       not let me zero it (chippy:735, finding 25: the space belongs to the element that follows), so the
       wrapper stands 16px taller than the block and the mark is offset by exactly that much to land inside
       it. Measured: the mark sits within the block's box on both themes, clear of every row. */
    position: absolute; right: var(--space-12); bottom: calc(var(--space-6) + 1em / 0.698);
    display: flex; align-items: center; color: var(--term-dim); pointer-events: none;
  }
  .blog .post__termmore .c-icon { margin-right: 0; --oversize-scale: 1.6; }
}
/* ── chippy:852, chippy:848: the mark belongs to a block that is actually scrolling ────────────── */
/* Measured on /blog/the-layout before this block, both themes, scrollWidth against clientWidth on
   every pre: at 390 the contract block is 452 in a 342 window, 110px of it hidden, and it carries no
   wrapper and no mark at any width; at 430 it hides 70; from 500 up it fits. The captures beside it
   hide 295 at 390 and 185 at 500, and they wear the mark. That is the inconsistency inside one post
   (chippy:848), and it is the whole of chippy:852.
   The same measurement caught the other half: at 700 and at 819 both captures are exactly as wide as
   their window, 652 and 639, nothing hidden at all, and the arrow is drawn anyway, because the rule
   above asks the VIEWPORT whether it is under 820px and the post's column stops growing before the
   window does. A mark over a block that does not scroll is a worse lie than no mark.
   So the question is overflow, not width. postscroll.js asks each block scrollWidth > clientWidth and
   writes data-scroll on its wrapper, on load, on resize and once the mono face has landed; these two
   rules are the answer and they beat the 820px rule on specificity in both directions. The 820px rule
   is left exactly as it stands as the floor with no JavaScript: a capture clips on every phone, so it
   keeps its cue there. The geometry is 816's, unchanged, because 816 measured it: the 1em is the
   margin a pre carries by default, which spacing_test.go will not let anyone zero (chippy:735 finding
   25), and the strip of padding under the last row is what keeps the arrow off a character. */
/* 816 gave the capture flex: 1 1 auto so the wrapper's flex row would not shrink it to its content. Every
   fence is in a wrapper now, so the plain block needs the same word or it stops filling the column: measured,
   the contract block went from 552px wide at a 600px window to 452px, its own content width, and its wash with
   it. Same two declarations, one selector wider. */
.blog .post__termwrap .post__code { flex: 1 1 auto; min-width: 0; }
/* Two questions, not one, and they are asked of different things. data-scroll is whether the block scrolls
   at all, and it owns the strip of padding, so the strip is reserved once and never moves under a reader's
   thumb. data-more is whether there is anything still to the right, and it owns the mark alone, through
   opacity, so the block's geometry does not change as the block is scrolled. 816 wrote the mark persistent
   and said why: making it go at the end of the scroll needed animation-timeline, and this site's own readers
   on Safari would not have got it. postscroll.js is not animation-timeline and every browser runs it, so the
   reason is spent. A cold reader at 390 put it plainly, scrolled to the far end: the arrow "keeps telling you
   that even when you have already seen everything". It goes at the end now and comes back on the way left.
   The transition is opacity and nothing else, no movement, so there is no reduced-motion rule here. There is
   also a reason not to write one: the reduced-motion guard in theme_test.go (chippy:857) finds its own
   block by the Reduced motion banner and names the html one-liner and that block, so a new
   @media (prefers-reduced-motion: reduce) anywhere in this file fails the suite by line until it is named
   there or folded into the banner block. */
.blog .post__termwrap[data-scroll="yes"] .post__code { padding-bottom: var(--space-24); }
.blog .post__termwrap[data-scroll="yes"] .post__termmore {
  position: absolute; right: var(--space-12); bottom: calc(var(--space-6) + 1em / 0.698);
  display: flex; align-items: center; color: var(--term-dim); pointer-events: none;
  opacity: 1; transition: opacity .15s linear;
}
.blog .post__termwrap[data-scroll="yes"][data-more="no"] .post__termmore { opacity: 0; }
.blog .post__termwrap[data-scroll="yes"] .post__termmore .c-icon { margin-right: 0; --oversize-scale: 1.6; }
.blog .post__termwrap[data-scroll="no"] .post__code { padding-bottom: var(--space-12); }
.blog .post__termwrap[data-scroll="no"] .post__termmore { display: none; }
.blog .post h2 { margin-top: 0; }
/* chippy:791: .blog .post h2 a is drawn with every other heading-that-is-an-anchor, at THE THREE KINDS. */
.blog .post__date { color: var(--lit-faint); font-family: var(--mono); font-size: var(--code-size); margin-top: var(--space-6); }
.blog h1 + .post__date { margin-top: var(--space-12); }
:root[data-theme="light"] .blog .post__date { color: var(--ink-faint); }
.blog .draft {
  display: inline-block; margin-left: var(--space-6); padding: 0 var(--space-6); border-radius: 999px;
  border: 1px solid var(--speak); color: var(--speak); font-family: var(--mono); font-size: var(--code-size);
}

/* ── chippy:757: breadcrumbs above the heading on the blog and the tutorials ─────────────────────── */
/* Z: "from the top of the page you can get back to where you came from, not just at the bottom." Home, the section,
   the page you are on (not a link), in the small step, the section's colour; the heading follows on the next rung. */
.blog .crumbs { display: flex; flex-wrap: wrap; gap: var(--space-6); color: var(--lit-soft); font-size: var(--small-size); line-height: var(--small-lh); }
/* chippy:791: a crumb is a navigation link, so it is kind (a) like the nav and the footer: the muted colour,
   no underline, the accent under the pointer. It used to underline on hover instead, which was a fourth
   behaviour for a link that does the nav's job. */
.blog .crumbs a { color: inherit; text-decoration: none; }
.blog .crumbs a:hover { color: var(--speak); text-decoration: none; }
/* chippy:791. The crumb you are on is a <span aria-current="page">, and it was drawn exactly like the crumbs
   either side of it that ARE links: the same colour, no underline. The 791 tester, walking /about by eye with no
   DOM: "two adjacent, identically-styled words, only one clickable." The trail's links keep (a)'s muted colour,
   because muted means link everywhere else on this site; the word for the page you are on takes the text colour,
   so it reads as the label it is and not as a link that does nothing. Scoped to the crumbs on purpose: chippy:688
   holds that the NAV draws nothing from aria-current, and after this it still draws nothing from it. */
.blog .crumbs [aria-current="page"] { color: var(--lit); }
.blog .crumbs__sep { color: var(--lit-faint); }
.blog .crumbs + h1 { margin-top: var(--space-12); }
:root[data-theme="light"] .blog .crumbs { color: var(--ink-soft); }
:root[data-theme="light"] .blog .crumbs__sep { color: var(--ink-faint); }

/* ── chippy:763: the index grids ──────────────────────────────────────────────────────────────── */
/* Z: "tutorials, use cases, blog, and the technical blog each need an index page with cute little previews to all the
   nested articles, and it should be more of a grid layout." One card: the picture sixteen by ten, the title, a meta
   line where there is one, one line of text; the whole card the link. As many columns as fit, one column narrow. */
/* The narrow column is 504 px wide (chippy:652, one width), so two cards of 240 sit side by side in it and one on a phone. */
.cards { display: grid; grid-template-columns: repeat(auto-fill, minmax(220px, 1fr)); gap: var(--space-24); margin-top: var(--space-48); }
/* chippy:877: the card's own gap is half a space, not a quarter. Its three words are ink now (the trim rule
   under the base type), so 6 drew 6 of white between a title and a line whose own lines stand 12.63 apart.
   Measured at 1280 on /blog and /use-cases, ink to ink: title to line 17.42 before, 12 after; the picture to
   the title 21.2 before, 24 after (this gap plus .card__title's own). */
.card, .legal a.card { display: flex; flex-direction: column; gap: var(--space-12); color: inherit; text-decoration: none; min-width: 0; }
.card__pic { display: block; aspect-ratio: 16 / 10; overflow: hidden; border-radius: var(--space-12); background: #000; outline: 1px solid var(--desk-edge); outline-offset: -1px; }
.card__pic img { display: block; width: 100%; height: 100%; object-fit: cover; }
.card__wait {
  display: grid; place-items: center; padding: var(--space-24); box-sizing: border-box; background: none;
  border: 1px dashed var(--lit-faint); color: var(--lit-soft); text-align: center; font-size: var(--small-size); line-height: var(--small-lh);
}
:root[data-theme="light"] .card__wait { border-color: var(--ink-faint); color: var(--ink-soft); }
/* chippy:862. Z, 2026-09-14 16:2x, of the index cards: "all these thumbnails need to show the artsy background behind
   chippy." The waiting card was a dashed box on the page's ground; it is now the desk's own painting (the sea, .ground
   above, the same file, cover and centred; chippy:866 took the desk's vignette off both) with its sentence over it, so it sits on the grid the
   way the filmed cards do. The ink is the dark theme's --lit as a literal in both themes, the way the desk keeps its
   own colours: a photograph is the same in daylight. A scrim darkens the middle so the sentence reads on the sky. */
.card__wait, :root[data-theme="light"] .card__wait {
  position: relative; border: 0; color: #f2f0ea; text-shadow: 0 1px 2px rgba(6, 8, 18, .6);
  /* chippy:866: the vignette and the inset 60px shadow are gone here too, the same pair by the same
     numbers. The scrim below stays: it is not a frame effect, it is what lets the sentence read on the sky. */
  background: radial-gradient(70% 70% at 50% 50%, rgba(6, 8, 18, .62) 0%, rgba(6, 8, 18, .38) 100%),
    #1c2742 url("art/desk-sea-v1.jpg") center / cover no-repeat;
}
.card__title { margin-top: var(--space-12); font-family: var(--sans); font-size: var(--title-size); line-height: var(--title-lh); font-weight: var(--title-weight); letter-spacing: var(--title-track); }
.card:hover .card__title { text-decoration: underline; text-decoration-thickness: 1px; text-underline-offset: calc(.15em / 0.698); }
.card__meta { color: var(--lit-faint); font-family: var(--mono); font-size: var(--code-size); }
:root[data-theme="light"] .card__meta { color: var(--ink-faint); }
.card__meta .tag {
  display: inline-block; margin-left: var(--space-6); padding: 0 var(--space-6); border-radius: 999px;
  border: 1px solid var(--lit-faint); color: var(--lit-soft); font-family: var(--mono); font-size: var(--code-size);
}
:root[data-theme="light"] .card__meta .tag { border-color: var(--ink-faint); color: var(--ink-soft); }
.card__line { color: var(--lit-soft); font-size: var(--small-size); line-height: var(--small-lh); }
:root[data-theme="light"] .card__line { color: var(--ink-soft); }
.blog__more { margin-top: var(--space-12); color: var(--lit-soft); font-size: var(--small-size); line-height: var(--small-lh); }
:root[data-theme="light"] .blog__more { color: var(--ink-soft); }
@media (max-width: 720px) { .cards { grid-template-columns: 1fr; } }

/* ── chippy:743: the tutorials ────────────────────────────────────────────────────────────────── */
/* One page per feature on the blog's shell: the paragraph, the film (sixteen by ten, the page in the home page's window
   over the painting, cut from the feature's story by qa-factory/tools/tutorials/films.mjs) or, for a feature only the app
   shows, one sentence in a dashed box where the film will go; then the steps. */
.tutorials .tut { margin-top: var(--space-48); }
.tutorials .tut h2 { margin-top: 0; }
/* chippy:791: .tutorials .tut h2 a is drawn with every other heading-that-is-an-anchor, at THE THREE KINDS. */
.tutorials .tut__film { display: block; width: 100%; height: auto; aspect-ratio: 16 / 10; margin-top: var(--space-24); border-radius: var(--space-12); background: #000; }
.tutorials .tut__wait {
  display: grid; place-items: center; aspect-ratio: 16 / 10; margin-top: var(--space-24); padding: var(--space-24); box-sizing: border-box;
  border: 1px dashed var(--lit-faint); border-radius: var(--space-12); color: var(--lit-soft); text-align: center;
}
:root[data-theme="light"] .tutorials .tut__wait { border-color: var(--ink-faint); color: var(--ink-soft); }
.tutorials .tut__steps { margin-top: var(--space-24); padding-left: var(--space-24); }
.tutorials .tut__steps li + li { margin-top: var(--space-12); }
.tutorials .tut__status { margin-top: var(--space-24); color: var(--lit-soft); font-size: var(--small-size); line-height: var(--small-lh); }
:root[data-theme="light"] .tutorials .tut__status { color: var(--ink-soft); }

/* ── chippy:741: the marks move their bars to a playing read ─────────────────────────────────── */
/* While the page's root carries is-reading (chippy.js sets it for any playing read: the hero's window or
   a window on /use-cases), the nav's mark and the footer's mark take their three heights from the read, one a spoken word, in
   the hover wave's range; heights only, the corners and the widths untouched, nothing moved. They rest the instant the
   class goes. Under reduced motion chippy.js never sets the class. */
html.is-reading .nav__brand .mark i, html.is-reading .foot__meta .mark i { transition: height .12s ease; }
html.is-reading .nav__brand .mark i:nth-child(1), html.is-reading .foot__meta .mark i:nth-child(1) { height: var(--mark-1, 55%); }
html.is-reading .nav__brand .mark i:nth-child(2), html.is-reading .foot__meta .mark i:nth-child(2) { height: var(--mark-2, 100%); }
html.is-reading .nav__brand .mark i:nth-child(3), html.is-reading .foot__meta .mark i:nth-child(3) { height: var(--mark-3, 30%); }
/* chippy:751: the hero image, sixteen by ten exactly, the home page's window over the painting, at the top of the
   post under its date and at the head of each card on the index. */
.blog .post__art { display: block; width: 100%; height: auto; aspect-ratio: 16 / 10; object-fit: cover; margin-top: var(--space-24); border-radius: var(--space-12); }
.blog .post .post__art { margin-top: 0; }
.blog .post .post__art + h2 { margin-top: var(--space-24); }

/* ══ THE DOORS ══════════════════════════════════════════════════════════════════════════════════
   chippy:878. Z, reviewing the nightly 2026-09-14 16:43, on /sign-up, where the heading and the
   paragraph ran left, the field sat beside a Join the waitlist button on one row, a Google button
   stood on its own row below at a different width and two lines of small print closed it: "this
   layout is a little weird; i would try to make it look a little more like this (not content or
   design, like layout and flow)", with a Stalkr sign-in as the reference FOR LAYOUT AND FLOW ONLY.
   So both doors are ONE CENTRED COLUMN OF ONE WIDTH: the mark, the heading, one line, the field at
   the column's full width, the primary button full width under it, a hairline with or, Continue
   with Google full width, the small print.
   THE WIDTH IS FOUR OF THE TWELVE TRACKS, --span-4, 328 px. The grid centres a block only on an even
   span, which leaves 152, 328 and 504 as the honest picks; 152 is a slot and 504 is what the page
   already was, so 328 is the one that reads as a narrow column. It is a max-width rather than a
   track pair so the column is the SAME 328 at every size instead of stepping with the breakpoints:
   measured before, the door was 504 at 1280, 672 at 720 and 342 at 390, three widths and none of
   them centred; after, 328 at all three, in both themes.
   chippy:795 still holds here: the control's EDGE is on the column's line, the field, the button and
   the Google button all share both edges, and only the text inside the column is centred.
   The home page's band is NOT a door and nothing here reaches it; .door is on the two door pages. */
main.wrap--narrow.door { text-align: center; }
main.wrap--narrow.door > * { grid-column: 1 / -1; width: 100%; max-width: var(--span-4); margin-left: auto; margin-right: auto; }
.door .door__mark { display: flex; justify-content: center; }
.door .door__mark ~ .h2 { margin-top: var(--space-24); } /* the space belongs to the element that follows, chippy:735 finding 25; a general sibling because /sign-up carries the waitlist heading and the download heading, and whichever of the two the build state shows is the one under the mark */
/* The field and its button stack, each the column's full width. The row keeps its class and its
   12 px step (chippy:733's one form rule, TestTheFormGapsAreOneRule); only its direction changes,
   and as a block rather than a column flex, because flex-basis on .signup__input is a WIDTH and
   would become a height the moment the row turned. */
.door .signup__row { display: block; }
.door .signup__input { display: block; width: 100%; text-align: left; }
/* chippy:916. Z, 2026-09-15 15:2x, over the centred EMAIL ADDRESS on /sign-up: "labels need to be left
   aligned; duh". The door centres the text in its column (main.wrap--narrow.door above) and the label,
   which declares no alignment of its own, inherited it: measured on the nightly, the label's text began
   107.05 px inside its own field's left edge, at 1280 and at 390 alike, on /sign-up, /sign-in and /start.
   The FIELD was exempted on the line above and the label over it was not; now the pair reads down one
   keyline. Only the alignment moves: the mono small caps in the theme blue are chippy:695's and stay. */
.door .signup__label { text-align: left; }
.door .signup__row .btn { width: 100%; margin-top: var(--space-12); }
.door .btn--google { width: 100%; }
/* The hairline with or between the two doors (site/google.go draws it inside the Google block, so a
   site with no Google client has no rule and no word either). The line is the site's own hairline
   token, the word the faint ink at the small step. */
.door .google__or { display: flex; align-items: center; gap: var(--space-12); color: var(--lit-faint); font-size: var(--small-size); line-height: var(--body-lh); }
.door .google__or::before, .door .google__or::after { content: ""; flex: 1 1 auto; height: 1px; background: var(--desk-edge); }
.door .google__or + .btn--google { margin-top: var(--space-24); } /* the space belongs to the element that follows, chippy:735's ladder */

/* ══ THE COLOUR DOT AND ITS PANEL (chippy:882) ══════════════════════════════════════════════════════
   Z's pick off prototypes/colour-dot/v1, 2026-09-14 16:55: the slider, three appearance segments, and
   "the dot doesn't really look clickable, it would need to look like [a ring with a filled centre] at
   rest i think". So the prototype's OPEN-state ring is worn AT REST: a thin ring in the current accent,
   a gap of the bar's own ground, and a filled centre in the same accent. A flat sample of colour reads
   as decoration; a ring with a gap reads as a control, which is the whole of the nuance.

   THE FIRST CUT OF THIS RULE FAILED THAT, AND IT WAS MEASURED FAILING. It drew a 14 px face with a
   1.5 px gap and a 1.5 px ring at opacity .72, carrying chippy:802's rest-dim over from the prototype.
   Sampled across the dot's diameter at device scale 3 (so a 1.5 px band is 4.5 device pixels and cannot
   be lost to rounding), the row through the centre came back: bar, ring 3 device px, gap 4, face 39,
   gap 4, ring 3, bar. A ONE PIXEL RING behind a 1.33 px gap, and the accent it was drawn in measured
   (86,128,193) against the theme's own (112,169,255), which is .72 of it. A fresh tester, asked before
   it pressed anything, read it as "a small, flat, filled circle with a thin darker rim ... a status
   light or color swatch", and said in as many words that it saw "no ring-with-gap". It was right.
   So the bands are drawn at a size a person can see: a 12 px face, 2 px of the bar's own ground as the
   gap, 2 px of the accent as the ring, 20 px across in all, inside the same 22 px control. And the rest
   state is FULL STRENGTH, because chippy:882's nuance and chippy:802's dim pull against each other and
   the nuance is the newer word: 802 called a 96 px rainbow slider and a sun beside it "too busy and
   distracting", and one 20 px dot is already the quiet version of that pair. The wake moved off opacity
   and onto the ground the bar's own rows use under a pointer (.nav__drop-item, .nav__phone-item), which
   is the site's vocabulary for "this is a thing you press" and says it without dimming the ring.
   Two box-shadows on one face, so the ring costs no layout; the inset hairline keeps the face's edge
   honest against its own hue. The face is var(--theme), so the control is a sample of what it sets and
   turns with the slider in the same frame.

   The names are .nav__dot and .nav__dot-face, not the prototype's .dot: .dot is taken on this site by
   the scene dots (chippy:875, above), and .seg is the two-up segment control (chippy:746), which this
   panel reuses with a modifier rather than forking. */
.nav__colour { position: relative; display: inline-flex; }
/* 28 px, and the number was measured too. The wake is the ground the bar's own rows use, and at 22 px
   the 20 px dot covered all but a 1 px sliver of it: a second tester, hovering and photographing,
   reported "no visible change I could detect", and it was right, because the tint was drawn where the
   dot already was. 28 leaves 4 px of that ground showing all the way round, which is a disc a person
   sees. It also makes the hit target 28 px rather than 22, which is what a phone wants: the same tester
   called the core "12x12 CSS px" and wondered about the hit area. The bar does not grow: its inner
   height is 30.4 px (54.4 less two 12 px paddings), so a 28 px control still sits inside it. */
.nav__dot {
  display: inline-flex; align-items: center; justify-content: center;
  width: 28px; height: 28px; padding: 0; margin: 0; border: 0; border-radius: 999px;
  background: none; cursor: pointer; transition: background-color .15s ease;
}
.nav__dot-face {
  display: block; width: 12px; height: 12px; border-radius: 999px;
  background: var(--theme);
  box-shadow: 0 0 0 1px var(--chippy-edge) inset, 0 0 0 2px var(--desk), 0 0 0 4px var(--theme);
}
.nav__dot:hover, .nav__dot:focus-visible, .nav__dot[aria-expanded="true"] { background: color-mix(in srgb, var(--desk-edge) 45%, transparent); }
.nav__dot:focus-visible { outline: 2px solid var(--theme); outline-offset: 2px; }

/* The panel is the Products menu's surface, token for token (chippy:804): the same ground, the one
   tinted hairline, the same radius and the same lift, and no colour of its own anywhere. It hangs from
   the RIGHT edge because the dot is the last thing in the row and a panel hung from its left would leave
   the page at 390 (chippy:851). It sizes the way .nav__drop sizes, a min-width and not a width off the
   grid: 248 px is what the slider wants, because 96 px in the row was always the reason a hue was hard
   to land on. */
.nav__panel {
  position: absolute; right: 0; top: calc(100% + var(--space-6)); z-index: var(--z-menu);
  min-width: 248px; padding: var(--space-12);
  background: var(--desk-deep); border: 1px solid var(--desk-edge); border-radius: 12px;
  box-shadow: var(--lift-2);
}
.nav__panel-label {
  margin: 0;
  font-family: var(--mono); font-size: var(--eyebrow-size); letter-spacing: var(--eyebrow-track); text-transform: uppercase;
  color: var(--lit-faint); line-height: calc(1 / 0.698); /* chippy:876: 1 / 0.698 */
}
.nav__panel-group + .nav__panel-group { margin-top: var(--space-12); padding-top: var(--space-12); border-top: 1px solid var(--desk-edge); }
.nav__panel-body { margin-top: var(--space-12); }
@media (prefers-reduced-motion: no-preference) {
  .nav__panel { animation: nav-panel-in .14s ease both; }
  @keyframes nav-panel-in { from { opacity: 0; transform: translateY(-4px); } }
}

/* The hue track: the retired .nav__hue's own declarations, full width and with the theme's lightness in
   the gradient rather than a hard-coded 72%, so the light theme's darker accents are what the track
   shows. Every hue on it was measured against the a11y floors before it was allowed to ship; the table
   is on chippy:882 and the sweep is TestEveryHueTheSliderCanProduceClearsTheContrastFloors in
   colour_dot_882_test.go. */
.nav__panel-hue {
  display: block; width: 100%; height: 8px; margin: 0; appearance: none; -webkit-appearance: none;
  border-radius: 999px; cursor: pointer;
  background: linear-gradient(90deg,
    hsl(0 100% var(--theme-l)), hsl(60 100% var(--theme-l)), hsl(120 100% var(--theme-l)),
    hsl(180 100% var(--theme-l)), hsl(240 100% var(--theme-l)), hsl(300 100% var(--theme-l)), hsl(360 100% var(--theme-l)));
}
.nav__panel-hue::-webkit-slider-thumb {
  appearance: none; -webkit-appearance: none; width: 14px; height: 14px; border-radius: 999px;
  background: var(--theme); border: 2px solid var(--lit);
}
.nav__panel-hue::-moz-range-thumb { width: 14px; height: 14px; border-radius: 999px; background: var(--theme); border: 2px solid var(--lit); }
.nav__panel-hue:focus-visible { outline: 2px solid var(--theme); outline-offset: 3px; }

/* THREE UP, NOT A SECOND SEGMENT CONTROL. .seg is the site's own (chippy:746) and every declaration it
   has is right here; what a third tab changes is the track count, the thumb's width and how far it
   travels. The tabs in the panel are <button> where /voice's are <a>, so this block adds the three
   properties a button does not inherit and nothing else. The thumb carries the PICK, not the painted
   theme: on System the System tab is the selected one whatever the Mac is showing. */
.seg--three { grid-template-columns: 1fr 1fr 1fr; margin-top: 0; }
/* aria-checked, not aria-selected: this is one choice out of three and not a tablist (download.go says
   why), so the two rules .seg writes against aria-selected are answered here at the same weight. */
.seg--three .seg__tab[aria-checked="true"] { color: var(--theme-ink); }
:root[data-theme="light"] .seg--three .seg__tab[aria-checked="true"] { color: #fff; }
.seg--three .seg__tab { border: 0; background: none; cursor: pointer; font-family: var(--sans); }
.seg--three .seg__thumb { width: calc((100% - var(--space-12)) / 3); }
:root[data-mode="dark"] .seg--three .seg__thumb { transform: translateX(100%); }
:root[data-mode="system"] .seg--three .seg__thumb { transform: translateX(200%); }

/* chippy:894. THE WARMTH ROW: the panel's third group, the label and one switch on one line, the way a
   settings row reads. The switch is the site's own tokens and nothing new: the track is the panel's hairline
   and the desk's 4% lift at rest (the .seg's own ground), the theme colour when on, the knob the lit ink off
   and the theme's ink on, which is the seg thumb's pairing. On the light paint the whole row dims and the
   switch is aria-disabled (theme.js), because there is no warm ground to turn on there: the kept pick is not
   lost, it waits for dark. */
.nav__panel-group--warmth { display: flex; align-items: center; justify-content: space-between; gap: var(--space-12); }
.nav__switch {
  position: relative; flex: 0 0 auto; width: 34px; height: 20px; padding: 0; margin: 0;
  border: 1px solid var(--desk-edge); border-radius: 999px; background: rgba(255,255,255,.04); cursor: pointer;
}
.nav__switch-knob {
  position: absolute; top: 2px; left: 2px; width: 14px; height: 14px; border-radius: 999px;
  background: var(--lit-soft); transition: transform .2s ease, background-color .2s ease;
}
.nav__switch[aria-checked="true"] { background: var(--theme); border-color: var(--theme); }
.nav__switch[aria-checked="true"] .nav__switch-knob { background: var(--theme-ink); transform: translateX(14px); }
.nav__switch:focus-visible { outline: 2px solid var(--theme); outline-offset: 2px; }
.nav__switch[aria-disabled="true"] { cursor: default; }
:root[data-theme="light"] .nav__panel-group--warmth { opacity: .4; }
:root[data-theme="light"] .nav__switch { background: rgba(0,0,0,.03); }
:root[data-theme="light"] .nav__switch-knob { background: var(--ink-soft); }
:root[data-theme="light"] .nav__switch[aria-checked="true"] { background: var(--theme-button); border-color: var(--theme-button); }
:root[data-theme="light"] .nav__switch[aria-checked="true"] .nav__switch-knob { background: #fff; }

/* THE PANEL AT PHONE WIDTH, and this was measured before it was written. chippy:851 put the controls in
   the bar under 720 and one Menu disclosure at the right edge, and the two are SIBLINGS in .nav__inner,
   not a row: measured at 390 on 7979, the pair sat at x 165.8 to 254.8 and Menu at 313 to 366, so the
   dot inherits a slot in the middle of the bar rather than its end. A 248 px panel hung from the right
   edge of a control at x 221 starts at -26.8 and leaves the page, which the first cut of this row did:
   measured at 390, left -26.8, right 221.3. The prototype never saw it, because there the dot WAS the
   last thing in the row.
   So under 720 the panel hangs from the PAGE rather than from the dot: .nav__colour stops being the
   containing block and .nav__inner becomes it, which is the bar itself, and the panel takes the page's
   own gutter. Measured after: left 118, right 366, inside the page at both edges, and the document is
   still 390 wide. Above 720 nothing changes and the panel hangs from the dot as before.
   Where the dot SITS in the bar at 390 is chippy:851's, not this row's, and it is not moved here. */
@media (max-width: 720px) {
  .nav__inner { position: relative; }
  .nav__colour { position: static; }
  .nav__panel { right: var(--gut); }
}

/* THE CONTROL DOES NOT COME TO A PHONE AT ALL (chippy:908). Z, 2026-09-15 15:2x, on a screenshot of the
   phone nav with only the theme dot standing in its centre: "don't bother with theming on mobile for now".
   .nav__colour is the wrapper around BOTH the dot and the panel under it (download.go, siteNav), so
   one declaration takes the pair and there is nothing left in the bar to press or to hang a panel from.
   IT SITS HERE, AT THE END OF THIS SECTION, AND NOT IN chippy:851's PHONE NAV BLOCK SIX HUNDRED LINES
   ABOVE, and that placement is the whole of the row. .nav__colour's base rule is up in this same section
   and comes LATER in the file than that block; both are one class of specificity, so a display: none
   written up there loses the cascade to display: inline-flex and hides nothing at all. After the rule it
   overrides, it wins. phone_theme_908_test.go asserts the byte offsets, because that is the defect.
   CSS ONLY, and the markup is deliberately untouched: theme.js keeps running and the head's pre-paint
   script keeps reading ?theme=, ?hue= and localStorage, so a theme picked on the desk or handed in the
   address still paints on a phone. Taking the control out of download.go would break exactly that.
   The two rules above stay where they are: they are chippy:882's, they cost nothing on a hidden wrapper,
   and they are what the desk-side panel falls back to if this hide is ever lifted. */
@media (max-width: 720px) {
  .nav__colour { display: none; }
}

/* ══ THE STEPPED FLOW AFTER SIGN-UP (chippy:880) ════════════════════════════════════════════════
   Z, 2026-09-14 16:50, asked whether the site had an onboarding story and then answered it:
   "directionally it should be like this", holding up two screens of another product's onboarding
   for SHAPE ONLY, with the rule on them, "only study it abstractly do not copy it verbatim". The
   shape he pointed at: the centred column, the mark over it, a row of thin step segments with the
   one you are standing on filled, the question as the heading, a grid of pick cards, a full-width
   Continue, a Skip under it.
   THE COLUMN IS ALREADY DRAWN. .door above (chippy:878) is that centred column, and it already
   centres the text, holds the page to --span-4, stacks the row and makes the button full width; the
   heading is .h2, the line under it .lede, the box to say more .signup__input, the button .btn--solid
   and the two quiet links .account__aside. Nothing here redraws any of them. What the site had no
   rule for is a bar of steps and a card you can pick, and that is the whole of this block.
   NO ICONS ON THE CARDS, so nothing here draws one. The icon set is closed by
   TestTheIconSetCarriesOnlyWhatTheSiteDraws, chippy:869 has not settled it, and it carries no honest
   glyph for "a friend" or "search"; chippy:799 is the precedent, Z's own words that the icons in the
   nav and the footer "are not helping as much as i thought".
   THE BAR AND THE CARD ARE CONTROLS, NOT TEXT BLOCKS, so both take text-box: normal out of
   chippy:872's trim, for the reason 872 itself gives the fields: the trim leaves a control's border
   box where it was but moves the text off the centre the UA gives it. The card's words sit in their
   own span, which the trim's selector list never reached, so they are unaffected either way. */
.onb__steps { display: flex; gap: var(--space-6); list-style: none; margin: var(--space-24) 0 0; padding: 0; }
.onb__step { flex: 1 1 0; height: 3px; border-radius: 2px; background: var(--desk-edge); text-box: normal; }
/* A step behind you is neither the hairline nor the accent: it is the accent mixed back into the
   hairline, so the bar reads left to right as one line that has been filled in this far. */
.onb__step--done { background: color-mix(in srgb, var(--theme) 45%, var(--desk-edge)); }
.onb__step--on { background: var(--theme); }

/* Two columns, as Z's reference had them, and one under 560 where two cards of these words would set
   three and four lines each. The grid's gap is the form's 12 px step (chippy:733's one form rule),
   not the 24 the page's ladder gives blocks, because these are controls in a group. */
.onb__grid { display: grid; grid-template-columns: repeat(2, minmax(0, 1fr)); gap: var(--space-12); margin-top: var(--space-24); }
@media (max-width: 560px) { .onb__grid { grid-template-columns: minmax(0, 1fr); } }

/* The card is the site's one panel again, the same parts .confirm and .signup__input are built from:
   the desk plus a 3% white lift inside a --desk-edge hairline. Picked, the hairline becomes the accent
   and the lift becomes a wash of it, which is the only state the flow actually needs a person to see. */
.onb__pick {
  position: relative; display: flex; align-items: center; justify-content: center;
  min-height: 64px; padding: var(--space-12); cursor: pointer; text-box: normal;
  border: 1px solid var(--desk-edge); border-radius: 10px;
  background: rgba(255,255,255,.03);
  color: var(--lit-soft); font-size: var(--small-size); line-height: var(--body-lh);
}
.onb__pick:hover { border-color: color-mix(in srgb, var(--theme) 40%, var(--desk-edge)); color: var(--lit); }
/* The input is the state and the label is the control, so the input is taken off the page but left
   where a keyboard can still reach it: never display:none, which would take it out of the tab order.
   It is sized by the card it lies in rather than by a width of its own, because chippy:735's finding
   22 is that a width in pixels is off the grid, and TestTheGridIsSixtyFourByTwentyFourAndTheOneWidthOnEveryPage
   fails the suite for one. Measured: the first cut declared 1px and that guard caught it. */
.onb__pick-in { position: absolute; inset: 0; opacity: 0; pointer-events: none; }
.onb__pick:has(.onb__pick-in:checked) { border-color: var(--theme); color: var(--lit); background: color-mix(in srgb, var(--theme) 12%, transparent); }
/* The ring the page draws on :focus-visible cannot show on a 1 px input, so the card wears it. */
.onb__pick:has(.onb__pick-in:focus-visible) { outline: 2px solid var(--speak); outline-offset: 3px; }

/* The label over the box to say more sits left, over a field the door already left-aligns. */
.onb__label { display: block; margin-top: var(--space-24); text-align: left; }

/* The step bar says where you are to somebody who cannot see it, and that sentence is for them only.
   Off-screen by a class, never by an inline style, and never by display:none, which would take it
   from a screen reader too. There is no pixel size here on purpose: chippy:735's finding 22 bans a
   width in pixels, and a zero box with its overflow hidden does the same job honestly.
   Whether the bar should also carry the words a person can SEE is Z's call, not this row's: his own
   reference at 2026-09-14 16:50 had a bare row of segments and no numbers on it. */
.onb__said { position: absolute; width: 0; height: 0; overflow: hidden; clip-path: inset(50%); white-space: nowrap; }

/* ══ MOBILE IS NOT INTERACTIVE (chippy:896) ═════════════════════════════════════════════════════
   Z, 2026-09-15 01:1x, dictated: "on mobile, maybe it shouldn't even be particularly interactive if it
   just needs to be pretty screenshots or something. Because mobile is a nightmare, and at least that way
   you're marketing and you're not selling ... I don't want mobile to cause me any anxiety at all. There's
   a million reasons it'll break on iOS."
   Measured before this block at 390x844 (Playwright, iPhone 13, WebKit): the hero's desk drew at 342x419
   with a draggable window, Chippy's window and its play button, and so did each use case's desk; the
   read never advanced after the tap in headless WebKit, and nothing said why.
   So under 720 the live desk is not drawn and a still the build already ships stands in its place: the
   Writing desktop for the hero, each case's own for /use-cases/<case> (public/art/use-cases/<case>.png,
   the card's picture, 1600x1000, the same desk with Chippy's window on it; no new media). Both blocks are
   in the served HTML; this sheet decides which one is drawn, and desk.js, main.js and use-cases.js read
   THE SAME FACT back from the desk's computed display (ChippyDesk.drawn) before they wire a handler, so
   the width the sheet names here is the only place the width is written and nothing has to agree with
   it. On a phone there is no clock, no drag, no play and no dots to pick a desktop with: .scene__foot
   goes too, because a row of tabs that switches nothing is a broken control, not a quiet one.
   THE HIDE SITS HERE, AT THE END OF THE FILE, for chippy:908's reason: a display: none for .desk written
   inside the phone block a thousand lines up would lose the cascade to .desk's own base rule, which comes
   later than that block at one class of specificity. After every rule it overrides, it wins.
   Above 720 nothing changes: the still is display: none at the base rule below and the desk is exactly
   what it was, byte for byte, so the desktop keeps the live scene. The still wears the card's own edge
   (chippy:763's .card__pic), because it is that picture. */
.still { display: none; border-radius: var(--space-12); overflow: hidden; background: #000; outline: 1px solid var(--desk-edge); outline-offset: -1px; }
.still img { display: block; width: 100%; height: auto; }
@media (max-width: 720px) {
  .scene .desk { display: none; }
  .scene .still { display: block; }
  .scene .scene__foot { display: none; }
}
