Skip to content

Case study · web

mcabello.dev

This site — an editorial portfolio built design-system-first: tokens, a fluid type scale, and a motion spec before any page.

01

Overview

A portfolio built the usual way — pick a template, drop in content — reads like a template with content dropped in. The goal here was a site that reads as considered: real typographic rhythm, a colour system that holds together in both themes, and motion that respects how someone actually wants to move through a page, not motion for its own sake.

02

Objective

Build the design system first — tokens, a fluid type scale, a motion spec — before writing a single page, so every page inherits the same rhythm instead of improvising its own.

03

Approach

Colour lives entirely in CSS custom-property triples (r g b) declared once in globals.css and consumed everywhere through Tailwind's rgb(var(--x) / <alpha-value>), so components never branch on theme — dark is the default, and a data-theme attribute switches the whole palette in one place. Type is a fluid clamp() scale (display down to meta) so headings hold their hierarchy from 360px up without breakpoint-specific overrides. Motion goes through a small set of shared primitives (a Reveal wrapper, a page-transition component) built on framer-motion, honouring prefers-reduced-motion by falling back to a plain, unanimated element rather than just shortening a duration.

The theme toggle avoids a flash of the wrong theme by setting data-theme from an inline, pre-paint <script> in <head> — before hydration, before the rest of the page gets a chance to paint in the wrong palette — reading localStorage first and falling back to prefers-color-scheme. The whole site is static: 17 prerendered routes, no middleware, no server actions, deployed on Vercel.

04

Solution

Security headers and a report-only Content-Security-Policy sit in next.config.ts — X-Frame-Options, Permissions-Policy, Strict-Transport-Security, and a CSP that documents exactly why 'unsafe-inline' is still there (the pre-paint theme script, the JSON-LD block, the App Router's own inline bootstrap scripts, framer-motion's inline styles) rather than pretending it isn't, with a TODO marking the hash-based upgrade path once that's worth the churn. A custom 404 and error boundary reuse the same primitives as every other page, so even the failure states look like they belong to the site instead of falling back to a framework default.

Case studies like this one are structured content, not hand-written pages — each project is one entry in a typed array, and an editable placeholder renders wherever a field is deliberately left blank, so a project can be listed honestly before its outcome is known, without ever inventing one.

05

Technology

  • Next.js
  • TypeScript
  • Tailwind CSS
  • Framer Motion
  • Vercel
06

Outcome

This is the site you're reading it on — the token system, the theme toggle, the motion spec, and the security-headers work described above are all live on the page in front of you, not a separate demo of themselves.