Campaign landing — Portuguese build
2026
SpaceX IPO
A page with a Three.js moon on it that ships no Three.js until you scroll near the moon.
Delivered to
Portugal / Brazil
Portuguese build
Framework
Astro 5, static output
3D: Three.js, dynamically imported
Techniques
3
5 constants, formulas and guards
A campaign landing page for a high-profile listing, carrying the things that make a page feel expensive and score badly: a real-time 3D model, scroll tweening, and a type stack requested from a third party. The Portuguese build rebuilt the delivery around a single rule — nothing heavy loads until the thing that needs it is about to be seen — while keeping the design it inherited pixel for pixel.
Offline
A campaign landing page in its Portuguese build. Not linked from this page — the deployment sits on a personal account belonging to the original author, and pointing a portfolio at someone else's host is not mine to do. Every claim below is made against the source.
Everything heavy moved off the critical path — the 3D engine loads only if you scroll to it.
Inherited — all on first paint
Rebuilt — hatched loads later
What was built, and for whom.
Portuguese build
Portugal / Brazil
pt
Ported to a static-first framework and re-engineered for mobile delivery, keeping the English build's design and translating its content.
Each one starts with something that was broken.
- 01
The 3D engine loads when the 3D is nearly visible
The failure
A WebGL model below the fold is the worst kind of weight: the visitor pays for the engine during the first paint they are actually watching, to render something they have not scrolled to yet, and some never scroll to it at all.
The decision
Import the engine and its loader inside the observer callback that fires as the container approaches the viewport, guarded so it happens once. The page's first paint carries no 3D code whatsoever; the moon assembles itself just ahead of being needed, and a visitor who never scrolls never downloads it.
- guardsrc/scripts/moon.ts
const THREE = await import('three') — inside the intersection handler, behind a `started` latch - guardsrc/scripts/moon.ts
renderer.setPixelRatio(Math.min(devicePixelRatio, isMobile ? 1.5 : 2))
What it cost
A visitor who scrolls very fast can reach the container before the engine finishes arriving, so the moon fades in slightly late on a slow connection. That is the correct thing to trade: a late ornament costs nothing, a slow first paint costs the visit.
- 02
Mobile gets fewer pixels, not a worse scene
The failure
A retina phone reports a device pixel ratio of three, which asks a mobile GPU to shade nine times the pixels of a logical one — on a hero the user is scrolling past. The easy answer, disabling 3D on mobile, gives the majority of the traffic the lesser page.
The decision
Keep the scene, cap the resolution, and cap it lower on phones than on desktops. The lighting, the tone mapping and the model are identical; only the sampling rate differs, which on a sphere lit from one side is very close to invisible.
- constantsrc/scripts/moon.ts
toneMapping = ACESFilmicToneMapping, exposure 1.6, outputColorSpace = SRGBColorSpace
- 03
Every heavy dependency sits behind its own gate
The failure
Fixing one blocking dependency and leaving the rest is how performance work fails to show up in the score — the page is still gated by whichever heavy thing you did not move.
The decision
Apply the same rule to all of them rather than only the obvious one: the tween library gets its own dynamically-imported chunk, the stylesheet is compiled rather than generated in the browser, and the fonts are bundled and preloaded instead of fetched from a third party. No single change here is clever; applying it to everything is the point.
- constantpackage.json
@fontsource/inter, @fontsource/barlow-condensed, @fontsource/space-mono - constantpackage.json
lightningcss — stylesheet minification at build
Every animation, with what it does when asked for less.
The last column is the one worth reading. “Respects reduced motion” is easy to assert and easy to skip, so where it is not true here, it says so.
Moon rotation
Continuous once loaded
Linear
Not gated in source. Recorded here because the motion table is not allowed to report only the flattering half.
Scroll tweens
Scrubbed to scroll position
Library defaults
Not gated in source.
What it was built on, what is claimed, and what is not.
Framework
Astro 5, static output
Chosen for one property above all: it defers and bundles every script block by default, so the fix for render-blocking scripts is the framework's default rather than a discipline somebody has to keep.
3D
Three.js, dynamically imported
Present on the page, absent from the initial bundle. See the first technique — this is the whole entry.
CSS
Tailwind compiled at build
The inherited build ran the CDN version, which ships a compiler to the browser to generate the stylesheet on arrival. Compiling at build turns megabytes of runtime into a small static file.
Type
Self-hosted and preloaded
A font stylesheet from a third-party origin blocks rendering on a connection it does not control. Bundling it removes a request, a DNS lookup and a privacy question together.
Claimed — and how to check it
Three.js loads only when the moon container nears the viewport
The dynamic import sits inside the intersection handler in moon.ts, behind a one-shot latch.
Device pixel ratio is capped, lower on mobile than desktop
The setPixelRatio call in moon.ts.
Fonts are bundled rather than requested from a font CDN
The @fontsource packages in package.json.
Not claimed
- THE VISUAL DESIGN IS NOT CLAIMED. This build is a port of an existing English site and keeps its design; the work in this repository is the localisation and the delivery engineering.
- No verified Lighthouse score. The 34 starting point and the ~95 target are the figures recorded in the repository's README — neither was measured here, and no after figure is asserted as fact.
- No traffic or conversion figure of any kind.
- No live link. The deployment is on a personal account belonging to the original author; this page does not point at it.
- The 3D model itself is an asset, not authored here.
Constants are read from the repository at the file named beside them. The 34 mobile score is the figure recorded in the repository's own README as the starting point; it was not re-measured here, and the ledger says so.