skills/gsap-animation-patterns/SKILL.md
Role-oriented GSAP ScrollTrigger animation patterns for Next.js. Each pattern includes working code, design-dna motion token consumption, Lenis integration, cleanup handling, and semantic CSS. Pattern names are ROLE-based not TECHNIQUE-based.
npx skillsauth add adilkalam/orca gsap-animation-patternsInstall 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.
All patterns consume design-dna motion tokens. Never hardcode values.
// Read from design-dna.json
interface MotionTokens {
easing: {
entrance: string; // e.g., "power2.out"
exit: string; // e.g., "power2.in"
emphasis: string; // e.g., "elastic.out(1, 0.3)"
smooth: string; // e.g., "none"
};
duration: {
fast: number; // e.g., 0.3
normal: number; // e.g., 0.6
slow: number; // e.g., 1.0
stagger: number; // e.g., 0.12
};
scroll: {
smoothing: boolean;
lenisLerp: number;
revealDistance: number; // e.g., 40 (px)
parallaxIntensity: number; // e.g., 0.3
scrubSmoothing: number; // e.g., 0.5
};
}
Role: Reveal a content section as it enters the viewport. Tier: 1 (CSS) or 2 (GSAP) depending on complexity.
'use client';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
import { ScrollTrigger } from 'gsap/ScrollTrigger';
gsap.registerPlugin(ScrollTrigger);
export function SectionEntrance({ children, className }: {
children: React.ReactNode;
className?: string;
}) {
const ref = useRef<HTMLElement>(null);
useEffect(() => {
const ctx = gsap.context(() => {
gsap.from(ref.current, {
y: designDna.motion.scroll.revealDistance,
opacity: 0,
duration: designDna.motion.duration.normal,
ease: designDna.motion.easing.entrance,
scrollTrigger: {
trigger: ref.current,
start: 'top 85%',
toggleActions: 'play none none none',
},
});
}, ref);
return () => ctx.revert();
}, []);
return <section ref={ref} className={`section-entrance ${className ?? ''}`}>{children}</section>;
}
.section-entrance {
will-change: transform, opacity;
}
Role: Stagger-reveal children elements within a container. Tier: 2 (GSAP -- stagger requires timeline control).
useEffect(() => {
const ctx = gsap.context(() => {
gsap.from('.content-reveal-item', {
y: designDna.motion.scroll.revealDistance,
opacity: 0,
duration: designDna.motion.duration.normal,
ease: designDna.motion.easing.entrance,
stagger: designDna.motion.duration.stagger,
scrollTrigger: {
trigger: containerRef.current,
start: 'top 80%',
toggleActions: 'play none none none',
},
});
}, containerRef);
return () => ctx.revert();
}, []);
.content-reveal-item {
will-change: transform, opacity;
}
Role: Pin a hero section and animate content through a sequence while scrolling. Tier: 2 (GSAP -- pinning requires ScrollTrigger).
useEffect(() => {
const ctx = gsap.context(() => {
const tl = gsap.timeline({
scrollTrigger: {
trigger: heroRef.current,
start: 'top top',
end: '+=200%',
pin: true,
scrub: designDna.motion.scroll.scrubSmoothing,
},
});
tl.from('.hero-title', {
y: 60, opacity: 0,
duration: designDna.motion.duration.slow,
ease: designDna.motion.easing.entrance,
})
.from('.hero-subtitle', {
y: 40, opacity: 0,
duration: designDna.motion.duration.normal,
ease: designDna.motion.easing.entrance,
}, '-=0.3')
.from('.hero-cta', {
scale: 0.9, opacity: 0,
duration: designDna.motion.duration.fast,
ease: designDna.motion.easing.emphasis,
});
}, heroRef);
return () => ctx.revert();
}, []);
Role: A section that scrolls horizontally while the user scrolls vertically. Tier: 2 (GSAP -- containerAnimation).
useEffect(() => {
const ctx = gsap.context(() => {
const panels = gsap.utils.toArray<HTMLElement>('.horizontal-panel');
gsap.to(panels, {
xPercent: -100 * (panels.length - 1),
ease: designDna.motion.easing.smooth,
scrollTrigger: {
trigger: sectionRef.current,
start: 'top top',
end: () => `+=${sectionRef.current!.scrollWidth}`,
pin: true,
scrub: designDna.motion.scroll.scrubSmoothing,
snap: 1 / (panels.length - 1),
invalidateOnRefresh: true,
},
});
}, sectionRef);
return () => ctx.revert();
}, []);
.horizontal-scroll-container {
display: flex;
flex-wrap: nowrap;
width: fit-content;
}
.horizontal-panel {
width: 100vw;
flex-shrink: 0;
}
Role: Multi-speed parallax layers creating depth perception. Tier: 2 (GSAP -- multiple synchronized scroll-linked transforms).
useEffect(() => {
const ctx = gsap.context(() => {
const intensity = designDna.motion.scroll.parallaxIntensity;
gsap.to('.parallax-bg', {
y: () => window.innerHeight * intensity,
ease: designDna.motion.easing.smooth,
scrollTrigger: {
trigger: sectionRef.current,
start: 'top bottom',
end: 'bottom top',
scrub: true,
},
});
gsap.to('.parallax-mid', {
y: () => window.innerHeight * intensity * 0.5,
ease: designDna.motion.easing.smooth,
scrollTrigger: {
trigger: sectionRef.current,
start: 'top bottom',
end: 'bottom top',
scrub: true,
},
});
}, sectionRef);
return () => ctx.revert();
}, []);
Role: Reveal text character-by-character or word-by-word. Tier: 2 (GSAP + SplitText plugin).
useEffect(() => {
const ctx = gsap.context(() => {
const split = new SplitText('.split-text-target', { type: 'chars,words' });
gsap.from(split.chars, {
opacity: 0,
y: 20,
duration: designDna.motion.duration.fast,
ease: designDna.motion.easing.entrance,
stagger: 0.02,
scrollTrigger: {
trigger: '.split-text-target',
start: 'top 80%',
toggleActions: 'play none none none',
},
});
}, containerRef);
return () => ctx.revert();
}, []);
Note: SplitText is a GSAP Club plugin. If not available, implement manual word splitting via <span> wrappers.
Role: Text scramble effect for dynamic headings. Tier: 2 (GSAP TextPlugin).
useEffect(() => {
const ctx = gsap.context(() => {
gsap.to('.scramble-target', {
duration: designDna.motion.duration.slow,
scrambleText: {
text: 'Final Text Here',
chars: 'upperCase',
revealDelay: 0.5,
speed: 0.3,
},
scrollTrigger: {
trigger: '.scramble-target',
start: 'top 80%',
toggleActions: 'play none none none',
},
});
}, containerRef);
return () => ctx.revert();
}, []);
Role: Smooth page transition between routes. Tier: 2 (GSAP + Next.js route handling).
// Wrap in a transition component
'use client';
import { usePathname } from 'next/navigation';
import { useEffect, useRef } from 'react';
import { gsap } from 'gsap';
export function PageTransition({ children }: { children: React.ReactNode }) {
const ref = useRef<HTMLDivElement>(null);
const pathname = usePathname();
useEffect(() => {
const ctx = gsap.context(() => {
gsap.fromTo(ref.current,
{ opacity: 0, y: 20 },
{
opacity: 1, y: 0,
duration: designDna.motion.duration.fast,
ease: designDna.motion.easing.entrance,
}
);
}, ref);
return () => ctx.revert();
}, [pathname]);
return <div ref={ref} className="page-transition-wrapper">{children}</div>;
}
Role: Header that transforms (shrinks, changes bg) on scroll. Tier: 2 (GSAP ScrollTrigger).
useEffect(() => {
const ctx = gsap.context(() => {
ScrollTrigger.create({
start: 'top -80',
onUpdate: (self) => {
if (self.direction === 1 && self.scroll() > 80) {
gsap.to(headerRef.current, {
height: 56,
backgroundColor: 'var(--color-surface-elevated)',
duration: designDna.motion.duration.fast,
ease: designDna.motion.easing.entrance,
});
} else if (self.scroll() <= 80) {
gsap.to(headerRef.current, {
height: 80,
backgroundColor: 'transparent',
duration: designDna.motion.duration.fast,
ease: designDna.motion.easing.entrance,
});
}
},
});
}, headerRef);
return () => ctx.revert();
}, []);
Role: Background image moves at slower rate than content. Tier: 2 (GSAP -- scroll-linked transform on background element).
useEffect(() => {
const ctx = gsap.context(() => {
gsap.to('.bg-parallax-image', {
yPercent: -20 * designDna.motion.scroll.parallaxIntensity,
ease: designDna.motion.easing.smooth,
scrollTrigger: {
trigger: sectionRef.current,
start: 'top bottom',
end: 'bottom top',
scrub: true,
},
});
}, sectionRef);
return () => ctx.revert();
}, []);
.bg-parallax-wrapper {
overflow: hidden;
position: relative;
}
.bg-parallax-image {
position: absolute;
inset: -20% 0;
width: 100%;
height: 140%;
object-fit: cover;
}
Role: Element scales up from slightly smaller size as it enters viewport. Tier: 1 (CSS) or 2 (GSAP).
// CSS-first (Tier 1) approach:
// Use CSS scroll-driven animation if no choreography needed
// GSAP approach (Tier 2) for coordinated reveals:
useEffect(() => {
const ctx = gsap.context(() => {
gsap.from('.scale-reveal', {
scale: 0.9,
opacity: 0,
duration: designDna.motion.duration.normal,
ease: designDna.motion.easing.entrance,
scrollTrigger: {
trigger: '.scale-reveal',
start: 'top 85%',
toggleActions: 'play none none none',
},
});
}, containerRef);
return () => ctx.revert();
}, []);
Role: Content slides in from left or right edge. Tier: 2 (GSAP).
useEffect(() => {
const ctx = gsap.context(() => {
gsap.from('.slide-from-left', {
x: -100,
opacity: 0,
duration: designDna.motion.duration.normal,
ease: designDna.motion.easing.entrance,
scrollTrigger: {
trigger: '.slide-from-left',
start: 'top 85%',
toggleActions: 'play none none none',
},
});
}, containerRef);
return () => ctx.revert();
}, []);
Every pattern MUST:
gsap.context() scoped to a refctx.revert() in useEffect cleanup// MANDATORY cleanup pattern
useEffect(() => {
const ctx = gsap.context(() => {
// All GSAP code here
}, containerRef); // Scope to ref
return () => ctx.revert(); // Cleanup
}, []);
Wrap viewport-dependent animations in matchMedia:
useEffect(() => {
const ctx = gsap.context(() => {
const mm = gsap.matchMedia();
mm.add('(min-width: 768px)', () => {
// Desktop animations
});
mm.add('(max-width: 767px)', () => {
// Simplified mobile animations or no-ops
});
}, containerRef);
return () => ctx.revert();
}, []);
testing
Run technical quality checks across accessibility, performance, theming, responsive design, and anti-patterns. Generates a scored report with P0-P3 severity ratings and actionable plan. Use when the user wants an accessibility check, performance audit, or technical quality review.
tools
Improves typography by fixing font choices, hierarchy, sizing, weight, and readability so text feels intentional. Use when the user mentions fonts, type, readability, text hierarchy, sizing looks off, or wants more polished, intentional typography.
tools
Three.js animation - keyframe animation, skeletal animation, morph targets, animation mixing. Use when animating objects, playing GLTF animations, creating procedural motion, or blending animations.
development
Plan the UX and UI for a feature before writing code. Runs a structured discovery interview, then produces a design brief that guides implementation. Use during the planning phase to establish design direction, constraints, and strategy before any code is written.