// Architecture Stack
Each layer serves a specific purpose β this is how the pieces fit together.
01 Three.js β Core WebGL (Vanilla JS)
Three.js is the standard for WebGL 3D in the browser. Works with plain HTML/JS β no React, no build step. CDN import via jsDelivr.
Setup β CDN Import Map (no build step)
// Paste in <head> β no npm, no build required
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.161.0/build/three.module.js",
"three/addons/": "https://cdn.jsdelivr.net/npm/three@0.161.0/examples/jsm/"
}
}
</script>
Basic 3D Scene β Scroll-Driven Torus Knot
import * as THREE from 'three';
import { gsap } from 'gsap';
import { ScrollTrigger } 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
// Scene setup
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, w/h, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ alpha: true, antialias: true });
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2));
renderer.setSize(window.innerWidth, window.innerHeight);
document.querySelector('#canvas-container').appendChild(renderer.domElement);
// Geometry β TorusKnot gives instant premium look
const geometry = new THREE.TorusKnotGeometry(0.5, 0.15, 128, 32);
const material = new THREE.MeshStandardMaterial({
color: 0xff6b00,
roughness: 0.2,
metalness: 0.8
});
const mesh = new THREE.Mesh(geometry, material);
scene.add(mesh);
// Lights
scene.add(new THREE.AmbientLight(0xffffff, 0.5));
const dirLight = new THREE.DirectionalLight(0xffffff, 2);
dirLight.position.set(5, 5, 5);
scene.add(dirLight);
// Scroll animation β geometry rotates as you scroll
gsap.to(mesh.rotation, {
scrollTrigger: {
trigger: 'body',
start: 'top top',
end: 'bottom bottom',
scrub: 1
},
x: Math.PI * 2,
y: Math.PI
});
// Render loop
function animate() {
requestAnimationFrame(animate);
renderer.render(scene, camera);
}
animate();
// Resize handler
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
Mobile Optimization β Three.js
// Cap pixel ratio on mobile β GPU killer otherwise
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 1.5));
// Detect mobile
const isMobile = /iPhone|iPad|iPod|Android/i.test(navigator.userAgent);
// Disable shadows on mobile (expensive)
if (!isMobile) {
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
}
// Lazy load on mobile β don't init until visible
const observer = new IntersectionObserver(entries => {
if (entries[0].isIntersecting) {
initScene();
observer.disconnect();
}
});
observer.observe(document.querySelector('#canvas-container'));
02 GSAP + ScrollTrigger β Scroll Animations
GSAP is the animation library. ScrollTrigger makes elements react to scroll position. Works with vanilla JS β no React, no build step.
CDN Setup
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/gsap.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/gsap/3.12.5/ScrollTrigger.min.js"></script>
Scroll-Triggered Reveals
gsap.registerPlugin(ScrollTrigger);
// Staggered section fade + slide
gsap.from('.section', {
scrollTrigger: {
trigger: '.section',
start: 'top 80%',
toggleActions: 'play none none reverse'
},
y: 60,
opacity: 0,
duration: 0.8,
stagger: 0.15,
ease: 'power2.out'
});
// Hero parallax background
gsap.to('.hero-bg', {
scrollTrigger: {
trigger: 'body',
start: 'top top',
end: 'bottom bottom',
scrub: true
},
yPercent: 30,
ease: 'none'
});
// Text clip reveal β clips from top to bottom
gsap.from('.reveal-text', {
scrollTrigger: {
trigger: '.reveal-text',
start: 'top 85%'
},
clipPath: 'inset(100% 0 0 0)',
duration: 1.2,
ease: 'power3.out'
});
Pin + Horizontal Scroll
// Pin section while scrolling horizontally through cards/steps
const sections = document.querySelectorAll('.horizontal-section');
gsap.to(sections, {
xPercent: -100 * (sections.length - 1),
ease: 'none',
scrollTrigger: {
trigger: '.horizontal-wrapper',
pin: true,
scrub: 1,
snap: 1 / (sections.length - 1),
end: () => '+=' + document.querySelector('.horizontal-wrapper').offsetWidth
}
});
Mobile β Replace Heavy Animations with Simple Fades
const isMobile = window.innerWidth < 768;
if (isMobile) {
// Replace scroll-linked 3D with simple fade on mobile
gsap.from('.section', {
opacity: 0,
y: 30,
duration: 0.5,
stagger: 0.1
});
} else {
init3DScrollAnimations();
}
03 A-Frame β Declarative HTML-First 3D
A-Frame builds 3D scenes with pure HTML markup. Good for simpler 3D needs. VR-ready, declarative, no JS required for basic scenes.
CDN Setup
<script src="https://aframe.io/releases/1.5.0/aframe.min.js"></script>
Basic A-Frame Scene (pure HTML)
<a-scene embedded vr-mode-ui="enabled: false" loading-screen="enabled: false">
<!-- Lights -->
<a-entity light="type: ambient; color: #222"></a-entity>
<a-entity light="type: directional; color: #fff; intensity: 0.8" position="1 3 2"></a-entity>
<!-- 3D Torus β rotating automatically -->
<a-torus position="0 1.6 -3" rotation="90 0 0"
color="#FF6B00" radius="0.8" tubular-segments="64"
animation="property: rotation; to: 90 360 0; dur: 8000; loop: true; easing: linear">
</a-torus>
<!-- Box with image texture + floating animation -->
<a-box position="0 0.8 -2" material="src: #hero-image; shader: flat"
animation="property: position; to: 0 1.2 -2; dir: alternate; dur: 2000; loop: true">
</a-box>
<a-camera position="0 1.6 0"></a-camera>
</a-scene>
<!-- Preload image asset -->
<a-assets>
<img id="hero-image" src="hero.webp">
</a-assets>
A-Frame + GSAP ScrollTrigger
AFRAME.registerComponent('gsap-scroll', {
schema: {
from: { type: 'string' },
to: { type: 'string' }
},
init() {
const el = this.el;
gsap.from(el.object3D.position, {
scrollTrigger: {
trigger: el.sceneEl,
start: 'top bottom',
end: 'center center',
scrub: 1
},
...JSON.parse(this.data.from)
});
}
});
04 Lottie β Lightweight Motion Assets
Lottie plays JSON animations exported from After Effects. Tiny file size, infinite scalability, works perfectly on mobile.
CDN Setup
<script src="https://cdnjs.cloudflare.com/ajax/libs/lottie-web/5.12.2/lottie.min.js"></script>
Load + Play on Scroll Into View
const anim = lottie.loadAnimation({
container: document.querySelector('#lottie-container'),
renderer: 'svg', // svg = best on mobile
loop: true,
autoplay: false,
path: 'animation.json' // export from After Effects
});
// Play when scrolled into view
const observer = new IntersectionObserver(entries => {
entries.forEach(entry => {
entry.isIntersecting ? anim.play() : anim.pause();
});
}, { threshold: 0.5 });
observer.observe(document.querySelector('#lottie-container'));
Scrub Lottie with Scroll
ScrollTrigger.create({
trigger: '#lottie-container',
start: 'top center',
end: 'bottom center',
onUpdate: self => {
anim.goToAndStop(self.progress * anim.totalFrames, true);
}
});
05 Mobile-First Responsive
Non-negotiable rules for mobile β performance and touch targets.
Viewport + Touch Targets
/* Viewport β required for mobile */
<meta name="viewport" content="width=device-width, initial-scale=1, maximum-scale=1, user-scalable=no">
/* Touch targets β 44px minimum (Apple/Google standard) */
button, a, .clickable {
min-height: 44px;
min-width: 44px;
}
/* Safe area for notched phones (iPhone X+) */
body {
padding-top: env(safe-area-inset-top);
padding-left: env(safe-area-inset-left);
padding-right: env(safe-area-inset-right);
padding-bottom: env(safe-area-inset-bottom);
}
/* Disable hover effects on touch devices */
@media (hover: none) {
.hover-effect:hover {
transform: none !important;
}
}
CSS 3D Transforms β No WebGL Fallback
/* CSS 3D β works everywhere, no WebGL needed */
.card-3d {
transform-style: preserve-3d;
perspective: 1000px;
}
.card-3d:hover .card-inner {
transform: rotateY(10deg) rotateX(5deg);
}
.card-inner {
transition: transform 0.4s ease;
transform-style: preserve-3d;
}
06 Mobile Performance Checklist
| Issue | Fix | Impact |
|---|---|---|
| WebGL on mobile | Cap DPR at 1.5, lazy-load with IntersectionObserver | High |
| Heavy GSAP scroll | Replace with simple fades on touch devices | High |
| Lottie on mobile | Use SVG renderer, preload only when visible | Medium |
| Images | WebP format, lazy-load below fold, explicit width/height | High |
| Fonts | font-display: swap, preload critical fonts only | Medium |
| Full-screen WebGL | Always have CSS 3D fallback on mobile | Critical |
Lazy Init Pattern
const init3D = () => {
const canvas = document.createElement('canvas');
document.querySelector('#hero').appendChild(canvas);
// Three.js scene setup...
};
// Only load 3D on desktop β CSS 3D fallback on mobile
if (window.innerWidth >= 768 && !/iPhone|iPad|iPod|Android/i.test(navigator.userAgent)) {
init3D();
} else {
document.querySelector('#hero').classList.add('css-3d-fallback');
}
07 Premium Design System Integration
Pair with the popular-web-designs skill for 54 real brand visual languages. Never design from scratch β pick an existing premium system.
| Style You Want | Use This Design System |
|---|---|
| Bold dark + motion | Linear, Superhuman, Raycast |
| Editorial luxury | Apple, Notion, Stripe |
| Geometric premium | SpaceX, Vercel, Figma |
| Warm minimal | Airbnb, Spotify, Revolut |
| Dark SaaS / dev tools | Cursor, Sentry, Supabase |
| Premium light | Stripe, Vercel, Cal.com |
Example: Stripe-Inspired Design Tokens
const design = {
colors: {
primary: '#635BFF', // Stripe purple
accent: '#0A2540', // Dark ink
surface: '#F6F9FC', // Light surface
text: '#1A1A2E', // Near black
},
typography: {
fontFamily: 'Source Sans 3, system-ui, sans-serif',
h1: { size: 'clamp(2.5rem, 5vw, 4rem)', weight: 600 },
h2: { size: 'clamp(1.5rem, 3vw, 2.5rem)', weight: 600 },
body: { size: '1.125rem', weight: 400, lineHeight: 1.7 }
},
spacing: {
section: 'clamp(80px, 12vw, 160px)',
component: '24px'
}
};
08 Build Workflow
Step-by-step process for building a premium 3D animated site.
-
Brief β Choose Design SystemUse popular-web-designs skill to pick a real brand's visual language (e.g. Stripe for B2B SaaS, Airbnb for consumer)
-
Extract Design TokensColors, typography scale, spacing system, shadow values from the chosen design system
-
Hero Concept β Three.js + GSAPTorusKnot or custom geometry with metallic material, scroll-driven rotation via ScrollTrigger
-
Content Sections β CSS 3D + GSAP RevealsStaggered fade + slide on scroll, clip-path text reveals, hover tilt on cards
-
Mobile CheckReplace 3D scroll with CSS fades on touch devices, cap DPR, lazy-load Three.js
-
Performance AuditWebP images, font-display: swap, 44px touch targets, prefers-reduced-motion
-
Deploy β Cloudflare Pageswrangler pages deploy . --project-name=PROJECT_NAME
-
Review β Screenshot + FixBrowser screenshot check, fix jank, verify on both mobile and desktop
-
Iterate β /goal Loop Until PremiumBuild β check β fix β deploy β repeat until it passes screenshot review
09 Anti-Slop Rules
What NOT to do β these are the markers of AI-generated generic sites.
/* Disable all animations for users who prefer reduced motion */
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
transition-duration: 0.01ms !important;
}
}
10 CDN Quick Reference
All libraries load directly from CDN β no npm, no build step, no framework lock-in.
// Related Skills
| Skill | What It Does |
|---|---|
| popular-web-designs | 54 real brand design systems β exact colors, typography, CSS values for Stripe, Linear, Apple, etc. |
| claude-design | Design process + anti-slop rules, variation generation, verification |
| website | Performance + accessibility baseline β mobile-first, WCAG, semantic HTML |
| web/firecrawl | Competitor intelligence β scrape live sites for layout and copy reference |
// Deploy to Cloudflare Pages
# Build locally then deploy with wrangler
cd /root/project-name/
npx wrangler pages deploy . --project-name=PROJECT_NAME --commit-dirty=true
# wrangler.toml β tells Cloudflare Pages where to serve from
name = "project-name"
compatibility_date = "2024-01-01"
pages_build_output_dir = "." # serves index.html at root