Skip to main content
To All Projects
Portfolio Website
Next.jsThree.jsGSAPWebGL

Portfolio Website/

2026

About/

This site. A multi-page portfolio built with Next.js App Router, with a WebGL particle background that reacts to scrolling and cursor movement. Three GLB models define different particle formations, and an orbit camera moves between them as you scroll.

All particle physics run on the GPU — positions, interactions, and colors computed in GLSL shaders. Page transitions, text reveals, and scroll effects are handled by GSAP and Lenis.

Particle System/

22,000 particles on desktop (15k on mobile), rendered as a Points mesh with custom ShaderMaterial. Geometry is sampled from three GLB models — the camera orbits between them on scroll via arc-length parameterized interpolation.

Each particle has five independent behaviors, all in one vertex shader pass: simplex noise wiggle, scroll inertia with per-particle lag, cursor trail (dragged along movement), cursor scatter (pushed away), and transition morphing between scenes.

GLSL
// Idle wiggle — per-particle simplex noise
float wiggleTime = uTime * uWiggleSpeed + wigglePhase;
vec3 wiggle = vec3(
  snoise(vec3(noiseKey, wiggleTime, 0.0)),
  snoise(vec3(noiseKey, wiggleTime, 43.0)),
  snoise(vec3(noiseKey, wiggleTime, 97.0))
) * uWigglePower * 0.008;
pos += wiggle;

// Scroll inertia — staggered per-particle
float inertiaLag = 0.2 + pseudoNoise(particleIndex + 13.0) * 1.6;
pos.y += uScrollVelocity * inertiaLag;

// Cursor scatter — random angles per particle
float scatterAngle = pseudoNoise(particleIndex + 42.0) * 6.2832;
float pushStrength = cursorStrength * uCursorPower * 0.05;
pos.x += cos(scatterAngle) * pushStrength;
pos.y += sin(scatterAngle) * pushStrength;

Color & Light/

A 3-stop color gradient driven by two inputs: transition velocity (mid-morph bloom) and cursor proximity. Computed branchless in the fragment shader — a step function and two nested mixes instead of conditionals.

GLSL
// 3-stop gradient: from → mid → to
// Branchless — single wavefront on GPU
float s = step(0.5, t);
vec3 color = mix(
  mix(uColorFrom, uColorMid, t * 2.0),
  mix(uColorMid, uColorTo, (t - 0.5) * 2.0),
  s
);

Scroll Effects/

Hero with circular CSS mask reveal that follows the cursor, then blur-to-clear transition with parallax. Sticky-stacking service cards with right-panel content swap. Text reveals via SplitText, scramble-decode on headings, icon flip-rotate grids. Section detent snap points that briefly lock scroll at headers.

All animations are GPU-accelerated (transform and opacity only). Lenis handles smooth scroll and syncs with ScrollTrigger every frame.

Stack/

  • Next.js 16 (App Router, SSG)
  • React 19
  • Three.js 0.182 / WebGL / GLSL
  • GSAP 3.14 + ScrollTrigger + SplitText + ScrambleText
  • Lenis 1.3 smooth scroll
  • Tailwind CSS 4 (inline @theme)
  • Vitest + React Testing Library, 950+ tests