2010–2016
Base card
Used as-is for story chapters. Background, border, radius, and hover lift live here once.
System
The tokens, components, and principles that hold paulmress.com together — documented here and pulled directly from the same stylesheet the live site runs on, so this page and the site can never quietly drift apart.
Foundations
Color, type, space, radius, and motion — five small scales that, used consistently, are most of what "design system" actually means.
Color
Typography · three families, three jobs
Loaded once from Google Fonts (the same <link>, byte-for-byte,
in every page’s <head>) — each family has exactly
one job, and mixing them outside these roles is the fastest way to make a page feel off-system.
Dr. Paul, DBA
What happens when marketers stop asking what AI can do and start experimenting?
01 · Story · 2010–2016
Type scale
Dr. Paul
The narrow path
As iron sharpens iron.
15+
Foundation
15+ years of experience.
Three values that power how he operates.
Connect on LinkedIn
years of performance marketing experience
01 · Story
2010–2016
Spacing scale · 4px grid
Radius
--radius-sm · 8px
--radius · 12px
--radius-full · pills & buttons
Motion · hover to feel each one
--duration-fast · buttons
--duration-base · card hover lift
--duration-slow + --ease-out · scroll reveal
Scroll reveal · [data-reveal] (used site-wide)
Every content block that should animate in on scroll carries a bare data-reveal
attribute — no class naming needed. CSS starts it faded and shifted 24px
down; a small IntersectionObserver in script.js adds
.is-visible the first time an element scrolls into view, which is the only
thing that animates it in. prefers-reduced-motion turns the whole pattern off
— elements render at full opacity, in place, with no transition — rather than firing the motion
and hoping no one minds.
Atoms
Buttons, pills, labels, inputs — built once, referenced everywhere. If a new one-off version of any of these shows up in the code, it should have been one of these instead.
Buttons · .btn
Pill · .pill
Kicker · .kicker
Example Section
Form input · .form-row (dark-surface component)
Scroll progress · .scroll-progress (used site-wide)
A fixed 3px bar pinned to the very top of every page, one per document. script.js
sets its width to the page's scroll percentage on every scroll tick — the only moving part is that
one inline width, everything else (position, gradient, z-index) lives in CSS.
Frozen at 62% scrolled, for demonstration — live on the page it tracks scrollY / (docHeight - viewportHeight).
Plain list · .plain-list (concise items inside a .compare-grid card, e.g. /lab/1/)
Content toolbar atoms · .search-field / .filter-chip / .sort-select / .toolbar-count (introduced in Experiment 4, used on /lab/ and /thinking/)
Four single-purpose controls, styled from the same tokens as .pill and
.form-row rather than inventing a new visual language: a search input with
an icon and a clear button, a toggleable chip, a mono dropdown, and a mono result count.
Molecules
Atoms combined into a repeatable unit — a stat tile, a section header, a card. Each shows up more than once on the site with identical structure.
Stat tile · .stats-grid (3-up, used on the About page)
4.0
GPA across both the MBA & DBA
Section header · kicker + title + lede
Values
Used identically at the top of every section, site-wide.
Card · .card (base, .card--value, .card--current)
2010–2016
Used as-is for story chapters. Background, border, radius, and hover lift live here once.
Adds the alt background and icon slot. Everything else is inherited from .card.
2025–Present
Adds the accent outline used to mark the active chapter.
How Might We · .hmw (Lab problem framing, e.g. /lab/1/)
A distinct signature from .lab-hypothesis: accent-2 label, no italic, no left
border — reads as a strategy-doc convention framing the question a Lab entry sets out to answer, not
a pull-quote answering it.
How might we
Replace a CMS with a marketing intent, reviewed by a human?
Filter group & sort control · .filter-group / .sort-control (introduced in Experiment 4)
A mono micro-label paired with a row of atoms — the same shape as .form-row,
just horizontal. These two molecules are what the Content Toolbar organism below assembles.
Organisms
Complete, page-level patterns. Shown here at reduced scale with real classes — see them full-size and in motion on the homepage.
Hero pattern
The scroll cue (.scroll-cue, bottom-center on
the homepage hero) is a small pill outline with an inner dot
(.scroll-cue span) that travels down and fades on a
1.8s loop, nudging visitors to keep scrolling. It's aria-hidden
since it's purely decorative — the page works the same without it — and its
animation stops under prefers-reduced-motion: reduce, leaving
the dot visible and static at its resting position rather than disappearing.
Dark surface pattern · Connect
“As iron sharpens iron, so one person sharpens another.” Proverbs 27:17
Connect
Timeline · .timeline (alternating milestones, used on /story/)
The spine connecting each chapter is a thin gradient line
(.timeline::before) with three small pulses
(.timeline-pulse) animating down it on an 8s loop —
a subtle nod to the same node-network language as the hero and nav, without
the weight of a full illustration. The final chapter hands off into
.frontier, a dark closing section with its own
soft pulsing glow (.frontier-node-glow /
.frontier-node-core).
Odd rows sit left of the center line, year on the left.
Same markup order, no per-item modifier class needed for the alternation.
Same modifier as the story cards — applied to the inner .timeline-card only, never the outer <article>.
Compare grid · .compare-grid (used on /lab/1/)
A plain 2-column grid for setting two .cards side by side —
deliberately kept separate from .explore-grid (3 columns, for promo
links) rather than overloading one class for two different column counts. The “after”
side is just a base .card with .card--current
added, the same modifier the active story chapter uses.
Before
No modifier — just the shared card primitive.
After
Same accent outline used to mark the active story chapter.
Process steps · .steps (not currently used on any live Lab page — superseded by the workflow-compare + flow-row Method centerpiece below; kept documented for a future page that genuinely needs a vertical multi-step walkthrough)
A single-column relative of the timeline above: the same gradient spine
(.steps::before) and the same timeline-flow
keyframes driving three pulses (.steps-pulse), just run through the
44px icon column instead of the center of an alternating layout. Markers are solid
var(--color-accent) circles with a var(--color-bg)
border, so they read as beads threaded on the line rather than icons floating beside it.
Same 44px icon-circle token as .promo-icon / .value-icon, just circular instead of rounded-square.
No per-step modifier needed — add or remove .step elements and the spine sizes to fit.
Receipts · .receipt-block (not currently used on any live Lab page as of Sept. 2026 — Experiment 3 used this pattern until it was rebuilt around the standard workflow-compare/flow-row Method; kept documented for a future entry that genuinely needs input/output/outcome receipts)
Three variants sharing one base: .receipt--input (neutral,
--color-ink-soft border) for what was actually asked,
.receipt--output (accent border) for what the AI produced, and
.receipt--outcome (accent-2 border, on
--color-bg-alt) for what actually happened. The single accent-2
border stays within Principle 02 — a spark on one edge, never a surface.
Input
What was actually typed — unedited.
AI output
What actually came back — summarized honestly, never invented.
Outcome
What happened next. This is the block that carries the accent-2 border.
Hypothesis block · .lab-hypothesis (legacy pull-quote style, superseded by .hyp-result below)
The light-mode sibling of .pull-quote — same italic display
type and accent left-border, but on --color-bg-alt instead of the
dark surface. Kept in the system for a standalone claim that isn’t paired with a result,
but every Lab experiment page now leads with .hyp-result instead,
since a hypothesis without its result invites the reader to wonder what happened.
I think a claim stated plainly, before the evidence, is what makes the evidence mean something.
Status pill · .pill--ongoing / .pill--supported / .pill--rejected / .pill--inconclusive (used on /lab/ and every Lab entry)
The Lab’s result vocabulary, as four tints of the same .pill
shape — no new hues added to the palette. --ongoing is
accent-soft (still active); --supported is solid ink (a settled,
confirmed result); --rejected is accent-2 (the hypothesis
didn’t hold — a valid result, not a failure, so it stays warm rather than reading
as an error state); --inconclusive is muted bg-alt (no strong
signal yet). Same four classes on the index list and on each experiment’s own
.hyp-result block, so status reads identically everywhere.
Hypothesis / Finding · .hyp-result (used on every Lab experiment page)
The most prominent element on an experiment page — sits directly under the header,
before any method or mechanics, so the claim and where it landed are visible in one glance.
Two-column, divided by a hairline; the result side sits on --color-bg-alt
to read as the “payoff” half. No status pill here — the header already carries
one, and repeating it here just duplicates the same signal. Optionally carries a one-line
limitations note underneath — this is single-subject, ongoing research, not a controlled
study, and the layout should never imply more rigor than that.
Hypothesis
The claim, stated plainly, before the evidence.
Finding
What actually happened — or where the test currently stands.
Optional: a one-line honesty check on how much this result can carry.
Experiment list · .experiment-list / .experiment-row (used on /lab/)
Replaces the old one-card-per-entry index. Built to hold hundreds of rows without each one costing a full card’s worth of space: a plain incrementing number for sort and scale, the question as the entry point, domain and model as small mono facts, a status pill, an arrow. Rows stack their fields on narrow viewports rather than truncating.
Content toolbar · .content-toolbar (Experiment 4, used on /lab/ and /thinking/)
The filter group and sort control above, stacked into one bordered bar with a result strip
underneath, plus script.js’s shared filter/sort/search logic. One
organism, imported by both pages — only the filter facet and sort options differ per page
(Status for the Lab, Topic for Thinking). Live below: try the search box or a chip.
Progressive scroll reveal · .scroll-sentinel / .is-scroll-hidden (Experiment 5, used on /lab/ and /thinking/)
An infinite-scroll feel for people, without ever hiding content from a crawler. Every item
still ships in the page’s raw HTML at load — nothing is fetched on scroll —
a .scroll-sentinel just tells the shared toolbar script above to
reveal the next batch, via .is-scroll-hidden, as a person scrolls
near the bottom. Since most AI crawlers never execute JavaScript, they read the same complete
list a search engine would — visibility state is a rendering concept, and a crawler that
never renders never sees it. Live in the toolbar demo above: scroll down and watch more rows
reveal themselves.
Path tracker · .path-tracker (Experiment 6, injected sitewide via script.js)
An opt-in, first-party, self-destructing cookie widget — not markup on any individual
page. script.js injects it into every page on load, so nothing
had to be added to any page’s HTML to ship it. Off by default: a
.path-tracker-pre block sits fixed in the corner — the
.path-tracker-toggle pill itself, plus a small
.path-tracker-privacy link straight to the Privacy Policy, so
the disclosure sits right next to the action rather than only living on a page most
visitors won’t seek out. Opting in swaps it for a .path-tracker-panel
showing a .path-tracker-countdown-label ("This cookie will
self-destruct in") above a live 15-minute countdown and the number of pages visited since
opt-in — a page-visit trail plus an expiry timestamp, never transmitted anywhere. The
panel’s .path-tracker-view
link opens the same trail as a full page (see .path-map below).
Next to it, a .path-tracker-stop hyperlink (orange,
--color-accent-2) reads "Stop tracking" and does the same thing
as the × button just after it — two ways to reach the same action, one worded
for clarity and one for a minimal footprint. It self-destructs automatically at 00:00, or
immediately on clicking .path-tracker-stop or
.path-tracker-dismiss — no renewal, no re-prompt either
way. See it live on this page’s own corner, or read the full disclosure on the
Privacy Policy. The two static frames below show both states;
the real widget in the corner is the live one.
Off (default)
Opted in
Path map · .path-map (on /cookie/)
The full-page view of the same trail the corner widget tracks — reads the identical
pmPathTracker cookie, so the two can never drift apart. Each
visited page becomes a node on a connected, vertically-growing path, in visit order; the
most recent page pulses via the same .path-tracker-dot--live
treatment as the corner widget’s live dot. With no active session, the static
.path-map-empty state below ships in the page’s raw HTML
— real content for anyone or anything not running JavaScript — and its
.path-map-start / .path-map-stop
buttons simply trigger the corner widget’s own toggle/dismiss controls rather than
duplicating the opt-in logic.
Sample trail (static, for illustration)
Accessibility widget · .a11y-widget (injected sitewide via script.js)
Opt-in reading and display controls — not markup on any individual page.
script.js’s initA11yWidget()
injects it into every page on load, same pattern as the path tracker above, stacked just
above it in the corner. Collapsed, it’s a single round .a11y-tab;
opening it shows an .a11y-panel with three
.a11y-size-btn text sizes and three .a11y-switch
toggles (higher contrast, reduce motion, underline links). Each choice sets a class on
<html> (.a11y-text-lg,
.a11y-text-xl, .a11y-contrast,
.a11y-motion-reduce, .a11y-underline-links)
and is saved under the pmA11yPrefs localStorage key on this
browser only — never transmitted anywhere. The two static frames below show both
states; the real widget in the corner is the live one.
Collapsed (default)
Open
Contrast checker · .contrast-tool (on /contrast-checker/, Experiment 8 companion)
A live, in-browser WCAG contrast-ratio tool — two color fields (each an
input[type=color] swatch synced to a hex text input),
a swap button, a live text-on-background preview, and four
.pill.pill--supported / .pill--rejected
Pass/Fail rows (AA/AAA × normal/large text) — reusing the existing pill
component rather than a new badge. Runs the identical relative-luminance math as
scripts/accessibility_check.py’s contrast_ratio(),
in script.js’s initContrastChecker(),
so the live page and the internal pre-deploy checker always agree. For demonstration
purposes only — not a certified accessibility auditor.
Worked / didn’t · .worked-item (used on every Lab entry)
Two-column honest ledger. .is-good gets a filled accent-soft
circle with a check; .is-bad gets a muted bg-alt circle with an
× — deliberately quieter than a red “error” color, since this isn’t
failure, it’s just where a human was still required.
.is-good
.is-bad
Provenance mark · .provenance (used site-wide)
A native <details>/<summary> disclosure — no JS,
keyboard- and screen-reader-accessible for free. Three tiers share one shape and swap only
the dot color and pill title: .provenance--human (muted, “Human
Creation”), .provenance--assisted (accent, “AI-Assisted
Creation”), and .provenance--collaborative (accent-2, “AI-Collaborative
Creation”). Expanding a mark reveals one sentence naming who did what, with
.provenance-author a real link to /about/ on
every instance — a byline a crawler or an LLM can follow to confirm who’s accountable,
not just a label. See the reasoning inside Human + AI Provenance.
Thought node · .thought-node (fortune-cookie length, used on /thinking/)
Replaced the old single long-form .thought-card essay layout:
Thinking is now a column of short, complete thoughts strung on the same gradient spine and
timeline-flow pulses as .steps
above, just run through a small glowing .thought-node-dot
instead of an icon circle. Each node is a native <details>
card — a tag, a headline teaser, and on open, a quote capped at roughly 150–300
characters. No preview-of-something-longer: the quote is the whole thought. A small
script mirrors each card’s [open] state onto its marker
(.thought-node--open) so the dot glows brighter while its card
is expanded. Per house rule, Claude doesn’t draft the words that go in these —
see Principle 07 below.
The tag and headline are the whole teaser — open the card and the quote is capped short too, not a preview of something longer.
.thought-node--open scales and glows the dot — toggled by JS listening to the <details> element’s native toggle event, nothing more.
Footer links · .footer-links-groups (used site-wide)
A complete site index — a small, quiet, mono-uppercase set of rows above the copyright line, out of the hamburger menu. The top row repeats the three primary-nav pages (Lab, Thinking, About) in the same order as the hamburger menu; a hairline divides it from a second, lighter row for Story and Design System, which aren’t in the hamburger menu at all. A third row (added 2026-09-04, prerequisite infrastructure for the path-tracker Lab experiment) holds Privacy Policy and Terms & Conditions — the site’s two legal pages, grouped together since neither belongs in primary nav or the Story/Design System meta row. Splitting the rows this way (rather than one flat pipe-separated list) is what lets each row wrap cleanly on narrow phones instead of overflowing.
Site navigation · hamburger menu
Every page shares one nav: a fixed logo, a .nav-toggle
hamburger that morphs into an × on open, and a .nav-panel
that slides in from the right with three links — Lab, Thinking, About. Story and
Design System live as a quiet, pipe-separated row in every footer instead. The panel runs
the node network canvas below as its background — only while it's open, so it costs
nothing the rest of the time.
.nav-toggle · closed / open
.nav-panel · boxed preview
Back link · .back-link (used on every non-home page)
A plain-text mono button, first thing inside <main> on
every page except the homepage. Calls history.back() so it
returns wherever the visitor actually came from — internal link, search result, or
a direct hit, in which case it falls back to / rather than
doing nothing. No href to keep in sync with the page it sits
on; the browser already knows.
Intro background wash · .hero-glow (used site-wide)
A single soft radial gradient, top-right, rgba(76, 58, 227, 0.28)
fading to transparent — the quiet color wash every page top should carry. This used to
drift: some intros had it, some didn’t. It’s now standard on every real
page’s intro section — no exceptions — independent of whether that page also
runs the node-network canvas below.
Node network canvas · shared pattern
One factory function, createNodeNetwork(canvas, boundsEl, opts)
in script.js, draws a field of drifting dots that link with a
faint line whenever two fall within range. It's the one JS-driven visual motif on the
site — reused rather than reinvented per section. initNetworkCanvases()
finds every .hero-canvas on the page and starts one automatically,
so any section can opt into the same background just by giving its canvas that class —
no per-page JS required:
frontier-pulse, just recentered
Workflow compare · .workflow-compare (Lab: Traditional vs. Experiment pipeline, used on /lab/1/, /lab/2/, /lab/3/, /lab/4/, /lab/5/, /lab/6/, /lab/7/, /lab/8/, and /lab/9/)
The strongest at-a-glance insight on an experiment page: two bordered rows, not a diagram, so it stays in
the site's restrained register. .workflow-compare-row--current outlines
whichever row is the page's actual subject. Content-specific to before/after comparison findings —
deliberately not (yet) folded into a generic Organism, since every Lab entry's comparison has a different
shape.
Flow row · .flow-row (horizontal process artifact, e.g. Brief → AI → Deploy on /lab/1/)
What should exist, and why.
Built it — code, copy, and design.
Reviewed, edited, decided what shipped.
Statement · .statement (single large centered claim, e.g. “Key observation” on /lab/1/)
The one moment on a page meant to stop a scanning reader. Deliberately not a full-bleed color-inverted
section — that read as the page footer and stopped people scrolling early. Same typographic recipe
as .about-title, centered, capped narrow so it never runs the full width of
the page.
Execution wasn’t the constraint.
Judgment was.
AI can quickly build what’s requested. The harder question is whether it’s worth building.
Templates
Everything above shows a component. A template is a whole page, pre-assembled from those components with the copy replaced by placeholders — something to duplicate and fill in, not just read.
Naming · /design-system/template-X/
Each template lives at its own template-X path under Design
System — visible only from here, not linked in primary nav, and marked
noindex since it isn’t real content. Copy the page,
replace the bracketed placeholders, rename the folder to its real destination.
A blank Lab experiment, built around the scientific-method structure every entry follows: Question (header), Hypothesis + Finding, Method (workflow-compare + flow-row + Tech), the Experiment itself, Finding → Implication, Next Question. Refreshed 2026-09-04 to match the current structure (it previously said “Result” instead of “Finding,” still referenced the retired slugged URL scheme, and still had the retired Evidence section) — still a copy-and-fill-in scaffold, not the preferred way to start a new page (see Data-driven templates below), but no longer stale as a shape.
Data-driven templates · templates/ + scripts/render_template.py
A second, newer way to start a page — alongside the copy-and-fill-in scaffold above,
not replacing it. A template file (templates/lab-experiment.template.html)
holds the markup with placeholders; a JSON data file holds one experiment’s content;
scripts/render_template.py combines them into a real HTML page,
which still gets hand-reviewed and pushed through the normal GitHub upload flow like any
other page — this doesn’t change how or where the site deploys, only how the
starting HTML gets produced. Shared chrome (nav, footer, the provenance mark) lives once as
partials under templates/partials/ instead of being copied into
every new page by hand.
The syntax intentionally echoes the real Swig template engine (double-brace variables, an include tag, an if/endif tag) even though the renderer is a small dependency-free script, not the actual library — this sandbox’s npm registry access is blocked, so the real package can’t be installed here. If that ever changes, the template files themselves should need little rework to move to it.
templates/lab-experiment.template.html encodes the
canonical page structure (workflow-compare Method centerpiece, a separate
Experiment section with a Worked/Constraint pair, consolidated Finding → Implication)
and is the source of truth for a new experiment page's shape — confirmed directly with
Dr. Paul 2026-09-04. Verified against Experiment 1’s real content: rendering it through
this template reproduces the shared nav/footer/provenance partials byte-for-byte. Experiment 2
is the template’s second real page, produced the same way. Experiments 3, 4, and 5 were
each rebuilt by hand 2026-09-04 to match this shape — Experiment 3 previously had its own
bespoke structure, and Experiments 4 and 5 had shipped a leaner Method-only variant (no
workflow-compare/flow-row/Tech pills, no separate Worked/Constraint pair). Experiment 6 was
built into this shape from the start, and Experiments 7, 8, and 9 followed the same shape from the start
too. All nine live experiments now share the one canonical
shape.
Lab URL scheme · /lab/N/
Built to scale to hundreds of entries. The number is a plain increment
(1, 2…
99, 100) with no descriptive slug
attached — e.g. /lab/1/, not
/lab/1-can-ai-replace-a-cms/. (Changed 2026-08-29 from an earlier
/lab/N-descriptive-slug/ convention — the slug added length
without adding clarity the plain number didn’t already carry.) Never reuse or renumber:
if an experiment moves or is superseded, its old URL gets a redirect stub (see below) pointing
at the new one — it never gets deleted outright.
Redirect stub · minimal meta-refresh page
When a page moves, the old URL keeps a tiny page in place rather than going to a 404:
<meta http-equiv="refresh" content="0; url=/new/path/">,
<meta name="robots" content="noindex, follow"> so it drops
from search results but still passes link equity, and a canonical
pointed at the new URL. Point every stub directly at the current canonical destination rather
than through an intermediate — update old stubs in place when something moves again,
instead of chaining redirects.
Structured Data
The visible .provenance mark tells a person how a page was made. Every page also carries the same disclosure as JSON-LD, so a search engine or AI crawler can read it too — not just a person clicking the mark.
Footer provenance mark · every page
The same .provenance component from Molecules, in a
.footer-provenance wrapper inside a .page-provenance
block — a sibling that sits directly above <footer class="site-footer">,
not nested inside it. The split is deliberate: .site-footer’s markup is
byte-identical on every real page (not the redirect stubs, which have no content of their own to attribute),
so a footer-wide edit is one shape to change everywhere, while the provenance mark stays free to vary
per page without touching that shared shape. This closes the drift Paul flagged: the mark used to appear
only on pages where it happened to get written in — now it’s a guarantee just above every
footer, the same way .hero-glow is a top-of-page guarantee.
Authorship JSON-LD · every <head>
Every page’s existing application/ld+json block
(ProfilePage, CollectionPage, TechArticle, or WebPage, depending on the page) carries four
extra properties that mirror the visible mark exactly — the schema is never allowed
to claim something the page itself doesn’t say:
author — always Dr. Paul Ress. He’s the byline, tier notwithstanding.creator — an array: Dr. Paul Ress, plus an Organization entry for Anthropic when Claude materially participated.accountablePerson — always Dr. Paul Ress. Whatever Claude drafted, he’s the one who approved it.digitalSourceType — the IPTC digital-source-type code for the page’s tier, via schema.org’s adopted vocabulary for exactly this: how much of a work is AI-originated. Only set for AI-assisted or AI-collaborative pages; omitted entirely for Human-authored, since the property’s own spec treats absence as the human-made default.
Today that’s CompositeWithTrainedAlgorithmicMediaDigitalSource on every real page, since every page is currently the AI-collaborative tier. The property exists so that changes — a future Human-authored or AI-assisted page — without touching this pattern.
Principles
Tokens and components only hold consistency together if the decisions around them are also written down.
New spacing or sizing needs a token, not a number. If nothing in the scale fits, that's a sign the scale needs a new step — not an excuse for a one-off pixel value.
--color-accent-2 (orange) is a spark, not a surface. It appears in gradients and single accents only — never as a button, background, or block of color on its own.
Cards always get the hover lift. If a new card-like component doesn't want transform: translateY(-4px) on hover, it isn't a card — give it its own name.
Section headers are always kicker + title + lede, in that order. The kicker (Story) is a short plain-text label above the title — no sequence number or dot, since numbering didn't scale once sections started getting reordered or nested.
Motion respects prefers-reduced-motion everywhere, including the hero network canvas. Nothing here should ever be the only way information is conveyed.
This page is documentation, not decoration. When a token or component changes in styles.css, this page changes with it automatically — if it ever doesn't, something is wrong.
Claude doesn't draft what goes in a Thinking node. Layout, spine, pulses, markup — all fair game. The tag, headline, and quote inside each .thought-node are Dr. Paul's own words; Claude asks for them rather than writing placeholders that could pass as real.
Documentation only self-heals if something checks it. Principle 06 says this page updates itself when tokens change — but nothing used to catch a new component shipping without a matching entry here, which is how the gaps behind this Principle got in. scripts/audit-site.py diffs every class used in HTML against every class defined in CSS (both directions — unstyled classes and dead CSS), and checks the footer is still byte-identical across every real page. Run it before every deploy.