Inspiration

Almost every website is a rectangle of text you scan once and forget. The web can hold depth, space and light, and it almost never bothers. LUCID started from that frustration, plus a fascination with lucid dreaming — a place that's familiar but slightly wrong, where you know you're inside something and can still move through it. We wanted a site that isn't a page you read but a place you fall into.

What it does

A single-page, scroll-driven 3D experience. You descend through five chambers — Threshold, Arrival, The Drift, The Archive, The Waking — and your scroll position is the only thing moving the camera. No pages, only depth. A single glass crystal is the throughline: dim and fractured at the Threshold, turning and drifting through Arrival, lit in dawn-amber by The Waking — the only place the temperature turns warm. Hover brightens it; clicking it opens a glass panel with that chamber's story.

How we built it

  • React Three Fiber + drei + @react-three/postprocessing on three, in a TanStack Start app with Tailwind for UI chrome.
  • One fixed full-screen canvas behind all five chambers — the DOM scrolls over a single scene.
  • Camera choreography: position and look-at travel along two THREE.CatmullRomCurve3 paths, one viewpoint per chamber, damped each frame against shared scroll progress.
  • Material: drei's MeshTransmissionMaterial with chromatic dispersion and teal attenuation on procedural octahedral geometry — no purchased assets.
  • Light: a real Poly Haven HDRI plus three-point lighting that lerps violet → amber across the final descent.
  • Atmosphere: two Sparkles layers, bloom tuned to catch only true speculars, faint vignette, subtle chromatic aberration.

Challenges we ran into

  • The camera faced the wrong way. Our first rig slerped a plain Object3D quaternion toward the target — but Object3D points +Z at a target while a camera looks down -Z, so we were aimed 180° away from the crystal. Fixed with a damped camera.lookAt() on a smoothed point along the look curve.
  • The crystal rendered invisible. Transmissive glass over a transparent canvas has nothing to refract. A large inverted void-colored backdrop sphere gave it depth.
  • Headless WebGL debugging. toDataURL kept returning stale or blank frames; we switched to Playwright screenshots and logged NDC coords to confirm framing.
  • Crisp vs. washed out. Raising reflectivity blew out highlights — we pulled ambient and fill back, dropped roughness, and raised the bloom threshold.
  • Performance on real devices. Transmission + postprocessing is expensive, so a capability probe clamps DPR, thins particles, and drops postprocessing on mobile/low-power hardware.

Accomplishments that we're proud of

  • One memorable image carries the whole visit, instead of five unrelated hero sections.
  • Scroll is the only input — nothing is on a timeline; the site reacts to you rather than performing at you.
  • It's actually accessible: reduced-motion gives a calm near-static version, every control has a visible focus ring, and all chamber content is keyboard-reachable — the 3D is enhancement, not a gate.
  • It holds up on a phone, rare for a transmission-heavy WebGL scene.
  • Everything is free or self-authored: procedural geometry, a Poly Haven HDRI, no copyrighted assets.

What we learned

  • Scroll-driven 3D lives or dies on damping — raw scroll feels twitchy; everything needed smoothing to feel cinematic.
  • MeshTransmissionMaterial is only as good as what's behind it; environment and backdrop matter more than material settings.
  • Restraint reads as craft. Spending all the boldness on one crystal made the whole thing feel more considered.
  • Building reduced-motion and low-power paths as you go beats retrofitting them.

What's next for LUCID

  • Custom shaders so the crystal's healing from Threshold to Waking is geometric, not just lighting.
  • Spatial audio shifting with depth — a low drone resolving into something warmer.
  • Pointer parallax on top of scroll, so the scene breathes even when you're still.
  • Shareable depth links — deep-link to a chamber and have the camera fly there.
  • A WebGPU path for higher-sample transmission, with WebGL as fallback.

Built With

Share this project:

Updates

Submission history