<!-- System · Adam Parish -->
> How this site is built: a palette from seven OKLCH inputs, one tempo for motion and type named by role. Live, with the knobs exposed.

(System) How this site is built

# System.

Decide it once, then build on it.

Everything on this site comes from a short list of rules. The colors
are worked out from seven numbers, motion runs on one set of
durations, and type is named for what it is used for. When I change
one of those inputs, everything built on it changes too. This page
uses the same tokens it describes, so the controls below actually
work.

Palette OKLCH formula

Motion Seven tiers

Type 18 steps by role

Source main.css

(00) Why

## Set the rules once, then build from them.

Polishing screens one at a time always ended the same way for me:
they slowly drifted apart. So now I start from a few named inputs
and work everything else out from those. Colors come from seven
numbers, sizes from a fixed scale, motion from a handful of
durations. Since nothing is picked by eye, nothing wanders off. It
holds up on a marketing site, a product or a native app, and it can
fit a brand that already exists. Run against one, the formula
matched the existing palette within ΔE 1.2, close enough that the
brand looked the same.
- Derive

### Start from inputs

A color is a step on a ladder. A size is a step on a scale. A
duration is one of seven tiers. Change an input and everything
built on it moves together, and the gaps that keep text readable
and spacing even stay where they were.
- Name

### Name things for their job

The tokens are called things like ink, line, control, card, hover
and layout, so you can tell what each one is for by reading it.
There is no token for a one-off value. If I want one, I need a
good reason.
- Enforce

### Let the computer check

A rule written down in a doc gets forgotten, by me included. So
the contrast minimums run as a script, every page is screenshotted
and compared before a change goes out, and a test makes sure
content never plays its entrance twice.
- Reuse

### One method, many sites

I have used the same approach on more than a dozen product and
client sites, and each one still looks like itself. The reusable
parts are
[open source](https://github.com/Adamparishspok/skills): the palette formula, the motion tiers and the screenshot
regression pass, written up as skills any agent can run.

(01) Color

## Every color comes from seven numbers.

I do not pick any of the greys by hand. Each theme sets seven
inputs: one hue for everything, three for backgrounds and lines
(paper, depth and tint) and three for text (ink, ink step and ink
tint). Every neutral is a step on a ladder, worked out in OKLCH as
the paper lightness plus depth times the step. The ladder stays put
and the inputs move. When I retint the site, every step shifts by
the same amount, so the gaps between them hold, and the gaps are
where the contrast comes from.

Open the tuner

/* a ground, a card, a hairline */
L = paper + depth × step C = tint H = hue
/* a line of text */
L = ink-l + ink-step × step C = ink-tint H = hue

Dark

Light

### The audit, live

CSS has no way to enforce a contrast minimum, so a script does it.
It checks every text color against every background and fails
anything under 7:1. Here it reruns as you tune, showing the worst
result for each text color.

(02) Motion

## One tempo for the whole site.

Instead of picking a duration for each component, I pick one of
seven tiers. Reusing the same few timings everywhere is a big part
of why the site feels like one place. Things arriving ease out.
Things leaving ease in and finish sooner. A button press answers
right away, and a phone turning into view gets about a second.

Played at quarter speed so the short ones are visible. The times are
real.

### Easing

(03) Type

## Type sizes come from a fixed list.

There is a set list of sizes, each named for what it is used for,
and anything off the list needs a reason. Small text stays the same
size everywhere. Headings grow and shrink with the screen, within
limits. The three largest sizes are each used in exactly one place:
the hero, project titles and the preloader count. Fraunces is the
display face, Archivo is for reading and Fragment Mono is for
labels.

(04) Verify

## Every rule gets a check.

Each rule on this page has a test that can fail, and the tests run
before anything ships.
- Contrast

### Every text color on every background

A script checks each text color against each background and fails
anything under 7:1, plus any control border under 3:1. It is the
same check running live under Color while you tune. When something
fails, I fix it by adjusting an input, so the fix carries
everywhere that input is used.
- Screens

### Before and after, every page

Playwright screenshots every page and every case study before and
after a change, then ranks what moved. I sort the noise from the
real regressions before anything gets merged.
- Motion

### Content never animates twice

A probe watches the page as it changes and fails if anything that
stayed put replays its entrance. Only new content should animate
in.

(05) Stack

## A short list of tools.

No template. The pages are hand-written HTML and CSS with some
TypeScript on top, and the interactive builds in the case studies
are React. I only add a library when it does something the browser
cannot, and it only loads on the pages that need it.
- Type

### Fraunces, Archivo, Fragment Mono

Fraunces is the display face. It is a variable serif with an
optical size axis, so the small name in the header and a
full-width headline each get letterforms drawn for their size.
Archivo is for body text, and Fragment Mono is for labels, dates
and anything technical. All three are self-hosted and preloaded,
and the two main faces fall back to system fonts adjusted to match
their metrics, so text does not jump when they load.
- Motion

### GSAP

Runs the preloader, the hero's line reveals, the manifesto that
scrubs with the scroll, and the cursor. It stays off the critical
path. On a phone it waits until the page is idle, and with reduced
motion turned on it never loads.
- Scroll

### Lenis

Smooths out wheel scrolling, so anything tied to the scroll moves
continuously instead of in steps. Anchor links go through it too.
With reduced motion on, you get the browser's normal scrolling.
- Atmosphere

### Three.js

Draws the smoke behind the hero, a single full-screen shader of
warped noise that leans toward your pointer. It is the heaviest
thing on the site, so it only runs on desktops that can handle it
and waits until the browser is idle to load. Everyone else gets a
CSS gradient in the same ember color. It pauses when the hero
scrolls out of view and follows the light and dark theme.
- Case studies

### React and Tailwind CSS

The interactive builds in the case studies are React, styled with
Tailwind CSS v4 on a shared set of tokens. Each one only loads on
its own case study.
- Build

### Vite+

Builds the pages and hashes the assets. The site itself is plain
TypeScript, because it is a document with some motion, not an app.

See it in use

[Selected work →](https://atomparish.com/#work)

Spokane, WA
Back to top ↑

## Customize the theme

Theme hue
Light mode saturation
Dark mode saturation
Light mode brightness
Dark mode brightness

Reset

Copy config
