Redesign in Prime Intellect's design language, both themes, three viewports
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:
@@ -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;
|
||||
}
|
||||
@@ -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',
|
||||
|
||||
@@ -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 Intellect’s 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;
|
||||
}
|
||||
@@ -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
@@ -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';
|
||||
|
||||
Reference in New Issue
Block a user