How this showpiece was concepted, art-directed and built: the brief, the palette, the actual image prompts, the WebGL and scroll techniques, and what each of the three iteration passes changed.
The brief: commercial space tourism sold with the calm confidence of a heritage travel house. Vast, silent, awe — Apple × NASA, never sci-fi kitsch. The voice is quiet, second-person and specific: “You will watch sixteen sunrises before lunch.” Numbers do the persuading — altitudes, sunrise counts, a 214/214 safety record — so the copy never has to shout.
The page is built as a single continuous descent: a fixed WebGL sky sits behind everything, and the content scrolls over it while the sky itself changes mood — hushed at boarding, streaking at ascent, blooming pink at the overview.
The aurora gradient is rationed: type accents, card borders, one CTA. Everything else stays void-dark so the gradient reads as an event, not a theme.
All imagery generated with gpt-image-2 at 1536×1024, quality high, exported as .webp. Each was visually inspected and kept only when genuinely awe-inducing.




A single Three.js canvas (position: fixed, z-index 0) renders two layers: ~6,200 shader points and a domain-warped fbm nebula on a far plane. All page content scrolls over it, so the sky becomes the connective tissue between sections.
Star drift is done in the vertex shader with a modulo wrap, so “ascent” is just a speed uniform — no per-frame buffer writes:
// vertex shader — uScroll accumulates speed × dt each frame
p.y = mod(p.y - uScroll * (0.55 + aRandom * 0.9) + 55.0, 110.0) - 55.0;
Pointer parallax lerps the camera a couple of units; the nebula plane sits at z = −120 so it parallaxes less than the stars — free depth.
vec2 q = vec2(fbm(uv + t), fbm(uv + vec2(5.2, 1.3) - t));
vec2 r = vec2(fbm(uv + (1.6 + uWarp) * q + ...), ...);
float f = fbm(uv + 1.8 * r); // two-pass domain warp
vec3 col = mix(paletteA(f), paletteB(f), uMix); // indigo/cyan → violet/pink
uMix, uWarp, intensity and star speed are eased toward stage targets every frame, so mood changes feel like weather, not switches.
GSAP ScrollTrigger pins the journey panel for 320% of viewport height. Scroll progress drives four things at once: stage copy crossfades, the outlined stage number, tabular-numeral mission readouts (T-clock, altitude to 460 km, velocity to 27,580 km/h), and the WebGL sky state — boarding is hushed, ascent streaks the stars, the overview blooms pink.
ScrollTrigger.create({
trigger: ".journey", start: "top top", end: "+=320%",
pin: ".journey__pin", scrub: true,
onUpdate(self) {
setJourneyData(self.progress); // T-clock, alt, vel
setStage(Math.floor(self.progress * 4));
applySkyStage(self.progress); // WebGL mood
}
});
A registered CSS custom property makes the conic gradient border rotate on hover — a genuine holo-foil move, 1px thick:
@property --holo-angle { syntax: "<angle>"; initial-value: 120deg; }
.card { padding: 1px; background: conic-gradient(from var(--holo-angle), ...); }
.card:hover { --holo-angle: 480deg; } /* transitions via @property */
body.no-webgl: the generated nebula still + two drifting CSS star layers.prefers-reduced-motion → static fallback sky, journey stages stack vertically un-pinned, counters render final values, no tilt, no grain shimmer.Full desktop + mobile sweep with console audit. Fixed: GSAP lag-smoothing stalling entrance tweens in throttled tabs; a WebGL canvas that could composite black in background tabs (preserved drawing buffer + hidden-tab render-on-scroll); an orphaned line break in the hero sub; the mobile nav pill wrapping; misaligned voyage-card bottoms; sub-AA contrast on small data labels. Elevated: craft-image hover zoom with cyan border glow, ambient “breathe” on the featured card, aurora gradient hairline on the footer.
Fixed: brand wordmark + CTA overflowing 375 px (wordmark hides, mark stays); craft gallery images with mismatched heights; the reserve headline being consumed by the hero’s load animation instead of its own scroll reveal. Elevated: manifesto converted to a scrubbed staggered fade-rise; aurora dash above every section head; card CTA arrow slide; editorial right-offset for the manifesto meta; oversized aurora quote glyph.
Verified the no-WebGL fallback and added a legibility scrim over the still; re-checked reduced-motion stacking; recompressed all four images (6.8 MB → 0.77 MB total, visually lossless at q82); halved the starfield on small screens; tested the reserve form’s inline confirmation; final desktop + mobile screenshots and a clean console.
Static files, no build step. From the site directory:
npx wrangler pages deploy . --project-name nebula-voyages