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.
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.
#F5F1E8#3F4A3C#A8B5A0#C67B4E#2A2620Fraunces (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.
Three photorealistic renders, all generated at 1536×1024, quality high, model gpt-image-2, saved as WebP. The actual prompts:
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.
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;
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.
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.
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.
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.
overflow:hidden + translateY).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.
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.
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.
The site is fully static — no build step. From the site directory:
npx wrangler pages deploy . --project-name terraforma