Awwwards Animations
Create premium web animations at Awwwards/FWA quality level. React-first approach. 60fps non-negotiable.
Decision Matrix
| Task |
Library |
Why |
| Scroll-driven animations |
GSAP + ScrollTrigger + useGSAP |
Industry standard, best control |
| Smooth scroll |
Lenis + ReactLenis |
Best performance, works with ScrollTrigger |
| React-native animations |
Motion (Framer Motion) |
Native React, useScroll/useTransform |
| Simple/lightweight effects |
Anime.js 4.0 |
Small footprint, clean API |
| Complex timelines |
GSAP |
Unmatched timeline control |
| SVG morphing |
GSAP MorphSVG or Anime.js |
Both excellent |
| 3D + animation |
Three.js + GSAP |
GSAP controls Three.js objects |
| Page transitions |
AnimatePresence or GSAP |
Motion for React, GSAP for complex |
| Geometric shapes (vector) |
SVG + GSAP/Motion |
Native, animable |
| Geometric shapes (canvas) |
Canvas 2D API |
Programmatic, performant |
| Pseudo-3D shapes |
Zdog |
Flat design 3D, ~2kb |
| Creative coding/generative |
p5.js |
Rich ecosystem |
| Audio reactive |
Tone.js |
Web Audio, synths, effects |
| Physics 2D |
Matter.js |
Gravity, collisions, constraints |
| Algorithmic/generative art |
Canvas 2D + p5.js |
Math-driven visuals |
| Fractals/L-systems |
Canvas 2D recursivo |
Recursive rendering |
| Tessellations/geometric puzzles |
SVG + GSAP |
Precise animated transforms |
| Kinetic typography advanced |
GSAP SplitText + Canvas |
Per-char control |
| Glitch effects |
CSS + GSAP |
Layered RGB split, clip-path |
| Brutalist animation |
CSS raw + Motion |
Hard cuts, no easing |
| Minimalist animation |
Motion springs |
Subtle, purposeful motion |
Installation (Latest Stable - 2025)
# GSAP + React hook (v3.14.1)
npm install gsap @gsap/react
# Lenis (v1.3.17) - includes React components
npm install lenis
# Motion (Framer Motion)
npm install motion
# Anime.js (v4.0.0)
npm install animejs
React Setup
1. GSAP Configuration (app-wide)
// lib/gsap.ts
'use client' // Next.js App Router
import gsap from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
import { useGSAP } from '@gsap/react'
// Register plugins once
gsap.registerPlugin(ScrollTrigger, useGSAP)
export { gsap, ScrollTrigger, useGSAP }
2. Lenis + GSAP ScrollTrigger Integration (Critical)
// components/SmoothScroll.tsx
'use client'
import { ReactLenis, useLenis } from 'lenis/react'
import { useEffect } from 'react'
import { gsap, ScrollTrigger } from '@/lib/gsap'
export function SmoothScroll({ children }: { children: React.ReactNode }) {
const lenis = useLenis()
useEffect(() => {
if (!lenis) return
lenis.on('scroll', ScrollTrigger.update)
gsap.ticker.add((time) => lenis.raf(time * 1000))
gsap.ticker.lagSmoothing(0)
return () => { gsap.ticker.remove(lenis?.raf) }
}, [lenis])
return (
<ReactLenis root options={{ lerp: 0.1, duration: 1.2, smoothWheel: true }}>
{children}
</ReactLenis>
)
}
// Wrap in layout: <SmoothScroll>{children}</SmoothScroll>
Core Patterns (React)
Detailed implementations in references:
- GSAP + useGSAP: See references/gsap-react.md
- Motion (Framer Motion): See references/motion-patterns.md
- Anime.js 4.0: See references/animejs-react.md
- Lenis React: See references/lenis-react.md
- Geometric Shapes: See references/geometric-shapes.md (SVG, Canvas, Zdog, p5.js, Tetris-style)
- Audio Reactive: See references/audio-reactive.md (Tone.js, Web Audio, scroll audio)
- Physics 2D: See references/physics-2d.md (Matter.js, collisions, constraints)
- Advanced (Three.js, WebGL): See references/advanced-patterns.md
- Algorithmic & Generative Art: See references/algorithmic-art.md (fractals, L-systems, flow fields, attractors, noise, sacred geometry)
- Advanced Text Effects: See references/text-effects.md (glitch, kinetic typography, morphing, explosion, circular text, scramble)
- Geometric Puzzles: See references/geometric-puzzles.md (Dudeney, tangram, tessellations, Penrose, polyominoes)
- Design Philosophy: See references/design-philosophy.md (brutalist, minimalist, abstract, mixing styles, palettes)
- Performance: See references/performance.md
Quick Patterns (React)
1. Magnetic Cursor (GSAP + useGSAP)
'use client'
import { useRef, useEffect } from 'react'
import { gsap, useGSAP } from '@/lib/gsap'
export function MagneticCursor() {
const cursorRef = useRef<HTMLDivElement>(null)
const pos = useRef({ x: 0, y: 0, cx: 0, cy: 0 })
useEffect(() => {
const h = (e: MouseEvent) => { pos.current.x = e.clientX; pos.current.y = e.clientY }
window.addEventListener('mousemove', h)
return () => window.removeEventListener('mousemove', h)
}, [])
useGSAP(() => {
gsap.ticker.add(() => {
const p = pos.current
p.cx += (p.x - p.cx) * 0.15; p.cy += (p.y - p.cy) * 0.15
gsap.set(cursorRef.current, { x: p.cx, y: p.cy })
})
})
return <div ref={cursorRef} className="fixed w-10 h-10 border border-white rounded-full pointer-events-none mix-blend-difference z-[9999] -translate-x-1/2 -translate-y-1/2" />
}
2. Magnetic Button (Motion)
'use client'
import { useRef, useState } from 'react'
import { motion } from 'motion/react'
export function MagneticButton({ children }: { children: React.ReactNode }) {
const ref = useRef<HTMLButtonElement>(null)
const [pos, setPos] = useState({ x: 0, y: 0 })
const React.MouseEvent) => {
const { left, top, width, height } = ref.current!.getBoundingClientRect()
setPos({ x: (e.clientX - left - width / 2) * 0.3, y: (e.clientY - top - height / 2) * 0.3 })
}
return (
<motion.button ref={ref} => setPos({ x: 0, y: 0 })}
animate={pos} transition={{ type: 'spring', stiffness: 150, damping: 15 }}
className="px-8 py-4 bg-white text-black rounded-full">{children}</motion.button>
)
}
3. Parallax Hero (GSAP + useGSAP)
'use client'
import { useRef } from 'react'
import { gsap, ScrollTrigger, useGSAP } from '@/lib/gsap'
export function ParallaxHero() {
const containerRef = useRef<HTMLDivElement>(null)
useGSAP(() => {
gsap.to('.parallax-bg', {
yPercent: 50,
ease: 'none',
scrollTrigger: {
trigger: containerRef.current,
start: 'top top',
end: 'bottom top',
scrub: true,
},
})
gsap.to('.hero-title', {
yPercent: 100,
opacity: 0,
scrollTrigger: {
trigger: containerRef.current,
start: 'top top',
end: '50% top',
scrub: true,
},
})
}, { scope: containerRef })
return (
<div ref={containerRef} className="relative h-screen overflow-hidden">
<div className="parallax-bg absolute inset-0 bg-cover bg-center" />
<h1 className="hero-title absolute inset-0 flex items-center justify-center text-6xl">
Hero Title
</h1>
</div>
)
}
4. Text Character Reveal (Motion)
'use client'
import { motion } from 'motion/react'
const container = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { staggerChildren: 0.02 },
},
}
const child = {
hidden: { opacity: 0, y: 50, rotateX: -90 },
visible: {
opacity: 1,
y: 0,
rotateX: 0,
transition: { type: 'spring', damping: 12 },
},
}
export function TextReveal({ text }: { text: string }) {
return (
<motion.span
variants={container}
initial="hidden"
whileInView="visible"
viewport={{ once: true }}
className="inline-block"
>
{text.split('').map((char, i) => (
<motion.span key={i} variants={child} className="inline-block">
{char === ' ' ? '\u00A0' : char}
</motion.span>
))}
</motion.span>
)
}
5. Image Reveal (GSAP)
'use client'
import { useRef } from 'react'
import { gsap, useGSAP } from '@/lib/gsap'
export function ImageReveal({ src, alt }: { src: string; alt: string }) {
const containerRef = useRef<HTMLDivElement>(null)
useGSAP(() => {
gsap.from(containerRef.current, {
clipPath: 'inset(100% 0% 0% 0%)',
duration: 1.2,
ease: 'power4.inOut',
scrollTrigger: {
trigger: containerRef.current,
start: 'top 80%',
},
})
gsap.from('.reveal-img', {
scale: 1.3,
duration: 1.5,
ease: 'power2.out',
scrollTrigger: {
trigger: containerRef.current,
start: 'top 80%',
},
})
}, { scope: containerRef })
return (
<div ref={containerRef} className="overflow-hidden">
<img src={src} alt={alt} className="reveal-img w-full h-full object-cover" />
</div>
)
}
6. Glitch Text Effect (CSS + GSAP)
'use client'
import { useRef, useEffect } from 'react'
import { gsap } from '@/lib/gsap'
export function GlitchText({ text }: { text: string }) {
const ref = useRef<HTMLDivElement>(null)
useEffect(() => {
const layers = ref.current!.querySelectorAll('.g-layer')
const tl = gsap.timeline({ repeat: -1, repeatDelay: 3 })
tl.to(layers[0], { x: -5, duration: 0.05, ease: 'none' }, 0)
.to(layers[0], { x: 5, duration: 0.05 }, 0.05)
.to(layers[0], { x: 0, duration: 0.05 }, 0.1)
.to(layers[1], { x: 5, duration: 0.05 }, 0.02)
.to(layers[1], { x: -5, duration: 0.05 }, 0.07)
.to(layers[1], { x: 0, duration: 0.05 }, 0.12)
return () => { tl.kill() }
}, [])
return (
<div ref={ref} className="relative font-mono text-5xl font-black">
<span className="relative z-10">{text}</span>
<span className="g-layer absolute inset-0 text-cyan-400 mix-blend-multiply" aria-hidden>{text}</span>
<span className="g-layer absolute inset-0 text-red-400 mix-blend-multiply" aria-hidden>{text}</span>
</div>
)
}
7. Fractal Tree (Canvas 2D)
'use client'
import { useRef, useEffect } from 'react'
export function FractalTree({ depth = 10, angle = 25 }: { depth?: number; angle?: number }) {
const canvasRef = useRef<HTMLCanvasElement>(null)
useEffect(() => {
const canvas = canvasRef.current!
const ctx = canvas.getContext('2d')!
canvas.width = canvas.offsetWidth * 2; canvas.height = canvas.offsetHeight * 2; ctx.scale(2, 2)
let progress = 0, raf = 0
function branch(x: number, y: number, len: number, a: number, d: number) {
if (d > depth || len < 2) return
const dp = Math.max(0, Math.min(1, progress * depth - d))
if (dp <= 0) return
const ex = x + Math.cos(a * Math.PI / 180) * len * dp
const ey = y - Math.sin(a * Math.PI / 180) * len * dp
ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(ex, ey)
ctx.strokeStyle = `hsl(${120 + d * 15}, 60%, ${30 + d * 5}%)`
ctx.lineWidth = Math.max(1, (depth - d) * 1.5); ctx.stroke()
branch(ex, ey, len * 0.72, a + angle, d + 1)
branch(ex, ey, len * 0.72, a - angle, d + 1)
}
const animate = () => {
progress = Math.min(1, progress + 0.008)
ctx.clearRect(0, 0, canvas.offsetWidth, canvas.offsetHeight)
branch(canvas.offsetWidth / 2, canvas.offsetHeight, canvas.offsetHeight * 0.28, 90, 0)
if (progress < 1) raf = requestAnimationFrame(animate)
}
animate()
return () => cancelAnimationFrame(raf)
}, [depth, angle])
return <canvas ref={canvasRef} className="w-full h-full bg-gray-950" />
}
See references/algorithmic-art.md for L-systems, flow fields, attractors, noise, sacred geometry.
8. Geometric Dissection (SVG + GSAP)
'use client'
import { useRef, useState } from 'react'
import { gsap } from '@/lib/gsap'
const P = [
{ id: 'A', tri: 'M 0,173 L 50,87 L 100,173 Z', sq: 'M 0,0 L 100,0 L 100,87 L 0,87 Z', c: '#f43f5e' },
{ id: 'B', tri: 'M 50,87 L 100,0 L 150,87 Z', sq: 'M 100,0 L 200,0 L 200,87 L 100,87 Z', c: '#8b5cf6' },
{ id: 'C', tri: 'M 100,173 L 150,87 L 200,173 Z', sq: 'M 0,87 L 100,87 L 100,173 L 0,173 Z', c: '#06b6d4' },
{ id: 'D', tri: 'M 50,87 L 100,173 L 150,87 L 100,0 Z', sq: 'M 100,87 L 200,87 L 200,173 L 100,173 Z', c: '#f59e0b' },
]
export function GeometricDissection() {
const svg = useRef<SVGSVGElement>(null)
const [isSq, setSq] = useState(false)
const morph = () => {
const t = !isSq
P.forEach((p, i) => {
const el = svg.current!.querySelector(`#d-${p.id}`)
if (el) gsap.to(el, { attr: { d: t ? p.sq : p.tri }, duration: 1.5, ease: 'power2.inOut', delay: i * 0.15 })
}); setSq(t)
}
return (
<div className="flex flex-col items-center gap-4">
<svg ref={svg} viewBox="-10 -10 220 200" className="w-64 h-64">
{P.map(p => <path key={p.id} id={`d-${p.id}`} d={p.tri} fill={p.c} stroke="#000" strokeWidth="1.5" />)}
</svg>
<button className="px-6 py-2 bg-white text-black font-mono text-sm">{isSq ? '△' : '□'}</button>
</div>
)
}
See references/geometric-puzzles.md for tangram, tessellations, Penrose tiles, polyominoes.
9. Brutalist Grid (Motion)
'use client'
import { motion } from 'motion/react'
export function BrutalistGrid({ items }: { items: string[] }) {
return (
<div className="grid grid-cols-3 border-2 border-black">
{items.map((item, i) => (
<motion.div key={i}
className="border-2 border-black p-6 font-mono font-black uppercase text-2xl"
style={{ mixBlendMode: i % 2 === 0 ? 'normal' : 'difference' }}
initial={{ opacity: 0 }} whileInView={{ opacity: 1 }} viewport={{ once: true }}
transition={{ duration: 0, delay: i * 0.1 }}
whileHover={{ backgroundColor: '#000', color: '#BAFF39', transition: { duration: 0 } }}
>{item}</motion.div>
))}
</div>
)
}
Design Philosophy (Quick Reference)
| Style |
Motion Feel |
Easing |
Typography |
Key Trait |
| Brutalist |
Hard, instant, jarring |
none / steps() |
Mono, 15-30vw |
Raw honesty |
| Minimalist |
Smooth, subtle, slow |
power2.out |
Sans-serif light |
Purposeful restraint |
| Abstract |
Noise-driven, parametric |
Organic/sine |
Varies |
Mathematical beauty |
| Neo-Brutalist |
Bold but controlled |
power1.out |
Mono + color |
Brutalism + restraint |
See references/design-philosophy.md for full guide with color palettes and mixing strategies.
Easing Reference
| Feel |
GSAP |
Motion |
| Smooth |
power2.out |
[0.16, 1, 0.3, 1] |
| Snappy |
power4.out |
[0.87, 0, 0.13, 1] |
| Bouncy |
back.out(1.7) |
{ type: 'spring', stiffness: 300, damping: 20 } |
| Dramatic |
power4.inOut |
[0.76, 0, 0.24, 1] |
Timing
- Micro-interactions: 150-300ms
- UI transitions: 300-500ms
- Page transitions: 500-800ms
- Stagger: 0.02-0.1s per item
Accessibility
// Motion: useReducedMotion() → conditionally disable/reduce animations
import { useReducedMotion } from 'motion/react'
const reduced = useReducedMotion() // true if prefers-reduced-motion: reduce
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
}
Performance Rules
- Only animate
transform and opacity
- Use
will-change sparingly
- Always cleanup:
useGSAP handles it automatically
- Scope GSAP selectors to container refs
- Use
contextSafe() for event handlers with GSAP
- Memoize Motion variants objects
Common Pitfalls
- Not integrating Lenis with ScrollTrigger
- Missing
scope in useGSAP
- Not using
contextSafe() for click handlers
- React 18 Strict Mode calling effects twice
- Forgetting
'use client' in Next.js App Router
- Not calling
ScrollTrigger.refresh() after dynamic content
Testing Checklist
Inspiration
Active Theory, Studio Freight, Locomotive, Resn, Aristide Benoist, Immersive Garden
1---2name: awwwards-animations3description: Professional React animation skill for creating Awwwards/FWA-level animations using GSAP (useGSAP), Motion (Framer Motion), Anime.js, and Lenis. Use when building premium scroll experiences, custom cursors, page transitions, text animations, parallax effects, micro-interactions, or any animation that needs to be 60fps and award-worthy. Triggers on requests for smooth scroll, ScrollTrigger, magnetic effects, reveal animations, horizontal scroll, pin sections, stagger effects, useScroll, useTransform, integration with Three.js/WebGL, algorithmic art, mathematical art, generative art, fractals, L-systems, flow fields, strange attractors, sacred geometry, geometric puzzles, Dudeney dissections, tangram, tessellations, Penrose tiles, kinetic typography, glitch effects, text explosion, morphing text, circular text, brutalist design, minimalist animation, neo-brutalism, or design philosophy mixing. React-first approach with proper cleanup and hooks.4---5
6# Awwwards Animations
7
8Create premium web animations at Awwwards/FWA quality level. **React-first approach**. 60fps non-negotiable.
9
10## Decision Matrix
11
12| Task | Library | Why |
13|------|---------|-----|
14| Scroll-driven animations | GSAP + ScrollTrigger + useGSAP | Industry standard, best control |
15| Smooth scroll | Lenis + ReactLenis | Best performance, works with ScrollTrigger |
16| React-native animations | Motion (Framer Motion) | Native React, useScroll/useTransform |
17| Simple/lightweight effects | Anime.js 4.0 | Small footprint, clean API |
18| Complex timelines | GSAP | Unmatched timeline control |
19| SVG morphing | GSAP MorphSVG or Anime.js | Both excellent |
20| 3D + animation | Three.js + GSAP | GSAP controls Three.js objects |
21| Page transitions | AnimatePresence or GSAP | Motion for React, GSAP for complex |
22| Geometric shapes (vector) | SVG + GSAP/Motion | Native, animable |
23| Geometric shapes (canvas) | Canvas 2D API | Programmatic, performant |
24| Pseudo-3D shapes | Zdog | Flat design 3D, ~2kb |
25| Creative coding/generative | p5.js | Rich ecosystem |
26| Audio reactive | Tone.js | Web Audio, synths, effects |
27| Physics 2D | Matter.js | Gravity, collisions, constraints |
28| Algorithmic/generative art | Canvas 2D + p5.js | Math-driven visuals |
29| Fractals/L-systems | Canvas 2D recursivo | Recursive rendering |
30| Tessellations/geometric puzzles | SVG + GSAP | Precise animated transforms |
31| Kinetic typography advanced | GSAP SplitText + Canvas | Per-char control |
32| Glitch effects | CSS + GSAP | Layered RGB split, clip-path |
33| Brutalist animation | CSS raw + Motion | Hard cuts, no easing |
34| Minimalist animation | Motion springs | Subtle, purposeful motion |
35
36## Installation (Latest Stable - 2025)
37
38```bash
39# GSAP + React hook (v3.14.1)
40npm install gsap @gsap/react
41
42# Lenis (v1.3.17) - includes React components
43npm install lenis
44
45# Motion (Framer Motion)
46npm install motion
47
48# Anime.js (v4.0.0)
49npm install animejs
50```
51
52## React Setup
53
54### 1. GSAP Configuration (app-wide)
55
56```tsx
57// lib/gsap.ts
58'use client' // Next.js App Router
59
60import gsap from 'gsap'
61import { ScrollTrigger } from 'gsap/ScrollTrigger'
62import { useGSAP } from '@gsap/react'
63
64// Register plugins once
65gsap.registerPlugin(ScrollTrigger, useGSAP)
66
67export { gsap, ScrollTrigger, useGSAP }
68```
69
70### 2. Lenis + GSAP ScrollTrigger Integration (Critical)
71
72```tsx
73// components/SmoothScroll.tsx
74'use client'
75import { ReactLenis, useLenis } from 'lenis/react'
76import { useEffect } from 'react'
77import { gsap, ScrollTrigger } from '@/lib/gsap'
78
79export function SmoothScroll({ children }: { children: React.ReactNode }) {
80 const lenis = useLenis()
81 useEffect(() => {
82 if (!lenis) return
83 lenis.on('scroll', ScrollTrigger.update)
84 gsap.ticker.add((time) => lenis.raf(time * 1000))
85 gsap.ticker.lagSmoothing(0)
86 return () => { gsap.ticker.remove(lenis?.raf) }
87 }, [lenis])
88
89 return (
90 <ReactLenis root options={{ lerp: 0.1, duration: 1.2, smoothWheel: true }}>
91 {children}
92 </ReactLenis>
93 )
94}
95// Wrap in layout: <SmoothScroll>{children}</SmoothScroll>
96```
97
98## Core Patterns (React)
99
100Detailed implementations in references:
101- **GSAP + useGSAP**: See [references/gsap-react.md](references/gsap-react.md)
102- **Motion (Framer Motion)**: See [references/motion-patterns.md](references/motion-patterns.md)
103- **Anime.js 4.0**: See [references/animejs-react.md](references/animejs-react.md)
104- **Lenis React**: See [references/lenis-react.md](references/lenis-react.md)
105- **Geometric Shapes**: See [references/geometric-shapes.md](references/geometric-shapes.md) (SVG, Canvas, Zdog, p5.js, Tetris-style)
106- **Audio Reactive**: See [references/audio-reactive.md](references/audio-reactive.md) (Tone.js, Web Audio, scroll audio)
107- **Physics 2D**: See [references/physics-2d.md](references/physics-2d.md) (Matter.js, collisions, constraints)
108- **Advanced (Three.js, WebGL)**: See [references/advanced-patterns.md](references/advanced-patterns.md)
109- **Algorithmic & Generative Art**: See [references/algorithmic-art.md](references/algorithmic-art.md) (fractals, L-systems, flow fields, attractors, noise, sacred geometry)
110- **Advanced Text Effects**: See [references/text-effects.md](references/text-effects.md) (glitch, kinetic typography, morphing, explosion, circular text, scramble)
111- **Geometric Puzzles**: See [references/geometric-puzzles.md](references/geometric-puzzles.md) (Dudeney, tangram, tessellations, Penrose, polyominoes)
112- **Design Philosophy**: See [references/design-philosophy.md](references/design-philosophy.md) (brutalist, minimalist, abstract, mixing styles, palettes)
113- **Performance**: See [references/performance.md](references/performance.md)
114
115## Quick Patterns (React)
116
117### 1. Magnetic Cursor (GSAP + useGSAP)
118
119```tsx
120'use client'
121import { useRef, useEffect } from 'react'
122import { gsap, useGSAP } from '@/lib/gsap'
123
124export function MagneticCursor() {
125 const cursorRef = useRef<HTMLDivElement>(null)
126 const pos = useRef({ x: 0, y: 0, cx: 0, cy: 0 })
127 useEffect(() => {
128 const h = (e: MouseEvent) => { pos.current.x = e.clientX; pos.current.y = e.clientY }
129 window.addEventListener('mousemove', h)
130 return () => window.removeEventListener('mousemove', h)
131 }, [])
132 useGSAP(() => {
133 gsap.ticker.add(() => {
134 const p = pos.current
135 p.cx += (p.x - p.cx) * 0.15; p.cy += (p.y - p.cy) * 0.15
136 gsap.set(cursorRef.current, { x: p.cx, y: p.cy })
137 })
138 })
139 return <div ref={cursorRef} className="fixed w-10 h-10 border border-white rounded-full pointer-events-none mix-blend-difference z-[9999] -translate-x-1/2 -translate-y-1/2" />
140}
141```
142
143### 2. Magnetic Button (Motion)
144
145```tsx
146'use client'
147import { useRef, useState } from 'react'
148import { motion } from 'motion/react'
149
150export function MagneticButton({ children }: { children: React.ReactNode }) {
151 const ref = useRef<HTMLButtonElement>(null)
152 const [pos, setPos] = useState({ x: 0, y: 0 })
153 const onMove = (e: React.MouseEvent) => {
154 const { left, top, width, height } = ref.current!.getBoundingClientRect()
155 setPos({ x: (e.clientX - left - width / 2) * 0.3, y: (e.clientY - top - height / 2) * 0.3 })
156 }
157 return (
158 <motion.button ref={ref} onMouseMove={onMove} onMouseLeave={() => setPos({ x: 0, y: 0 })}
159 animate={pos} transition={{ type: 'spring', stiffness: 150, damping: 15 }}
160 className="px-8 py-4 bg-white text-black rounded-full">{children}</motion.button>
161 )
162}
163```
164
165### 3. Parallax Hero (GSAP + useGSAP)
166
167```tsx
168'use client'
169import { useRef } from 'react'
170import { gsap, ScrollTrigger, useGSAP } from '@/lib/gsap'
171
172export function ParallaxHero() {
173 const containerRef = useRef<HTMLDivElement>(null)
174
175 useGSAP(() => {
176 gsap.to('.parallax-bg', {
177 yPercent: 50,
178 ease: 'none',
179 scrollTrigger: {
180 trigger: containerRef.current,
181 start: 'top top',
182 end: 'bottom top',
183 scrub: true,
184 },
185 })
186
187 gsap.to('.hero-title', {
188 yPercent: 100,
189 opacity: 0,
190 scrollTrigger: {
191 trigger: containerRef.current,
192 start: 'top top',
193 end: '50% top',
194 scrub: true,
195 },
196 })
197 }, { scope: containerRef })
198
199 return (
200 <div ref={containerRef} className="relative h-screen overflow-hidden">
201 <div className="parallax-bg absolute inset-0 bg-cover bg-center" />
202 <h1 className="hero-title absolute inset-0 flex items-center justify-center text-6xl">
203 Hero Title
204 </h1>
205 </div>
206 )
207}
208```
209
210### 4. Text Character Reveal (Motion)
211
212```tsx
213'use client'
214import { motion } from 'motion/react'
215
216const container = {
217 hidden: { opacity: 0 },
218 visible: {
219 opacity: 1,
220 transition: { staggerChildren: 0.02 },
221 },
222}
223
224const child = {
225 hidden: { opacity: 0, y: 50, rotateX: -90 },
226 visible: {
227 opacity: 1,
228 y: 0,
229 rotateX: 0,
230 transition: { type: 'spring', damping: 12 },
231 },
232}
233
234export function TextReveal({ text }: { text: string }) {
235 return (
236 <motion.span
237 variants={container}
238 initial="hidden"
239 whileInView="visible"
240 viewport={{ once: true }}
241 className="inline-block"
242 >
243 {text.split('').map((char, i) => (
244 <motion.span key={i} variants={child} className="inline-block">
245 {char === ' ' ? '\u00A0' : char}
246 </motion.span>
247 ))}
248 </motion.span>
249 )
250}
251```
252
253### 5. Image Reveal (GSAP)
254
255```tsx
256'use client'
257import { useRef } from 'react'
258import { gsap, useGSAP } from '@/lib/gsap'
259
260export function ImageReveal({ src, alt }: { src: string; alt: string }) {
261 const containerRef = useRef<HTMLDivElement>(null)
262
263 useGSAP(() => {
264 gsap.from(containerRef.current, {
265 clipPath: 'inset(100% 0% 0% 0%)',
266 duration: 1.2,
267 ease: 'power4.inOut',
268 scrollTrigger: {
269 trigger: containerRef.current,
270 start: 'top 80%',
271 },
272 })
273
274 gsap.from('.reveal-img', {
275 scale: 1.3,
276 duration: 1.5,
277 ease: 'power2.out',
278 scrollTrigger: {
279 trigger: containerRef.current,
280 start: 'top 80%',
281 },
282 })
283 }, { scope: containerRef })
284
285 return (
286 <div ref={containerRef} className="overflow-hidden">
287 <img src={src} alt={alt} className="reveal-img w-full h-full object-cover" />
288 </div>
289 )
290}
291```
292
293### 6. Glitch Text Effect (CSS + GSAP)
294
295```tsx
296'use client'
297import { useRef, useEffect } from 'react'
298import { gsap } from '@/lib/gsap'
299
300export function GlitchText({ text }: { text: string }) {
301 const ref = useRef<HTMLDivElement>(null)
302
303 useEffect(() => {
304 const layers = ref.current!.querySelectorAll('.g-layer')
305 const tl = gsap.timeline({ repeat: -1, repeatDelay: 3 })
306 tl.to(layers[0], { x: -5, duration: 0.05, ease: 'none' }, 0)
307 .to(layers[0], { x: 5, duration: 0.05 }, 0.05)
308 .to(layers[0], { x: 0, duration: 0.05 }, 0.1)
309 .to(layers[1], { x: 5, duration: 0.05 }, 0.02)
310 .to(layers[1], { x: -5, duration: 0.05 }, 0.07)
311 .to(layers[1], { x: 0, duration: 0.05 }, 0.12)
312 return () => { tl.kill() }
313 }, [])
314
315 return (
316 <div ref={ref} className="relative font-mono text-5xl font-black">
317 <span className="relative z-10">{text}</span>
318 <span className="g-layer absolute inset-0 text-cyan-400 mix-blend-multiply" aria-hidden>{text}</span>
319 <span className="g-layer absolute inset-0 text-red-400 mix-blend-multiply" aria-hidden>{text}</span>
320 </div>
321 )
322}
323```
324
325### 7. Fractal Tree (Canvas 2D)
326
327```tsx
328'use client'
329import { useRef, useEffect } from 'react'
330
331export function FractalTree({ depth = 10, angle = 25 }: { depth?: number; angle?: number }) {
332 const canvasRef = useRef<HTMLCanvasElement>(null)
333
334 useEffect(() => {
335 const canvas = canvasRef.current!
336 const ctx = canvas.getContext('2d')!
337 canvas.width = canvas.offsetWidth * 2; canvas.height = canvas.offsetHeight * 2; ctx.scale(2, 2)
338 let progress = 0, raf = 0
339
340 function branch(x: number, y: number, len: number, a: number, d: number) {
341 if (d > depth || len < 2) return
342 const dp = Math.max(0, Math.min(1, progress * depth - d))
343 if (dp <= 0) return
344 const ex = x + Math.cos(a * Math.PI / 180) * len * dp
345 const ey = y - Math.sin(a * Math.PI / 180) * len * dp
346 ctx.beginPath(); ctx.moveTo(x, y); ctx.lineTo(ex, ey)
347 ctx.strokeStyle = `hsl(${120 + d * 15}, 60%, ${30 + d * 5}%)`
348 ctx.lineWidth = Math.max(1, (depth - d) * 1.5); ctx.stroke()
349 branch(ex, ey, len * 0.72, a + angle, d + 1)
350 branch(ex, ey, len * 0.72, a - angle, d + 1)
351 }
352 const animate = () => {
353 progress = Math.min(1, progress + 0.008)
354 ctx.clearRect(0, 0, canvas.offsetWidth, canvas.offsetHeight)
355 branch(canvas.offsetWidth / 2, canvas.offsetHeight, canvas.offsetHeight * 0.28, 90, 0)
356 if (progress < 1) raf = requestAnimationFrame(animate)
357 }
358 animate()
359 return () => cancelAnimationFrame(raf)
360 }, [depth, angle])
361 return <canvas ref={canvasRef} className="w-full h-full bg-gray-950" />
362}
363```
364
365See [references/algorithmic-art.md](references/algorithmic-art.md) for L-systems, flow fields, attractors, noise, sacred geometry.
366
367### 8. Geometric Dissection (SVG + GSAP)
368
369```tsx
370'use client'
371import { useRef, useState } from 'react'
372import { gsap } from '@/lib/gsap'
373
374const P = [
375 { id: 'A', tri: 'M 0,173 L 50,87 L 100,173 Z', sq: 'M 0,0 L 100,0 L 100,87 L 0,87 Z', c: '#f43f5e' },
376 { id: 'B', tri: 'M 50,87 L 100,0 L 150,87 Z', sq: 'M 100,0 L 200,0 L 200,87 L 100,87 Z', c: '#8b5cf6' },
377 { id: 'C', tri: 'M 100,173 L 150,87 L 200,173 Z', sq: 'M 0,87 L 100,87 L 100,173 L 0,173 Z', c: '#06b6d4' },
378 { id: 'D', tri: 'M 50,87 L 100,173 L 150,87 L 100,0 Z', sq: 'M 100,87 L 200,87 L 200,173 L 100,173 Z', c: '#f59e0b' },
379]
380export function GeometricDissection() {
381 const svg = useRef<SVGSVGElement>(null)
382 const [isSq, setSq] = useState(false)
383 const morph = () => {
384 const t = !isSq
385 P.forEach((p, i) => {
386 const el = svg.current!.querySelector(`#d-${p.id}`)
387 if (el) gsap.to(el, { attr: { d: t ? p.sq : p.tri }, duration: 1.5, ease: 'power2.inOut', delay: i * 0.15 })
388 }); setSq(t)
389 }
390 return (
391 <div className="flex flex-col items-center gap-4">
392 <svg ref={svg} viewBox="-10 -10 220 200" className="w-64 h-64">
393 {P.map(p => <path key={p.id} id={`d-${p.id}`} d={p.tri} fill={p.c} stroke="#000" strokeWidth="1.5" />)}
394 </svg>
395 <button onClick={morph} className="px-6 py-2 bg-white text-black font-mono text-sm">{isSq ? '△' : '□'}</button>
396 </div>
397 )
398}
399```
400
401See [references/geometric-puzzles.md](references/geometric-puzzles.md) for tangram, tessellations, Penrose tiles, polyominoes.
402
403### 9. Brutalist Grid (Motion)
404
405```tsx
406'use client'
407import { motion } from 'motion/react'
408
409export function BrutalistGrid({ items }: { items: string[] }) {
410 return (
411 <div className="grid grid-cols-3 border-2 border-black">
412 {items.map((item, i) => (
413 <motion.div key={i}
414 className="border-2 border-black p-6 font-mono font-black uppercase text-2xl"
415 style={{ mixBlendMode: i % 2 === 0 ? 'normal' : 'difference' }}
416 initial={{ opacity: 0 }} whileInView={{ opacity: 1 }} viewport={{ once: true }}
417 transition={{ duration: 0, delay: i * 0.1 }}
418 whileHover={{ backgroundColor: '#000', color: '#BAFF39', transition: { duration: 0 } }}
419 >{item}</motion.div>
420 ))}
421 </div>
422 )
423}
424```
425
426## Design Philosophy (Quick Reference)
427
428| Style | Motion Feel | Easing | Typography | Key Trait |
429|-------|------------|--------|------------|-----------|
430| Brutalist | Hard, instant, jarring | `none` / `steps()` | Mono, 15-30vw | Raw honesty |
431| Minimalist | Smooth, subtle, slow | `power2.out` | Sans-serif light | Purposeful restraint |
432| Abstract | Noise-driven, parametric | Organic/sine | Varies | Mathematical beauty |
433| Neo-Brutalist | Bold but controlled | `power1.out` | Mono + color | Brutalism + restraint |
434
435See [references/design-philosophy.md](references/design-philosophy.md) for full guide with color palettes and mixing strategies.
436
437## Easing Reference
438
439| Feel | GSAP | Motion |
440|------|------|--------|
441| Smooth | `power2.out` | `[0.16, 1, 0.3, 1]` |
442| Snappy | `power4.out` | `[0.87, 0, 0.13, 1]` |
443| Bouncy | `back.out(1.7)` | `{ type: 'spring', stiffness: 300, damping: 20 }` |
444| Dramatic | `power4.inOut` | `[0.76, 0, 0.24, 1]` |
445
446## Timing
447
448- Micro-interactions: 150-300ms
449- UI transitions: 300-500ms
450- Page transitions: 500-800ms
451- Stagger: 0.02-0.1s per item
452
453## Accessibility
454
455```tsx
456// Motion: useReducedMotion() → conditionally disable/reduce animations
457import { useReducedMotion } from 'motion/react'
458const reduced = useReducedMotion() // true if prefers-reduced-motion: reduce
459```
460
461```css
462@media (prefers-reduced-motion: reduce) {
463 *, *::before, *::after { animation-duration: 0.01ms !important; transition-duration: 0.01ms !important; }
464}
465```
466
467## Performance Rules
468
4691. Only animate `transform` and `opacity`
4702. Use `will-change` sparingly
4713. Always cleanup: `useGSAP` handles it automatically
4724. Scope GSAP selectors to container refs
4735. Use `contextSafe()` for event handlers with GSAP
4746. Memoize Motion variants objects
475
476## Common Pitfalls
477
4781. Not integrating Lenis with ScrollTrigger
4792. Missing `scope` in useGSAP
4803. Not using `contextSafe()` for click handlers
4814. React 18 Strict Mode calling effects twice
4825. Forgetting `'use client'` in Next.js App Router
4836. Not calling `ScrollTrigger.refresh()` after dynamic content
484
485## Testing Checklist
486
487- [ ] 60fps on scroll (Chrome DevTools Performance)
488- [ ] Keyboard navigation works
489- [ ] Respects prefers-reduced-motion
490- [ ] No layout shifts (CLS)
491- [ ] Mobile touch works
492- [ ] ScrollTrigger markers removed in prod
493- [ ] No memory leaks on unmount
494
495## Inspiration
496
497Active Theory, Studio Freight, Locomotive, Resn, Aristide Benoist, Immersive Garden