Skill de Motion Design para animações, transições e micro-interações. Use quando precisar definir ou implementar animações de interface, transições entre páginas, efeitos de hover/click/focus, loading states animados, ou qualquer interação visual com movimento. Trigger em: "animacao", "transicao", "motion", "micro-interacao", "framer motion", "spring", "easing", "parallax", "scroll animation", "hover effect".
88
Quality
86%
Does it follow best practices?
Impact
Pending
No eval scenarios have been run
Passed
No known issues
O Motion Designer entra após o Frontend, quando os componentes estão prontos para receber vida. Toda animação deve ser intencional, performática e acessível.
Esta skill segue GLOBAL.md, policies/execution.md, policies/handoffs.md, policies/token-efficiency.md, policies/stack-flexibility.md e policies/evals.md.
Para exemplos extensos de tokens e variants, consultar docs/skill-guides/motion-design.md apenas quando necessario.
Animações: Framer Motion
Transições: CSS Transitions / CSS Animations
Estilo: Tailwind CSS
Performance: requestAnimationFrame
Acessibilidade: prefers-reduced-motionexport const motionTokens = {
duration: {
instant: 0.1,
fast: 0.2,
normal: 0.3,
slow: 0.5,
dramatic: 0.8,
},
easing: {
default: [0.25, 0.1, 0.25, 1],
smooth: [0.4, 0, 0.2, 1],
snappy: [0.2, 0, 0, 1],
bounce: [0.34, 1.56, 0.64, 1],
spring: { type: 'spring', stiffness: 300, damping: 20 },
},
stagger: {
fast: 0.03,
normal: 0.05,
slow: 0.1,
},
} as const;import { Variants } from 'framer-motion';
import { motionTokens } from '@/lib/motion-tokens';
const { duration, easing } = motionTokens;
export const fadeIn: Variants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { duration: duration.normal, ease: easing.smooth },
},
};
export const slideUp: Variants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: { duration: duration.normal, ease: easing.smooth },
},
};
export const slideInFromLeft: Variants = {
hidden: { opacity: 0, x: -30 },
visible: {
opacity: 1,
x: 0,
transition: { duration: duration.normal, ease: easing.snappy },
},
};
export const scaleIn: Variants = {
hidden: { opacity: 0, scale: 0.9 },
visible: {
opacity: 1,
scale: 1,
transition: { duration: duration.fast, ease: easing.bounce },
},
};export const fadeOut: Variants = {
visible: { opacity: 1 },
exit: {
opacity: 0,
transition: { duration: duration.fast, ease: easing.default },
},
};
export const slideDown: Variants = {
visible: { opacity: 1, y: 0 },
exit: {
opacity: 0,
y: 20,
transition: { duration: duration.fast, ease: easing.smooth },
},
};export const staggerContainer: Variants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: motionTokens.stagger.normal,
delayChildren: 0.1,
},
},
};
export const staggerItem: Variants = {
hidden: { opacity: 0, y: 15 },
visible: {
opacity: 1,
y: 0,
transition: { duration: duration.normal, ease: easing.smooth },
},
};import { motion } from 'framer-motion';
import { staggerContainer, staggerItem } from '@/lib/motion-variants';
function AnimatedList({ items }: { items: Item[] }) {
return (
<motion.ul
variants={staggerContainer}
initial="hidden"
animate="visible"
>
{items.slice(0, 10).map((item) => (
<motion.li key={item.id} variants={staggerItem}>
{item.name}
</motion.li>
))}
</motion.ul>
);
}export const pageTransition: Variants = {
hidden: { opacity: 0, y: 10 },
visible: {
opacity: 1,
y: 0,
transition: { duration: duration.normal, ease: easing.smooth },
},
exit: {
opacity: 0,
y: -10,
transition: { duration: duration.fast, ease: easing.default },
},
};import { AnimatePresence, motion } from 'framer-motion';
import { pageTransition } from '@/lib/motion-variants';
function PageWrapper({ children, key }: { children: React.ReactNode; key: string }) {
return (
<AnimatePresence mode="wait">
<motion.div
key={key}
variants={pageTransition}
initial="hidden"
animate="visible"
exit="exit"
>
{children}
</motion.div>
</AnimatePresence>
);
}import { motion } from 'framer-motion';
import { motionTokens } from '@/lib/motion-tokens';
function ButtonPress({ children, onClick }: { children: React.ReactNode; onClick: () => void }) {
return (
<motion.button
whileTap={{ scale: 0.97 }}
whileHover={{ scale: 1.02 }}
transition={{ duration: motionTokens.duration.instant, ease: motionTokens.easing.snappy }}
onClick={onClick}
>
{children}
</motion.button>
);
}
function CardHover({ children }: { children: React.ReactNode }) {
return (
<motion.div
whileHover={{
y: -4,
boxShadow: '0 10px 30px rgba(0, 0, 0, 0.12)',
}}
transition={{ duration: motionTokens.duration.fast, ease: motionTokens.easing.smooth }}
>
{children}
</motion.div>
);
}
function FocusRing({ children }: { children: React.ReactNode }) {
return (
<motion.div
whileFocus={{
boxShadow: '0 0 0 3px rgba(59, 130, 246, 0.5)',
}}
transition={{ duration: motionTokens.duration.instant }}
>
{children}
</motion.div>
);
}import { motion, AnimatePresence } from 'framer-motion';
import { motionTokens } from '@/lib/motion-tokens';
function SkeletonToContent({
isLoading,
skeleton,
children,
}: {
isLoading: boolean;
skeleton: React.ReactNode;
children: React.ReactNode;
}) {
return (
<AnimatePresence mode="wait">
{isLoading ? (
<motion.div
key="skeleton"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
exit={{ opacity: 0 }}
transition={{ duration: motionTokens.duration.fast }}
>
{skeleton}
</motion.div>
) : (
<motion.div
key="content"
initial={{ opacity: 0 }}
animate={{ opacity: 1 }}
transition={{ duration: motionTokens.duration.normal }}
>
{children}
</motion.div>
)}
</AnimatePresence>
);
}
function SpinnerRotate({ size = 24 }: { size?: number }) {
return (
<motion.div
animate={{ rotate: 360 }}
transition={{
duration: motionTokens.duration.dramatic,
repeat: Infinity,
ease: 'linear',
}}
style={{ width: size, height: size }}
className="border-2 border-gray-300 border-t-blue-500 rounded-full"
/>
);
}import { motion } from 'framer-motion';
import { motionTokens } from '@/lib/motion-tokens';
function ScrollFadeIn({ children }: { children: React.ReactNode }) {
return (
<motion.div
initial={{ opacity: 0, y: 30 }}
whileInView={{ opacity: 1, y: 0 }}
viewport={{ once: true, margin: '-50px' }}
transition={{
duration: motionTokens.duration.slow,
ease: motionTokens.easing.smooth,
}}
>
{children}
</motion.div>
);
}width, height, top, left, margin, paddingtransform (translate, scale, rotate) e opacitywill-change com moderação e apenas quando necessárioprefers-reduced-motion.animated-element {
will-change: transform, opacity;
}
.animated-element.done {
will-change: auto;
}import { useEffect, useState } from 'react';
export function useReducedMotion(): boolean {
const [prefersReduced, setPrefersReduced] = useState(false);
useEffect(() => {
const mediaQuery = window.matchMedia('(prefers-reduced-motion: reduce)');
setPrefersReduced(mediaQuery.matches);
const handler = (event: MediaQueryListEvent) => {
setPrefersReduced(event.matches);
};
mediaQuery.addEventListener('change', handler);
return () => mediaQuery.removeEventListener('change', handler);
}, []);
return prefersReduced;
}import { useReducedMotion } from '@/hooks/useReducedMotion';
function AnimatedComponent({ children }: { children: React.ReactNode }) {
const prefersReduced = useReducedMotion();
return (
<motion.div
initial={prefersReduced ? false : { opacity: 0, y: 20 }}
animate={{ opacity: 1, y: 0 }}
transition={
prefersReduced
? { duration: 0 }
: { duration: motionTokens.duration.normal, ease: motionTokens.easing.smooth }
}
>
{children}
</motion.div>
);
}A ordem de entrada dos elementos segue a hierarquia visual:
1. Página → Fade/slide da página inteira
2. Seções → Stagger das seções principais
3. Componentes → Entrada individual de cards, listas, etc.
4. Conteúdo → Texto, ícones, badges dentro dos componentesCada nível espera o anterior iniciar antes de começar. Usar delayChildren e staggerChildren para orquestrar a cascata.
const sectionVariants: Variants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: {
staggerChildren: motionTokens.stagger.slow,
delayChildren: motionTokens.duration.fast,
},
},
};
const componentVariants: Variants = {
hidden: { opacity: 0, y: 20 },
visible: {
opacity: 1,
y: 0,
transition: {
duration: motionTokens.duration.normal,
ease: motionTokens.easing.smooth,
staggerChildren: motionTokens.stagger.fast,
},
},
};
const contentVariants: Variants = {
hidden: { opacity: 0 },
visible: {
opacity: 1,
transition: { duration: motionTokens.duration.fast },
},
};prefers-reduced-motion consideradoMotion NAO cria componentes novos — adiciona animacao e transicoes aos componentes existentes do Frontend. O codigo de motion e adicionado ao MESMO codebase do Frontend. Apos Motion finalizar, o repositorio do Frontend contem os componentes com animacao integrada.
ZERO comentários no código. O código deve ser autoexplicativo através de:
524725e
If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.