diff --git a/public/img/clouds.png b/public/img/clouds.png
deleted file mode 100644
index 528ca99d..00000000
Binary files a/public/img/clouds.png and /dev/null differ
diff --git a/public/img/clouds.webp b/public/img/clouds.webp
new file mode 100644
index 00000000..a8a10249
Binary files /dev/null and b/public/img/clouds.webp differ
diff --git a/public/img/earth-blue-marble.jpg b/public/img/earth-blue-marble.jpg
deleted file mode 100644
index e673a7c5..00000000
Binary files a/public/img/earth-blue-marble.jpg and /dev/null differ
diff --git a/public/img/earth-blue-marble.webp b/public/img/earth-blue-marble.webp
new file mode 100644
index 00000000..39fe98e7
Binary files /dev/null and b/public/img/earth-blue-marble.webp differ
diff --git a/src/app/(marketing)/page.tsx b/src/app/(marketing)/page.tsx
index ea5b022e..1caecd5a 100644
--- a/src/app/(marketing)/page.tsx
+++ b/src/app/(marketing)/page.tsx
@@ -43,9 +43,9 @@ export default async function Landing() {
return (
<>
- {/* Preload globe textures so they're cached before the client component mounts */}
-
-
+ {/* ponytail: no texture preload — LiveGlobe is dynamic({ ssr: false }), so it
+ mounts after hydration anyway. Preloading pulled 6.5MB into the critical
+ window ahead of the render-blocking CSS that gates first paint. */}
diff --git a/src/components/features/globe/LiveGlobe.tsx b/src/components/features/globe/LiveGlobe.tsx
index 9cceedfc..762b9cc3 100644
--- a/src/components/features/globe/LiveGlobe.tsx
+++ b/src/components/features/globe/LiveGlobe.tsx
@@ -160,7 +160,7 @@ export function LiveGlobe({
// Add clouds
if (showClouds) {
- new TextureLoader().load("/img/clouds.png", (texture) => {
+ new TextureLoader().load("/img/clouds.webp", (texture) => {
if (cancelled || !globe) return;
clouds = new Mesh(
new SphereGeometry(
@@ -168,8 +168,12 @@ export function LiveGlobe({
75,
75,
),
+ // ponytail: alphaMap, not map. The old RGBA PNG was white wherever
+ // alpha > 0 and black elsewhere, so its colour channel carried no
+ // information the mask doesn't — dropping it let the mask compress
+ // as plain greyscale (4.8MB -> 922KB) at full 4096x2048.
new MeshPhongMaterial({
- map: texture,
+ alphaMap: texture,
transparent: true,
opacity: 0.3,
}),
@@ -533,7 +537,7 @@ export function LiveGlobe({
width={width}
height={height}
backgroundColor="rgba(0,0,0,0)"
- globeImageUrl="/img/earth-blue-marble.jpg"
+ globeImageUrl="/img/earth-blue-marble.webp"
showAtmosphere={false}
onGlobeReady={() => {
globeReadyRef.current = true;