Redesign in Prime Intellect's design language, both themes, three viewports
ci / web (push) Successful in 2m42s
ci / python (push) Successful in 3m8s

Eighteen agents: three design directions judged on whether a COO on a phone
actually learns what an environment is, on craft, and on landing without a
rewrite; one spec; a foundation of measured tokens; six build lanes; three
browser verifiers; a final gate pass.

The materials are Prime Intellect's, measured from their site: near-black
grounds, one green, sharp radii, mono small-caps labels, Geist and Geist Mono
self-hosted because production CSP is font-src 'self'. Two of their own greys
fail contrast on their own ground (#737373 is 4.02:1, #6E6E6E is 3.73:1 on
#0F0F0F), so --muted is lifted and the CSS comment carries the number — or
someone will 'correct' it back. Every text-on-ground pair in both themes is
tabulated in src/index.css with its measured ratio.

The two rules that resolved every conflict: data is mono, sentences are sans;
the language wins on materials, the lesson wins on legibility. Light mode is a
finished paper theme, not an inversion.

What did not change: the derived-tabs contract, the honesty markers, the
isolation lint, every gate. 419 contract checks, entry chunk at 74% of budget,
zero horizontal overflow on any route at 390/1024/1440 in either theme.

Also flips Alert Triage to status 'live' — the pipeline built it but never
promoted it, so it was badged SPEC on its own playable page and the home page
counted one environment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_019mt6sHQHEnEYrJZvoMCJSB
This commit is contained in:
karti-ai
2026-08-28 21:41:15 -07:00
parent af15ba584e
commit a21596b3e4
85 changed files with 4717 additions and 2230 deletions
+67
View File
@@ -0,0 +1,67 @@
/**
* The four parts of an environment, as data.
*
* This is the site's one transferable piece of explanation. The landing strip
* (`DefinitionStrip`) and every demo page's four boxes (`EnvAnatomy`) both read
* these strings, so a visitor who learns the labels on the word game reads the
* fraud screen as "same machine, different grader". That only works if the
* four labels never change between surfaces — which is why they live here and
* nowhere else. Do not paraphrase them in a page.
*
* `key` is the field name on `DemoModule.anatomy`, so a demo's own text for
* each part is looked up with it.
*/
import type { DemoModule } from '@/lib/demo-kit/types';
export type AnatomyKey = keyof DemoModule['anatomy'];
export interface AnatomyTerm {
key: AnatomyKey;
/** Zero-padded ordinal, rendered in mono: `01``04`. */
index: string;
/** The label. Sans, 600. The same four words on every surface. */
kicker: string;
/** One line, for the landing strip. A sentence, not a fragment. */
gloss: string;
/** The question the box answers, for the full `EnvAnatomy`. */
question: string;
}
export const ANATOMY: readonly AnatomyTerm[] = [
{
key: 'task',
index: '01',
kicker: 'The task',
gloss: 'What the agent is asked to do.',
question: 'What is the agent asked to do?',
},
{
key: 'actions',
index: '02',
kicker: 'Legal actions',
gloss: 'What it is allowed to do.',
question: 'What is it allowed to do?',
},
{
key: 'grader',
index: '03',
kicker: 'The grader',
gloss: 'Code that computes, never a judge.',
question: 'Who decides whether it was good?',
},
{
key: 'score',
index: '04',
kicker: 'The score that moves',
gloss: 'The number you train against.',
question: 'What number does that produce?',
},
];
/** One term by its key. Throws at build time rather than rendering a blank box. */
export function anatomyTerm(key: AnatomyKey): AnatomyTerm {
const term = ANATOMY.find((t) => t.key === key);
if (!term) throw new Error(`No anatomy term "${key}".`);
return term;
}
+7
View File
@@ -59,6 +59,13 @@ export const trainingResult = {
method: 'SFT warm-up, then multi-turn RL with group-relative advantages (GRPO)',
/** From the same README: 20 held-out words, 3 rollouts each. */
evalDescription: '20 held-out words the model never trained on, played 3 times each',
/**
* Rendered wherever the number is, in these words. Home and Honesty both
* print it, and a caveat that appears on one page and not the other is the
* kind of thing a careful reader notices first.
*/
caveat:
'We quote the win rate only. The reward numbers in that write-up span versions of the environment and were never re-measured together.',
source: {
label: 'prime-rl / examples / basic / wordle',
href: 'https://github.com/PrimeIntellect-ai/prime-rl/tree/main/examples/basic/wordle',
+161
View File
@@ -0,0 +1,161 @@
/**
* Every piece of jargon the site uses, with the one-sentence gloss a visitor
* gets when they hover or tap it.
*
* One list, two audiences. The five `core` terms are the vocabulary of the
* idea itself and are rendered as a set: in the header's "How it works" panel
* and on the landing page. The extended terms are glossed inline, one at a
* time, by `Term` wherever a page or the shell first uses the word.
*
* Glosses are sentences a board member can repeat. They say what the thing IS
* before they say what it is for, and they never lean on another term from
* this list to do it.
*/
export type GlossaryKey =
| 'environment'
| 'rollout'
| 'reward'
| 'harness'
| 'held-out'
| 'seed'
| 'arm'
| 'objective'
| 'counterweight'
| 'gate'
| 'intervention'
| 'verify'
| 'taskset'
| 'verifiers'
| 'tokens'
| 'latency';
export interface GlossaryEntry {
key: GlossaryKey;
/** As it is written in running text. Sentence case. */
term: string;
gloss: string;
/** One of the five rendered as a set. */
core: boolean;
}
export const GLOSSARY: readonly GlossaryEntry[] = [
{
key: 'environment',
term: 'Environment',
gloss:
'The task, the legal moves and the grader, packaged so anyone can install and run it.',
core: true,
},
{
key: 'rollout',
term: 'Rollout',
gloss: 'One episode. The model acts, the environment answers, and every turn is recorded.',
core: true,
},
{
key: 'reward',
term: 'Reward',
gloss: 'The number the run is scored on. You write it, so you decide what "good" means.',
core: true,
},
{
key: 'harness',
term: 'Harness',
gloss: 'The runner that plays a taskset against a model and keeps the receipts.',
core: true,
},
{
key: 'held-out',
term: 'Held-out grading',
gloss:
'Scored on problems the model has never seen, which is the only way the score means anything.',
core: true,
},
{
key: 'seed',
term: 'Seed',
gloss:
'The number that fixes the board. The same seed reproduces the same hidden word, so two agents can be compared on the same puzzle.',
core: false,
},
{
key: 'arm',
term: 'Arm',
gloss:
'One model under one setting, with its recorded runs. The ranking compares arms, not companies.',
core: false,
},
{
key: 'objective',
term: 'Objective',
gloss:
'What the task is for: the term the agent is paid to maximise. Every other term in the reward exists to keep this one honest.',
core: false,
},
{
key: 'counterweight',
term: 'Counterweight',
gloss:
'The term that takes points away. It pulls against the objective so the crude way of winning does not pay.',
core: false,
},
{
key: 'gate',
term: 'Gate',
gloss:
'A term every good attempt also scores full marks on. It rules out cheating; it does not rank.',
core: false,
},
{
key: 'intervention',
term: 'Intervention',
gloss:
'Something done to a run that was not training: a changed prompt, a hint. Always labelled, never presented as a training result.',
core: false,
},
{
key: 'verify',
term: 'Verify',
gloss:
'Re-run the grader in your browser over the recorded moves and compare the answer to the number shipped in the file.',
core: false,
},
{
key: 'taskset',
term: 'Taskset',
gloss: 'The list of problems an environment draws from. On the word game, the hidden words.',
core: false,
},
{
key: 'verifiers',
term: 'verifiers',
gloss:
'Prime Intellects open-source library for writing environments. Every environment on this site is built with it.',
core: false,
},
{
key: 'tokens',
term: 'Tokens',
gloss:
'The units a model reads and writes. The counts here are what the recording measured, not a price.',
core: false,
},
{
key: 'latency',
term: 'Latency',
gloss:
'Wall-clock time per model call, as recorded on that day against that endpoint. Not a benchmark.',
core: false,
},
];
/** The five rendered as a set, in the order they are taught. */
export const CORE_TERMS: readonly GlossaryEntry[] = GLOSSARY.filter((entry) => entry.core);
/** One entry by key. Throws at build time rather than rendering an empty tooltip. */
export function glossaryEntry(key: GlossaryKey): GlossaryEntry {
const entry = GLOSSARY.find((e) => e.key === key);
if (!entry) throw new Error(`No glossary entry "${key}".`);
return entry;
}
+61
View File
@@ -4,6 +4,10 @@
* This is the ONLY place the marketing pages touch the registry. Home, Gallery
* and Vertical all read demos through here, so when the registry's export name
* or path moves, it moves in one line instead of in four pages.
*
* Every count a page prints comes from a list in this file. A typed "twelve"
* on a site whose whole argument is that its claims are checkable goes stale
* the first time a demo ships.
*/
import { demos } from '@/lib/demo-kit/registry';
@@ -38,6 +42,8 @@ export const allDemos: readonly DemoMeta[] = demos;
export const liveDemos: readonly DemoMeta[] = allDemos.filter((d) => d.status === 'live');
export const specDemos: readonly DemoMeta[] = allDemos.filter((d) => d.status === 'spec');
/**
* The demo the site leads with. `reference` is the hello-world vertical, and
* the first live one in it is the front door; if there is none yet, any live
@@ -74,3 +80,58 @@ export function verticalForDemo(demo: DemoMeta): VerticalEntry | undefined {
/** The lineup, in the order we would build it. */
export const lineup: readonly VerticalEntry[] = [...VERTICALS].sort((a, b) => a.rank - b.rank);
/**
* Lineup entries with nothing behind them: no live demo and no published spec
* demo. This is the honest count for "environments that do not exist", and it
* shrinks by one every time a proposal is built or specified.
*/
export const unbuiltLineup: readonly VerticalEntry[] = lineup.filter(
(v) => v.key === null || demosForVertical(v.key).length === 0,
);
/* ---------------------------------------------------------------- format */
/** `4` → `04`. Rank numerals and counts in mono labels. */
export function pad2(n: number): string {
return String(Math.max(0, Math.trunc(n))).padStart(2, '0');
}
const WORDS = [
'zero',
'one',
'two',
'three',
'four',
'five',
'six',
'seven',
'eight',
'nine',
'ten',
'eleven',
'twelve',
'thirteen',
'fourteen',
'fifteen',
'sixteen',
'seventeen',
'eighteen',
'nineteen',
'twenty',
] as const;
/**
* A count as a word inside a sentence: "Two of these are built". Digits past
* twenty, because "twenty-three of these" is where a word stops reading.
*/
export function countWord(n: number): string {
const whole = Math.max(0, Math.trunc(n));
return WORDS[whole] ?? String(whole);
}
/** Capitalised, for a sentence that starts with the number. */
export function CountWord(n: number): string {
const word = countWord(n);
return word.charAt(0).toUpperCase() + word.slice(1);
}
+94 -22
View File
@@ -6,6 +6,16 @@
* characters of Tailwind copied into five pages and drifting apart. Strings,
* not components, so nothing here reaches for React.
*
* Two rules from the design spec are enforced by what is and is not in here:
*
* 1. Data is mono, sentences are sans. The `label`, `chip*`, `monoData` and
* `codeBlock` styles carry `font-mono`; nothing with a verb in it does.
* 2. Nothing that explains is smaller than 16px. `prose` is the floor, and
* `small` / `caption` exist for metadata, never for a paragraph.
*
* Weight ceiling is 600 — the type scale in tailwind.config.js bakes weight
* into each size, so `font-bold` never appears on a page.
*
* Horizontal padding is written against the safe-area tokens rather than a
* flat value: on a notched phone in landscape, a flat `px-5` puts the first
* character of every heading under the rounded corner.
@@ -15,47 +25,109 @@
export const shell =
'mx-auto w-full max-w-canvas pl-[max(1.25rem,var(--safe-left))] pr-[max(1.25rem,var(--safe-right))] sm:pl-[max(2rem,var(--safe-left))] sm:pr-[max(2rem,var(--safe-right))]';
/** Vertical rhythm between top-level sections. */
/** Vertical rhythm between top-level sections. The language's density, not an essay's. */
export const section = 'py-12 sm:py-16';
/** Small uppercase label above a section heading. */
export const eyebrow = 'text-xs font-semibold uppercase tracking-[0.14em] text-muted';
/* ------------------------------------------------------------------- type */
export const h1 =
'text-[2rem] leading-[1.08] font-extrabold tracking-tight text-fg sm:text-5xl lg:text-6xl';
/** Mono small-caps section label. The same style as every chip and table head. */
export const label = 'font-mono text-label uppercase text-muted';
export const h2 = 'text-2xl font-bold tracking-tight text-fg sm:text-3xl';
/** Kept under its old name: the shell and the router read `eyebrow`. */
export const eyebrow = label;
export const h3 = 'text-base font-semibold text-fg';
/** The landing h1 only. 36 → 48 → 56. */
export const display = 'text-display text-fg lg:text-display-lg xl:text-display-xl';
export const lede = 'text-lg leading-relaxed text-muted sm:text-xl';
/** Every other page's h1. 28 → 32. */
export const h1 = 'text-h1 text-fg lg:text-h1-lg';
export const prose = 'text-[0.9375rem] leading-relaxed text-muted';
export const h2 = 'text-h2 text-fg lg:text-h2-lg';
/** Filled call to action. 44px tall via `.tap`. */
export const btnPrimary =
'tap inline-flex items-center justify-center gap-2 rounded-lg bg-brand px-5 py-2.5 text-sm font-semibold text-accent-on transition-colors duration-2 ease-enter hover:bg-accent-fg';
export const h3 = 'text-h3 text-fg';
/** Under an h1. Prose ink, 72ch. */
export const lede = 'max-w-measure-wide text-lede text-fg-2 lg:text-lede-lg';
/** The education floor: 16/1.6, prose ink, 62ch. Never smaller. */
export const prose = 'max-w-measure text-prose text-fg-2';
/** Metadata beside a thing, never a paragraph. */
export const small = 'text-small text-muted';
export const caption = 'text-caption text-muted';
/** A number or an identifier in running chrome. */
export const monoData = 'font-mono text-mono-data';
/* ---------------------------------------------------------------- buttons */
const btnBase =
'tap inline-flex items-center justify-center gap-2 rounded-md px-5 py-2.5 text-ui font-medium transition-colors duration-1 ease-enter';
/**
* Filled call to action. The 1px `accent-edge` border is what makes it a
* control on a light projector, where the fill is 1.47:1 against white. The
* neon fill has no darker sibling on paper, so light hovers to the subtle
* ground with accent ink; dark dims the fill.
*/
export const btnPrimary = `${btnBase} border border-accent-edge bg-brand text-accent-on hover:bg-accent-subtle hover:text-accent-fg dark:hover:bg-brand/90 dark:hover:text-accent-on`;
/** Outlined call to action, for the second-choice action beside a primary. */
export const btnSecondary =
'tap inline-flex items-center justify-center gap-2 rounded-lg border border-border bg-surface px-5 py-2.5 text-sm font-semibold text-fg transition-colors duration-2 ease-enter hover:bg-surface-2';
export const btnSecondary = `${btnBase} border border-border-strong bg-surface text-fg hover:bg-surface-2`;
/** Inline link inside running text. Underlined, because colour alone is not a link. */
export const link =
'font-medium text-accent-fg underline decoration-accent-fg/40 underline-offset-4 transition-colors duration-1 ease-enter hover:decoration-accent-fg';
'font-medium text-accent-fg underline decoration-border-strong underline-offset-4 transition-colors duration-1 ease-enter hover:decoration-accent-fg';
/** Small pill. Neutral by default; pass a colour class after it. */
export const pill =
'inline-flex items-center gap-1.5 rounded-md border border-border bg-surface-2 px-2 py-1 text-xs font-medium text-muted';
/** A quiet navigation link — back, next, "all twelve". 44px tall. */
export const navLink =
'tap inline-flex items-center gap-1.5 text-ui font-medium text-muted transition-colors duration-1 ease-enter hover:text-fg';
/* ------------------------------------------------------------------ chips */
/**
* The chip family. One shape, mono 11 uppercase, 4px radius. A visitor learns
* after the second one that this shape means "a thing we are telling you about
* the data", so no chip on a page is ever sans or rounder than this.
*/
const chipBase =
'inline-flex items-center gap-1 rounded-sm border px-1.5 py-0.5 font-mono text-label uppercase';
/** Outline: SPEC, PROPOSED BY PIG-DEMO, OBJECTIVE, RANK. */
export const chipOutline = `${chipBase} border-border-strong text-muted`;
/** Muted: RECORDED RUN, NOT IN THE FIRST SET, GATE. */
export const chipMuted = `${chipBase} border-transparent bg-surface-3 text-muted`;
/** Solid: LIVE. Pass `rounded-full` for a status pill. */
export const chipSolid = `${chipBase} border-accent-edge bg-brand text-accent-on`;
/** Warning: EDITED, COUNTERWEIGHT, WHERE THIS STOPS. */
export const chipWarning = `${chipBase} border-transparent bg-warning-subtle text-warning`;
/** Kept under the old names for the two callers outside this lane. */
export const pill = chipOutline;
/** The standing "these are our proposals" badge. */
export const proposalPill =
'inline-flex items-center gap-1.5 rounded-md border border-border bg-surface-2 px-2 py-1 text-[0.6875rem] font-semibold uppercase tracking-wider text-muted';
export const proposalPill = chipOutline;
/* ------------------------------------------------------------- surfaces */
/** A card that is also a link: the whole rectangle is the target. */
export const cardLink =
'card group flex flex-col p-5 transition-colors duration-2 ease-enter hover:border-brand/40 hover:bg-surface-2';
'card group flex flex-col p-4 transition-colors duration-1 ease-enter hover:border-border-strong sm:p-5';
/** A list row that is a link. 64px on a phone, 56px from lg. */
export const rowLink =
'flex min-h-16 items-center gap-3 py-3 transition-colors duration-1 ease-enter hover:bg-surface-2 lg:min-h-14';
/** Monospaced command block. Scrolls itself rather than the page. */
export const codeBlock =
'nums overflow-x-auto rounded-lg border border-border bg-surface-2 p-3 font-mono text-[0.8125rem] leading-relaxed text-fg';
'overflow-x-auto rounded-md border border-border bg-surface-3 p-3 font-mono text-code text-fg [font-feature-settings:"liga"_0]';
/** The buyer's question, as a quiet quote. 2px rule, no italics. */
export const quote = 'max-w-[60ch] border-l-2 border-border-strong pl-4 text-prose text-fg-2';
/** A hairline between blocks. */
export const rule = 'border-t border-border';