Help us improve
Share bugs, ideas, or general feedback.
Provides code examples and techniques for artistic animations like liquid buttons, morphing blobs, glitch effects, and distortion using Framer Motion in React TSX components. Activates for creative visuals and transitions.
npx claudepluginhub markus41/claude --plugin react-animation-studioHow this skill is triggered — by the user, by Claude, or both
Slash command
/react-animation-studio:creative-effectsThe summary Claude sees in its skill listing — used to decide when to auto-load this skill
Expert knowledge for artistic and creative animation effects - morphing shapes, liquid animations, glitch effects, distortion, and experimental visual techniques that push creative boundaries.
Provides pre-built animated React components from Magic UI (Tailwind/Framer Motion/shadcn-ui) and React Bits for landing pages, dashboards, and interactive UIs.
Creates animated backgrounds with gradient flows, particle systems, mesh gradients, aurora effects, wave patterns, and dynamic textures for hero sections, landing pages, and interfaces.
Builds production-ready React/Next.js components for premium interactive experiences: canvas visualizations, glassmorphism, Framer Motion/GSAP animations, Three.js/Spline 3D, micro-interactions, particles, and sound design.
Share bugs, ideas, or general feedback.
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