My own portfolio, designed and built by me in 2026. One mark, one angle and one scroll clock, with a raymarched tunnel through the logo and no 3D engine underneath it.

Client
Myself
Role
Identity, UI, front end, shader
Stack
Next.js 16, GSAP, Lenis, WebGL2
Year
2026
LiveDrawing
01Brief

One angle, both sides.

I design products and I build them, so the portfolio had to prove both in the same breath. The whole identity is one move: a d, turned 180°, becomes a p. Design turns into production without changing hands.

Everything else follows from the slash between them. It sits at 23.6°, and that one number cuts every transition, mask and reveal on the site. If something moves, it moves along that line.

The mark, constructed: a pivot, one angle, eleven points, then a solid.

Start from a pivot

Every mark needs a centre it can turn around. Mine sits exactly between the d and the p.

23.6°, everywhere

The slash sets one angle. Every cut, band and transition on this site borrows it.

Eleven points

Each half is eleven points. Turn one 180° around the pivot and it lands as the other.

Drawn, then built

The lines become a solid, the same way a file becomes a product.

02 · The mark, constructed

03The tunnelRaw WebGL2 · no three.js

A logo you can fly through.

The hero is one full-screen fragment shader. The marks are signed distance fields, so the same eleven points that draw the logo in the nav also build the solids you fly through.

Inside the tunnel: the solid marks part around the slash
Fig. 02Diving between the solids
Deeper in: the mark as a blueprint, outlines and a graph grid
Fig. 03The blueprint marks, drawn without marching
/* each half of the mark is one 11-point polygon */
float sdLogo(vec2 p) {
  /* the p is the d, turned 180° */
  return min(sdPoly(p), sdPoly(-p));
}

/* march until the surface is closer than a pixel */
for (int i = 0; i < 220; i++) {
  if (i >= N || t >= tMax) break;
  float d = mapT(ro + rd * t, id, lq, lz);
  if (d < 0.0004 * max(t, 1.0)) { hit = true; break; }
  t += d * 0.9;
}
  • 11points per half of the mark, the only geometry in the scene
  • 150march steps per pixel, with 4 more for ambient occlusion
  • 62%starting render scale, stepping down to 42% when frames run slow
  • 34 mscounts as a slow frame. Still too slow at 42%, and the hero swaps to a still
  • 0march steps for the blueprint marks: 12 ray and plane hits, solved directly
  • 4 coresor fewer, reduced motion, or no WebGL2: the still and a frame-by-frame story instead
04The slash system

One function cuts everything.

The slash is a number, not a picture. One function projects any point onto the 23.6° line, and CSS clips along the same line, so a pointer, a scroll position and a page transition all cut the same way.

LiveDrawing

Move across it, or scroll: the slash follows

/* 0: the line has not entered, 1: it has left */
slashAt = (px, py, w, h) =>
  clamp01((py + 0.4369 * (px - w / 2) + 0.2185 * w)
    / (h + 0.4369 * w));

/* and in CSS, the live side is clipped to match */
.sl-live {
  clip-path: polygon(0 0, 100% 0,
    100% calc(var(--s) * (100cqh + 43.69cqw) - 43.69cqw),
    0 calc(var(--s) * (100cqh + 43.69cqw)));
}
  • The case heroes and the /work list: the drawing under, the live site over
  • The end of the tunnel, which opens onto the first project
  • Both Sides, scrubbed by the pointer
  • The page transitions, a panel skewed to 23.6°
  • The name and number wipes, a 156.4° mask (180° minus 23.6°)
05Stack

One clock, no engine.

  • Framework

    Next.js 16 with the App Router. Every route is static HTML, so the first paint never waits on a server.

  • Motion

    Lenis, GSAP and ScrollTrigger share one ticker. Scroll is read once per frame and written into CSS custom properties, and the CSS does the rest with calc().

  • 3D

    Raw WebGL2: one program, one triangle, about 7 KB of shader. No three.js, nothing to download before the tunnel starts.

  • Type

    Archivo as a variable font. The width axis runs from 62% in the intro to 118% in the stats, so one file covers every voice.

  • Drawings

    The drawing side of each preview is an edge pass over the live screenshot, made at build time by a small Python script, until the real Figma frames replace it.

06PerformanceLighthouse 13 · production build

Heavy on the eye, light on the wire.

  • 86Performance, home page on a throttled phone
  • 97Performance, home page on desktop
  • 0Layout shift, on every page measured
07Next case03 / 09 · Content platform
WordsburgSee the case