git:20261101.fa55736 to git:20261101.317e537

23 added, 1 removed. Audit A to A.

# NeuralMind site design system
The site wears **Console**, a material from
[Hallmark](https://github.com/nutlope/hallmark) (MIT): a graphite ground in
three lightness steps, hairline panels cut from one surface, one face for
everything a person reads and a mono face only for what an instrument wrote,
and two signal colours with one meaning each. Console is a public theme, so
the material alone would make this one of many pages that look alike. Four
things are the site's own and are what to protect:
1. **The colourway, "NeuralMind graphite".** Console's amber for a person, and
in place of its sky blue the blue this site has always had, now meaning
only "measured".
2. **The engram** (`src/components/ui/Logo.tsx`), the mark below.
3. **The record** in the homepage hero: one public-benchmark query drawn from
`bench/public/results.json` as Console's reading and diff.
4. **The liquid** (`src/components/ui/LiquidBackground.tsx`), the field behind
every page.
The tokens live in [`tailwind.config.ts`](tailwind.config.ts); the rules that
need raw CSS (controls, surfaces, labels, readings, the data table, the
liquid) live in [`src/app/globals.css`](src/app/globals.css), whose `:root`
variables mirror the config and whose first lines carry the Hallmark stamp.
Change the config first, then the CSS mirror. The docs site
(`docs/_layouts/default.html`) still carries the previous palette and is
restyled separately.
`tests/test_site_design_tokens.py` (stdlib-only) fails the build on an
arbitrary type value, a raw Tailwind palette colour, a radius outside the
three below, a class naming a colour the config doesn't define, or a token
missing from this file.
## Colour
Two colours do jobs; everything else is graphite. **Never swap them**: an
amber reading or a blue button is the system lying.
| Token | Value | Use |
|---|---|---|
| `electric` | `#F7B83D` | **Amber: a person.** The action you take (the primary button, one per view), the focus ring, a hovered link, the `$` a person types, the learned edge in the mark, the pointer's swirl in the liquid. |
| `electric-bright` | `#FFCC69` | Hover state of the primary button. |
| `electric-dim` | `#38280A` | Fills behind amber. |
| `proton` | `#92B3FB` | **Blue: an instrument.** A number that was measured, a CI-gated or reproducible badge, the NeuralMind row in a results table, a commit sha. Never decoration, never a link. |
| `proton-dim` | `#1F2B44` | Fills behind blue (the `.is-ours` row). |
| `iris` | `#9DA2A8` | Retained so stray references resolve (the `muted` value); don't use in new work. |
The names are legacy (`electric` was the old interactive blue, `proton` the
old evidence green); 300+ usages reference them, so the values were
repointed rather than the call sites renamed.
### Surfaces
A surface is a 1px rule with one step of graphite inside it. No shadow, glow,
blur or gradient, and panels that sit together are one surface divided by
1px rules.
| Token | Value | Use |
|---|---|---|
| `carbon` | `#0D1013` | The ground: `html`, the navbar (also the `theme-color` meta). |
| `carbon-card` | `#15191C` | Cards, panels, the ledger grids. One step up. |
| `carbon-raised` | `#06080B` | The deepest step, despite its name: code wells, inline code, terminal bars, a figure's caption band. |
| `carbon-border` | `#2D3135` | Hairlines, card borders, table row rules. |
| `carbon-line` | `#484E54` | The strong rule: table heads, outlines, the secondary button, the one call-to-action panel, link underlines. |
### Text
All four clear WCAG AA (4.5:1) on every surface and on the liquid, which is
clamped below the luminance where `faint` would fail. Ratios on `carbon` /
`carbon-card` / `carbon-raised`.
| Token | Value | Contrast | Use |
|---|---|---|---|
| `ink` | `#ECEFF2` | 16.5 / 15.3 / 17.4 | Page default, links, inline code, emphasis. Headings use `text-white`. |
| `body` | `#C3C8CC` | 11.3 / 10.5 / 11.9 | Running copy, table cells you should read. |
| `muted` | `#9DA2A8` | 7.4 / 6.9 / 7.8 | Ledes, labels, icons, table cells you scan. |
| `faint` | `#848A90` | 5.5 / 5.1 / 5.7 | Captions, legal lines. The floor: nothing dimmer carries text. |
### Verdicts
Console's ok and bad, for diffs and verdict tables only.
| Token | Value | Use |
|---|---|---|
| `danger` | `#F66D67` | Refuted, failing, a published loss, the minus row of a diff. |
| `warn` | `#F7B83D` | Modeled, mixed, aspirational, overclaim. Amber, because a deviation is something a person should look at, and a third signal colour is what the material forbids. |
| `success` | `#61CB7C` | A status that passes, checklist ticks, the plus row of a diff. |
A row graded **Measured** is evidence, so it takes `proton`, not `success`.
## Type
**Host Grotesk** for everything a person reads, heads included, at 400 for
reading and 500 for every head: the config maps `font-semibold` and
`font-bold` to 500, and `strong` is 600. **Atkinson Hyperlegible Mono**
(`font-mono`, `.metric`, `.reading`) only for what an instrument or the
system wrote: a measured figure, a command, a path, a version, a sha. Its
slashed zero and tailed one are the point. Never mono on a label, a head or a
button. `font-display` resolves to Host Grotesk; the class stays so the heads
that name it keep it.
Heads are one weight and one colour: no accented word inside a head, no
italic word in a roman one.
| Token | Size / line height | Use |
|---|---|---|
| `caption` | 13px / 20px | Labels (`.label`), table heads and row heads, terminal text, small print. |
| `body-sm` | 15px / 24px | Card ledes, mobile nav links. |
| `title-sm` | 17px / 24px | Card titles, the wordmark. |
| `stat` | 28px / 1 | The value in a `Stat`. |
| `display` | 44px / 1.05 | Page `h1` and section `h2` at `md:` and up. |
| `hero` | 56px / 1.05 | The homepage `h1` only, at `lg:`. |
| `code-inline` | 0.875em | `<code>` inside running text; relative, so it follows the paragraph. |
Line height `leading-display` (1.05) goes on display headings. Letter
spacing: `tracking-tighter` (-0.03em) on every display head.
**No eyebrows.** A small label over a heading, and any label in tracked
capitals, are gone from the site; each heading already says what its section
is. A label (a stat's name, a panel's name, a footer column) is `.label`: the
body face at `caption`, 500, sentence case, untracked, in `muted`.
## Radius
The config replaces Tailwind's radius scale, so these are the only ones that
generate CSS.
| Token | Value | Use |
|---|---|---|
| `control` | 6px | Anything you click or that sits inline: buttons, chips, badges, inline code, code wells, step numbers. |
| `card` | 10px | A surface: cards, panels, callouts, pre blocks, tables. |
| `pill` | full | Pill tags. Status dots are gone: a dot with no words is decoration. |
Hairline ledger grids (`StatGrid`, the feature and step grids) are square:
their cells draw their own bottom/right rules, which a radius would clip.
They take the `carbon-card` fill so the liquid never runs through data.
## Motion
A value changes when its source does, over `--dur-short` (140ms) on
`--ease-out`. No reveal, parallax or hover lift; `transform` and `opacity`
only, never `transition: all`. Every animation stops under
- `prefers-reduced-motion`. The liquid is the one thing that moves on its own.
+ `prefers-reduced-motion`. The liquid, with the graph that condenses out of
+ it, is the one thing that moves on its own.
## The mark — `@/components/ui/Logo`
An engram: an activation pattern on a 4 by 4 grid. The two full columns are
two files your agent used together (`ink`); the two diagonal cells are the
association NeuralMind learned between them (`electric`, amber, because it was
learned from how a person works); the six cells that never fired stay drawn
in `carbon-line`. The lit cells spell the N. It takes no colour class: the
cells colour themselves.
`public/icon.svg`, `public/favicon.ico` (16 to 64px) and
`public/apple-touch-icon.png` (180px, opaque) draw the same grid on a `carbon`
tile; `public/social-preview.png` sets it beside the homepage headline and
three readings from `claims.json` over a still of the liquid. Change them with
the component.
The mark is a glyph, not a character: a mascot, when there is one, sits beside
it rather than replacing it.
## The liquid — `@/components/ui/LiquidBackground`
A fixed WebGL canvas behind every page, mounted once in `app/layout.tsx`: a
domain-warped noise field in the graphite steps, hairline currents along its
level sets, a faint `proton` sheen where it runs fast, and an `electric`
swirl where the pointer is. It flows on its own and drifts as the page
scrolls. It is the owner's addition over Console's no-texture rule, so it
carries the rule's reasons as constraints:
- **Contrast.** The shader clamps relative luminance to 0.014, under the
0.0155 where `faint` drops below 4.5:1. Raise `CAP` and AA breaks.
- **Motion.** One still frame under `prefers-reduced-motion`; paused while
the tab is hidden.
- **Cost.** Rendered at 0.55 canvas pixels per CSS pixel, at most 1100 on a
side, every other frame.
- **Failure.** No WebGL or no JavaScript leaves the solid `carbon` ground. The
canvas fades in only after its first frame.
- **Layering.** `body` has no background; the ground is on `html`, the canvas
sits between them at `z-index: -1`. Panels and wells keep their solid steps
on top.
- **Colour.** Read from the `:root` tokens at run time, never repeated.
+
+ ### The graph — `@/components/ui/CondensingGraph`
+
+ Over the first screen, mounted after the liquid at the same `z-index`, ASCII
+ particles drift as a gas and condense into a code graph like the one `build`
+ makes: modules as clusters of nodes, calls as edges. The solid spreads from
+ one node along the edges, the way recall spreads, holds, evaporates, and
+ condenses into a new graph every 22 seconds. The gas is `ink`, the solid
+ `electric`: amber because, as in the mark, an association NeuralMind learned
+ is the person's colour.
+
+ It is part of the liquid, so the liquid's constraints apply, with two
+ specifics:
+
+ - **Contrast.** Each glyph colour is its token dimmed in linear light to at
+ most the liquid's `CAP` (the gas to 0.8 of it). A glyph over the liquid is
+ a mix of two colours under the cap, so it stays under it. The masks that
+ fade it out below the first screen and halve it across the text column
+ are for looks, not for AA.
+ - **Cost.** A 2D canvas at 15 frames a second, stopped when the tab is
+ hidden or the layer is off screen.
## The mascot — The Pair
The pair programmer who sits in the corner of your editor: short, an
oversized `carbon-line` hoodie with the engram patch on the hood, silver-white
`ink` hair, `electric` amber eyes and drawstrings, headphones whose small LEDs
are the only `proton` blue on it. Its line is "Want me to remember that?".
It is built in VRoid Studio from [`mascot/SPEC.md`](mascot/SPEC.md), which
holds the palette, the build steps, the export settings and how to run the
build with a computer-use agent; the decals to import are in
`mascot/textures/`.
On the site it is `MascotSlot` (`@/components/ui/MascotSlot`), placed in the
hero so The Pair peeks over the record from behind it, at `lg:` and up:
- **It appears when the model does.** The slot checks at build time for
`public/mascot/pair.vrm` and renders nothing without it; dropping the file
in and rebuilding is the whole integration. An optional `pair.png` beside
it is shown while the model loads.
- **`Mascot` draws it** with three.js and `@pixiv/three-vrm`, both fetched
as their own chunk only once the box is on screen and the page has loaded.
A box hidden by CSS never loads the model, so phones don't download it.
- **It behaves like a colleague:** blinks, breathes, its eyes and head follow
the pointer (amber means a person, and the person is what it watches), and
it smiles when a command is copied (`COPIED_EVENT` in `@/lib/events`, fired
by `CommandLine`).
- **Under reduced motion it holds one still pose.** With no WebGL, or a model
that fails to load, the box collapses and the page is as it was.
- **Budget:** the `.vrm` under 8 MB (Cloudflare Pages refuses files over
25 MiB). The export settings in the spec get it there.
## Components
### Controls — `.btn-primary`, `.btn-secondary`, `.link`
CSS classes, applied to an `<a>` or `<Link>` (every button on the site
navigates). Primary is solid `electric` with dark ink, hover
`electric-bright`; secondary is a `carbon-card` panel in a `carbon-line`
outline. One primary per view. There is deliberately no `Button` component: a
second definition is how a page ends up with the old gradient.
`.link` is every link in running text: `ink`, underlined in `carbon-line`,
amber on hover. Link text carries no arrow.
### `Stat` and `StatGrid` — `@/components/ui/Stat`
```tsx
<StatGrid className="sm:grid-cols-3">
<Stat label="Gold file found" value="40 of 40" note="…" evidence="reproducible" />
</StatGrid>
```
| Prop | Type | |
|---|---|---|
| `label` | string | The `.label` above the value (the `<dt>`). |
| `value` | node | The number, in `.metric text-stat`. Must be in `claims.json`. |
| `note` | node? | One or two sentences of context in `muted`. |
| `evidence` | `EvidenceLevel`? | Renders an `EvidenceBadge` under the note. |
| `figure` | boolean = true | `false` for a word rather than a number: set in the text face. |
`StatGrid` takes the column classes; any count closes cleanly.
### `EvidenceBadge` — `@/components/ui/EvidenceBadge`
`<EvidenceBadge level="ci-gated" />`, a chip in the label face. The levels
are the keys of `_evidence_levels` in [`claims.json`](claims.json), and the
badge's tooltip is that level's definition from the same file; a level added
there without a label here fails the TypeScript build.
| `level` | Label | Look |
|---|---|---|
| `ci-gated` | CI-gated | `proton`, filled |
| `reproducible` | Reproducible | `proton`, outline |
| `field-report` | Field report | `body`, neutral outline |
| `community` | Community | `muted`, neutral outline |
| `pilot` | Pilot | `body`, neutral outline — pre-registered and measured, but exploratory |
| `disclosure` | Not measured | `faint`, dashed |
`children` replaces the label ("Field report — not CI-gated").
### `Callout` — `@/components/ui/Callout`
`<Callout variant="not-measured">…</Callout>` renders an `<aside role="note">`
in `card` radius.
| `variant` | Look | Use |
|---|---|---|
| `note` (default) | card surface | Context the reader can skip. |
| `evidence` | `proton` border and label | Points at a measured result. |
| `caution` | `warn` border and label | A limit or a known loss. |
| `not-measured` | dashed, `faint`, titled "Not measured" | What the site does not measure. Pair it with a `disclosure` entry in `claims.json`. |
`title` overrides the label.
### The record — `components/sections/Hero.tsx`
The homepage's signature, built from `publicQuery()` so nothing in it is
typed by hand: the question and its gold file, then Console's **reading**
(ripgrep's token count struck in `faint`, NeuralMind's set at display size in
`.reading`), then its **diff** (the gold file on a `success` or `danger` tint
with a plus or minus gutter, and the other files each retriever sent). It
shows the two counts and never a per-query ratio, which `claims.json` does not
list.
### Data table — `.data-table`
```tsx
<div className="overflow-x-auto">
<table className="data-table min-w-[34rem]">
<thead><tr><th scope="col">Backend</th><th scope="col" className="num">Recall</th></tr></thead>
<tbody>
<tr className="is-ours"><th scope="row">neuralmind</th><td className="num">1.00</td></tr>
</tbody>
</table>
</div>
```
Head row in the label style over a `carbon-line` rule; cells in `muted` with
`carbon-border` rules; row heads in mono `caption`, because a backend's name
there is what the benchmark wrote. `.num` right-aligns on tabular figures and
sets the cell (not its head) in mono; `.is-ours` puts the measured subject's
row on `proton-dim` with its head in `proton`. Give the table a `min-w-*`
inside an `overflow-x-auto` box so a phone scrolls the table, not the page.
### Surfaces and text helpers
- `.card` — a surface you can click (border brightens on hover). `.panel` —
the same surface, no hover. `.panel-accent` — the one call to action on a
page, marked by the strong rule rather than a wash.
- `.label` — a label. `.metric` — mono, tabular, slashed zero: any measured
number. `.reading` — one measured value set at display size.
`SectionHeader` — a `display` title and a `muted` lede, used by every
homepage section.
### Numbers — `@/lib/evidence`
A page doesn't type a measured number. It asks `lib/evidence.ts`, which reads
`site/claims.json`, the committed public-benchmark run
(`bench/public/results.json`) and the 150-question retrieval eval at build
time:
| Export | Gives |
|---|---|
| `ratio(48.8)`, `ratioRange(31, 67)` | `{ text, evidence, source, reproduce }`. Throws, failing `next build`, when claims.json doesn't list the value. |
| `headline('ripgrep (query keywords')` | A non-ratio claim by a phrase from its text, with its evidence level. |
| `publicBenchmark` | Per-repo tables, `found('ripgrep')` → "32 of 40", token ranges. Each per-repo ratio is checked against claims.json. |
| `publicQuery(repo, id)` | One query's context files and tokens per backend (the homepage's record). |
| `retrievalEval` | The 150-question top-5 rate and miss count, counted from the raw ranks. |
| `disclosures` | The `disclosure` entries, for a `not-measured` `Callout`. |
Pass a figure's `evidence` straight to `Stat` or `EvidenceBadge`, so the badge
can't claim more than claims.json does. The module is server-only: its imports
are large, and a client component that imported it would ship them. Copy that
quotes a number literally still works and is still read by
`tests/test_site_claims.py`; prefer the module for anything in a stat or table.
### `CommandLine` — `@/components/ui/CommandLine`
A copyable shell command on the deepest step. `className` sizes the box
(`flex-1` to fill a row, so a stack of commands lines up).
## Not covered yet
- `site/public/graph-view.webp` is a real `neuralmind serve` capture of the
psf/requests index in the graph view's own colours (re-capture it the same
way, cropped above the sidebar's per-query ratios, which aren't in
claims.json).
- `docs/_layouts/default.html` and `docs/about.html` keep their own palettes;
the docs site is restyled separately from neuralmind.uk.
- The Pair's model itself: until `public/mascot/pair.vrm` is committed, the
hero renders without it. The slot was tested with pixiv's public VRM
sample, which is not in the repo.