Case Study · Episode 04 · Real-Time 3D

A star on your desk: 3D scenes with Claude Code & Three.js

A premium product landing page for HELIOS — a fictional consumer fusion-energy company — with a real-time 3D hero: a glowing fusion core suspended in physical glass, orbitable, zoomable, tappable. No 3D artist, no build step, no editor. One agent, one design system, and 633 lines of Three.js.

BrandHELIOS · fictional, built for the demo
StackThree.js via CDN — zero build step
Scene code633 lines, hand-readable
Runs onIntegrated GPUs, 60fps
The Interactive

Don't watch it — touch it

This is the actual site, embedded live. Drag the device to orbit it, scroll inside the scene to zoom, and tap the core for an ignition flare.

drag · orbit scroll · zoom hover · glass glow tap the core · ignition flare Open full site ↗
Overview

No 3D artist. No build step. No editor.

The whole thing is three files — HTML overlay, CSS tokens, one scene script — with Three.js loaded from a CDN import map. It deploys as static files and runs at 60fps on an integrated laptop GPU.

633Lines of scene code
3Files · html, css, js
0Build step — CDN import map
60fpsOn an integrated GPU
The Scene

Anatomy of a product hero

The scene language: matte near-black metal, physical glass, and one emissive "alive" element pushed into HDR so the bloom pass makes it glow like a small sun.

HELIOS hero — fusion core in glass on a brushed-metal device
1The fusion core — an emissive sphere with RGB pushed above 1.0 (HDR), so the UnrealBloom pass blooms it into a glow
2Physical glass — real transmission material: refraction, roughness, edge highlights
3The containment ring — pulsing emissive, animated in HDR rather than raising bloom strength
4Brushed-metal device — matte near-black, warm key light + cool blue rim ("amber is the star, blue is the sky")
5Reflector floor + fog — grounds the device in deep-space navy, fading with FogExp2
Fig. 1 — every material rule and light in the scene derives from the same CSS design tokens the page UI uses.
The Feel

Interaction with inertia

What separates "a spinning cube" from a product you want to touch: every input has weight, decay and clamps. Try each one in the embed above.

🖐
Drag → orbit

Rotation carries momentum after release, decaying at velocity × 0.94 per frame — the device feels like it has mass.

🖱
Scroll → zoom

Zoom factor clamped to [0.72 – 1.4] and eased at 6% per frame — you can never lose the device or clip through it.

Hover → glow

A raycaster (aimed from the frame's own rect, not the window) lights the glass edges when your cursor crosses the cube.

💥
Tap → ignition

A click under 6px of drag fires uFlare = 1, decaying ×0.955 — driving a shader boost, a shockwave ring, and a bloom surge at once.

Behind the Scenes

Learned the hard way

Real-time 3D fails in spectacular, non-obvious ways. These six rules are written in hours of debugging — each one now lives in the project's skill so no future session repeats them.

BUGOne NaN pixel turns the whole frame black

A shader's pow(x, 0.5) on a negative base produces NaN; the bloom pass smears that single pixel across the entire frame. Every fractional pow now clamps its base. It looked like random flicker — it was math.

BUGGlass that flickers

Transmission glass rendered DoubleSide re-sorts its own faces every frame → shimmer. The fix: FrontSide, depthWrite: false, high render order.

BUGKeep glass away from mirrors

The floor Reflector re-rendering the glass from a second camera thrashed the transmission buffer. Glass now lives on its own layer the reflector can't see.

RULENo hot lights behind glass

The transmission pass samples the backbuffer — a bright point light behind the cube smears into blobs on its edges. The halo light sits low, offset, far.

RULEZ-fighting is a 0.4% problem

Edge overlay lines fought the glass surface. Scaling the line segments to 1.004 — four tenths of a percent — ended it.

RULEGlow in HDR, not in the bloom slider

Wanting more glow? Don't raise bloom strength (it hazes everything). Push the emissive color above RGB 1.0 and let the threshold do the work.

The Loop

The agent checks its own pixels

Screenshots of WebGL canvases are unreliable from the outside — so the workflow makes the page photograph itself: the canvas posts a JPEG of its own framebuffer to a tiny local receiver, and the agent reads the file back. Every visual change is verified in actual pixels before it's called done. A ~6KB file means a black frame — and that means go hunt the NaN.

$ claude "make the core pulse brighter"
▸ edit main.js — ring emissive → RGB 2.2 (HDR)
▸ reload · canvas → toDataURL → POST localhost:4599
▸ read shot.jpg… 41 KB — frame renders, glow verified
▸ done — seen, not assumed
Toolbox

Everything used, and what it did

Three.js
Scene, materials, bloom — loaded from jsDelivr, no bundler
OPEN SOURCE
Claude Code
Wrote the scene, the brand, the tokens — and debugged its own pixels
AGENT
The three-hero skill
Scene language, pitfalls & perf budget, packaged for reuse
SKILL
GitHub Pages
Static deploy — the embed above is the production site
FREE
The Repo ↗
index.html + main.js + tokens — read the 633 lines
GITHUB