Skip to content

Colophon

This website was designed and developed from scratch. It serves as both a portfolio and an ongoing experiment in frontend engineering, interaction design, and systems thinking.

Architecture

Built on Next.js 16 with the App Router, React 19 and TypeScript. Every route is prerendered at build time. The download figures are the one live part: they come from pepy and PyPI, and the pages that show them regenerate every hour. Most of the interface still runs in the browser, because the cursor, the scroll choreography and both particle fields need a real window to exist.

Design & Styling

Styled with Tailwind CSS v4, configured CSS-first. Every colour, gradient and shadow is a CSS variable with one value per mode, so no utility hard-codes a colour.

There are two modes. Daylight Folio, the light one, moves through cream, sage, apricot and sand as you scroll; Deep Obsidian, the dark one, is a near-black spine with a pale bridge through the middle. A visit opens in your device’s setting, and the switch holds your choice until you close the tab. Body text clears WCAG AA contrast in both.

Typography is set entirely in Geist Sans and Geist Mono, an excellent typeface family engineered for high-density interfaces and code by Vercel, served from this domain.

Interactions

Page transitions and content reveals run on Framer Motion, none longer than 350ms. The one-time intro, an iris wipe, runs on GSAP, which loads only on the visit that plays it. With reduced motion set, nothing slides, and the particle fields hold a still frame.

The cursor is a hyper-minimal 5px dot. In dark mode it uses mix-blend-mode: difference, which inverts against any background; in light mode it is a solid ink dot with a paper halo, because difference drops out over the particle pigment. Position tracking uses raw DOM events mixed with React context at 60fps; the dot scales to 8px on hover via a 150ms spring. It runs only while the page has focus, so it never sits beside the system pointer.

WebGL & Physics

The two interactive particle fields are built on React Three Fiber and Three.js. Both use custom GLSL ShaderMaterials with a Gaussian falloff fragment (exp(−d²×9)). In dark mode they use THREE.AdditiveBlending, so overlapping particles accumulate into luminous glowing pools; in light mode they lay pigment with normal blending, because added light vanishes against paper.

Two sources deposit a tangential silk-vortex swirl into a coarse 64×40 wake field: an ambient Lissajous drift that never stops, and the pointer while one is present. One amplitude law covers cursor, touch and drift. Particles glide toward base + wake with a first-order lag (worst-case gain 0.55), sampling at their rest position, so nothing overshoots or feeds back.

The 5,000-particle silhouette uses area-weighted generation (r = R·√u) across four layered populations: disc haze, Fibonacci/Fermat spiral arms, sinuous filaments, and sparse cosmic dust. A per-particle aSize attribute creates a 70/30 grain-to-node size split, placing large particles precisely on dense strand intersections for maximum additive glow.

The second field, under Contact, is the opposite in character: 8,000 particles that morph between a lightbulb, a rocket, a diamond, a bridge and a DNA helix. Rest the pointer on the shape, or hold a finger on it, and a tension ring charges; the shape shatters, falls under gravity, bounces off the floor and reforms into the next one, with a new word each time.

Hosting & Privacy

Hosted on Vercel. There are no analytics, no cookies and no trackers, and every script, style and font is served from this domain; the privacy page lists the two things the site writes to your browser. Every link opens in the same tab, so Back always brings you here.

Source

I believe in learning through shared code. If you’re curious about how specific components or animations were built, the full source code for this website is on GitHub.