The system behind this site. Primitives hold raw values and nothing in the interface uses them directly. Semantic roles describe what a value is for, and each one carries a light and a dark value — so a theme change is one attribute, not a rewrite.

Values below are read live from globals.css

Colour

Semantic roles, in both themes.

Surfaces

canvasPage background
surfaceTinted section bands
band-stoneWarm band variant (research sections)
cardFeature and process cards
footerFooter block
deepShowcase sections

Text

fgPrimary text, headings
fg-mutedMeta, captions, eyebrows
fg-softSecondary copy inside cards
on-deepText on showcase sections

Interactive

inversePrimary button, number badges
on-inverseText on inverse
chipSecondary button
lineBorders, dividers, outlines
arrowConnectors between steps

Status

successShipped tag
dangerForm errors

Primitives

paper-50paper-100paper-150paper-200grey-50grey-150grey-500warm-500ink-900night-950night-900night-800night-750night-600warm-450bone-100green-700red-700

Typography

Five families, each with one job.

Hero

DM Serif Display · 100 / 100 · −4%

Hero statements, footer sign-off

.text-hero

Purposeful thinking.

Page title

DM Serif Display · 48 · uppercase

Index pages

.text-page-title

Works

Title

Lora Italic 600 · 40 / 44 (28 on phones)

Section and project titles

.text-title

Good design starts with curiosity.

Eyebrow

Inter 500 · 18 · uppercase

Section labels

.text-eyebrow

Testimonials

Body

IBM Plex Mono · 14 / 28

All running copy

.text-body

My work begins by understanding people. It stops when the problem is solved.

Meta

IBM Plex Mono 500 · 10 / 16 · +20% · uppercase

Navigation, tags, captions

.text-meta

Design Systems · AI Product · 2026

Button

DM Mono 500 · 12 · +20% · uppercase

Buttons

.text-button

Explore my work

Components

The small set everything is built from.

Tags

Password lockedShippedAlso: Photography →

Skill card

AI Interaction Design

LLM interfaces · agent flows

Stats

85%

Task completion

500+

Families

3,000+

Graduates

Step card

01

Audit

Read the shipped code to separate real variation from drift.

Numbered item

01.

System ownership

Tokens, components, documentation, and the loop back from code.

Motion

One easing, used everywhere.

Every transition eases out on cubic-bezier(0.22, 1, 0.36, 1) — fast to start, long to settle. Scroll effects are tied to the smoothed scroll position, and all of it switches off for visitors who ask for reduced motion.

SmoothScroll

Inertia scrolling site-wide; off for reduced-motion users

motion/SmoothScroll.tsx

ParallaxImage

Wipes in from the bottom, then drifts inside its frame on scroll

motion/ParallaxImage.tsx

SplitText

Words rise from behind a mask, staggered

motion/SplitText.tsx

CountUp

Stats count up when they appear

motion/CountUp.tsx

VelocityMarquee

Drifts, speeds up with scroll, reverses on scroll-up

motion/VelocityMarquee.tsx

Reveal / FadeIn

Fade-up on scroll / on load

Reveal.tsx · motion/FadeIn.tsx

ThemeToggle

New theme spreads in a circle from the switch

ThemeToggle.tsx

Layout

Three breakpoints, inherited from the original site.

Phone

< 810px

Gutter 20px

Tablet

810 – 1199px

Gutter 40 – 60px

Desktop

≥ 1200px

Gutter 60px · case-study text column 960px