skills/design/web-animation/SKILL.md
Build immersive, scroll-driven websites with GSAP ScrollTrigger, Lenis smooth scroll, parallax effects, and cinematic page transitions. Use when building premium corporate sites, landing pages, or marketing microsites that need motion and polish beyond static designs.
npx skillsauth add liauw-media/codeassist web-animationInstall this skill globally with one command. Works with Claude Code, Cursor, and Windsurf.
3 of 9 scanners reported clean
Some scanners were skipped, did not run, or reported a non-clean status. Review each row below.
Build websites with scroll-driven animations, parallax, smooth scrolling, and page transitions — the kind of motion that makes a site feel premium (like mont-fort.com, linear.app, stripe.com).
/design produces good static design but needs animation layerStatic site with animations?
Astro + GSAP + Lenis + View Transitions (recommended)
React/Next.js app?
Next.js + Framer Motion + Lenis + GSAP ScrollTrigger
Vanilla HTML?
GSAP + ScrollTrigger + Lenis + Barba.js
npm install lenis
// src/lib/lenis.ts
import Lenis from 'lenis'
const lenis = new Lenis({
duration: 1.2,
easing: (t) => Math.min(1, 1.001 - Math.pow(2, -10 * t)),
orientation: 'vertical',
smoothWheel: true,
})
function raf(time: number) {
lenis.raf(time)
requestAnimationFrame(raf)
}
requestAnimationFrame(raf)
export default lenis
/* Required CSS */
@import 'lenis/css';
html.lenis, html.lenis body {
height: auto;
}
.lenis.lenis-smooth {
scroll-behavior: auto !important;
}
npm install gsap
import gsap from 'gsap'
import { ScrollTrigger } from 'gsap/ScrollTrigger'
gsap.registerPlugin(ScrollTrigger)
// Sync Lenis with ScrollTrigger
import lenis from './lenis'
lenis.on('scroll', ScrollTrigger.update)
gsap.ticker.add((time) => lenis.raf(time * 1000))
gsap.ticker.lagSmoothing(0)
npm install motion
import { motion, useScroll, useTransform } from 'motion/react'
function ParallaxSection() {
const { scrollYProgress } = useScroll()
const y = useTransform(scrollYProgress, [0, 1], [0, -200])
return <motion.div style={{ y }}>Content</motion.div>
}
// Reveal elements as they enter viewport
gsap.utils.toArray('.reveal').forEach((el) => {
gsap.from(el as Element, {
y: 60,
opacity: 0,
duration: 1,
ease: 'power3.out',
scrollTrigger: {
trigger: el as Element,
start: 'top 85%',
toggleActions: 'play none none none',
},
})
})
<section class="reveal">
<h2>Section Title</h2>
<p>Content fades in as you scroll</p>
</section>
gsap.utils.toArray('.stagger-group').forEach((group) => {
const children = (group as Element).querySelectorAll('.stagger-item')
gsap.from(children, {
y: 40,
opacity: 0,
duration: 0.8,
stagger: 0.15,
ease: 'power2.out',
scrollTrigger: {
trigger: group as Element,
start: 'top 80%',
},
})
})
gsap.utils.toArray('.parallax-img').forEach((img) => {
gsap.to(img as Element, {
yPercent: -20,
ease: 'none',
scrollTrigger: {
trigger: img as Element,
start: 'top bottom',
end: 'bottom top',
scrub: true,
},
})
})
.parallax-container {
overflow: hidden;
position: relative;
}
.parallax-img {
width: 100%;
height: 120%; /* Extra height for parallax travel */
object-fit: cover;
}
const section = document.querySelector('.sticky-section')
const content = section?.querySelector('.sticky-content')
ScrollTrigger.create({
trigger: section,
start: 'top top',
end: '+=200%',
pin: true,
scrub: 1,
onUpdate: (self) => {
// Use self.progress (0-1) to drive animations
gsap.to(content, { opacity: self.progress, y: -50 * self.progress })
},
})
const container = document.querySelector('.horizontal-scroll')
const panels = gsap.utils.toArray('.panel')
gsap.to(panels, {
xPercent: -100 * (panels.length - 1),
ease: 'none',
scrollTrigger: {
trigger: container,
pin: true,
scrub: 1,
snap: 1 / (panels.length - 1),
end: () => '+=' + (container as HTMLElement).offsetWidth,
},
})
Use a safe DOM approach for splitting text into animatable elements:
function splitText(element: Element) {
const text = element.textContent || ''
const words = text.split(' ')
// Clear and rebuild with spans using DOM API
element.textContent = ''
words.forEach((word, i) => {
const outer = document.createElement('span')
outer.className = 'word'
outer.style.display = 'inline-block'
outer.style.overflow = 'hidden'
const inner = document.createElement('span')
inner.className = 'word-inner'
inner.style.display = 'inline-block'
inner.textContent = word
outer.appendChild(inner)
element.appendChild(outer)
if (i < words.length - 1) {
element.appendChild(document.createTextNode(' '))
}
})
return element.querySelectorAll('.word-inner')
}
gsap.utils.toArray('.text-reveal').forEach((el) => {
const words = splitText(el as Element)
gsap.from(words, {
yPercent: 100,
duration: 0.8,
stagger: 0.05,
ease: 'power3.out',
scrollTrigger: {
trigger: el as Element,
start: 'top 85%',
},
})
})
gsap.utils.toArray('.clip-reveal').forEach((el) => {
gsap.from(el as Element, {
clipPath: 'inset(100% 0 0 0)',
duration: 1.2,
ease: 'power4.inOut',
scrollTrigger: {
trigger: el as Element,
start: 'top 80%',
},
})
})
.clip-reveal {
clip-path: inset(0 0 0 0);
will-change: clip-path;
}
gsap.utils.toArray('.counter').forEach((el) => {
const target = parseInt((el as Element).getAttribute('data-target') || '0')
gsap.to(el, {
textContent: target,
duration: 2,
ease: 'power2.out',
snap: { textContent: 1 },
scrollTrigger: {
trigger: el as Element,
start: 'top 85%',
},
})
})
// Run once on page load — orchestrated entrance
const tl = gsap.timeline({ defaults: { ease: 'power3.out' } })
tl.from('.hero-title', { y: 80, opacity: 0, duration: 1 })
.from('.hero-subtitle', { y: 40, opacity: 0, duration: 0.8 }, '-=0.5')
.from('.hero-cta', { y: 30, opacity: 0, duration: 0.6 }, '-=0.4')
.from('.hero-image', {
scale: 1.1,
opacity: 0,
duration: 1.2,
clipPath: 'inset(10% 10% 10% 10%)',
}, '-=0.8')
.from('.nav', { y: -20, opacity: 0, duration: 0.5 }, '-=0.6')
---
// src/layouts/Layout.astro
import { ViewTransitions } from 'astro:transitions'
---
<html>
<head>
<ViewTransitions />
</head>
<body>
<slot />
</body>
</html>
<!-- Named transitions for specific elements -->
<h1 transition:name="page-title" transition:animate="slide">
{title}
</h1>
<img transition:name="hero-image" transition:animate="fade" src={image} />
npm install @barba/core
import barba from '@barba/core'
barba.init({
transitions: [{
name: 'fade',
leave(data) {
return gsap.to(data.current.container, {
opacity: 0,
duration: 0.5,
})
},
enter(data) {
return gsap.from(data.next.container, {
opacity: 0,
duration: 0.5,
})
},
}],
})
ALWAYS respect motion preferences:
const prefersReducedMotion = window.matchMedia('(prefers-reduced-motion: reduce)')
if (prefersReducedMotion.matches) {
// Disable smooth scroll
lenis.destroy()
// Kill all ScrollTrigger animations
ScrollTrigger.getAll().forEach((st) => st.kill())
// Set all animated elements to final state
gsap.set('.reveal, .stagger-item, .text-reveal', {
opacity: 1,
y: 0,
clipPath: 'none',
})
}
@media (prefers-reduced-motion: reduce) {
*, *::before, *::after {
animation-duration: 0.01ms !important;
animation-iteration-count: 1 !important;
transition-duration: 0.01ms !important;
scroll-behavior: auto !important;
}
}
will-change sparingly — only on elements about to animatewidth, height, top, leftscrub: true for scroll-linked (not scrub: 0.1 unless you need lag)gsap.set() for initial states instead of CSS (avoids FOUC)// Cleanup pattern for React/Next.js
useEffect(() => {
const ctx = gsap.context(() => {
// All animations here
}, containerRef)
return () => ctx.revert() // Kills all animations + ScrollTriggers
}, [])
1. HERO
- Full viewport height
- Large serif heading with text-reveal animation
- Subtle parallax background image
- Scroll indicator at bottom (opacity pulse)
- Page load sequence: title -> subtitle -> CTA -> image
2. INTRO / ADVANTAGE
- Centered text block with generous padding
- Fade + slide reveal on scroll
- Supporting image with clip-reveal
3. FEATURES / SERVICES
- Staggered card grid or alternating image-text rows
- Each card fades in with stagger delay
- Icons or images with parallax offset
4. LEADERSHIP / TEAM
- Grid with hover effects (scale + overlay)
- Staggered entrance
5. STATS / NUMBERS
- Counter animation on scroll
- Large display numbers
6. CTA / CONTACT
- Sticky section or full-viewport
- Background color transition
7. FOOTER
- Theme-aware (dark/light based on scroll context)
- Minimal, multi-column layout
For maximum animation capability, install these alongside this skill:
# Option A: Full animation skillstack (22 skills)
claude plugin marketplace add freshtechbro/claudedesignskills
# Option B: Individual skills
# Lenis smooth scroll
npx -y @lobehub/market-cli skills install orkait-ai-skill-agent-lenis-react
# Scroll Experience Architect
npx -y @lobehub/market-cli skills install rootcastleco-rei-skills-scroll-experience
| Do NOT | Do Instead |
|--------|-----------|
| Animate everything on scroll | Pick 3-5 key moments per page |
| Use bounce or elastic easing on corporate sites | Use power3.out or power4.inOut |
| Apply parallax to text (readability) | Apply parallax to images/backgrounds only |
| Fade in every paragraph | Fade in section headings and key visuals |
| Use animation delays > 0.3s between items | Keep stagger tight: 0.1-0.2s |
| Ignore mobile (touch scrolling is different) | Test on real devices, reduce motion on mobile |
| Stack multiple animation libraries | Pick one primary: GSAP OR Framer Motion |
This skill extends the /design command. Use them together:
/design corporate landing page for energy company # Static design
# Then add animation layer using this skill's patterns
Or request animation directly:
/design animated corporate site like mont-fort.com with scroll reveals and parallax
development
Use when decomposing complex work. Dispatch fresh subagent per task, review between tasks. Flow: Load plan → Dispatch task → Review output → Apply feedback → Mark complete → Next task. No skipping reviews, no parallel dispatch.
development
# Server Documentation System Set up a documentation system that tracks changes and maintains server/project documentation with Claude Code hooks. ## When to Use - Setting up a new server or development environment - Need to track configuration changes over time - Want automatic documentation of work sessions - Maintaining changelog for infrastructure ## Directory Structure ``` ~/docs/ # User home directory (cross-platform) ├── changelog.md # Global over
development
Delegate tasks to remote Claude Code agent containers for parallel execution, long-running analysis, or resource-intensive operations.
development
Use when working on multiple features simultaneously. Creates isolated workspaces without branch switching, enabling parallel development.