Awwwards-style one-page portfolio. Vanilla HTML/CSS/JS — no build step.
- Three.js (CDN, ES module) — interactive hero particle field: custom GLSL shaders, simplex-noise wave displacement, mouse repulsion, scroll-driven dispersion, adaptive particle count (13k desktop / 4.5k mobile), capped DPR, pauses offscreen.
- GSAP + ScrollTrigger (CDN) — preloader, char-split reveals, scrub word fades, sticky stacking project cards, counters, velocity-reactive marquee, magnetic buttons.
- Lenis (CDN) — smooth scrolling (native scroll, so
position: stickyworks). - Fonts — Clash Display (Fontshare), Inter + JetBrains Mono (Google Fonts).
Any static server, e.g.:
npx serve -l 4173 .
# → http://localhost:4173Hero (Three.js particle field) → marquee → about → selected work (sticky stack, AI-generated cover art with cursor-origin "spill" reveal + 3D tilt) → also-building list (cursor-following artwork previews) → capabilities → process (scroll-drawn SVG path) → playground (pinned 3D GLSL morph blob: FORM → FLUX → PULSE, drag to rotate, live uniform HUD) → experience → under the hood (working terminal + live telemetry) → contact (2D canvas dot-field + SVG scribble) → footer.
Covers are generated with the Gemini API (gemini-3-pro-image, 2K) and downscaled
to 1200px JPEGs:
$env:GEMINI_API_KEY = "..." # never committed, never shipped to the client
node scripts/generate-art.mjs # batch 1: project covers
node scripts/generate-art-2.mjs # batch 2: brand-personalized + list previews
node scripts/generate-art-3.mjs # batch 3: StudyDrop
./scripts/optimize-art.ps1 # PNG → web JPEG (assets/art/*.jpg)The site only references the JPEGs; the 2K PNG originals stay local.
prefers-reduced-motiondisables Lenis, the preloader, and scroll animations.- Custom cursor + magnetic effects only activate on fine pointers (desktop).
- Split-text keeps screen-reader output intact via
aria-labelon split elements. - Verified in Chrome DevTools: no console errors, LCP ~143 ms, CLS 0.03, no horizontal overflow at 390 px, 60 fps idle.