03Personal
Stephan.in
Personal Portfolio & Design System
This site. Next.js 16 and Tailwind v4, built on a design system where off-system styling is a build-visible failure rather than a slow drift.
- Company
- Personal Project
- Role
- Design and engineering
- Category
- Web Development
/images/work/stephan-portfolio/cover.webp
Full-bleed project cover. Wide editorial crop of the interface or its subject matter.
01Impact
By the numbers.
37
Static routes
5
Colours in the system
2
Typefaces
0
Dark-mode variants
02Overview
The brief.
Rebuilt from the ground up in three phases: strip the previous interface while preserving every piece of content, encode the new visual language as an enforceable design system, then compose the pages from it. Swiss editorial typography, a single accent colour, and hairline rules — the restraint is the design.
Problem
Most student portfolios look like student portfolios: many colours, many gradients, decoration standing in for hierarchy. And a design system written only as documentation drifts the moment someone adds a shadow "just for this card".
Solution
The design tokens wipe Tailwind’s defaults entirely — `--color-*: initial` and `--text-*: initial` — so `bg-red-500` and `text-4xl` do not exist. An off-system value produces no styling at all, which makes drift immediately visible instead of gradual. The rules live in a skill that loads automatically whenever the interface is edited.
03Features
What it does.
Hard-locked design tokens
Tailwind’s default palette and type scale are wiped, so only system values resolve. Off-system styling fails visibly.
- Tailwind CSS v4
- @theme
Design system as a loadable skill
Rules, token tables, and component contracts encoded so they enter context automatically on any interface work.
- Markdown
- Progressive disclosure
Media manifest
Every image slot is declared data. A build-time filesystem check renders a self-documenting placeholder until the real file exists, then swaps automatically.
- Node fs
- next/image
Motion primitives
Reveal, stagger, and word-by-word heading animation, each returning visible content under reduced-motion rather than a stalled hidden state.
- Motion
- IntersectionObserver
Smooth scroll
Lenis for eased scrolling, unmounted entirely under reduced-motion so native behaviour is restored.
- Lenis
- React
Server-first rendering
Client boundaries kept at the leaves — only interactive primitives ship JavaScript; pages stay server components.
- React 19
- Next.js App Router
04Architecture
How it's built.
framework
- Next.js 16
- React 19
- App Router
- Turbopack
styling
- Tailwind CSS v4
- CSS-first @theme
- Custom utilities
motion
- Motion
- Lenis
- Reduced-motion fallbacks
typography
- Geist
- Geist Mono
- Fluid clamp() scale
05Challenges
What was hard.
Design systems drift the moment they are only documentation
Wiped Tailwind’s defaults in the theme layer so off-system values resolve to nothing — a mistake you can see rather than one that accumulates.
Scroll animation that hides content when animation is disabled
Every motion primitive returns the visible state under reduced-motion, never a hidden initial state that then fails to animate in.
Filesystem checks are server-only, but filtering needs client state
Split the media component into a presentational frame and a server wrapper, with the server page resolving asset availability and passing it down.
06Gallery
A closer look.
/images/work/stephan-portfolio/01.webp
Interface detail. Crop tight on one screen or one interaction — not a full-page screenshot shrunk down.
/images/work/stephan-portfolio/02.webp
Interface detail. Crop tight on one screen or one interaction — not a full-page screenshot shrunk down.
/images/work/stephan-portfolio/03.webp
Interface detail. Crop tight on one screen or one interaction — not a full-page screenshot shrunk down.
/images/work/stephan-portfolio/04.webp
Interface detail. Crop tight on one screen or one interaction — not a full-page screenshot shrunk down.
07Stack
Built with.
- Next.js 16
- React 19
- Tailwind CSS v4
- Motion
- Lenis
- Geist
- Vercel
Outcomes
- 37 statically generated routes
- Five colours and two typefaces across the entire site
- Off-system styling made a build-visible failure
- Reduced-motion honoured by every animation