← Back to site

How Terraforma was built

A design guide for this showpiece: the concept, the AI image pipeline, the signature motion techniques, and the three quality passes — everything you'd need to reproduce or extend it.

Concept

Brief: a portfolio site for a fictional biophilic architecture studio. Mood: organic modernism — "buildings that breathe". Calm, warm, tactile, editorial. Nothing gridded-to-death; edges soften, imagery lives inside organic shapes, and the page itself drifts like a canopy.

Palette

Bone#F5F1E8
Deep moss#3F4A3C
Sage#A8B5A0
Terracotta#C67B4E
Soil#2A2620

Type

Fraunces (variable) for display — its optical-size axis is used deliberately: headlines render at opsz 144 for high-contrast, soft "wonky serif" letterforms, while smaller display moments sit near opsz 100. Italic swashes carry the accent words. Karla for body and UI — a grotesque with just enough warmth to not fight the serif.

Assets — GPT Image 2

Three photorealistic renders, all generated at 1536×1024, quality high, model gpt-image-2, saved as WebP. The actual prompts:

Curved mass-timber pavilion in old-growth forest Atrium with cascading hanging gardens and god-rays Moss and basalt stone wall in warm light
hero-pavilion.webpArchitectural photography of a curved mass-timber pavilion embedded in an old-growth Pacific Northwest forest, sweeping glulam ribs and a living green roof merging into moss-covered Douglas firs, floor-to-ceiling glass reflecting ferns, soft morning mist, golden hour sunlight raking through the canopy, shot on medium format 45mm lens f/8, warm earthy palette of honey timber, deep moss green and bone white, calm editorial mood, ultra photorealistic, no people, no text, no watermark
atrium-gardens.webpInterior architectural photography of a grand courtyard atrium with cascading hanging gardens, tiers of trailing plants spilling from warm timber balconies, volumetric god-rays streaming through a skylight grid, thin haze, terracotta clay floor tiles, sage and deep green foliage, a slender indoor tree, warm bone-colored walls, serene biophilic modernism, shot on 24mm tilt-shift lens, soft natural light, ultra photorealistic, no people, no text, no watermark
moss-wall.webpClose interior architectural photograph of a living moss and stacked basalt stone feature wall, lush preserved moss in varied deep greens between rough warm grey stone courses, a low brass wall sconce casting warm amber light across the textures, shallow pockets of shadow, adjacent smooth clay plaster wall in bone white, tactile organic modernism, 50mm lens f/4, intimate warm mood, ultra photorealistic, no people, no text, no watermark

All three came back portfolio-grade on the first take. They were then recompressed with Pillow (WebP q80, method 6) from 5.8 MB total down to ~0.75 MB with no visible loss — the whole site ships under 1 MB.

Techniques

1 · Morphing blob hero mask

The hero image sits inside an SVG <clipPath> whose path is rebuilt every frame. Eight anchor points orbit a centre; each point's radius "breathes" with two layered sinusoids (different speed, phase and amplitude per point), and a Catmull-Rom → cubic-Bézier conversion turns the points into a smooth closed spline. Because the motion is continuous — not tweened between keyframes — the blob never pops or repeats. A sage outline path renders the same spline 0.25 s in the past, trailing the mask like a slow afterimage.

var r = BASE * s.squish
      + Math.sin(t * s.a1 + s.p1) * s.amp1
      + Math.sin(t * s.a2 + s.p2 + i) * s.amp2;
// Catmull-Rom to Bezier:
c1 = p1 + (p2 - p0) / 6;  c2 = p2 - (p3 - p1) / 6;

2 · Multi-layer scroll parallax

The interlude stacks four layers — atrium photo, back canopy silhouette (moss), pull-quote, front canopy silhouette (soil) — each driven by GSAP ScrollTrigger with scrub and a different travel distance (−90 to −260px), so the foreground canopy visibly drifts over the architecture as you scroll. The silhouettes themselves are generated at runtime: a seeded PRNG feeds three sine "octaves" of different frequency into a Catmull-Rom spline, producing soft rolling ridges in the same organic language as the hero blob.

3 · Pollen canvas

A fixed, pointer-transparent <canvas> spans the viewport. ~54 spores (26 on mobile) rise slowly, swaying on a per-particle sine and twinkling in opacity, in bone / sage / terracotta at 18–58% alpha. Delta-time stepped, capped at devicePixelRatio 2, paused when the tab is hidden, removed entirely under prefers-reduced-motion.

4 · Scroll-triggered counters

IntersectionObserver fires once at 60% visibility; numbers ease with 1 − (1−p)⁴ over 1.8s (214 hectares, 8.4M kWh, 96%, 42 species). Reduced motion sets final values instantly.

5 · Drawn process line

The "Living Systems" S-curve is a single SVG path; getTotalLength() feeds stroke-dasharray/-offset, scrubbed by ScrollTrigger so the line literally draws itself from soil to canopy as you scroll, with terracotta nodes popping in via back.out easing.

6 · Micro-interactions

Process — three passes

Pass 1

Desktop + mobile screenshots, console and structure snapshot. Recompressed the three renders from 5.8 MB to 0.75 MB. The first foliage silhouettes read as cartoon bumps (and a literal fir-treeline rewrite read as spikes) — both were rejected for generated multi-octave canopy ridges. Fixed a merged sentence when the quote's <br> is hidden on mobile, gave the cite a soil chip for contrast, strengthened the interlude scrim, and stacked project-card metadata on small screens. Added: the card-title arrow micro-interaction.

Pass 2

Contrast + rhythm hunt. Small terracotta text on bone measured 2.9:1 — added an AA-safe terracotta ink (#9E5A34) for eyebrows. Killed an orphaned "— 2024" wrap in card metadata. Verified the drawn process line's full geometry and the card hover end-states. Added: the blob's 0.25 s outline echo, the hero blob's scale-and-settle entrance, and hover rules on the studio awards list.

Pass 3

Final sweep: zero console errors, no horizontal overflow at 375/1280, reduced-motion audit (particles removed, morphs frozen, counters jump to final values), OG/meta/alt-text audit, total weight ~1 MB. Added: alternating opacity pulse on the process-line nodes.

Ship it

The site is fully static — no build step. From the site directory:

npx wrangler pages deploy . --project-name terraforma