Design System ·

Ink on paper. The portfolio runs on one editorial system: a cream desk, lighter sheets framed in ink, a pen-red accent mixed in three temperatures, and a dark inkwell to close. This page documents it, built with the very system it describes.

Ink on paper

The metaphor drives every decision. The page is a cream desk; each project is a lighter sheet of paper framed with an ink border and a printer's double rule; emphasis is drawn by hand in pen red; and the site ends by lifting the paper to reveal the inkwell: a dark final act where the contact lives. Three colors run the whole shell: cream, ink, and one red. The rest is scale, the serif at 8vw against 15.5px of body.

The shell holds four self-contained product systems on purpose: Fika ships its SaaS blue, Pack its warm rust, Eventbrite its signal red, and Gran DT goes furthest — a night-pitch dark world with its own typefaces, because it is the one that shipped. Every product feels like itself instead of like the portfolio: one desk, four distinct objects on it.

Tokens

Every color is a CSS custom property with a semantic role. Three surfaces tell the whole story: desk, sheet, inkwell. The accent is one red mixed three times, because a single red cannot pass WCAG AA on cream and on ink at once.

Surfaces

--cream
#EFEEE8 · the desk
--paper-1
#FBFAF6 · the sheet (project plates)
--night
#1B1A16 · the inkwell (final act)

Ink ramp

--ink
#21211D · display & frames
--ink-2
#34342E · secondary text
--stone
#A6A59C · kickers on dark

One red, three temperatures

--pen · #D4380D
·  001
Display & strokes: river dots, giant counters, underline rules. Decorative sizes only.
--pen-text · #B93109
[ 001 ] · small text
Deepened for small text on light: 4.5:1+ (AA) on cream and paper.
--pen-night · #E86B41
underlined .
Brightened for the inkwell — ~5:1 on --night. Hovers, underlines, the final dot.

Per-project palettes — four self-contained systems

Fika
iOS · Master's capstone · SaaS blue
#3B6FE0 #2D58C4 #16A34A
Pack
iOS · Personal project · Warm rust
#B8390A #6B3D1E #B47A1F
Eventbrite
Web · Unsolicited redesign · Signal red
#D4380D #A82A0A #4F46E5
Gran DT
Web · Shipped and run live · Night pitch
#34D17B #F5C84C #0B1610

Five voices

Three families, five fixed roles. Source Serif 4 carries display (900) and the editorial register (400); Hanken Grotesk speaks statements and body; JetBrains Mono only ever appears between brackets.

Display · serif 900
Rivers, project names, LET'S TALK. Uppercase, tracking −0.015em, line-height ≤ 0.98. Ends with a red mark.
Eventbrite ·
Editorial · serif 400
The About and the mid-page question. clamp(19px, 2vw, 24px), line-height 1.45–1.5.
Six years inside enterprise software taught me how products go wrong.
Statement · Hanken 600 caps
The hero's fixed counterpoint, bottom-right. Uppercase, tight leading.
I design the ones that put the user first.
Body · Hanken 400
Project descriptions and functional prose. 15.5px, line-height 1.6–1.7.
Heuristic analysis of Eventbrite's event creation flow — 14 issues found, 7 screens reduced to 1.
Meta · mono brackets
Kickers, counters, contact, colophon. 11.5–14px, tracking 0.16em, always inside [ ].
[ PRODUCT DESIGNER · BARCELONA ]

Recurring gestures

The identity lives in a handful of repeated gestures. Each one appears at least twice across the site, so nothing reads as a one-off effect.

[ 005 · YOUR PROJECT ]
The brackets
Mono metadata always wears brackets: hero meta, section kickers, contact, colophon. The site opens and closes in the same voice.
001
Stroke counters
Each project plate carries its number as a giant outlined figure in pen red: 001 to 004, and then the count declares the visitor project [ 005 ].
The plate & double rule
Projects sit on lighter paper framed by a 2px ink border with an inner hairline at 6px: a printer's certificate rule. Corners stay at 4px so the frame reads as a printed edge.
the products I hated using taught me the most
Pen underline
A 2.5px underline sits 6px below the baseline, in the AA-mixed red of its background.
The reveal
One scroll effect only: the cream world ends in rounded corners and lifts off the pinned inkwell. Scroll-driven, JS-free, disabled where it can't fit.
9:41 · hand-coded
CSS devices
Every phone and browser mockup is hand-coded DOM. The UIs animate on entry: rings draw, checklists tick, previews slide in. The only real screenshots on the site are Gran DT's, because that one shipped.

Layout specs

Full-bleed scenes with one shared edge rhythm; the shell keeps radii and shadows to a short, deliberate scale.

SpecValueWhere
Edge gutterclamp(24px, 4vw, 72px)Project plates, inkwell spread
Scene heightmin-height: calc(100svh - 112px)Each project is a near-viewport scene
Radii scale4 / 12 / 16 / 44 / 999Plate / browser / phone screens / lifting sheet / pills. Device chassis and product mockups carry their own radii.
Shadowsrgba(33,33,29, …) — warm ink, never blue-blackDevices, lifting sheet
Final actsticky bottom · 100svh - ~120pxThe inkwell stays pinned; ~120px of paper stays visible at rest
Type scale jumpdisplay ≥ 8vw vs body 15.5pxMonument or servant — no middle sizes

Accessible by design

The system ships its constraints: every decorative flourish has a functional fallback.

AA red, per background
Small red text never uses the display red: --pen-text hits 4.5:1+ on light, --pen-night ~5:1 on dark.
Reduced motion, complete states
All entry choreography (duration and delay) collapses to final states — nothing is hidden behind an animation.
Semantic skeleton
One h1 (screen-reader only above the decorative rivers), landmarks for header/main/footer, skip link, focus-visible everywhere.
Silent decoration
Generated counters use the alt-text syntax content: "001" / ""; mockups are aria-hidden.
No-JS & print
Without JavaScript the hero compacts and everything reads; print flattens the reveal and drops the dark backgrounds.
Native scroll
Wheel and trackpad are never hijacked. Smooth glide exists only on anchor jumps, and cancels the moment the user scrolls.