| name | creative-3d-components |
| version | 1.0 |
| description | Opinionated creative skill for building stunning 3D, particle, and generative visual components. Use when building any Three.js, Canvas, WebGL, or visually complex component. Provides specific visual recipes, mathematical patterns, and creative direction — not API docs. |
Creative 3D & Visual Components
You are a creative technologist building visual components that belong on Awwwards. This skill gives you specific recipes and creative direction — not API documentation.
Creative Philosophy
What makes a component "wow"
- One hero effect, perfectly executed — don't combine 5 techniques badly. Pick one (glass refraction, particle morphing, procedural noise) and make it flawless.
- Organic motion > mechanical motion — nature doesn't move linearly. Use sine waves, noise, springs. Nothing should move at constant speed.
- Depth and atmosphere — fog, bloom, depth-of-field, ambient occlusion. A scene without atmosphere looks like a tech demo.
- Light tells the story — a single dramatic light source beats 4 flat ones. Rim lighting creates silhouettes. Backlight creates mystery.
- Restraint in color — 2-3 colors max. One dominant, one accent, one for depth. Monochromatic + one warm accent is almost always stunning.
The "screenshot test"
If someone paused the animation at any random frame, would that single frame look beautiful as a still image? If yes, the component is good. If it only looks good in motion, the design needs work.
This Project's Patterns
Components in this project use raw Three.js (not React Three Fiber) and Canvas 2D API. Follow these established patterns:
Three.js in React (the project way)
'use client'
import { useEffect, useRef } from 'react'
export function MyComponent() {
const containerRef = useRef<HTMLDivElement>(null)
useEffect(() => {
const container = containerRef.current
if (!container) return
let alive = true
import('three').then((THREE) => {
if (!alive) return
const W = container.clientWidth || 500
const H = container.clientHeight || 500
const scene = new THREE.Scene()
const camera = new THREE.PerspectiveCamera(50, W / H, 0.1, 100)
const renderer = new THREE.WebGLRenderer({ antialias: true })
renderer.setSize(W, H)
renderer.setPixelRatio(Math.min(window.devicePixelRatio, 2))
container.appendChild(renderer.domElement)
let raf: number
const loop = () => {
raf = requestAnimationFrame(loop)
renderer.render(scene, camera)
}
loop()
return () => {
cancelAnimationFrame(raf)
renderer.dispose()
container.removeChild(renderer.domElement)
}
})
return () => { alive = false }
}, [])
return (
<div ref={containerRef} className="flex h-full w-full items-center justify-center bg-sand-950" />
)
}
Canvas 2D pattern (for 2D generative/particle work)
const canvasRef = useRef<HTMLCanvasElement>(null)
useEffect(() => {
const canvas = canvasRef.current
if (!canvas) return
const ctx = canvas.getContext('2d')!
}, [])
Visual Recipes
Glass / Crystal
The most requested "wow" effect. Key: transmission + environment + caustics feel.
const glass = new THREE.MeshPhysicalMaterial({
transmission: 1,
roughness: 0.05,
thickness: 0.5,
ior: 1.5,
chromaticAberration: 0.03,
envMapIntensity: 1.5,
})
const pmremGenerator = new THREE.PMREMGenerator(renderer)
scene.environment = pmremGenerator.fromScene(
new THREE.RoomEnvironment()
).texture
Glow / Bloom
Bloom makes things feel alive. Two approaches:
Approach 1: Fake bloom with sprites (lightweight, this project's preferred pattern)
const size = 64
const canvas = document.createElement('canvas')
canvas.width = canvas.height = size
const ctx = canvas.getContext('2d')!
const grad = ctx.createRadialGradient(size/2, size/2, 0, size/2, size/2, size/2)
grad.addColorStop(0, 'rgba(255,200,100,1)')
grad.addColorStop(0.3, 'rgba(255,200,100,0.4)')
grad.addColorStop(1, 'rgba(255,200,100,0)')
ctx.fillStyle = grad
ctx.fillRect(0, 0, size, size)
Approach 2: Real postprocessing bloom (heavier, more cinematic)
import { EffectComposer } from 'three/addons/postprocessing/EffectComposer.js'
import { UnrealBloomPass } from 'three/addons/postprocessing/UnrealBloomPass.js'
import { RenderPass } from 'three/addons/postprocessing/RenderPass.js'
const composer = new EffectComposer(renderer)
composer.addPass(new RenderPass(scene, camera))
composer.addPass(new UnrealBloomPass(
new THREE.Vector2(W, H),
0.8,
0.3,
0.85
))
Particles That Feel Alive
Never use random velocity. Use forces and fields.
const angle = noise3D(p.x * 0.01, p.y * 0.01, time * 0.001) * Math.PI * 2
p.vx += Math.cos(angle) * 0.02
p.vy += Math.sin(angle) * 0.02
p.vx *= 0.98
p.vy *= 0.98
Particle count guidelines:
- Canvas 2D sprites: 2,000-10,000 comfortable
- Three.js Points (BufferGeometry): 10,000-100,000
- GPU instanced meshes: 100,000+
Liquid / Organic Surfaces
Use simplex noise displacement on a sphere or plane:
const geo = new THREE.SphereGeometry(1, 128, 128)
const positions = geo.attributes.position
const original = positions.array.slice()
function animate(time) {
for (let i = 0; i < positions.count; i++) {
const x = original[i * 3]
const y = original[i * 3 + 1]
const z = original[i * 3 + 2]
const displacement = 1 + 0.15 * noise3D(
x * 2 + time * 0.001,
y * 2 + time * 0.0007,
z * 2 + time * 0.0012
)
positions.array[i * 3] = x * displacement
positions.array[i * 3 + 1] = y * displacement
positions.array[i * 3 + 2] = z * displacement
}
positions.needsUpdate = true
geo.computeVertexNormals()
}
Wireframe with Depth
The project already does this well (torus-knot, sphere-lines). Key principles:
- Depth-sorted segments — closer lines are brighter/thicker
- Alpha by depth — back faces very dim (0.05-0.15), front faces brighter (0.3-0.6)
- Line width variation — thicker at equator, thinner at poles
- Never fully opaque — max alpha 0.6 keeps wireframes ethereal
Mathematical Beauty
Noise Functions
For organic, natural-feeling motion. If the project doesn't have a noise library, implement simplex noise or use this minimal approach:
function noise2D(x: number, y: number): number {
const n = Math.sin(x * 12.9898 + y * 78.233) * 43758.5453
return n - Math.floor(n)
}
Parametric Curves
For generating shapes mathematically (like the torus knot):
x = Math.sin(a * t + deltaX)
y = Math.sin(b * t + deltaY)
z = Math.sin(c * t + deltaZ)
x = Math.sin(t) + 2 * Math.sin(2 * t)
y = Math.cos(t) - 2 * Math.cos(2 * t)
z = -Math.sin(3 * t)
r = Math.cos(k * theta)
Spring Physics (for interaction response)
const spring = {
value: 0,
target: 1,
velocity: 0,
stiffness: 180,
damping: 12,
}
function stepSpring(s, dt = 1/60) {
const force = -s.stiffness * (s.value - s.target)
const dampForce = -s.damping * s.velocity
s.velocity += (force + dampForce) * dt
s.value += s.velocity * dt
}
Color Palettes That Work on Dark Backgrounds
Since component previews are always on bg-sand-950 (#110F0C):
| Mood | Primary | Accent | Depth |
|---|
| Warm embers | #FF6B35 | #FFD700 | #1A0A00 |
| Cool crystal | #00D4FF | #7B68EE | #000D1A |
| Toxic/neon | #39FF14 | #FF00FF | #001A00 |
| Minimal mono | #FFFFFF | #666666 | #0A0A0A |
| Sunset | #FF4500 | #FF8C42 | #1A0500 |
| Ocean depth | #006994 | #40E0D0 | #001A2E |
| Rose gold | #B76E79 | #F4C2C2 | #1A0A0D |
Rule: bright accent colors on dark backgrounds need to be used sparingly — 10-20% of the visual area. The rest is darkness and depth.
Interaction Patterns
Hover response (the minimum)
Every component should respond to hover. Even if subtle:
- Rotation speed change (idle: 0.003, hover: 0.012 — the project pattern)
- Color intensity shift
- Particle density increase
- Camera subtle zoom
Mouse-follow (for premium feel)
const mouse = { x: 0, y: 0 }
container.addEventListener('mousemove', (e) => {
const rect = container.getBoundingClientRect()
mouse.x = ((e.clientX - rect.left) / rect.width) * 2 - 1
mouse.y = -((e.clientY - rect.top) / rect.height) * 2 + 1
})
camera.position.x += (mouse.x * 0.5 - camera.position.x) * 0.05
camera.position.y += (mouse.y * 0.5 - camera.position.y) * 0.05
camera.lookAt(scene.position)
Anti-Patterns (Never Do These)
- Flat ambient-only lighting — looks like a 2005 screensaver. Always add at least one directional light.
- Default MeshBasicMaterial — has no lighting response. Use MeshStandardMaterial minimum.
- Random particle velocity — looks like TV static. Use noise fields or forces.
- Constant rotation speed — feels robotic. Add slight sine-wave variation.
- Pure white on dark — too harsh. Use off-white (#E8E8E8) or tinted white.
- Ignoring cleanup — dispose renderers, geometries, materials, textures. Memory leaks crash the page.
- Hardcoded canvas size — always read from container. Components must flex.