git:20260719.b172db1 to git:20260919.e20f73c

24 added, 71 removed. Audit A to A.

# Design
- Visual system for the Roblox Suite marketing site. Strategy and audience live in [PRODUCT.md](./PRODUCT.md); this file records how it looks.
-
- The form language is **injection-moulded plastic**: flat saturated fills, solid 2px ink edges, and hard offset shadows. There are no glows, no glass, no gradients on text, and no ambient blur anywhere in the system.
+ The site uses molded-brick shapes: saturated fills, solid ink borders, and hard offset shadows. [PRODUCT.md](PRODUCT.md) defines the audience and copy. [global.css](src/styles/global.css) owns the color, type, spacing, and motion values.
- ## Theme
+ ## Surfaces and color
- Drenched. The cobalt is the surface, not an accent on a neutral ground. Sections alternate between three grounds so the scroll has rhythm rather than one repeated template:
+ **A section background is cobalt or yellow. Nothing else.** Cobalt carries the page; yellow closes it, one yellow section at most and always the last one. Paper never drenches a section — it is a panel fill, and a page-wide field of it reads as a different site.
- | Ground | Used for |
+ | Surface | Use |
| --- | --- |
- | `brand` cobalt | Hero, correction panel, catalog index, page headers |
- | `ink` near-black | Ticker band, footer, code panels |
- | `paper` off-white | Long-form reading: how-it-works, skill body, evidence table |
- | `toy-yellow` | The closing CTA only, as the page's brightest surface |
-
- ## Color
-
- All values OKLCH. Neutrals are tinted toward the brand hue (258), never toward warm.
-
- | Token | Value | Role |
- | --- | --- | --- |
- | Primary | `oklch(0.45 0.235 258)` | `--brand`, the drench |
- | Brand deep | `oklch(0.33 0.19 258)` | Recessed brand surfaces |
- | Brand lift | `oklch(0.56 0.22 258)` | Raised brand surfaces, brick blue |
- | Ink | `oklch(0.17 0.035 258)` | Near-black ground and every border |
- | Ink soft | `oklch(0.24 0.04 258)` | Dividers inside ink panels |
- | Paper | `oklch(0.965 0.012 258)` | Off-white ground and light text |
- | Accent | `oklch(0.87 0.185 92)` | `--toy-yellow`, second voice, primary CTA |
- | Deprecated | `oklch(0.63 0.235 27)` | `--toy-red`, dead APIs |
- | Current | `oklch(0.75 0.19 148)` | `--toy-green`, working APIs |
+ | `brand` cobalt | Every content section, and the header |
+ | `toy-yellow` | The closing section, pressed keys, and primary actions |
+ | `ink` near-black | Footer, code panels, and panels that want weight |
+ | `paper` off-white | Panels and controls only: skill tiles, unpressed keys, the search field, the sources disclosure |
- Muted text is a `color-mix` tint of its own ground, never flat gray on a colored surface. Every pair is verified at WCAG AA: the weakest is muted-on-cobalt at 4.72:1 and ink-on-red at 4.88:1. Paper on `toy-red` measures 3.54:1 and is **not** used for small text.
+ Tint muted text toward its surface color. Pair status colors with labels rather than leaving them to carry meaning alone. Paper on `toy-red` is unsuitable for small text; use ink. On cobalt, a link is `toy-yellow` (5.16:1); on paper or yellow it is `brand` (6.93:1 and 5.16:1). `brand` on cobalt and `toy-red` on cobalt both fail — never pair them.
- Deprecated and current are never signalled by hue alone; a `-` / `+` gutter and a text label carry the same distinction.
+ Keep flat fills and hard shadows. Avoid glass effects, text gradients, glow shadows, and ambient blur.
## Typography
- | Role | Face | Notes |
- | --- | --- | --- |
- | Wordmark | Unbounded Variable 700 | Wide geometric, reads extruded |
- | Display | Unbounded Variable 700 | All h1–h3 |
- | Body | Hanken Grotesk Variable 400 | Humanist, high legibility |
- | Code | JetBrains Mono Variable | Literal code only, never decoration |
-
- Paired on a contrast axis (geometric display against humanist body) rather than two near-identical sans faces. Both are self-hosted via `@fontsource-variable`; nothing loads from a CDN.
-
- Modular scale, ≥1.25 between steps. Display is capped at `4rem` rather than the 6rem ceiling because Unbounded is wide and ran to five lines at larger sizes. Tracking bottoms out at `-0.03em`. Light-on-dark body runs at 1.7 line-height to offset optical lightening.
-
- ```
- display clamp(2.25rem, 4.8vw, 4rem) lh 0.98
- title clamp(1.875rem, 4.2vw, 3.25rem)
- heading clamp(1.375rem, 2.4vw, 1.875rem)
- lead 1.375rem
- base 1.0625rem
- small 0.9375rem
- ```
+ Use Unbounded for the wordmark and headings, Hanken Grotesk for body text, and JetBrains Mono for code. Fonts are self-hosted through `@fontsource-variable`.
- Running prose is capped at 68ch via the `measure` utility.
+ Unbounded is wide, so check headline wrapping at phone widths after copy changes. Use the `measure` utility for running prose. Keep commands and URLs readable in full by wrapping them.
## Components
- **Panel** — `panel` / `panel-sm`. 2px ink border, 12px radius (6px small), hard offset shadow `6px 6px 0`. The one container primitive; there are no nested panels. Variants: ink, paper, brand, yellow-shadow.
-
- **Pressable** — `pressable`. Hover lifts 2px and deepens the shadow; active travels 6px into it, like a key bottoming out. Transform and box-shadow only, so nothing reflows. Frozen under reduced motion.
-
- **Buttons** — Primary is yellow fill with ink text (the GitHub star). Secondary is ink fill with paper text. Both are panels with `pressable`.
-
- **Brick scene** — `BrickScene.tsx`. Hand-written 3D canvas renderer: perspective projection, painter's-algorithm depth sort, flat-shaded faces with 2px ink strokes, projected elliptical studs. Bricks free-fall into a tower on a 7.5s loop. Ambient shading floor is high (0.78) so faces read as coloured plastic rather than grey against the cobalt. Renders one settled frame under reduced motion and pauses off-screen via IntersectionObserver.
-
- **Correction panel** — `CorrectionPanel.tsx`. The product's argument staged as a real diff. First paint is never gated behind hydration; only transitions between corrections animate.
-
- **Skill index** — `SkillIndex.astro`. A grouped index of full-width rows with rules between them, deliberately not a grid of sixteen equal cards. Rows fill yellow on hover and focus.
-
- **Ticker** — `DeprecatedTicker.tsx`. Two counter-scrolling marquees of dead APIs, pausing on hover.
-
- ## Motion
-
- Exponential ease-out (`--ease-out-expo`, `--ease-out-quart`). No bounce, no elastic, and no animation of layout properties. Scroll reveals use `animation-timeline: view()` and layer on top of already-visible content, so nothing ships blank when scripting or hydration does not run. Every animation has a reduced-motion path; the brick scene freezes rather than merely slowing.
-
- ## Layout
+ - `panel` and `panel-sm` provide borders, rounded corners, and hard shadows. Avoid nested panels.
+ - `pressable` lifts on hover and moves into its shadow when pressed. Animate transforms and shadows without reflow.
+ - A selected key — a tab, a filter chip — travels the full shadow offset and stays bottomed out in `toy-yellow`. A partial press reads as misalignment.
+ - Primary buttons use yellow with ink text; secondary buttons use ink with paper text.
+ - `BrickScene.tsx` draws the brick tower on canvas, leans it toward the pointer, pauses off-screen, and renders a still frame that ignores the pointer under reduced motion.
+ - `CopyCommand.tsx` is the only install-command control. Use it everywhere a command appears, so the copy affordance never moves.
+ - `JobPicker.astro` opens the catalog by goal rather than by subsystem, using the `does` label from `catalog.ts`.
+ - `SkillFinder.tsx` searches and filters the catalog. Groups stay as sections, because the grouping is what says how the skills relate; filtering hides empty groups rather than flattening them.
- Semantic z-scale: `--z-scene: 1`, `--z-content: 2`, `--z-sticky: 20`, `--z-modal: 40`, `--z-toast: 60`. No arbitrary values.
+ ## Motion and layout
- Grid children that contain nowrap content carry `min-w-0`, since `min-width: auto` otherwise forces the column wider than the viewport and the section clips it.
+ Use the easing tokens in `global.css`. Animate properties that do not trigger layout, and provide a reduced-motion path. Content must remain visible when scripts or hydration fail — the catalog renders on the server, so it still works as a static list without JavaScript.
- ## Deliberately excluded
+ Give grid children containing commands or URLs `min-w-0`. Without it, their minimum content width can force the page wider than the viewport. Check install commands and skill titles on narrow screens after edits.
- Glassmorphism, gradient text, glow shadows on dark, violet/cyan palettes, tracked uppercase eyebrows above section headings, numbered section markers, identical card grids, hero-metric stat rows, Inter, and any component-library decoration. Each is a recognizable generated-UI tell; see the anti-references in PRODUCT.md.
+ Code blocks are the one place a horizontal scroll is correct: wrapping a Luau line changes what it looks like it does. Everything else wraps.