Claude-skill-registry bellog-animations
Provides Framer Motion animation patterns and best practices specific to the Bellog blog project. Triggers when implementing animated components or interactions.
git clone https://github.com/majiayu000/claude-skill-registry
T=$(mktemp -d) && git clone --depth=1 https://github.com/majiayu000/claude-skill-registry "$T" && mkdir -p ~/.claude/skills && cp -r "$T/skills/data/bellog-animations" ~/.claude/skills/majiayu000-claude-skill-registry-bellog-animations && rm -rf "$T"
skills/data/bellog-animations/SKILL.mdBellog Animation Patterns
This skill provides the animation patterns and best practices used throughout the Bellog blog project.
Core Animation Principles
- Organic movement - Use easing curves, never linear
- Stagger for rhythm - Create visual flow with staggered animations
- Living elements - Ambient animations that breathe life
- Consistent timing - Follow project timing standards
Animation Timing Standards
// Interaction timings const INTERACTION_FAST = 0.2; // Button press, hover start const INTERACTION_NORMAL = 0.3; // Standard hover effects const INTERACTION_SLOW = 0.5; // Modal open, drawer slide // Transition timings const TRANSITION_FAST = 0.3; // Quick state changes const TRANSITION_NORMAL = 0.4; // Page transitions (standard) const TRANSITION_SLOW = 0.6; // Heavy content transitions // Ambient timings const AMBIENT_SLOW = 3; // Slow blob movement const AMBIENT_NORMAL = 4; // Standard blob rhythm const AMBIENT_FAST = 5; // Faster ambient motion
Pattern 1: Stagger Children
Use for lists, grids, and groups of elements.
When to use: Animating multiple items that should appear in sequence
Example from Intro.tsx:
const container = { hidden: { opacity: 0 }, show: { opacity: 1, transition: { staggerChildren: 0.1, // 100ms delay between each child delayChildren: 0.2 // Start after 200ms } } }; const item = { hidden: { y: 20, opacity: 0 }, show: { y: 0, opacity: 1, transition: { duration: 0.5, ease: "easeInOut" } } }; // Usage <motion.div variants={container} initial="hidden" animate="show" > {items.map(item => ( <motion.div key={item.id} variants={item}> {item.content} </motion.div> ))} </motion.div>
Pattern 2: Living Blob Animations
Use for decorative elements, background shapes, ambient animations.
When to use: Creating organic, perpetual movement
Example from Intro.tsx:
const blobVariants = { initial: { scale: 1, x: 0, y: 0 }, hover: { scale: [1, 1.2, 0.9, 1.1, 1], // Keyframes x: [0, 20, -10, 5, 0], y: [0, -15, 10, -5, 0], transition: { duration: 4, repeat: Infinity, repeatType: "mirror", ease: "easeInOut" } } }; // Multiple blobs with different rhythms const blob1 = { duration: 3, ... }; const blob2 = { duration: 4.5, ... }; const blob3 = { duration: 5.2, ... };
Best practices:
- Layer multiple blobs with different durations for organic feel
- Use
for smooth loopsrepeatType: "mirror" - Keep movements subtle (scale: 0.9-1.2 range)
- Use blur and opacity to create depth
Pattern 3: Page Transitions
Use for route changes, content swapping.
When to use: Navigating between pages or major content changes
Example from template.tsx:
<motion.div initial={{ opacity: 0, y: 20 }} animate={{ opacity: 1, y: 0 }} exit={{ opacity: 0, y: 20 }} transition={{ ease: "easeInOut", duration: 0.4 }} > {children} </motion.div>
Variations:
// Fade only initial={{ opacity: 0 }} animate={{ opacity: 1 }} // Slide from right initial={{ opacity: 0, x: 20 }} animate={{ opacity: 1, x: 0 }} // Scale + fade initial={{ opacity: 0, scale: 0.95 }} animate={{ opacity: 1, scale: 1 }}
Pattern 4: Hover Interactions
Use for buttons, cards, clickable elements.
When to use: Adding interactivity to user-actionable elements
const cardVariants = { initial: { scale: 1 }, hover: { scale: 1.02, transition: { duration: 0.3, ease: "easeInOut" } }, tap: { scale: 0.98 } }; <motion.div variants={cardVariants} initial="initial" whileHover="hover" whileTap="tap" >
Common hover patterns:
- Cards: scale(1.02) + shadow increase
- Buttons: scale(1.05) + slight lift
- Icons: rotate or scale
- Links: underline expand
Pattern 5: Scroll-Based Animations
Use for parallax, fade-ins, progress indicators.
When to use: Animations triggered by scroll position
import { useScroll, useTransform } from "framer-motion"; const { scrollYProgress } = useScroll(); const opacity = useTransform(scrollYProgress, [0, 0.5], [0, 1]); const y = useTransform(scrollYProgress, [0, 0.5], [50, 0]); <motion.div style={{ opacity, y }}> {/* Content */} </motion.div>
Example: Progress bar
const { scrollYProgress } = useScroll(); <motion.div style={{ scaleX: scrollYProgress }} className="fixed top-0 left-0 right-0 h-1 bg-primary origin-left" />
Pattern 6: Enter/Exit Animations
Use with AnimatePresence for conditional rendering.
When to use: Elements that appear and disappear
import { AnimatePresence } from "framer-motion"; const variants = { hidden: { opacity: 0, y: -10 }, visible: { opacity: 1, y: 0 }, exit: { opacity: 0, y: -10 } }; <AnimatePresence> {isVisible && ( <motion.div variants={variants} initial="hidden" animate="visible" exit="exit" transition={{ duration: 0.3 }} > {content} </motion.div> )} </AnimatePresence>
Easing Functions
Use these, not "linear":
// Bellog standard ease: "easeInOut" // Default for most animations // Other options ease: "easeOut" // For entrances ease: "easeIn" // For exits ease: [0.43, 0.13, 0.23, 0.96] // Custom cubic-bezier
Animation Checklist
Before finalizing animations:
- Timing follows project standards (0.2-0.5s for interactions)
- Uses
pattern (not inline animation props)variants - Easing is
or appropriate alternativeeaseInOut - No linear easing
- Stagger delay appropriate for number of items
- Exit animations defined if using AnimatePresence
- Performance: No layout thrashing (avoid animating width/height)
- Accessibility: Respects
if criticalprefers-reduced-motion
Performance Tips
-
Transform over top/left:
// ✅ Good (GPU accelerated) { x: 100 } { translateX: "100px" } { scale: 1.1 } // ❌ Bad (layout recalc) { left: 100 } { width: "100%" } -
Will-change hint:
style={{ willChange: "transform" }} -
Layout animations (use sparingly):
<motion.div layout>
Reduced Motion
Respect user preferences:
import { useReducedMotion } from "framer-motion"; const shouldReduceMotion = useReducedMotion(); const variants = { hidden: { opacity: 0, y: shouldReduceMotion ? 0 : 20 }, visible: { opacity: 1, y: 0 } };
Common Mistakes to Avoid
❌ Don't: Inline animation props
<motion.div animate={{ opacity: 1 }} initial={{ opacity: 0 }}>
✅ Do: Use variants
const variants = { hidden: { opacity: 0 }, visible: { opacity: 1 } }; <motion.div variants={variants} initial="hidden" animate="visible">
❌ Don't: Linear easing
transition={{ duration: 0.3, ease: "linear" }}
✅ Do: Use curves
transition={{ duration: 0.3, ease: "easeInOut" }}
❌ Don't: Animate width/height directly
animate={{ width: "100%" }}
✅ Do: Use scale or layout
animate={{ scaleX: 1 }} // or <motion.div layout>
Quick Reference
// Standard fade + slide in { initial: { opacity: 0, y: 20 }, animate: { opacity: 1, y: 0 } } // Standard hover { whileHover: { scale: 1.02 }, transition: { duration: 0.3 } } // Standard stagger { variants: { container: { transition: { staggerChildren: 0.1 } }, item: { hidden: { y: 20, opacity: 0 }, show: { y: 0, opacity: 1 } } } } // Standard exit { exit: { opacity: 0, y: -10 }, transition: { duration: 0.2 } }
Remember: Animations should enhance the experience, not distract from it. When in doubt, keep it subtle and fast.