System

Design 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.

Live reference — loads ../styles.css directly, not a copy

Foundations

The primitives everything else is built from

Color, type, space, radius, and motion — five small scales that, used consistently, are most of what "design system" actually means.

Color

Background

--color-bg #FAF8F4

Background alt

--color-bg-alt #F1ECE3

Ink

--color-ink #17181C

Ink soft

--color-ink-soft #4B4E57

Line

--color-line #E1DACB

Accent

--color-accent #4C3AE3

Accent soft

--color-accent-soft #E4E1FA

Accent 2

--color-accent-2 #E4572E

Dark surface

--color-dark #14131B

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.

--font-display“Fraunces”, Georgia, serif · weights 400/500/600/700 · hero headlines, section titles, pull-quotes — anywhere a line needs weight and warmth

Dr. Paul, DBA

--font-body“Inter”, system-ui, -apple-system, “Segoe UI”, sans-serif · weights 400/500/600/700/800 · paragraph copy, nav, buttons, form fields — anything read at length or clicked

What happens when marketers stop asking what AI can do and start experimenting?

--font-mono“JetBrains Mono”, “SFMono-Regular”, Menlo, Consolas, monospace · weights 500/600 · kickers, pills, timestamps, code, footer links — the site’s “metadata” voice

01 · Story · 2010–2016

Type scale

--text-heroclamp(2.6rem, 5.4vw, 4rem) · hero headline

Dr. Paul

--text-heading-lgclamp(1.8rem, 3vw, 2.3rem) · section titles

The narrow path

--text-heading-mdclamp(1.3rem, 2.6vw, 1.8rem) · pull-quotes

As iron sharpens iron.

--text-3xl2rem · stat numbers

15+

--text-2xl1.3rem · card headings

Foundation

--text-xl1.2rem · ledes, value-card headings

15+ years of experience.

--text-lg1.05rem · section ledes

Three values that power how he operates.

--text-base0.95rem · nav, buttons, body copy

Connect on LinkedIn

--text-sm0.85rem · labels, captions

years of performance marketing experience

--text-xs0.78rem · kickers, pills

01 · Story

--text-2xs0.72rem · timeline years

2010–2016

Spacing scale · 4px grid

--space-1 4px
--space-2 8px
--space-3 12px
--space-4 16px
--space-5 20px
--space-6 24px
--space-7 32px
--space-8 40px
--space-9 48px
--space-10 64px
--space-11 88px
--space-12 108px

Radius

--radius-sm · 8px

--radius · 12px

--radius-full · pills & buttons

Motion · hover to feel each one

Fast · 150ms

--duration-fast · buttons

Base · 200ms

--duration-base · card hover lift

Slow · 700ms

--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.

[data-reveal] once .is-visible is added

Atoms

The smallest reusable pieces

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

AI Strategy AI Enablement AI Governance

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/)

  • Code
  • Iteration
  • Troubleshooting
  • Deployment

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.

Supported Rejected 2 of 4

Molecules

Small groups with one job

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

Human Ops Model

Used identically at the top of every section, site-wide.

Card · .card (base, .card--value, .card--current)

2010–2016

Base card

Used as-is for story chapters. Background, border, radius, and hover lift live here once.

.card--value

Adds the alt background and icon slot. Everything else is inherited from .card.

2025–Present

.card--current

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.

Status All Supported Rejected
Sort

Organisms

Full sections, assembled from the above

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.

Bend Your Mind With

Dr. Paul, DBA

AI Strategy AI Enablement AI Governance
Connect on LinkedIn

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).

2005–10
University of Louisville

A degree earned the hard way

Odd rows sit left of the center line, year on the left.

2014–16
The Learning House

Even rows flip via nth-child(even)

Same markup order, no per-item modifier class needed for the alternation.

2026–
Marketing AI

.card--current marks the active chapter

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

Base .card

No modifier — just the shared card primitive.

After

.card--current

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.

Step one

Same 44px icon-circle token as .promo-icon / .value-icon, just circular instead of rounded-square.

Step two

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.

Ongoing Supported Rejected Inconclusive

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)

Opt in to track my path Privacy policy

Opted in

This cookie will self-destruct in 14:52 3 pages View path

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)

11:24 3 pages visited
  1. Home /
  2. Lab /lab/
  3. Lab · Experiment 6 /lab/6/

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

Read it your way
Higher contrast
Reduce motion
Underline links

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.

Foreground
#17181c
Background
#faf8f4
16.72 : 1 Contrast ratio
AA · Normal (4.5:1) Pass
AAA · Normal (7:1) Pass

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.

Human Creation

Written solo by Dr. Paul. AI touched nothing worth crediting.

AI-Assisted Creation

The thinking is by Dr. Paul. AI helped edit, research, or draft faster.

AI-Collaborative Creation

AI materially drafted the artifact under Dr. Paul’s direction and judgment.

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.

Example · Collapsed

Collapsed by default

The tag and headline are the whole teaser — open the card and the quote is capped short too, not a preview of something longer.

Example · Open

The marker glows on open

.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.

.hero-glow, on its own, over a plain surface.

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:

Hero background · linkDist 140 · density 16000 · speed 0.25 · runs continuously Nav panel background · linkDist 110 · density 9000 · speed 0.18 · runs only while open Intro background · same defaults as the hero · used on /story/, /thinking/, /lab/, /about/, and every Lab entry (e.g. /lab/1/) Thinking pulse · a centered, breathing glow layered behind the network on /thinking/ only — reuses 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.

Traditional
Marketing CMS Developer / Agency
Experiment
Marketing intent AI Human review

Flow row · .flow-row (horizontal process artifact, e.g. Brief → AI → Deploy on /lab/1/)

Brief

What should exist, and why.

AI

Built it — code, copy, and design.

Human judgment

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

Starter scaffolds, not just documentation

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

Provenance, machine-readable

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

How to use this without breaking it

Tokens and components only hold consistency together if the decisions around them are also written down.

01

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.

02

--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.

03

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.

04

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.

05

Motion respects prefers-reduced-motion everywhere, including the hero network canvas. Nothing here should ever be the only way information is conveyed.

06

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.

07

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.

08

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.