Design System
The system behind this portfolio
This page walks through the actual tokens, type scale, and components running the site you're on right now - not a separate mockup. Every color, spacing value, button, and section pattern below is imported straight from the production code (down to the case-study block components), so what you see here is exactly what's shipping. It's meant to be enough on its own to build another page in this system.
Foundations
Layout & spacing
Every page sits in a max-w-shell (1280px) container with px-[6vw] side margins, so gutters scale with the viewport instead of snapping between fixed breakpoints. Vertical rhythm between major sections is py-16 md:py-24. Corners use one radius token, rounded-ds (2px) - sharp enough to read as structural, not soft/decorative. Borders are almost always border-paper/10 to border-paper/18, never solid paper.
Spacing scale · base 4px (Tailwind default)
Radius · --radius-ds: 2px
One radius everywhere - buttons, cards, images, badges. No larger "soft" radius exists in this system.
Foundations
Accessible color palette
A fixed dark shell, no light mode, built around one paper-on-ink text pairing and three accent colors. I check every accent's contrast ratio against the ink background against WCAG 2.1 before I let it carry text. If a color only passes at large sizes, it's restricted to labels and headlines, never body copy.
--inkBase surface
The fixed dark shell every page sits on. Never used as a text color on light surfaces.
--paperPrimary text
Body copy, headlines, and primary UI text on the ink surface.
--pinkPrimary accent
Primary buttons, links, and the loudest accent. Safe for normal-size text on ink.
--violetSecondary accent
Numbered labels and secondary emphasis. It clears AA for normal text on ink, but only just, so I keep it to short labels, not paragraphs.
--limeInteractive / focus
Hover states, focus rings, and active nav indicators. High contrast, used sparingly so it stays meaningful.
--lilacSoft accent
Image captions in case studies - high contrast that still reads as soft/secondary rather than an interactive accent, and doesn't wash out next to light-background screenshots the way muted gray text did.
Foundations
Typography
Archivo carries every weight of the interface, from body copy to the heaviest display headlines. Caveat, a handwritten accent face, only shows up for a single emphasized word here and there. I never let it carry a full sentence.
H1 · font-display · 900 · clamp(36px, 6.5vw, 64px)
Designing systems people trust
H2 · font-display · 900 · 30 to 48px
Products, not just screens
H3 · font-display · 900 · 20 to 24px
A section heading, like inside a case study
Body · font-sans (Archivo) · 400 · 16px
Body copy sits at a comfortable reading measure, medium contrast against ink at paper/70, and generous line height, so a long case-study passage stays easy to scan.
Label / eyebrow · font-mono · 10px · uppercase · tracked
Section label example
Accent · font-accent (Caveat) · single word only
Designing systems people actually trust.
Components
Buttons
Four variants, two sizes. primary is the only filled button in the system - use it once per view for the one action that matters most.
variant="primary"
variant="outline"
variant="arrow" · text link with a hover-expanding gap
variant="destructive" · reserved for delete/remove actions (admin tool)
disabled state
Components
Badges & tags
Three small pill patterns cover every "label on top of content" need in the system.
Kind badge · case study header
ShippedSkill tag · About section
Interaction DesignProject tag chip · overlaid on an image
Product DesignComponents
Numbered section header
Every top-level homepage section (Work, Approach, About, Contact) opens with this exact pattern: a violet index number, a lime mono eyebrow, then the display headline. Copy this block verbatim and renumber it for a new section.
05New Section
Headline goes here
Components
Cards
One bordered card shape, two ways to fill it: an icon + title + body (Approach, case-study principle cards), or plain content. Both use rounded-ds border border-paper/12 p-6.
Icon card title
Icon in a bordered box, then a medium-weight title, then paper/60 body copy. Used for principles, pillars, and "what shipped" summaries.
Plain card title
Same shell without the icon slot - for content-only cards where an icon would just be decoration.
Case study components
The full case-study block vocabulary
These are the actual components (components/case-study-template.tsx) that every case study on this site is built from - imported here directly, not recreated, so there's zero drift from production. Compose a new case study (or any long-form page) by stringing these together.
type: "text"
A text block
The default building block - a heading (optional) plus one or more paragraphs. Use it for narrative sections: the challenge, a reframe, a reflection.
type: "list"
A bulleted list
Pink-dot bullets, used for scannable enumerations:
- First point
- Second point
- Third point
type: "quote"
A large pink-bordered pull-quote - only ever real copy pulled verbatim from research, testing, or product text, never invented for effect.
Attribution or context line
type: "stat"
Headline numbers
Big Archivo-900 figures for scannable stats, instead of burying a number in a paragraph.
86%
expected a behavior the product didn't support
+45%
playlist task success in a prototype test
2.3x
faster completion time
type: "tradeoffs" · Negotiations & trade-offs
A real constraint, decision, and resolution
Surfaces the negotiation behind a decision - never a hypothetical.
Constraint
What forced the decision - a deadline, a technical limit, a research finding.
Decision
What was chosen because of that constraint.
Resolution
How it was validated or why it held up.
type: "tiers" · MVP / ideal scoping
Tiered solution options
Single-patty-to-3x3 style scoping, so a reviewer can see the range considered.
Single
MVP
The smallest version that solves the core problem.
Shipped for MVP
Double-double
Next iteration
Adds the highest-value follow-up, still scoped tightly.
3x3
Long-term vision
The full-featured version, for later.
type: "process" · numbered steps
A numbered process
Step one
What happens first, and why it comes first.
Step two
What gets decided or discovered at this stage.
Step three
How the previous step changes what happens next.
type: "cards" · icon grid
Principles or takeaways
First principle
One sentence explaining why it matters.
Second principle
One sentence explaining why it matters.
Third principle
One sentence explaining why it matters.
type: "metrics" · results table
A/B or usability results
Row-level results, with a highlight color for the standout win.
| Task | Success | Time | Ease |
|---|---|---|---|
| Task A | +45% | 2.3x faster | 2.2x more direct |
| Task B | N/A | 22% faster | Fewer misclicks |
An optional footnote for context that doesn't fit in the table.
type: "compare" · before/after
Before and after
Two images side by side, each with its own caption - the clearest way to show what changed.
type: "split" · alternating text + image
Text next to its supporting visual
Pairs an insight directly with a screenshot instead of always stacking full-width blocks. Pass flip: true to mirror which side the image sits on.
type: "placeholder" · honest "coming soon"
Visual coming soon
For a visual that doesn't exist yet - never a broken image.
Foundations
Motion & accessibility
- Every animation (orbs, blink, view transitions) is wrapped in a
prefers-reduced-motion: reducemedia query in globals.css that forces durations to ~0 - motion is never load-bearing for understanding content. - Focus states use
focus-visible:outline focus-visible:outline-lime- keyboard focus is always visibly distinct from hover. A November 2026 keyboard check found the shared Button had no visible focus indicator; it now uses the same outline. - Text selection is styled (
::selection) to lime-on-ink instead of the browser default, so it stays legible against the dark shell. - Every accent color is contrast-checked against ink (see the palette above) before it's allowed to carry text, not just used decoratively.
Principles
Usability heuristics
The tokens above decide how things look. Jakob Nielsen's 10 usability heuristics decide how they behave - what the interface tells you, what it lets you undo, and how it fails. Every change to this site, and to the AI-assisted admin tool behind it, is held to all ten. Here's what each one means in practice here, not in the abstract.
01Visibility of system status
Anything slower than about a second says it's working and roughly how long it will take - "Re-analyzing... (34s)" in the résumé tool, a typing state in Ask My Portfolio. Counts that need action live as badges in the admin nav and refresh as you move around.
02Match between system and the real world
Numbers are labeled with what they actually measure. A fit score reads "Your fit for this role" and sits next to "Role type", so the two never get read as the same thing. The command-menu hint shows ⌘K on a Mac and Ctrl+K everywhere else.
03User control and freedom
Destructive actions get a short Undo window instead of a "Are you sure?" dialog. Long AI work can be stopped or cancelled - including Ask My Portfolio mid-answer - and every dead end links somewhere useful.
04Consistency and standards
One meaning per color: errors use a single red, warnings a single amber, and every interactive element gets the same lime focus outline. A fit recommendation looks identical everywhere it appears.
05Error prevention
Submit buttons stay disabled until the input is valid, and constraints are stated up front ("At least 6 characters") rather than discovered after an error. Even a harmless-looking dropdown that removes something gets an undo.
06Recognition rather than recall
Anything you need to act on is reachable from navigation, not buried behind another page. When the résumé tool reuses an earlier answer it shows which one, so you never have to remember it. Icon-only buttons always carry a label.
07Flexibility and efficiency of use
The ⌘K / Ctrl+K command menu, suggested questions in Ask My Portfolio, and answers that carry over between job postings so the same question is never asked twice.
08Aesthetic and minimalist design
The visual system on this page handles the look. Behaviorally: one line of explanation right where it's needed, not a wall of text, and no chrome a state doesn't need.
09Help users recognize, diagnose, and recover from errors
Failures are shown where they happened, in plain words, with a way forward - "Try again" rather than "Request failed with 500". A failed load never masquerades as an empty list or a finished state.
10Help and documentation
Short help at the point of decision - "How was this calculated?" under a score, a one-line explainer under a chart - instead of separate help pages. This page is the documentation for everything else.
InlineError · components/admin/feedback.tsx
The one error pattern: plain words where the failure happened, plus a “Try again” when there's something to retry. Never a raw status code, never a browser alert.
Undo window · useUndoableAction
Replaces “Are you sure?” The item disappears immediately and is only really removed after six seconds - leaving the page commits it, so nothing is silently lost.
Principles
Gestalt principles
Gestalt principles describe how people group what they see before they read a word of it. These four came out of auditing this site screen by screen - each one fixed a real layout that was grouping the wrong things together.
Similarity · same look, same meaning
Pink is reserved for the primary action, so navigation links stay off-white and lime marks where you are. Admin count badges encode what a number asks of you, not whether it's good news:
Only things you can click get hover states - static tags don't, so they never read as buttons.
Proximity · space follows meaning
Things that belong together sit closer than things that don't. A form's main action sits at the end of that form, not in a corner of the page. Education sits under the career timeline it continues. An alternative path - like the ⌘K hint under Ask My Portfolio - gets more space than the module's own internal rhythm, so it reads as separate.
Continuity · one edge, one line
Content hangs from the same 6vw edge as the heading above it. Lists that wrap use a hanging indent so the eye follows the text, not the bullet:
Before
- A wrapped line restarts under the bullet and breaks the column
After
- A wrapped line lines up with the text above it
Common region · group the decision
When a row holds a decision and some utilities, the decision gets its own labeled group, set apart by a rule - so approving an item never looks like the same kind of action as deleting it.
Process
How this gets built
This site isn't hand-coded line by line, and it isn't a no-code builder either. Every page here, including this one, is built through direct AI pairing - I decide what should change and why, an agent edits the actual files in this repo, and nothing ships without a check.
The loop
Direct it in conversation
I describe what should change and why - a content fix, a new feature, a bug - the same way I'd brief a teammate, not a ticket queue.
The agent edits the real files
Claude Code / GitHub Copilot edits this repo directly - the components, tokens, and case-study data you're looking at right now, not a separate mockup.
Verify before it ships
Every change gets a TypeScript check and a real browser pass - visually and functionally - before it's trusted, not just "looks right in chat."
Ship
Committed to GitHub with a real message, auto-deployed by Vercel - usually live within minutes of the conversation that started it.
Swappable
VS Code + GitHub Copilot Chat
Claude Sonnet coding sessions
Vercel hosting
Durable
Design tokens in globals.css
The case-study data model (lib/case-studies.ts)
This page - documents the system so any future session can pick it back up
What that actually produces
One real design system
Every token and component on this page is the same code running the live site, not redrawn for show.
Checked against real audiences
Résumé and case-study copy get reviewed against ATS parsing, recruiter scan time, and hiring-manager depth before anything ships.
Version-controlled, not vibes
Every change is a real git commit with a message - reversible and deployed through the same pipeline as a production app.
Foundations
Logo (extra credit)
A simple wordmark, built entirely from the type system above. No separate illustration tool, so it scales and recolors exactly like the rest of the interface.
In production
High-fidelity design
I didn't build a separate high-fidelity mockup for this one. The system is implemented directly on the live, shipped site, so the homepage link below isthe high-fidelity design. It's already built and deployed with the exact tokens and type scale documented on this page.
For the showcased project, I'm linking the SoundCloud UX Research case study. It's fully public, with final copy, layout, and visuals all done, which makes it the cleanest example of the system running against real content.