(10 minute read)
I recently attended a fantastic design workshop by Meng To and it got me thinking about how I could jazz up the Hezo homepage with something that was really eye-catching and yet elegant. I wanted something that would convey the same information as the current page and yet visually lead the reader towards the call-to-action.
You can see the finished animation at the top! And see the live version at hezo.ai.
It's a Three.js scene running on WebGPU. You start in front of a temple gate, and scrolling walks you through it, along a stone causeway over a lake, and up a mountain to a second temple. It has a day mode and a night mode. Nothing ships as an asset, and there are no model files or texture images. Everything is procedurally generated in the browser from code. It's also optimised differently based on device (e.g. lighter generation on mobile).
The total download size for the page on first load is <1 MB!
It took four models and about a week: Higgsfield for the reference video, Fable 5 for the mockup, GPT-6 for the build, Opus 5 for the optimisation. Each one was handed the artifact the previous one produced, and there were several iterations within each model session.
In this post I will detail the overall flow I took to make this come alive.
A key takeaway from Meng's course was the importance of a reference video. Basically, you get the agent to build the graphics scene and self-verify it against a reference video, iterating until the two match.
So my first artifact was a clip generated using Higgsfield MCP, and I started as broadly as I could since I didn't even know what type of video I wanted as reference:
come up with several landing page concept for https://hezo.ai as a video. we want to emphasize:
- collaborating with large fleets of ai agents to create great things that you couldn't otherwise do by yourself
- it sits above your existing ai providers and harnesses (codex, claude code, etc) and supercharges the workflow and your life
explore themes around:
- achieveing great things (e.g climbing mountain, launching into space et al, etc)
- a sense of smooth, stress-free living and being in flow
- doing amazing things start off with an image of the concept and then make videos where i ask for further refinement. note that https://hezo.ai has light-mode and dark-mode - let's base our inspiration on light-mode.
It gave me a few options and I told it to go with its instinct and pick the one it felt best represented the core concepts:
This didn't quite feel right but I liked the idea of getting to a summit so I then started thinking about the idea of a temple at the summit and the user slowly walking up towards the temple door as a final destination.
I dictated the exact scene I was looking for in one long paragraph (and this worked really well!):
So what I want is to do a... let's start the video out looking at the front entrance of a grand temple based on the reference image. So the idea is that we look at this temple and we can see that some vegetation outside it and the temple has some little birds fluttering in front of it. And as we scroll down the page, we walk in through the entrance of the temple, but we can already see in the distance, in the hallway, through the hallway, there is a walkway that leads away from the temple, from its back, in the outside. And as we scroll down the page, the way that works is the animation then takes us up that walkway to an even greater temple at the top of the mountain. So that means at the very beginning, the temple we're looking at isn't really a temple, it's just a gate, but a very elaborate gate styled like a temple. And we can still already see the mountains in the distance in the background. So we're going to walk through this gate and up the walkway with beautiful vegetation around us till we get to the very top of the mountain to see a beautifully grand temple. And because the website supports day mode and night mode, at night mode we should be seeing lanterns lighting the way, we should be seeing fireflies and other such things. And at daytime, obviously, we see the birds, we don't see the lanterns lit up. So it has to be sort of appropriate for both day and night mode. But for now, let's just use Hicksfield to do the daytime animation.
This then gave me:
This was a great starting point! But notice the stairway appearing out of nowhere.
I spent a few prompts describing how it should work (i.e. the camera should move up the existing walkway at speed to arrive at the temple) but that didn't fix things. The problem is that interpolating between a distant start frame and a close end frame leaves a large unconstrained middle, and the model fills that middle by inventing whatever gets it from one end to the other. The solution was to be specific about not having things suddenly appear out of nowhere:
again it needs to show the viewer moving up the linear walkway at pace and not have the final template and/or walkway suddenly appear out of nothing.
The model then accurately created a continuous journey animation by using keyframes internally:
I then spent a few more prompts creating the night-mode version of this.
Before getting Astra to render this, I decided to figure out how the site content placement and scrolling should work. For this I went to Fable 5 with the final video above as reference:
have a look at https://hezo.ai (website repo).
then have a look at this video: https://d8j0...
my plan is to redo the hezo homepage such that the background is a three.js rendering of this video animation whereby as you scroll down the page it transitions through the animation, but all as a 3d render.
but first i want you to figure out the best way of combing the current website content with such a rendering. and note that the rendering will need to be able to day and night mode, to match the theme switcher.
show me some mock visuals, with various options and choices.
This then gave me a surprisingly good mockup with various options to choose from in terms of layout and flow. The best bit was that it created a low-res version of the video as a render using three.js.
I picked a layout and set two constraints that never changed afterwards:
animations are all code, not assets. this has to be procedurally rendered.
From then on it was just a matter of iterating with prompts until I got something that felt ready for the final push.
In total there were about eighteen rounds of redo visuals, which is the unglamorous middle of any design process: station panels alternating along the causeway instead of sitting in a grid, the section title timed to appear just as you clear the gate, birds cut back to the gate only, mouse movement turning the camera slightly the opposite way, red lanterns with the Hezo logo hanging from the gate ceiling.
Finally I had the mockup I was looking for:
Notice: I even experimented with clickable birds in the mockup above!
It was time to get Astra to build the final render!
The build prompt to GPT-6 shows why keeping the video and the mockup separate helped:
look at the reference video attached. i want to recreate the fidelity and scene from this video exactly using three.js and webgpu renderer as the new homepage for https://hezo.ai
i have created a mockup version of the new homepage (see [artifact link]) with the desired visual elements. so all i need you to do is redo the background from this mockup with a high-fidelity version of what's in the reference video.
the visual flow should be exactly as in the mockup. it's just that the bg rendering should be high-fidelity and matching what's in the reference video in terms of detail, vegentation, water, etc.
self-verify the final against the reference and your plan and keep iterating until you get it. use subagents to split up the work, and use adversarial subagents to verify each others' work.
note that the scene must be procedurally rendered and must match the fidelity in the video in terms of fine detail. don't do the bare minimum you think, do the maximum possible in terms of matching the fine detail, but obviously we want 60 fps.
Some key points to note:
The first pass cleared 60 fps but looked wrong: foliage as large faceted masses, distant cliffs with no natural detail. So several iterations were needed to get Astra to refine the look. Along the way I experimented with adding interaction to the render:
additionally, when the user clicks/taps the vegetation it should animate slightly as if it has been touched. ... the vegetation click detection needs improving. let's change it from click to mouseover (on mobile it can still be tap) ... when we click the hanging lanterns they should gently swing with real physics motion
Although the scene looked right, the page took far too long (>10 seconds) to show anything. And this is on my 2025 MacBook Pro.
I switched to Opus 5 to improve it (I was out of Astra tokens, and I had already used up a lot of my Fable 5 weekly allocation).
My first prompt:
we need to make sure it loads well esp. on slow connections, ui shouldn't freeze up whilst rendering is taking place, and can we load in the elements progressively (front to back)?
But this didn't work as well as the following prompt:
don't code yet.
just figure out if there is a way to speed up the rendering from page load start time (simulate with a slow 4g connection like you get in chrome devtools)
This worked much better. A model asked to make something faster will produce plausible micro-optimisations straight away, and you will spend time reviewing changes that don't really move the needle. Asked to investigate against a named network condition, with code explicitly forbidden, it comes back with a list you can approve or reject. This is similar to the plan-then-action flow that is often recommended.
Since hezo.ai supports 12 languages, most of the bundle size drop actually came from loading only the active language rather than every translation. Astra had already done the basic optimisation (such as using a background worker to avoid hanging the main UI thread) as part of its initial build so my focus was more on three.js usage optimisation.
I had it measure the before-after in Chrome against the Slow 4G network preset:
| before | after | |
|---|---|---|
| first 3D preview (cold, Slow 4G) | 11.1 s | 6.8 s |
| all loading stages complete | 32.3 s | 23.5 s |
| scene worker starts downloading | 7.2 s | 1.5 s |
| app bundle, compressed | 283 KB | 30 KB |
Pretty good.
More load-time optimisations followed:
| Change | Shader builds | Shader build time | Readiness |
|---|---|---|---|
| baseline | 583 | 3.3 s | 8.1-8.4 s |
| bake light architecture into shared-shader plain meshes | 379 | 1.7 s | 5.6-5.7 s |
| batch the distant canopy by view variant and valley sector | 195 | 1.49 s | ~0.3 s sooner |
Part of this involved adding a loader animation which you can see on the live site. I had Opus come up with a few alternatives first, and then chose the one that looks similar to the actual rendering.
The internal rule in the code is that if rendering takes longer than 5 seconds then the content gets shown (with the ability to scroll and see it) and the rendering can then fill in the background as and when it comes online:
I got my friend to load the site and his phone browser crashed :/ Redmi Note 14 Pro+, Snapdragon 7s Gen 3, Adreno 710. Not a shader fault, an out-of-memory kill, because a phone tab shares its budget with the operating system.
Chrome on Android clamps navigator.deviceMemory to 8 and reports hardwareConcurrency of 8, which are the numbers a high-end desktop reports. Safari omits deviceMemory altogether. There is no browser signal that says this one will die.
The solution was to split the rendering into scene tiers, with each tier specifying a different level of rendering detail so that on more constrained devices and smaller screens we render less detail:
| knob | full | reduced (narrow desktop) | light (tablet) | phone |
|---|---|---|---|---|
| quality | 1 | .6 | .3 | .12 |
| min / max pixel ratio | 1.5 / 2 | 1 / 1.5 | 1 / 1 | 1 / 1 |
| shadow map | 2048 | 1024 | 512 | none |
| AO samples | 16/8/6 | 8/6/4 | 4/3/2 | none |
| lake reflection | yes | yes | yes | none |
| bloom / sky noise | yes | yes | yes | no |
| touch | yes | yes | yes | no |
| mountain radial x levels | 128x96 | 80x56 | 56x40 | 56x28 |
| crowns / leaves | 96 / 3200 | 32 / 1200 | 16 / 500 | 0 / 0 |
| crown detail | 1 | 1 | 1 | 0 |
| forest spacing | 1 | 1.3 | 1.3 | 1.8 |
| spray (understory / canopy) | 24 / 64 | 16 / 40 | 16 / 40 | 6 / 14 |
| fern | 6 fronds, 17 pinnules | 5, 17 | 5, 17 | 3, 8 |
| ground grid | 300x144 | 300x144 | 300x144 | 150x72 |
| architecture | 1 | .65 | .65 | .4 |
| impostor | 512 px, 4 views | 512, 4 | 512, 4 | 256, 2 |
| cloud bake | 512x320, 48 steps | 256x160, 32 | none | none |
Alongside this, Opus also suggested adding a crash sentinel: if the renderer crashes, the page records this in localStorage so that the next time the page is loaded it skips rendering and shows the fallback plain static background, similar to the old version of the homepage.
When the user navigated to another page on the site and then went back to the homepage they would again see the loading animation and the rendering would start afresh. This sucked - I wanted them to be taken back to the exact scroll position in the final render that they had initially navigated away from.
The main solution was to save the scroll position and also save the rendered scene itself in memory, taking up about 1 GB of RAM on desktops. But since this is a lot of RAM I decided to limit the time this is kept:
Now the homepage scroll position is maintained during navigation back and forth and it's very, very smooth:
I hope this write-up helps anyone looking to do something similar with their own homepage.