Design System

The design system behind this portfolio. Color tokens and typography are defined as CSS custom properties and composition classes in globals.css. Layout, spacing, and responsive behavior use Tailwind v3 utility classes. Toggle the theme to see both modes.

Color Tokens

Defined as CSS custom properties in globals.css with light and dark values. Mapped to Tailwind via tailwind.config.js so utilities like text-fg and bg-surface resolve to the active theme.

Background

--bg

Light: #fffbf5

Dark: #1c1a16

Background Subtle

--bg-subtle

Light: #fdf8f2

Dark: #201e1a

Surface

--surface

Light: #f4f1ec

Dark: #28251f

Foreground

--fg

Light: #222222

Dark: #fffbf5

Secondary

--fg-secondary

Light: #6e6562

Dark: #c2bab0

Accent

--accent

Light: #813746

Dark: #e36f86

On Accent

--on-accent

Light: #fffbf5

Dark: #1c1a16

Border

--border

Light: rgba(92,82,80,0.1)

Dark: rgba(176,168,158,0.1)

Uses alpha transparency so one token works on all three background layers.

Noise overlay

A fractal noise SVG texture covers all surfaces at 2.5% opacity via .noise::after, adding analog grain to flat backgrounds. Color picker values will differ from token hex values by 1-2 units due to compositing.

Accessibility

Every text and background combination used on this site meets WCAG 2.1 AA. Accessibility is structural, not an afterthought.

Light theme

Aa

--fg on --bg (#fffbf5)

15.43:1 -- AAA

Aa

--fg-secondary on --bg (#fffbf5)

5.50:1 -- AA

Aa

--fg-secondary on --bg-subtle (#fdf8f2)

5.37:1 -- AA

Aa

--fg-secondary on --surface (#f4f1ec)

5.04:1 -- AA

Aa

--accent on --bg (#fffbf5)

7.93:1 -- AAA

Dark theme

Aa

--fg on --bg (#1c1a16)

16.85:1 -- AAA

Aa

--fg-secondary on --bg (#1c1a16)

9.05:1 -- AAA

Aa

--fg-secondary on --bg-subtle (#201e1a)

8.67:1 -- AAA

Aa

--fg-secondary on --surface (#28251f)

7.96:1 -- AAA

Aa

--accent on --bg (#1c1a16)

5.70:1 -- AA

Implementation

Skip link

"Skip to main content" hidden until keyboard focus. Lets screen reader and keyboard users bypass the nav.

Focus-visible

Accent-colored ring on keyboard focus only. No ring on mouse click. Consistent across all interactive elements.

Reduced motion

All animations and transitions disabled via prefers-reduced-motion: reduce. No partial reductions.

VoiceOver-safe animations

data-animate-y uses translateY only (no opacity) so headings are always discoverable by screen readers.

ARIA labels

Dynamic labels on theme toggle, hamburger menu, and email copy button. States update on interaction.

Semantic landmarks

nav, main, footer with aria-label on navigation regions. Sitemap uses labeled nav.

Focus trap

Mobile nav overlay traps keyboard focus within links and the hamburger button. Escape closes the overlay.

Color never alone

Status and emphasis always pair color with text or shape. No information conveyed through color alone (WCAG 1.4.1).

Typography

Composition classes defined in @layer components in globals.css. Display sizes use fluid clamp() for smooth scaling across viewports. Body sizes step at the md breakpoint (16px to 20px).

Display 2XL

Cabinet Grotesk / clamp(48-96px)

The quick brown fox

Display XL

Cabinet Grotesk / clamp(40-80px)

The quick brown fox

Display LG

Cabinet Grotesk / clamp(32-60px)

The quick brown fox

Display MD

Cabinet Grotesk / clamp(24-36px)

The quick brown fox

Display SM

Cabinet Grotesk / clamp(20-24px)

The quick brown fox

Body

Areal / 16px / 20px

The quick brown fox

Label

Areal / 16px / 20px

UPPERCASE LABEL TEXT

Link

Areal / 16px / 20px

Badge

Areal / 16px / 20px

The quick brown fox

Meta

Areal / 14px

Small metadata text

Font families

Aa

Cabinet Grotesk

Display headings

Aa

Areal

Body, UI, labels

Aa

Ogg

Editorial accent

Code

The code element inherits its parent's font size and uses --fg-secondary text on a --surface background with a subtle border.

Example: use globals.css for token definitions and tailwind.config.js for utility mapping.

Layout

Responsive layout using Tailwind utilities. No custom grid system.

Container

All page content wraps in a Container component: max-w-7xl (1280px), centered with mx-auto, and px-6 (24px) side padding. On viewports narrower than 1280px, the max-width has no effect and the padding creates the gutter.

Default (lg)

max-w-7xl / 1280px

Medium (md)

max-w-4xl / 896px

Small (sm)

max-w-3xl / 768px

Breakpoints

sm (640px)

Case study nav stacks to side-by-side

md (768px)

Desktop nav visible, hamburger hidden. Body text steps to 20px. Grids expand to multi-column.

Three-layer structure

Every page uses three background layers. Shadows mark the transitions between them. Cards sit on the middle layer.

--bg (nav, hero, footer)

--bg-subtle (content sections)

--surface (cards)

Spacing

4px base grid using the standard Tailwind numeric scale. All spacing uses utility classes (gap-4, p-6, mb-8).

4px t-1
8px t-2
12px t-3
16px t-4
24px t-6
32px t-8
48px t-12
64px t-16
96px t-24

Components

Astro components in src/components/ui/. Styled with Tailwind utilities and design tokens. No client-side JavaScript shipped for any component.

Button

Badge

Figma React Tailwind User Research Design Systems

Card

Card

Surface background with border for elevation. Shadows are reserved for section boundaries.

Media card

Tinted container for images and video.

Animations

CSS only. No JS animation libraries. Respects prefers-reduced-motion.

Scroll reveal

data-animate (fade + translateY) and data-animate-y (translateY only, VoiceOver safe for headings).

Theme transition

All color properties transition over 300ms. Toggle the theme to see it.