Expert knowledge for creating stunning animated backgrounds - gradient flows, particle systems, mesh gradients, aurora effects, wave patterns, and dynamic textures that bring depth and life to your interfaces.
From react-animation-studionpx claudepluginhub markus41/claude --plugin react-animation-studioThis skill uses the workspace's default tool permissions.
Guides Next.js Cache Components and Partial Prerendering (PPR) with cacheComponents enabled. Implements 'use cache', cacheLife(), cacheTag(), revalidateTag(), static/dynamic optimization, and cache debugging.
Migrates code, prompts, and API calls from Claude Sonnet 4.0/4.5 or Opus 4.1 to Opus 4.5, updating model strings on Anthropic, AWS, GCP, Azure platforms.
Generates FastAPI project templates with async routes, dependency injection, Pydantic schemas, repository patterns, middleware, and config for PostgreSQL/MongoDB backends.
Expert knowledge for creating stunning animated backgrounds - gradient flows, particle systems, mesh gradients, aurora effects, wave patterns, and dynamic textures that bring depth and life to your interfaces.
Activate this skill when:
**/*.tsx with background components**/components/Background*.tsx**/components/Hero*.tsximport { motion } from 'framer-motion';
export function FlowingGradient() {
return (
<motion.div
className="absolute inset-0 -z-10"
style={{
background: 'linear-gradient(-45deg, #ee7752, #e73c7e, #23a6d5, #23d5ab)',
backgroundSize: '400% 400%',
}}
animate={{
backgroundPosition: ['0% 50%', '100% 50%', '0% 50%'],
}}
transition={{
duration: 15,
repeat: Infinity,
ease: 'linear',
}}
/>
);
}
export function RadialPulse() {
return (
<motion.div
className="absolute inset-0 -z-10"
animate={{
background: [
'radial-gradient(circle at 50% 50%, #667eea 0%, transparent 50%)',
'radial-gradient(circle at 50% 50%, #764ba2 0%, transparent 70%)',
'radial-gradient(circle at 50% 50%, #667eea 0%, transparent 50%)',
],
}}
transition={{
duration: 4,
repeat: Infinity,
ease: 'easeInOut',
}}
/>
);
}
export function MeshGradient() {
return (
<div className="absolute inset-0 -z-10 overflow-hidden">
<motion.div
className="absolute w-[150%] h-[150%] -left-[25%] -top-[25%]"
style={{
background: `
radial-gradient(at 40% 20%, #ff6b6b 0px, transparent 50%),
radial-gradient(at 80% 0%, #feca57 0px, transparent 50%),
radial-gradient(at 0% 50%, #48dbfb 0px, transparent 50%),
radial-gradient(at 80% 50%, #ff9ff3 0px, transparent 50%),
radial-gradient(at 0% 100%, #54a0ff 0px, transparent 50%),
radial-gradient(at 80% 100%, #5f27cd 0px, transparent 50%)
`,
}}
animate={{
rotate: [0, 360],
scale: [1, 1.1, 1],
}}
transition={{
rotate: { duration: 60, repeat: Infinity, ease: 'linear' },
scale: { duration: 10, repeat: Infinity, ease: 'easeInOut' },
}}
/>
</div>
);
}
import { motion } from 'framer-motion';
import { useMemo } from 'react';
interface Particle {
id: number;
x: number;
y: number;
size: number;
duration: number;
delay: number;
}
export function FloatingParticles({ count = 50 }: { count?: number }) {
const particles = useMemo<Particle[]>(() =>
Array.from({ length: count }, (_, i) => ({
id: i,
x: Math.random() * 100,
y: Math.random() * 100,
size: Math.random() * 4 + 2,
duration: Math.random() * 20 + 10,
delay: Math.random() * 5,
})),
[count]
);
return (
<div className="absolute inset-0 -z-10 overflow-hidden">
{particles.map((particle) => (
<motion.div
key={particle.id}
className="absolute rounded-full bg-white/20"
style={{
left: `${particle.x}%`,
top: `${particle.y}%`,
width: particle.size,
height: particle.size,
}}
animate={{
y: [0, -100, 0],
x: [0, Math.random() * 50 - 25, 0],
opacity: [0, 1, 0],
}}
transition={{
duration: particle.duration,
delay: particle.delay,
repeat: Infinity,
ease: 'easeInOut',
}}
/>
))}
</div>
);
}
export function Starfield({ count = 200 }: { count?: number }) {
const stars = useMemo(() =>
Array.from({ length: count }, (_, i) => ({
id: i,
x: Math.random() * 100,
y: Math.random() * 100,
size: Math.random() * 2 + 0.5,
twinkleDuration: Math.random() * 3 + 2,
delay: Math.random() * 3,
})),
[count]
);
return (
<div className="absolute inset-0 -z-10 bg-slate-950 overflow-hidden">
{stars.map((star) => (
<motion.div
key={star.id}
className="absolute rounded-full bg-white"
style={{
left: `${star.x}%`,
top: `${star.y}%`,
width: star.size,
height: star.size,
}}
animate={{
opacity: [0.3, 1, 0.3],
scale: [1, 1.2, 1],
}}
transition={{
duration: star.twinkleDuration,
delay: star.delay,
repeat: Infinity,
ease: 'easeInOut',
}}
/>
))}
</div>
);
}
export function AnimatedWaves() {
return (
<div className="absolute bottom-0 left-0 right-0 -z-10 h-64 overflow-hidden">
<svg
viewBox="0 0 1440 320"
className="absolute bottom-0 w-full"
preserveAspectRatio="none"
>
{/* Wave 1 - Back */}
<motion.path
fill="rgba(99, 102, 241, 0.3)"
d="M0,192L48,197.3C96,203,192,213,288,229.3C384,245,480,267,576,250.7C672,235,768,181,864,181.3C960,181,1056,235,1152,234.7C1248,235,1344,181,1392,154.7L1440,128L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z"
animate={{
d: [
"M0,192L48,197.3C96,203,192,213,288,229.3C384,245,480,267,576,250.7C672,235,768,181,864,181.3C960,181,1056,235,1152,234.7C1248,235,1344,181,1392,154.7L1440,128L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z",
"M0,128L48,154.7C96,181,192,235,288,234.7C384,235,480,181,576,181.3C672,181,768,235,864,250.7C960,267,1056,245,1152,229.3C1248,213,1344,203,1392,197.3L1440,192L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z",
],
}}
transition={{
duration: 8,
repeat: Infinity,
repeatType: 'reverse',
ease: 'easeInOut',
}}
/>
{/* Wave 2 - Middle */}
<motion.path
fill="rgba(99, 102, 241, 0.5)"
d="M0,256L48,261.3C96,267,192,277,288,266.7C384,256,480,224,576,213.3C672,203,768,213,864,224C960,235,1056,245,1152,234.7C1248,224,1344,192,1392,176L1440,160L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z"
animate={{
d: [
"M0,256L48,261.3C96,267,192,277,288,266.7C384,256,480,224,576,213.3C672,203,768,213,864,224C960,235,1056,245,1152,234.7C1248,224,1344,192,1392,176L1440,160L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z",
"M0,160L48,176C96,192,192,224,288,234.7C384,245,480,235,576,224C672,213,768,203,864,213.3C960,224,1056,256,1152,266.7C1248,277,1344,267,1392,261.3L1440,256L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z",
],
}}
transition={{
duration: 6,
repeat: Infinity,
repeatType: 'reverse',
ease: 'easeInOut',
}}
/>
{/* Wave 3 - Front */}
<motion.path
fill="rgba(99, 102, 241, 0.8)"
d="M0,288L48,282.7C96,277,192,267,288,272C384,277,480,299,576,293.3C672,288,768,256,864,250.7C960,245,1056,267,1152,272C1248,277,1344,267,1392,261.3L1440,256L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z"
animate={{
d: [
"M0,288L48,282.7C96,277,192,267,288,272C384,277,480,299,576,293.3C672,288,768,256,864,250.7C960,245,1056,267,1152,272C1248,277,1344,267,1392,261.3L1440,256L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z",
"M0,256L48,261.3C96,267,192,277,288,272C384,267,480,245,576,250.7C672,256,768,288,864,293.3C960,299,1056,277,1152,272C1248,267,1344,277,1392,282.7L1440,288L1440,320L1392,320C1344,320,1248,320,1152,320C1056,320,960,320,864,320C768,320,672,320,576,320C480,320,384,320,288,320C192,320,96,320,48,320L0,320Z",
],
}}
transition={{
duration: 4,
repeat: Infinity,
repeatType: 'reverse',
ease: 'easeInOut',
}}
/>
</svg>
</div>
);
}
export function AuroraBackground() {
return (
<div className="absolute inset-0 -z-10 overflow-hidden bg-slate-950">
{/* Aurora Layer 1 */}
<motion.div
className="absolute inset-0 opacity-50"
style={{
background: 'linear-gradient(180deg, transparent 0%, #00ff8855 30%, #00ffff33 50%, transparent 70%)',
filter: 'blur(60px)',
}}
animate={{
x: ['-20%', '20%', '-20%'],
skewX: ['-5deg', '5deg', '-5deg'],
}}
transition={{
duration: 15,
repeat: Infinity,
ease: 'easeInOut',
}}
/>
{/* Aurora Layer 2 */}
<motion.div
className="absolute inset-0 opacity-40"
style={{
background: 'linear-gradient(180deg, transparent 0%, #ff00ff44 40%, #00ffff22 60%, transparent 80%)',
filter: 'blur(80px)',
}}
animate={{
x: ['20%', '-20%', '20%'],
skewX: ['5deg', '-5deg', '5deg'],
}}
transition={{
duration: 20,
repeat: Infinity,
ease: 'easeInOut',
}}
/>
{/* Aurora Layer 3 */}
<motion.div
className="absolute inset-0 opacity-30"
style={{
background: 'linear-gradient(180deg, transparent 0%, #00ff0033 35%, #ff880022 55%, transparent 75%)',
filter: 'blur(100px)',
}}
animate={{
x: ['-10%', '10%', '-10%'],
y: ['-5%', '5%', '-5%'],
}}
transition={{
duration: 25,
repeat: Infinity,
ease: 'easeInOut',
}}
/>
</div>
);
}
export function GrainOverlay() {
return (
<motion.div
className="pointer-events-none fixed inset-0 z-50 opacity-20"
style={{
backgroundImage: `url("data:image/svg+xml,%3Csvg viewBox='0 0 256 256' xmlns='http://www.w3.org/2000/svg'%3E%3Cfilter id='noiseFilter'%3E%3CfeTurbulence type='fractalNoise' baseFrequency='0.9' numOctaves='4' stitchTiles='stitch'/%3E%3C/filter%3E%3Crect width='100%25' height='100%25' filter='url(%23noiseFilter)'/%3E%3C/svg%3E")`,
}}
animate={{
x: [0, -10, 0],
y: [0, -10, 0],
}}
transition={{
duration: 0.2,
repeat: Infinity,
}}
/>
);
}
export function AnimatedGrid() {
return (
<div className="absolute inset-0 -z-10 overflow-hidden">
<motion.div
className="absolute inset-0"
style={{
backgroundImage: `
linear-gradient(rgba(99, 102, 241, 0.1) 1px, transparent 1px),
linear-gradient(90deg, rgba(99, 102, 241, 0.1) 1px, transparent 1px)
`,
backgroundSize: '50px 50px',
}}
animate={{
backgroundPosition: ['0px 0px', '50px 50px'],
}}
transition={{
duration: 20,
repeat: Infinity,
ease: 'linear',
}}
/>
{/* Glow effect at center */}
<div
className="absolute inset-0"
style={{
background: 'radial-gradient(circle at 50% 50%, transparent 0%, rgba(0,0,0,0.8) 100%)',
}}
/>
</div>
);
}
export function MorphingBlobs() {
const blobVariants = {
animate: {
d: [
"M440,320Q410,390,340,410Q270,430,200,410Q130,390,100,320Q70,250,100,180Q130,110,200,90Q270,70,340,90Q410,110,440,180Q470,250,440,320Z",
"M440,320Q440,390,370,420Q300,450,230,420Q160,390,120,320Q80,250,120,180Q160,110,230,80Q300,50,370,80Q440,110,440,180Q440,250,440,320Z",
"M440,320Q410,390,340,410Q270,430,200,410Q130,390,100,320Q70,250,100,180Q130,110,200,90Q270,70,340,90Q410,110,440,180Q470,250,440,320Z",
],
},
};
return (
<div className="absolute inset-0 -z-10 overflow-hidden">
<svg viewBox="0 0 500 500" className="absolute w-full h-full">
<defs>
<linearGradient id="blob-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#667eea" />
<stop offset="100%" stopColor="#764ba2" />
</linearGradient>
</defs>
<motion.path
fill="url(#blob-gradient)"
variants={blobVariants}
animate="animate"
transition={{
duration: 10,
repeat: Infinity,
ease: 'easeInOut',
}}
style={{ filter: 'blur(40px)', opacity: 0.6 }}
/>
</svg>
</div>
);
}
will-change: transform for moving elementsfilter: blur() sparingly - it's expensiverequestAnimationFrame for canvas-based effectsCreated by Brookside BI as part of React Animation Studio