Expert knowledge for artistic and creative animation effects - morphing shapes, liquid animations, glitch effects, distortion, and experimental visual techniques that push creative boundaries.
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 artistic and creative animation effects - morphing shapes, liquid animations, glitch effects, distortion, and experimental visual techniques that push creative boundaries.
Activate this skill when:
**/*.tsx with creative components**/components/Creative*.tsx**/components/Effect*.tsx**/components/Liquid*.tsximport { motion, useAnimation } from 'framer-motion';
export function LiquidButton({ children }: { children: React.ReactNode }) {
const controls = useAnimation();
const liquidVariants = {
rest: {
d: "M0,20 Q25,0 50,20 T100,20 L100,80 Q75,100 50,80 T0,80 Z",
},
hover: {
d: "M0,30 Q25,10 50,30 T100,30 L100,70 Q75,90 50,70 T0,70 Z",
},
tap: {
d: "M0,25 Q25,15 50,25 T100,25 L100,75 Q75,85 50,75 T0,75 Z",
},
};
return (
<motion.button
className="relative px-8 py-4 text-white font-medium"
initial="rest"
whileHover="hover"
whileTap="tap"
>
<svg
className="absolute inset-0 w-full h-full"
viewBox="0 0 100 100"
preserveAspectRatio="none"
>
<motion.path
fill="url(#liquid-gradient)"
variants={liquidVariants}
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
/>
<defs>
<linearGradient id="liquid-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#667eea" />
<stop offset="100%" stopColor="#764ba2" />
</linearGradient>
</defs>
</svg>
<span className="relative z-10">{children}</span>
</motion.button>
);
}
export function MorphingBlob() {
const paths = [
"M45.3,-51.2C58.3,-40.8,68.4,-25.6,71.8,-8.4C75.2,8.8,71.9,28,61.6,41.8C51.3,55.6,34,64,15.6,68.9C-2.8,73.8,-22.3,75.2,-38.1,67.6C-53.9,60,-66,43.4,-71.4,24.9C-76.8,6.4,-75.5,-14,-67.4,-30.7C-59.3,-47.4,-44.4,-60.4,-28.6,-69.7C-12.8,-79,-0.1,-84.6,8.9,-78.5C17.9,-72.4,32.3,-54.6,45.3,-51.2Z",
"M39.9,-47.1C53.3,-36.9,66.7,-26.3,72.2,-11.9C77.7,2.5,75.4,20.7,66.4,35.1C57.4,49.5,41.7,60.1,24.6,66.2C7.5,72.3,-11,73.9,-27.8,68.4C-44.6,62.9,-59.7,50.3,-67.8,34.3C-75.9,18.3,-77,-1.1,-72.1,-18.7C-67.2,-36.3,-56.3,-52.1,-42.4,-62.1C-28.5,-72.1,-11.6,-76.3,1.8,-78.5C15.2,-80.7,26.5,-57.3,39.9,-47.1Z",
"M42.7,-49.7C55.5,-38.8,66.1,-25.3,69.7,-9.8C73.3,5.7,69.9,23.2,60.6,37.4C51.3,51.6,36.1,62.5,19.3,68.3C2.5,74.1,-15.9,74.8,-32.8,68.7C-49.7,62.6,-65.1,49.7,-72.6,33.4C-80.1,17.1,-79.7,-2.6,-73.3,-20C-66.9,-37.4,-54.5,-52.5,-40.1,-63C-25.7,-73.5,-9.3,-79.4,3.6,-83.7C16.5,-88,29.9,-60.6,42.7,-49.7Z",
];
return (
<motion.svg viewBox="-100 -100 200 200" className="w-64 h-64">
<motion.path
fill="url(#blob-gradient)"
animate={{
d: paths,
}}
transition={{
duration: 10,
repeat: Infinity,
ease: 'easeInOut',
}}
/>
<defs>
<linearGradient id="blob-gradient" x1="0%" y1="0%" x2="100%" y2="100%">
<stop offset="0%" stopColor="#667eea" />
<stop offset="50%" stopColor="#764ba2" />
<stop offset="100%" stopColor="#f093fb" />
</linearGradient>
</defs>
</motion.svg>
);
}
export function GlitchText({ children }: { children: string }) {
return (
<div className="relative inline-block">
{/* Main text */}
<span className="relative z-10">{children}</span>
{/* Glitch layer 1 - Red */}
<motion.span
className="absolute inset-0 text-red-500 opacity-80"
style={{ clipPath: 'inset(0 0 0 0)' }}
animate={{
x: [-2, 2, -2],
clipPath: [
'inset(40% 0 61% 0)',
'inset(92% 0 1% 0)',
'inset(43% 0 1% 0)',
'inset(25% 0 58% 0)',
],
}}
transition={{
duration: 0.3,
repeat: Infinity,
repeatType: 'reverse',
}}
>
{children}
</motion.span>
{/* Glitch layer 2 - Cyan */}
<motion.span
className="absolute inset-0 text-cyan-500 opacity-80"
style={{ clipPath: 'inset(0 0 0 0)' }}
animate={{
x: [2, -2, 2],
clipPath: [
'inset(85% 0 7% 0)',
'inset(15% 0 80% 0)',
'inset(45% 0 50% 0)',
'inset(70% 0 25% 0)',
],
}}
transition={{
duration: 0.2,
repeat: Infinity,
repeatType: 'reverse',
}}
>
{children}
</motion.span>
</div>
);
}
export function GlitchImage({ src, alt }: { src: string; alt: string }) {
return (
<div className="relative overflow-hidden">
<img src={src} alt={alt} className="relative z-10" />
<motion.img
src={src}
alt=""
className="absolute inset-0 opacity-50"
style={{ mixBlendMode: 'multiply' }}
animate={{
x: [-5, 5, -5],
filter: ['hue-rotate(0deg)', 'hue-rotate(90deg)', 'hue-rotate(0deg)'],
}}
transition={{
duration: 0.2,
repeat: Infinity,
}}
/>
<motion.div
className="absolute inset-0 bg-gradient-to-r from-red-500/20 to-cyan-500/20"
animate={{
opacity: [0, 0.3, 0],
scaleY: [1, 1.02, 1],
}}
transition={{
duration: 0.1,
repeat: Infinity,
repeatDelay: 2,
}}
/>
</div>
);
}
export function LiquidFill({ progress = 0.7 }: { progress?: number }) {
const fillHeight = progress * 100;
return (
<div className="relative w-32 h-48 bg-slate-800 rounded-2xl overflow-hidden">
{/* Liquid */}
<motion.div
className="absolute bottom-0 left-0 right-0 bg-gradient-to-t from-blue-600 to-blue-400"
initial={{ height: 0 }}
animate={{ height: `${fillHeight}%` }}
transition={{ duration: 2, ease: 'easeOut' }}
>
{/* Wave effect */}
<svg
className="absolute top-0 left-0 w-full"
viewBox="0 0 100 20"
preserveAspectRatio="none"
style={{ transform: 'translateY(-50%)' }}
>
<motion.path
fill="currentColor"
className="text-blue-400"
d="M0,10 Q25,0 50,10 T100,10 L100,20 L0,20 Z"
animate={{
d: [
"M0,10 Q25,0 50,10 T100,10 L100,20 L0,20 Z",
"M0,10 Q25,20 50,10 T100,10 L100,20 L0,20 Z",
"M0,10 Q25,0 50,10 T100,10 L100,20 L0,20 Z",
],
}}
transition={{
duration: 2,
repeat: Infinity,
ease: 'easeInOut',
}}
/>
</svg>
</motion.div>
{/* Bubbles */}
{Array.from({ length: 5 }).map((_, i) => (
<motion.div
key={i}
className="absolute w-2 h-2 bg-white/30 rounded-full"
style={{
left: `${20 + i * 15}%`,
bottom: 0,
}}
animate={{
y: [0, -fillHeight * 2],
opacity: [0, 1, 0],
scale: [0.5, 1, 0.5],
}}
transition={{
duration: 2 + Math.random(),
delay: i * 0.3,
repeat: Infinity,
}}
/>
))}
</div>
);
}
export function MagneticButton({ children }: { children: React.ReactNode }) {
const ref = useRef<HTMLButtonElement>(null);
const [position, setPosition] = useState({ x: 0, y: 0 });
const handleMouseMove = (e: React.MouseEvent) => {
if (!ref.current) return;
const rect = ref.current.getBoundingClientRect();
const centerX = rect.left + rect.width / 2;
const centerY = rect.top + rect.height / 2;
const distanceX = e.clientX - centerX;
const distanceY = e.clientY - centerY;
setPosition({
x: distanceX * 0.3,
y: distanceY * 0.3,
});
};
const handleMouseLeave = () => {
setPosition({ x: 0, y: 0 });
};
return (
<motion.button
ref={ref}
className="px-8 py-4 bg-gradient-to-r from-purple-500 to-pink-500 rounded-xl text-white font-medium"
animate={{ x: position.x, y: position.y }}
transition={{ type: 'spring', stiffness: 150, damping: 15 }}
onMouseMove={handleMouseMove}
onMouseLeave={handleMouseLeave}
>
{children}
</motion.button>
);
}
export function ElasticContainer({ children }: { children: React.ReactNode }) {
const [isPressed, setIsPressed] = useState(false);
return (
<motion.div
className="cursor-pointer"
animate={{
scaleX: isPressed ? 1.1 : 1,
scaleY: isPressed ? 0.9 : 1,
}}
transition={{
type: 'spring',
stiffness: 400,
damping: 10,
}}
onMouseDown={() => setIsPressed(true)}
onMouseUp={() => setIsPressed(false)}
onMouseLeave={() => setIsPressed(false)}
>
{children}
</motion.div>
);
}
export function DNALoader() {
const dots = Array.from({ length: 10 });
return (
<div className="flex items-center justify-center h-20">
{dots.map((_, i) => (
<div key={i} className="relative w-4 mx-0.5">
<motion.div
className="absolute w-3 h-3 bg-blue-500 rounded-full"
animate={{
y: [-15, 15, -15],
scale: [1, 0.8, 1],
opacity: [1, 0.5, 1],
}}
transition={{
duration: 1,
delay: i * 0.1,
repeat: Infinity,
ease: 'easeInOut',
}}
/>
<motion.div
className="absolute w-3 h-3 bg-purple-500 rounded-full"
animate={{
y: [15, -15, 15],
scale: [0.8, 1, 0.8],
opacity: [0.5, 1, 0.5],
}}
transition={{
duration: 1,
delay: i * 0.1,
repeat: Infinity,
ease: 'easeInOut',
}}
/>
</div>
))}
</div>
);
}
export function OrbitingLoader() {
const orbits = [
{ radius: 20, duration: 1.5, color: '#667eea' },
{ radius: 35, duration: 2, color: '#764ba2' },
{ radius: 50, duration: 2.5, color: '#f093fb' },
];
return (
<div className="relative w-32 h-32">
{/* Center dot */}
<motion.div
className="absolute top-1/2 left-1/2 w-4 h-4 -ml-2 -mt-2 bg-white rounded-full"
animate={{ scale: [1, 1.2, 1] }}
transition={{ duration: 1, repeat: Infinity }}
/>
{/* Orbiting dots */}
{orbits.map((orbit, i) => (
<motion.div
key={i}
className="absolute top-1/2 left-1/2 w-3 h-3 -ml-1.5 -mt-1.5 rounded-full"
style={{ backgroundColor: orbit.color }}
animate={{
x: [orbit.radius, 0, -orbit.radius, 0, orbit.radius],
y: [0, orbit.radius, 0, -orbit.radius, 0],
}}
transition={{
duration: orbit.duration,
repeat: Infinity,
ease: 'linear',
}}
/>
))}
</div>
);
}
export function MaskReveal({
children,
delay = 0,
}: {
children: React.ReactNode;
delay?: number;
}) {
return (
<div className="relative overflow-hidden">
<motion.div
initial={{ y: '100%' }}
animate={{ y: '0%' }}
transition={{
duration: 0.8,
delay,
ease: [0.25, 0.1, 0.25, 1],
}}
>
{children}
</motion.div>
<motion.div
className="absolute inset-0 bg-gradient-to-r from-purple-500 to-pink-500"
initial={{ x: '0%' }}
animate={{ x: '100%' }}
transition={{
duration: 0.6,
delay: delay + 0.2,
ease: [0.25, 0.1, 0.25, 1],
}}
/>
</div>
);
}
export function SplitReveal({ children }: { children: React.ReactNode }) {
return (
<div className="relative overflow-hidden">
<div className="relative z-10">{children}</div>
{/* Top curtain */}
<motion.div
className="absolute inset-x-0 top-0 h-1/2 bg-slate-900 origin-top"
initial={{ scaleY: 1 }}
animate={{ scaleY: 0 }}
transition={{ duration: 0.8, ease: [0.25, 0.1, 0.25, 1] }}
/>
{/* Bottom curtain */}
<motion.div
className="absolute inset-x-0 bottom-0 h-1/2 bg-slate-900 origin-bottom"
initial={{ scaleY: 1 }}
animate={{ scaleY: 0 }}
transition={{ duration: 0.8, ease: [0.25, 0.1, 0.25, 1] }}
/>
</div>
);
}
Created by Brookside BI as part of React Animation Studio