Expert knowledge for SVG path animations, morphing shapes, and vector graphics effects that create stunning visual experiences with resolution-independent graphics.
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.
Configures VPN and dedicated connections like Direct Connect, ExpressRoute, Interconnect for secure on-premises to AWS, Azure, GCP, OCI hybrid networking.
Expert knowledge for SVG path animations, morphing shapes, and vector graphics effects that create stunning visual experiences with resolution-independent graphics.
Activate this skill when:
**/*.svg files**/*.tsx containing SVG elements**/icons/*.tsx with animated icons**/components/**/Animated*.tsx with SVGpathLength, stroke-dasharrayimport { motion } from 'framer-motion';
function DrawPath() {
return (
<svg viewBox="0 0 100 100" className="w-24 h-24">
<motion.path
d="M10 50 Q 50 10, 90 50 T 90 90"
fill="transparent"
stroke="#3B82F6"
strokeWidth="4"
initial={{ pathLength: 0 }}
animate={{ pathLength: 1 }}
transition={{ duration: 2, ease: 'easeInOut' }}
/>
</svg>
);
}
.svg-path {
stroke-dasharray: 1000; /* Total path length */
stroke-dashoffset: 1000; /* Start hidden */
animation: draw 2s ease-in-out forwards;
}
@keyframes draw {
to {
stroke-dashoffset: 0;
}
}
// Get actual path length for accurate animation
useEffect(() => {
const path = document.querySelector('.animated-path') as SVGPathElement;
if (path) {
const length = path.getTotalLength();
console.log('Path length:', length);
}
}, []);
import { motion } from 'framer-motion';
function AnimatedCheck({ isVisible }: { isVisible: boolean }) {
return (
<svg viewBox="0 0 24 24" className="w-6 h-6">
{/* Circle background */}
<motion.circle
cx="12"
cy="12"
r="10"
fill="#22C55E"
initial={{ scale: 0 }}
animate={{ scale: isVisible ? 1 : 0 }}
transition={{ type: 'spring', stiffness: 300, damping: 20 }}
/>
{/* Checkmark path */}
<motion.path
d="M6 12l4 4 8-8"
fill="transparent"
stroke="white"
strokeWidth="2"
strokeLinecap="round"
strokeLinejoin="round"
initial={{ pathLength: 0 }}
animate={{ pathLength: isVisible ? 1 : 0 }}
transition={{ delay: 0.2, duration: 0.3, ease: 'easeOut' }}
/>
</svg>
);
}
import { motion } from 'framer-motion';
function MenuIcon({ isOpen }: { isOpen: boolean }) {
const variant = isOpen ? 'open' : 'closed';
const topLineVariants = {
closed: { rotate: 0, y: 0 },
open: { rotate: 45, y: 8 },
};
const middleLineVariants = {
closed: { opacity: 1 },
open: { opacity: 0 },
};
const bottomLineVariants = {
closed: { rotate: 0, y: 0 },
open: { rotate: -45, y: -8 },
};
return (
<svg viewBox="0 0 24 24" className="w-6 h-6">
<motion.line
x1="4" y1="6" x2="20" y2="6"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
variants={topLineVariants}
animate={variant}
transition={{ duration: 0.3 }}
style={{ transformOrigin: 'center' }}
/>
<motion.line
x1="4" y1="12" x2="20" y2="12"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
variants={middleLineVariants}
animate={variant}
transition={{ duration: 0.2 }}
/>
<motion.line
x1="4" y1="18" x2="20" y2="18"
stroke="currentColor"
strokeWidth="2"
strokeLinecap="round"
variants={bottomLineVariants}
animate={variant}
transition={{ duration: 0.3 }}
style={{ transformOrigin: 'center' }}
/>
</svg>
);
}
import { motion } from 'framer-motion';
function Spinner() {
return (
<svg viewBox="0 0 50 50" className="w-12 h-12">
{/* Background circle */}
<circle
cx="25"
cy="25"
r="20"
fill="none"
stroke="#E5E7EB"
strokeWidth="4"
/>
{/* Animated arc */}
<motion.circle
cx="25"
cy="25"
r="20"
fill="none"
stroke="#3B82F6"
strokeWidth="4"
strokeLinecap="round"
initial={{ pathLength: 0, rotate: 0 }}
animate={{
pathLength: [0, 0.5, 0],
rotate: 360,
}}
transition={{
pathLength: {
duration: 1.5,
repeat: Infinity,
ease: 'easeInOut',
},
rotate: {
duration: 1.5,
repeat: Infinity,
ease: 'linear',
},
}}
style={{ transformOrigin: 'center' }}
/>
</svg>
);
}
import { motion, useAnimation } from 'framer-motion';
const shapes = {
circle: 'M50,10 A40,40 0 1,1 50,90 A40,40 0 1,1 50,10',
square: 'M10,10 L90,10 L90,90 L10,90 Z',
triangle: 'M50,10 L90,90 L10,90 Z',
};
function MorphingShape() {
const [currentShape, setCurrentShape] = useState<keyof typeof shapes>('circle');
return (
<svg viewBox="0 0 100 100" className="w-32 h-32">
<motion.path
fill="#3B82F6"
animate={{ d: shapes[currentShape] }}
transition={{ duration: 0.5, ease: 'easeInOut' }}
/>
</svg>
);
}
import gsap from 'gsap';
import { MorphSVGPlugin } from 'gsap/MorphSVGPlugin';
gsap.registerPlugin(MorphSVGPlugin);
function GSAPMorph() {
useEffect(() => {
gsap.to('#shape', {
duration: 1,
morphSVG: '#targetShape',
ease: 'power2.inOut',
});
}, []);
return (
<svg>
<path id="shape" d="M10,10 L90,10 L90,90 L10,90 Z" />
<path id="targetShape" d="M50,10 A40,40 0 1,1 50,90 A40,40 0 1,1 50,10" style={{ visibility: 'hidden' }} />
</svg>
);
}
function AnimatedLogo() {
const controls = useAnimation();
const logoVariants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: 0.1,
delayChildren: 0.2,
},
},
};
const pathVariants = {
hidden: { pathLength: 0, opacity: 0 },
visible: {
pathLength: 1,
opacity: 1,
transition: { duration: 1, ease: 'easeInOut' },
},
};
const fillVariants = {
hidden: { fillOpacity: 0 },
visible: {
fillOpacity: 1,
transition: { delay: 0.5, duration: 0.5 },
},
};
return (
<motion.svg
viewBox="0 0 200 60"
className="w-48"
variants={logoVariants}
initial="hidden"
animate="visible"
>
{/* Letter outlines */}
<motion.path
d="M10,50 L10,10 L40,10 L40,30 L20,30 L20,50"
fill="none"
stroke="#3B82F6"
strokeWidth="2"
variants={pathVariants}
/>
{/* Fill after stroke */}
<motion.path
d="M10,50 L10,10 L40,10 L40,30 L20,30 L20,50"
fill="#3B82F6"
variants={fillVariants}
/>
</motion.svg>
);
}
function AnimatedBarChart({ data }: { data: number[] }) {
const maxValue = Math.max(...data);
return (
<svg viewBox="0 0 300 200" className="w-full h-48">
{data.map((value, index) => {
const barHeight = (value / maxValue) * 160;
const barWidth = 40;
const gap = 20;
const x = index * (barWidth + gap) + 20;
const y = 180 - barHeight;
return (
<motion.rect
key={index}
x={x}
y={180}
width={barWidth}
height={0}
fill="#3B82F6"
rx="4"
initial={{ height: 0, y: 180 }}
animate={{ height: barHeight, y }}
transition={{
duration: 0.8,
delay: index * 0.1,
ease: [0.25, 0.1, 0.25, 1],
}}
/>
);
})}
</svg>
);
}
function CircleProgress({ progress }: { progress: number }) {
const radius = 40;
const circumference = 2 * Math.PI * radius;
return (
<svg viewBox="0 0 100 100" className="w-24 h-24 -rotate-90">
{/* Background circle */}
<circle
cx="50"
cy="50"
r={radius}
fill="none"
stroke="#E5E7EB"
strokeWidth="8"
/>
{/* Progress circle */}
<motion.circle
cx="50"
cy="50"
r={radius}
fill="none"
stroke="#3B82F6"
strokeWidth="8"
strokeLinecap="round"
initial={{ strokeDasharray: circumference, strokeDashoffset: circumference }}
animate={{ strokeDashoffset: circumference * (1 - progress) }}
transition={{ duration: 1, ease: 'easeOut' }}
/>
{/* Percentage text */}
<motion.text
x="50"
y="50"
textAnchor="middle"
dominantBaseline="middle"
className="text-2xl font-bold fill-current rotate-90"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
>
{Math.round(progress * 100)}%
</motion.text>
</svg>
);
}
will-change: transform for GPU accelerationpathLength property for easy animations<g> for batch transformsCreated by Brookside BI as part of React Animation Studio