What it is
A sales site for Aster Bay, a fictional off-plan tower on a lagoon island, of the kind developers commission for a launch. It is a scroll story in nine chapters plus the explorer: arrival at golden hour; the masterplan with labels anchored to the model; the neighbourhood (a cable-stayed bridge to the mainland, fictional places and travel times); construction, where scrolling builds the tower floor by floor with a climbing crane and a milestone readout; the twisting Spire close up; amenities (canopied drop-off, lobby, podium pool deck, gym and spa, lagoon pool, beach club); a sun study that scrolls a whole day from above while the shadow swings round; floor by floor, where the floors pull apart to show the homes inside; and availability at night, where every one of the 225 homes is a block of light with a matching HTML grid. The last screen is the explorer: filter by bedrooms, budget, availability and sea view, slice the tower open at any floor, pick a home, read its floor plan and payment plan, shortlist it, and stand on its balcony to see the view from that height at any time of day. Every home, price, place, time and plan is invented and generated from a fixed seed; nothing on the page describes a real project.
Engineering decisions
- Plain Three.js without React Three Fiber: the scene is one imperative module (
world.ts) with a frame(input) call, which keeps this page's JavaScript smaller than an R3F page and makes the render loop explicit. React only renders the HTML around it. - Everything is generated in code: no model files and no image downloads. The floor plate is a superellipse around a round core; each floor's glass wall and slab is one instance of an instanced mesh, rotated 1.6° more than the one below. Each home is the sector between core and facade, extruded, and drawn as one instance per floor, so the 225 homes are nine draw calls.
- Detail pass (2026-10-14): balconies have posts, planters with shrubs and a table with chairs per home; the facade has deep mullions every fourth fin, a transom and a spandrel band; the roof has a sky lounge with pergola, pool and loungers, plant with fans, a water tank and a facade-cleaning crane; the podium has floor bands, pilasters, a cornice, double-height lobby glazing with mullions and a cantilevered entrance canopy over a drop-off loop with a fountain. Around it: an access road with dashed markings and parking bays, a cable-stayed bridge to the mainland with lamps, about fifty cars (one instanced, vertex-coloured mesh; parked, circling the loop and crossing the bridge with head and tail lights after dusk), up to 150 people strolling the promenade or standing on the decks, broadleaf trees and benches. Every repeated part is instanced and every part is one merged geometry, so draw calls stay low; the low tier uses fewer cars, people and trees.
- Detail is modelled, not textured: every floor has a wrap-around balcony slab, a glass balustrade with a metal handrail and 64 vertical facade fins; the roof has a crown of blades and a ring that lights up at night. Around the tower: a podium with a pool and loungers, mid-rise blocks with balcony bands and rooftop plant, two-storey villas with glazing, pergolas and private pools, palms with drooping fronds, lamp posts along a promenade, a beach band, lawns and paths, yachts with pointed hulls in the marina and out at sea, and islands on the horizon. Each part is one merged geometry (
detail.ts, mergeGeometries) and is instanced where it repeats, so the extra detail adds few draw calls. - Homes are coloured per instance (available, reserved, sold, filtered out, hovered, selected). A small
onBeforeCompile patch adds the instance colour as emissive light, so the blocks glow at night without post-processing. During the x-ray a growth front climbs the tower floor by floor with a scanner ring riding on it. - Slicing a floor lifts every floor above it and drops the camera to the cut; the ring then marks the floor. A floor's homes are also buttons in the panel, so the whole explorer works without the canvas.
- Time of day drives one sun direction, which mixes four light moods (day, golden hour, blue hour, night) for the sky shader, fog, sun, hemisphere light, water and lit windows. The sky shader (gradient, sun disc, fbm clouds, stars) is also rendered into the environment map through
PMREMGenerator, re-baked only when the hour moves enough, so glass reflections follow the sky without an HDRI. - Lit windows are one canvas texture used as an emissive map; an instance attribute shifts it per floor so stacked floors never repeat.
- The balcony view is a camera pose computed from the home's floor, plate radius and window bisector, so every home gets its own view (sea, marina, park or the city across the lagoon). The camera stands just inside the home's own balustrade, and the near plane drops in that mode so it is not clipped.
- Floor plans are SVG generated from the same plate geometry as the 3D homes (
plan.ts): outline, balcony line, service band and room walls split by angle, turned so the windows face up, drawn in with a stroke animation. The payment plan is a 60/40 schedule whose instalments always add up to the price exactly (unit tested). - The story is one scrubbed GSAP tween on a progress value; camera pose, hour, build, explode, x-ray and label groups are keyframes in
data.ts, and the last keyframe is whatever the explorer's orbit, time slider and x-ray toggle say, so the story ends exactly where the explorer starts. The camera rests on each chapter and glides between them with a quintic ease; the path swings round the tower axis (radius and angle interpolate) and lifts in an arc on long moves, so it never cuts through a building. Time of day and the build are linear tracks over the scroll, and a chapter can sweep a range (the sun study runs 07:00 to 18:36, construction runs from the piles to most of the frame), so they move directly with the scrollbar. - Construction: each floor's slab scales in as the frame front passes it and the glass, fins and balconies follow ten per cent behind; the concrete core leads and the crane rides on top. Exploding adds a gap per floor to the same matrices as slicing.
- Story cards enter as one timeline each: the card rises, a gold rule draws, the title's words slide up out of masks, then copy, list items and the availability grid rows follow; scrolling past plays it back out. A chapter rail (wide screens) shows the current chapter and scroll progress and links to every chapter. Labels in a group come in one after another as their chapter settles. Build and sun readouts in the cards are written by the render loop, not React.
- Masterplan labels and the hover tag are HTML positioned from projected 3D points; raycasting runs only after the pointer moves.
Performance notes
Three.js loads only on this page, after the poster is in view. The scene renders continuously only on the high tier, while the stage is on screen and the tab is visible (for water, boats, yachts and the beacon); on low, under reduced motion and when nothing changes it renders on demand. The low tier (phones, 4 cores or fewer, or ?quality=low) drops shadows and antialiasing, caps the pixel ratio at 1.25, uses fewer palms and city towers and re-bakes reflections less often. Everything created is disposed on unmount.
Reduced motion
Scroll steps cut to their pose behind a 0.15 s fade (the construction chapter shows the tower half built, the sun study shows midday); cards, words, labels and grid rows appear without movement; the chapter rail has no transitions; slicing, turning and the balcony view jump instead of flying; the water, boats and yachts are still and the selected home does not pulse; the floor plan appears without the drawing animation.