09 / 11 — Case study

Portfolio

This site: a Next.js 16 App Router portfolio with a WebGL2 black hole hero, a full markdown content pipeline, and a motion system built on LazyMotion.

2026 · Next.js / React 19 / GSAP / Motion / Tailwind CSS
Portfolio

The website you are reading this on. Next.js 16 with the App Router, React 19, TypeScript in strict mode, and a design system built from CSS custom properties rather than a component kit.

Problem

Portfolios tend to be either a list of links with good typography, or a motion demo that loads slowly and hides what it is demonstrating. I wanted the second one to be legible as the first one, on a phone, on a bad connection.

The honest framing: this is the project with the least external constraint and therefore the most self-inflicted scope. It is also the one where the engineering decisions are most visible in the result.

What it does

A single-page scroll structure - hero, work, about, blog, books, contact - plus statically generated detail routes for projects and blog posts, a now page, and a server-rendered OG image route. Content lives in markdown files in the repository, not in a CMS.

How it's built

Animation. Framer Motion is loaded through LazyMotion, so the bundle ships the domMax feature set asynchronously and every component uses the m.* primitives rather than motion.*. GSAP handles the preloader timeline and the scroll-driven work; Motion handles component state. A shared token module exports the easing curves and durations, so no component writes its own bezier literal. Reduced-motion preferences are respected at the provider level, not per component.

Content. Projects are a single YAML-fronted markdown file parsed at build time with gray-matrix's sibling gray-matter; blog posts are one file per post. Both are validated during the build - every referenced image path is asserted to exist under public/, which turns a dead image into a failed build rather than a 404.

Theming. next-themes with semantic colour tokens exposed as CSS custom properties (--color-bg, --color-primary, --color-surface, --color-accent), consumed through Tailwind so bg-background and text-secondary flip with the theme without a single dark: variant. Theme changes go through the View Transitions API for the cross-fade.

Typography. Locally hosted Mona Sans with three optical width axes exposed as font-stretch utilities, and a fluid type scale defined in tailwind.config.js via clamp, so text-display-2 is one token rather than five breakpoints.

FIG. 01 — The hero: a WebGL2 black hole renderer with a live quality watchdog.

Status

Live at iamkhalid.xyz, deployed on Vercel. Currently mid-rebuild toward an editorial layout system - the work detail pages you are reading were part of it.

Credits

  • Vercel and the Next.js team
  • Motion (Framer Motion) and GSAP
  • Mono, for Mona Sans
  • The authors of the black-hole references listed on the simulation's own page, whose physics is reused here

Copyright © 2026

Designed, Developed & Maintained by

Mohammed Khalid