← Home Adam Parish©

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

PaletteOKLCH formula
MotionSeven tiers
Type18 steps by role
Sourcemain.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.

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

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

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

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

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

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

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

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

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

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

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

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

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

          6. 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 →
          Spokane, WA · 00:00:00 Back to top ↑