[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6QQ7ATTPDSDM7J3EP8TWTKS":3,"skill-same:01M6QQ7ATTPDSDM7J3EP8TWTKS":69},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":23,"publishedAt":43,"linkOnly":44,"markdownContent":45,"markdownBody":46,"frontMatter":47,"refName":48,"commitSha":49,"skillDirectory":50,"forksCount":51,"repositoryHtmlUrl":52,"repositoryHomepageUrl":6,"ownerHtmlUrl":53,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":54,"install":55,"resources":68},"01M6QQ7ATTPDSDM7J3EP8TWTKS","animations",null,"Animations and motion design with motion\u002Freact. Load whenever animating any UI element — entrance\u002Fexit, hover\u002Ftap, drag, swipe-to-dismiss, scroll reveal, layout\u002Fshared transition (layoutId, LayoutGroup), page change, staggered list, tab indicator, parallax, scroll progress, imperative sequencing (useAnimate), motion values + transforms (rotate-on-drag, opacity-on-scroll), or prefers-reduced-motion.","SimaDevelopment\u002Fopsiforce","SimaDevelopment","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F153810655?v=4","01M6QQ054MKBDSS43AND3FNHDK","Opsima","01M6QQ058VFT6EY7HCBVN49G8Z",42,"AGPL-3.0","https:\u002F\u002Fgithub.com\u002FSimaDevelopment\u002Fopsiforce\u002Fblob\u002Fmain\u002Fagent-config\u002Fskills\u002Fanimations\u002FSKILL.md",[18],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发","SCENARIO","使用场景",[5,24,25,26,27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42],"progress","reveal","transition","parallax","transforms","drag","sequencing","swipe","element","design","layout","shared","tap","list","page","ui","hover","change","react","2026-11-10 10:12:30",false,"---\nname: animations\ndescription: Animations and motion design with motion\u002Freact. Load whenever animating any UI element — entrance\u002Fexit, hover\u002Ftap, drag, swipe-to-dismiss, scroll reveal, layout\u002Fshared transition (layoutId, LayoutGroup), page change, staggered list, tab indicator, parallax, scroll progress, imperative sequencing (useAnimate), motion values + transforms (rotate-on-drag, opacity-on-scroll), or prefers-reduced-motion.\n---\n\n# Animations (motion\u002Freact)\n\nThe project uses **motion** (formerly Framer Motion) — preinstalled. All imports come from `motion\u002Freact`, not `framer-motion`. The runtime API is identical.\n\n```tsx\nimport {\n  motion,\n  AnimatePresence,\n  LayoutGroup,\n  useInView,\n  useScroll,\n  useTransform,\n  useMotionValue,\n  useAnimate,\n  useReducedMotion,\n} from \"motion\u002Freact\"\n```\n\n## Project rules\n\n- **Animate `transform` and `opacity`, not `width`\u002F`height`\u002F`top`\u002F`left`** — only those are GPU-accelerated. Animating layout properties causes jank.\n- **Wrap conditional renders in `AnimatePresence`** — exit animations don't fire without it. Every direct child needs a unique `key`.\n- **Honor `prefers-reduced-motion`** — use `useReducedMotion` (or the helper below) to disable \u002F shorten animations for users who set the OS flag.\n- **Spring physics for UI feedback, tweens for content reveals** — springs feel natural for buttons\u002Ftabs; tweens are predictable for hero fades and stagger lists.\n\n## Mount animation\n\n```tsx\n\u003Cmotion.div\n  initial={{ opacity: 0, y: 20 }}\n  animate={{ opacity: 1, y: 0 }}\n  transition={{ duration: 0.3 }}\n>\n  Content\n\u003C\u002Fmotion.div>\n```\n\n## Hover \u002F tap\n\n```tsx\n\u003Cmotion.button\n  whileHover={{ scale: 1.02 }}\n  whileTap={{ scale: 0.98 }}\n  transition={{ type: \"spring\", stiffness: 400, damping: 17 }}\n>\n  Click me\n\u003C\u002Fmotion.button>\n\n\u003Cmotion.div\n  whileHover={{ y: -2, boxShadow: \"0 4px 12px rgba(0,0,0,0.1)\" }}\n  transition={{ duration: 0.2 }}\n>\n  \u003CCard>Hoverable card\u003C\u002FCard>\n\u003C\u002Fmotion.div>\n```\n\n## Modals\n\nA modal is the `ui` skill's `Dialog`, which brings the focus trap, ARIA and ESC-to-close. Add motion to its open\u002Fclose only when you need a non-default entrance.\n\n## Variants — staggered list\n\nVariants attach named animation states to a parent and let children inherit them. The `staggerChildren` transition fires the children one after another.\n\n```tsx\nconst container = {\n  hidden: { opacity: 0 },\n  visible: {\n    opacity: 1,\n    transition: { staggerChildren: 0.05, delayChildren: 0.1 },\n  },\n}\n\nconst item = {\n  hidden: { opacity: 0, y: 10 },\n  visible: { opacity: 1, y: 0, transition: { type: \"spring\", stiffness: 300, damping: 24 } },\n}\n\n\u003Cmotion.ul initial=\"hidden\" animate=\"visible\" variants={container}>\n  {items.map((i) => (\n    \u003Cmotion.li key={i.id} variants={item} className=\"border rounded-lg p-4\">\n      {i.title}\n    \u003C\u002Fmotion.li>\n  ))}\n\u003C\u002Fmotion.ul>\n```\n\n## Animate list add \u002F remove\n\n```tsx\n\u003CAnimatePresence mode=\"popLayout\">\n  {items.map((item) => (\n    \u003Cmotion.div\n      key={item.id}\n      layout\n      initial={{ opacity: 0, scale: 0.95 }}\n      animate={{ opacity: 1, scale: 1 }}\n      exit={{ opacity: 0, scale: 0.95 }}\n      transition={{ duration: 0.2 }}\n    >\n      {item.title}\n    \u003C\u002Fmotion.div>\n  ))}\n\u003C\u002FAnimatePresence>\n```\n\n`layout` slides the remaining items into place with transforms, so no `height` is animated.\n\n## Shared layout — tab indicator, expanding card\n\n`layoutId` makes Motion smoothly animate the same logical element between positions when it remounts at a different parent.\n\n```tsx\nfunction Tabs({ tabs, activeTab, onChange }: {\n  tabs: string[]\n  activeTab: string\n  onChange: (tab: string) => void\n}) {\n  return (\n    \u003CLayoutGroup>\n      \u003Cdiv className=\"flex gap-1 relative\">\n        {tabs.map((tab) => (\n          \u003Cbutton\n            key={tab}\n            onClick={() => onChange(tab)}\n            className=\"relative px-4 py-2 text-sm\"\n          >\n            \u003Cspan className=\"relative z-10\">{tab}\u003C\u002Fspan>\n            {activeTab === tab && (\n              \u003Cmotion.div\n                layoutId=\"active-tab\"\n                className=\"absolute inset-0 bg-primary\u002F10 rounded-md\"\n                transition={{ type: \"spring\", stiffness: 500, damping: 30 }}\n              \u002F>\n            )}\n          \u003C\u002Fbutton>\n        ))}\n      \u003C\u002Fdiv>\n    \u003C\u002FLayoutGroup>\n  )\n}\n```\n\nThe `layoutId` string must match across renders. `LayoutGroup` scopes the id so it doesn't collide with other tab groups elsewhere on the page.\n\n## Page route transitions (react-router)\n\n```tsx\nimport { Outlet, useLocation } from \"react-router-dom\"\n\nfunction AnimatedOutlet() {\n  const location = useLocation()\n  return (\n    \u003CAnimatePresence mode=\"wait\">\n      \u003Cmotion.div\n        key={location.pathname}\n        initial={{ opacity: 0, y: 10 }}\n        animate={{ opacity: 1, y: 0 }}\n        exit={{ opacity: 0, y: -10 }}\n        transition={{ duration: 0.2 }}\n      >\n        \u003COutlet \u002F>\n      \u003C\u002Fmotion.div>\n    \u003C\u002FAnimatePresence>\n  )\n}\n```\n\nUse `\u003CAnimatedOutlet \u002F>` inside your `Layout` component instead of the bare `\u003COutlet \u002F>`. `mode=\"wait\"` ensures the exit completes before the new page mounts (no double-render flash).\n\n## Drag\n\n```tsx\nfunction DraggableCard() {\n  return (\n    \u003Cmotion.div\n      drag\n      dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}\n      dragElastic={0.2}\n      dragTransition={{ bounceStiffness: 600, bounceDamping: 20 }}\n      whileDrag={{ scale: 1.05, cursor: \"grabbing\" }}\n      className=\"w-32 h-32 rounded-lg bg-primary cursor-grab\"\n    \u002F>\n  )\n}\n```\n\nConstrain to one axis with `drag=\"x\"` or `drag=\"y\"`. `dragElastic` (0–1) controls how far past the constraint it can stretch.\n\n## Swipe to dismiss\n\n```tsx\nfunction SwipeToDelete({ onDelete, children }: {\n  onDelete: () => void\n  children: React.ReactNode\n}) {\n  return (\n    \u003Cmotion.div\n      drag=\"x\"\n      dragConstraints={{ left: 0, right: 0 }}\n      onDragEnd={(_event, info) => {\n        if (info.offset.x \u003C -100) onDelete()\n      }}\n      className=\"relative bg-card\"\n    >\n      {children}\n    \u003C\u002Fmotion.div>\n  )\n}\n```\n\nThreshold (`-100` here) is the distance the user has to drag before the action fires. `info.velocity.x` is also available if you'd rather trigger on a fast flick.\n\n## Scroll-triggered reveal\n\n```tsx\nimport { useRef } from \"react\"\nimport { motion, useInView } from \"motion\u002Freact\"\n\nfunction ScrollReveal({ children }: { children: React.ReactNode }) {\n  const ref = useRef(null)\n  const isInView = useInView(ref, { once: true, margin: \"-100px\" })\n\n  return (\n    \u003Cmotion.div\n      ref={ref}\n      initial={{ opacity: 0, y: 30 }}\n      animate={isInView ? { opacity: 1, y: 0 } : {}}\n      transition={{ duration: 0.5 }}\n    >\n      {children}\n    \u003C\u002Fmotion.div>\n  )\n}\n```\n\n`once: true` plays the animation only on first reveal. `margin: \"-100px\"` triggers when the element is 100px from the viewport (so it animates as it enters, not after).\n\n## Scroll progress bar\n\n```tsx\nfunction ScrollProgress() {\n  const { scrollYProgress } = useScroll()\n  return (\n    \u003Cmotion.div\n      style={{ scaleX: scrollYProgress }}\n      className=\"fixed top-0 left-0 right-0 h-1 bg-primary origin-left z-50\"\n    \u002F>\n  )\n}\n```\n\n`scrollYProgress` is a `MotionValue` between 0 and 1 — bind it to `scaleX` for a width-based bar, no recalculation needed.\n\n## Parallax with useScroll + useTransform\n\n```tsx\nfunction ParallaxHero() {\n  const { scrollY } = useScroll()\n  const y = useTransform(scrollY, [0, 500], [0, 150])\n  const opacity = useTransform(scrollY, [0, 300], [1, 0])\n\n  return (\n    \u003Cmotion.div\n      style={{ y, opacity }}\n      className=\"h-screen flex items-center justify-center\"\n    >\n      \u003Ch1 className=\"text-6xl font-bold\">Hero\u003C\u002Fh1>\n    \u003C\u002Fmotion.div>\n  )\n}\n```\n\n`useTransform(input, inputRange, outputRange)` maps scroll position (or any motion value) to a derived value without React re-renders.\n\n## Imperative sequencing — useAnimate\n\nFor multi-step animations triggered by an event (form submit, success ping):\n\n```tsx\nfunction SubmitButton() {\n  const [scope, animate] = useAnimate()\n\n  const handleClick = async () => {\n    await animate(scope.current, { scale: 0.95 }, { duration: 0.1 })\n    await animate(scope.current, { scale: 1 }, { type: \"spring\" })\n    await animate(scope.current, { backgroundColor: \"hsl(143 71% 45%)\" }, { duration: 0.2 })\n  }\n\n  return (\n    \u003Cmotion.button ref={scope} onClick={handleClick} className=\"px-4 py-2 rounded-md bg-primary text-primary-foreground\">\n      Submit\n    \u003C\u002Fmotion.button>\n  )\n}\n```\n\nUse `animate(scope.current, ...)` for the root, or pass a CSS selector to target descendants: `animate(\"li\", { opacity: 1 }, { delay: stagger(0.1) })`.\n\n## Drag-driven values — useMotionValue + useTransform\n\nBind a draggable element's `x` value to derived properties (rotate, opacity, color) so the transform follows the gesture:\n\n```tsx\nfunction RotatingCard() {\n  const x = useMotionValue(0)\n  const rotateY = useTransform(x, [-200, 200], [-45, 45])\n  const opacity = useTransform(x, [-200, 0, 200], [0.5, 1, 0.5])\n\n  return (\n    \u003Cmotion.div\n      drag=\"x\"\n      dragConstraints={{ left: -200, right: 200 }}\n      style={{ x, rotateY, opacity }}\n      className=\"w-64 h-96 rounded-xl bg-gradient-to-br from-primary to-accent\"\n    \u002F>\n  )\n}\n```\n\nMotion values bypass React's render loop — only the affected style updates each frame.\n\n## Reusable AnimatedContainer\n\nProject-friendly entrance wrapper. Pick one of a small set of named entrances; pass `delay` for staggered hero sections.\n\n```tsx\nimport { motion, type Variants } from \"motion\u002Freact\"\n\nconst presets: Record\u003Cstring, Variants> = {\n  fadeIn:       { hidden: { opacity: 0 },             visible: { opacity: 1 } },\n  fadeInUp:     { hidden: { opacity: 0, y: 20 },      visible: { opacity: 1, y: 0 } },\n  fadeInDown:   { hidden: { opacity: 0, y: -20 },     visible: { opacity: 1, y: 0 } },\n  scaleIn:      { hidden: { opacity: 0, scale: 0.9 }, visible: { opacity: 1, scale: 1 } },\n  slideInLeft:  { hidden: { opacity: 0, x: -50 },     visible: { opacity: 1, x: 0 } },\n  slideInRight: { hidden: { opacity: 0, x: 50 },      visible: { opacity: 1, x: 0 } },\n}\n\ninterface AnimatedContainerProps {\n  children: React.ReactNode\n  animation?: keyof typeof presets\n  delay?: number\n  duration?: number\n  className?: string\n}\n\nexport function AnimatedContainer({\n  children,\n  animation = \"fadeInUp\",\n  delay = 0,\n  duration = 0.5,\n  className,\n}: AnimatedContainerProps) {\n  return (\n    \u003Cmotion.div\n      variants={presets[animation]}\n      initial=\"hidden\"\n      animate=\"visible\"\n      transition={{ duration, delay, ease: \"easeOut\" }}\n      className={className}\n    >\n      {children}\n    \u003C\u002Fmotion.div>\n  )\n}\n```\n\n## Transition presets\n\nCentralize transition objects so the app feels consistent:\n\n```ts\n\u002F\u002F frontend\u002Fsrc\u002Flib\u002Ftransitions.ts\nexport const transitions = {\n  spring:       { type: \"spring\", stiffness: 300, damping: 24 },   \u002F\u002F default UI feedback\n  springSnappy: { type: \"spring\", stiffness: 400, damping: 17 },   \u002F\u002F buttons, tabs\n  springSoft:   { type: \"spring\", stiffness: 300, damping: 30 },   \u002F\u002F layout shifts\n  smooth:       { type: \"tween\", duration: 0.3, ease: \"easeInOut\" },     \u002F\u002F content reveals\n  fast:         { type: \"tween\", duration: 0.15, ease: [0.25, 0.1, 0.25, 1] }, \u002F\u002F micro-interactions\n  page:         { type: \"tween\", duration: 0.2, ease: \"easeOut\" },        \u002F\u002F route transitions\n} as const\n\n\u002F\u002F Usage\nimport { transitions } from \"@\u002Flib\u002Ftransitions\"\n\u003Cmotion.div transition={transitions.springSnappy} \u002F>\n```\n\n## Reduced motion (accessibility)\n\n```tsx\nimport { useReducedMotion } from \"motion\u002Freact\"\n\nfunction AccessibleFadeIn({ children }: { children: React.ReactNode }) {\n  const reduced = useReducedMotion()\n  return (\n    \u003Cmotion.div\n      initial={{ opacity: 0, y: reduced ? 0 : 20 }}\n      animate={{ opacity: 1, y: 0 }}\n      transition={{ duration: reduced ? 0 : 0.5 }}\n    >\n      {children}\n    \u003C\u002Fmotion.div>\n  )\n}\n```\n\nThe rule of thumb: when `reduced` is true, **keep opacity changes** (low motion, useful for state) but **drop transforms** (parallax, slide-in, scale) to zero or skip them. Don't disable animation entirely — fades are fine for almost all users.\n\n## Best practices\n\n- **Add the `layout` prop** to a `motion.div` whose size or position changes due to React state — Motion infers the FLIP transition for free: `\u003Cmotion.div layout>`.\n- **Stagger lists**, don't animate items individually — much smoother and far less code.\n- **Use `AnimatePresence mode=\"wait\"`** for page transitions, default mode for list add\u002Fremove.\n- **Test on a low-end device.** Spring animations with 50+ children will drop frames on a budget Android phone — switch to `duration`-based tweens.\n- **Stick to a small palette of presets** (the `transitions` object above). Inconsistent timings make a UI feel chaotic.\n\n## Common mistakes\n\n1. **Missing unique `key` on `AnimatePresence` children** — exit transitions never fire if React reuses the DOM node (a subtler failure than omitting `AnimatePresence` entirely).\n2. **Mismatched `layoutId`** — `layoutId` only animates between elements that share the exact same string. If the active tab id changes shape, layout transitions break.\n3. **Heavy spring on long lists** — visible jank past ~30 items. Use `duration: 0.2, ease: \"easeOut\"` for big lists.\n4. **Triggering `useInView` re-renders** — pass `once: true` for one-shot reveals, otherwise the hook fires every time the element enters\u002Fexits.\n5. **Importing from `framer-motion`** — the package was renamed to `motion`. Always import from `motion\u002Freact`.\n","# Animations (motion\u002Freact)\n\nThe project uses **motion** (formerly Framer Motion) — preinstalled. All imports come from `motion\u002Freact`, not `framer-motion`. The runtime API is identical.\n\n```tsx\nimport {\n  motion,\n  AnimatePresence,\n  LayoutGroup,\n  useInView,\n  useScroll,\n  useTransform,\n  useMotionValue,\n  useAnimate,\n  useReducedMotion,\n} from \"motion\u002Freact\"\n```\n\n## Project rules\n\n- **Animate `transform` and `opacity`, not `width`\u002F`height`\u002F`top`\u002F`left`** — only those are GPU-accelerated. Animating layout properties causes jank.\n- **Wrap conditional renders in `AnimatePresence`** — exit animations don't fire without it. Every direct child needs a unique `key`.\n- **Honor `prefers-reduced-motion`** — use `useReducedMotion` (or the helper below) to disable \u002F shorten animations for users who set the OS flag.\n- **Spring physics for UI feedback, tweens for content reveals** — springs feel natural for buttons\u002Ftabs; tweens are predictable for hero fades and stagger lists.\n\n## Mount animation\n\n```tsx\n\u003Cmotion.div\n  initial={{ opacity: 0, y: 20 }}\n  animate={{ opacity: 1, y: 0 }}\n  transition={{ duration: 0.3 }}\n>\n  Content\n\u003C\u002Fmotion.div>\n```\n\n## Hover \u002F tap\n\n```tsx\n\u003Cmotion.button\n  whileHover={{ scale: 1.02 }}\n  whileTap={{ scale: 0.98 }}\n  transition={{ type: \"spring\", stiffness: 400, damping: 17 }}\n>\n  Click me\n\u003C\u002Fmotion.button>\n\n\u003Cmotion.div\n  whileHover={{ y: -2, boxShadow: \"0 4px 12px rgba(0,0,0,0.1)\" }}\n  transition={{ duration: 0.2 }}\n>\n  \u003CCard>Hoverable card\u003C\u002FCard>\n\u003C\u002Fmotion.div>\n```\n\n## Modals\n\nA modal is the `ui` skill's `Dialog`, which brings the focus trap, ARIA and ESC-to-close. Add motion to its open\u002Fclose only when you need a non-default entrance.\n\n## Variants — staggered list\n\nVariants attach named animation states to a parent and let children inherit them. The `staggerChildren` transition fires the children one after another.\n\n```tsx\nconst container = {\n  hidden: { opacity: 0 },\n  visible: {\n    opacity: 1,\n    transition: { staggerChildren: 0.05, delayChildren: 0.1 },\n  },\n}\n\nconst item = {\n  hidden: { opacity: 0, y: 10 },\n  visible: { opacity: 1, y: 0, transition: { type: \"spring\", stiffness: 300, damping: 24 } },\n}\n\n\u003Cmotion.ul initial=\"hidden\" animate=\"visible\" variants={container}>\n  {items.map((i) => (\n    \u003Cmotion.li key={i.id} variants={item} className=\"border rounded-lg p-4\">\n      {i.title}\n    \u003C\u002Fmotion.li>\n  ))}\n\u003C\u002Fmotion.ul>\n```\n\n## Animate list add \u002F remove\n\n```tsx\n\u003CAnimatePresence mode=\"popLayout\">\n  {items.map((item) => (\n    \u003Cmotion.div\n      key={item.id}\n      layout\n      initial={{ opacity: 0, scale: 0.95 }}\n      animate={{ opacity: 1, scale: 1 }}\n      exit={{ opacity: 0, scale: 0.95 }}\n      transition={{ duration: 0.2 }}\n    >\n      {item.title}\n    \u003C\u002Fmotion.div>\n  ))}\n\u003C\u002FAnimatePresence>\n```\n\n`layout` slides the remaining items into place with transforms, so no `height` is animated.\n\n## Shared layout — tab indicator, expanding card\n\n`layoutId` makes Motion smoothly animate the same logical element between positions when it remounts at a different parent.\n\n```tsx\nfunction Tabs({ tabs, activeTab, onChange }: {\n  tabs: string[]\n  activeTab: string\n  onChange: (tab: string) => void\n}) {\n  return (\n    \u003CLayoutGroup>\n      \u003Cdiv className=\"flex gap-1 relative\">\n        {tabs.map((tab) => (\n          \u003Cbutton\n            key={tab}\n            onClick={() => onChange(tab)}\n            className=\"relative px-4 py-2 text-sm\"\n          >\n            \u003Cspan className=\"relative z-10\">{tab}\u003C\u002Fspan>\n            {activeTab === tab && (\n              \u003Cmotion.div\n                layoutId=\"active-tab\"\n                className=\"absolute inset-0 bg-primary\u002F10 rounded-md\"\n                transition={{ type: \"spring\", stiffness: 500, damping: 30 }}\n              \u002F>\n            )}\n          \u003C\u002Fbutton>\n        ))}\n      \u003C\u002Fdiv>\n    \u003C\u002FLayoutGroup>\n  )\n}\n```\n\nThe `layoutId` string must match across renders. `LayoutGroup` scopes the id so it doesn't collide with other tab groups elsewhere on the page.\n\n## Page route transitions (react-router)\n\n```tsx\nimport { Outlet, useLocation } from \"react-router-dom\"\n\nfunction AnimatedOutlet() {\n  const location = useLocation()\n  return (\n    \u003CAnimatePresence mode=\"wait\">\n      \u003Cmotion.div\n        key={location.pathname}\n        initial={{ opacity: 0, y: 10 }}\n        animate={{ opacity: 1, y: 0 }}\n        exit={{ opacity: 0, y: -10 }}\n        transition={{ duration: 0.2 }}\n      >\n        \u003COutlet \u002F>\n      \u003C\u002Fmotion.div>\n    \u003C\u002FAnimatePresence>\n  )\n}\n```\n\nUse `\u003CAnimatedOutlet \u002F>` inside your `Layout` component instead of the bare `\u003COutlet \u002F>`. `mode=\"wait\"` ensures the exit completes before the new page mounts (no double-render flash).\n\n## Drag\n\n```tsx\nfunction DraggableCard() {\n  return (\n    \u003Cmotion.div\n      drag\n      dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}\n      dragElastic={0.2}\n      dragTransition={{ bounceStiffness: 600, bounceDamping: 20 }}\n      whileDrag={{ scale: 1.05, cursor: \"grabbing\" }}\n      className=\"w-32 h-32 rounded-lg bg-primary cursor-grab\"\n    \u002F>\n  )\n}\n```\n\nConstrain to one axis with `drag=\"x\"` or `drag=\"y\"`. `dragElastic` (0–1) controls how far past the constraint it can stretch.\n\n## Swipe to dismiss\n\n```tsx\nfunction SwipeToDelete({ onDelete, children }: {\n  onDelete: () => void\n  children: React.ReactNode\n}) {\n  return (\n    \u003Cmotion.div\n      drag=\"x\"\n      dragConstraints={{ left: 0, right: 0 }}\n      onDragEnd={(_event, info) => {\n        if (info.offset.x \u003C -100) onDelete()\n      }}\n      className=\"relative bg-card\"\n    >\n      {children}\n    \u003C\u002Fmotion.div>\n  )\n}\n```\n\nThreshold (`-100` here) is the distance the user has to drag before the action fires. `info.velocity.x` is also available if you'd rather trigger on a fast flick.\n\n## Scroll-triggered reveal\n\n```tsx\nimport { useRef } from \"react\"\nimport { motion, useInView } from \"motion\u002Freact\"\n\nfunction ScrollReveal({ children }: { children: React.ReactNode }) {\n  const ref = useRef(null)\n  const isInView = useInView(ref, { once: true, margin: \"-100px\" })\n\n  return (\n    \u003Cmotion.div\n      ref={ref}\n      initial={{ opacity: 0, y: 30 }}\n      animate={isInView ? { opacity: 1, y: 0 } : {}}\n      transition={{ duration: 0.5 }}\n    >\n      {children}\n    \u003C\u002Fmotion.div>\n  )\n}\n```\n\n`once: true` plays the animation only on first reveal. `margin: \"-100px\"` triggers when the element is 100px from the viewport (so it animates as it enters, not after).\n\n## Scroll progress bar\n\n```tsx\nfunction ScrollProgress() {\n  const { scrollYProgress } = useScroll()\n  return (\n    \u003Cmotion.div\n      style={{ scaleX: scrollYProgress }}\n      className=\"fixed top-0 left-0 right-0 h-1 bg-primary origin-left z-50\"\n    \u002F>\n  )\n}\n```\n\n`scrollYProgress` is a `MotionValue` between 0 and 1 — bind it to `scaleX` for a width-based bar, no recalculation needed.\n\n## Parallax with useScroll + useTransform\n\n```tsx\nfunction ParallaxHero() {\n  const { scrollY } = useScroll()\n  const y = useTransform(scrollY, [0, 500], [0, 150])\n  const opacity = useTransform(scrollY, [0, 300], [1, 0])\n\n  return (\n    \u003Cmotion.div\n      style={{ y, opacity }}\n      className=\"h-screen flex items-center justify-center\"\n    >\n      \u003Ch1 className=\"text-6xl font-bold\">Hero\u003C\u002Fh1>\n    \u003C\u002Fmotion.div>\n  )\n}\n```\n\n`useTransform(input, inputRange, outputRange)` maps scroll position (or any motion value) to a derived value without React re-renders.\n\n## Imperative sequencing — useAnimate\n\nFor multi-step animations triggered by an event (form submit, success ping):\n\n```tsx\nfunction SubmitButton() {\n  const [scope, animate] = useAnimate()\n\n  const handleClick = async () => {\n    await animate(scope.current, { scale: 0.95 }, { duration: 0.1 })\n    await animate(scope.current, { scale: 1 }, { type: \"spring\" })\n    await animate(scope.current, { backgroundColor: \"hsl(143 71% 45%)\" }, { duration: 0.2 })\n  }\n\n  return (\n    \u003Cmotion.button ref={scope} onClick={handleClick} className=\"px-4 py-2 rounded-md bg-primary text-primary-foreground\">\n      Submit\n    \u003C\u002Fmotion.button>\n  )\n}\n```\n\nUse `animate(scope.current, ...)` for the root, or pass a CSS selector to target descendants: `animate(\"li\", { opacity: 1 }, { delay: stagger(0.1) })`.\n\n## Drag-driven values — useMotionValue + useTransform\n\nBind a draggable element's `x` value to derived properties (rotate, opacity, color) so the transform follows the gesture:\n\n```tsx\nfunction RotatingCard() {\n  const x = useMotionValue(0)\n  const rotateY = useTransform(x, [-200, 200], [-45, 45])\n  const opacity = useTransform(x, [-200, 0, 200], [0.5, 1, 0.5])\n\n  return (\n    \u003Cmotion.div\n      drag=\"x\"\n      dragConstraints={{ left: -200, right: 200 }}\n      style={{ x, rotateY, opacity }}\n      className=\"w-64 h-96 rounded-xl bg-gradient-to-br from-primary to-accent\"\n    \u002F>\n  )\n}\n```\n\nMotion values bypass React's render loop — only the affected style updates each frame.\n\n## Reusable AnimatedContainer\n\nProject-friendly entrance wrapper. Pick one of a small set of named entrances; pass `delay` for staggered hero sections.\n\n```tsx\nimport { motion, type Variants } from \"motion\u002Freact\"\n\nconst presets: Record\u003Cstring, Variants> = {\n  fadeIn:       { hidden: { opacity: 0 },             visible: { opacity: 1 } },\n  fadeInUp:     { hidden: { opacity: 0, y: 20 },      visible: { opacity: 1, y: 0 } },\n  fadeInDown:   { hidden: { opacity: 0, y: -20 },     visible: { opacity: 1, y: 0 } },\n  scaleIn:      { hidden: { opacity: 0, scale: 0.9 }, visible: { opacity: 1, scale: 1 } },\n  slideInLeft:  { hidden: { opacity: 0, x: -50 },     visible: { opacity: 1, x: 0 } },\n  slideInRight: { hidden: { opacity: 0, x: 50 },      visible: { opacity: 1, x: 0 } },\n}\n\ninterface AnimatedContainerProps {\n  children: React.ReactNode\n  animation?: keyof typeof presets\n  delay?: number\n  duration?: number\n  className?: string\n}\n\nexport function AnimatedContainer({\n  children,\n  animation = \"fadeInUp\",\n  delay = 0,\n  duration = 0.5,\n  className,\n}: AnimatedContainerProps) {\n  return (\n    \u003Cmotion.div\n      variants={presets[animation]}\n      initial=\"hidden\"\n      animate=\"visible\"\n      transition={{ duration, delay, ease: \"easeOut\" }}\n      className={className}\n    >\n      {children}\n    \u003C\u002Fmotion.div>\n  )\n}\n```\n\n## Transition presets\n\nCentralize transition objects so the app feels consistent:\n\n```ts\n\u002F\u002F frontend\u002Fsrc\u002Flib\u002Ftransitions.ts\nexport const transitions = {\n  spring:       { type: \"spring\", stiffness: 300, damping: 24 },   \u002F\u002F default UI feedback\n  springSnappy: { type: \"spring\", stiffness: 400, damping: 17 },   \u002F\u002F buttons, tabs\n  springSoft:   { type: \"spring\", stiffness: 300, damping: 30 },   \u002F\u002F layout shifts\n  smooth:       { type: \"tween\", duration: 0.3, ease: \"easeInOut\" },     \u002F\u002F content reveals\n  fast:         { type: \"tween\", duration: 0.15, ease: [0.25, 0.1, 0.25, 1] }, \u002F\u002F micro-interactions\n  page:         { type: \"tween\", duration: 0.2, ease: \"easeOut\" },        \u002F\u002F route transitions\n} as const\n\n\u002F\u002F Usage\nimport { transitions } from \"@\u002Flib\u002Ftransitions\"\n\u003Cmotion.div transition={transitions.springSnappy} \u002F>\n```\n\n## Reduced motion (accessibility)\n\n```tsx\nimport { useReducedMotion } from \"motion\u002Freact\"\n\nfunction AccessibleFadeIn({ children }: { children: React.ReactNode }) {\n  const reduced = useReducedMotion()\n  return (\n    \u003Cmotion.div\n      initial={{ opacity: 0, y: reduced ? 0 : 20 }}\n      animate={{ opacity: 1, y: 0 }}\n      transition={{ duration: reduced ? 0 : 0.5 }}\n    >\n      {children}\n    \u003C\u002Fmotion.div>\n  )\n}\n```\n\nThe rule of thumb: when `reduced` is true, **keep opacity changes** (low motion, useful for state) but **drop transforms** (parallax, slide-in, scale) to zero or skip them. Don't disable animation entirely — fades are fine for almost all users.\n\n## Best practices\n\n- **Add the `layout` prop** to a `motion.div` whose size or position changes due to React state — Motion infers the FLIP transition for free: `\u003Cmotion.div layout>`.\n- **Stagger lists**, don't animate items individually — much smoother and far less code.\n- **Use `AnimatePresence mode=\"wait\"`** for page transitions, default mode for list add\u002Fremove.\n- **Test on a low-end device.** Spring animations with 50+ children will drop frames on a budget Android phone — switch to `duration`-based tweens.\n- **Stick to a small palette of presets** (the `transitions` object above). Inconsistent timings make a UI feel chaotic.\n\n## Common mistakes\n\n1. **Missing unique `key` on `AnimatePresence` children** — exit transitions never fire if React reuses the DOM node (a subtler failure than omitting `AnimatePresence` entirely).\n2. **Mismatched `layoutId`** — `layoutId` only animates between elements that share the exact same string. If the active tab id changes shape, layout transitions break.\n3. **Heavy spring on long lists** — visible jank past ~30 items. Use `duration: 0.2, ease: \"easeOut\"` for big lists.\n4. **Triggering `useInView` re-renders** — pass `once: true` for one-shot reveals, otherwise the hook fires every time the element enters\u002Fexits.\n5. **Importing from `framer-motion`** — the package was renamed to `motion`. Always import from `motion\u002Freact`.\n",{"name":5,"description":7},"main","c54d8e35f50ac35b3b2c7ba8d4fca2205a740cb9","agent-config\u002Fskills\u002Fanimations",9,"https:\u002F\u002Fgithub.com\u002FSimaDevelopment\u002Fopsiforce","https:\u002F\u002Fgithub.com\u002FSimaDevelopment","2026-11-05 06:13:10",{"source":8,"skillName":5,"commands":56,"prompt":66,"zipAvailable":67},[57,60,63],{"tool":58,"command":59},"npx","npx skills add SimaDevelopment\u002Fopsiforce --skill animations",{"tool":61,"command":62},"bunx","bunx skills add SimaDevelopment\u002Fopsiforce --skill animations",{"tool":64,"command":65},"pnpm","pnpm dlx skills add SimaDevelopment\u002Fopsiforce --skill animations","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FSimaDevelopment\u002Fopsiforce\u002Fblob\u002Fmain\u002Fagent-config\u002Fskills\u002Fanimations\u002FSKILL.md",true,[],[70,108,128,149,159,184],{"skillNo":71,"name":72,"nameZh":6,"description":73,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":74,"categories":75,"keywords":87,"publishedAt":43},"01M6QQKHZFGFB6A111RW2A9WC0","figma-use","**MANDATORY prerequisite** — you MUST invoke this skill BEFORE every `use_figma` tool call. NEVER call `use_figma` directly without loading this skill first. Skipping it causes common, hard-to-debug failures. Trigger whenever the user wants to perform a write action or a unique read action that requires JavaScript execution in the Figma file context — e.g. create\u002Fedit\u002Fdelete nodes, set up variables or tokens, build components and variants, modify auto-layout or fills, bind variables to properties, or inspect file structure programmatically.","https:\u002F\u002Fgithub.com\u002FSimaDevelopment\u002Fopsiforce\u002Fblob\u002Fmain\u002Ffrontend\u002Fopencode\u002Fpackages\u002Fplugins\u002Ffigma\u002Fskills\u002Ffigma-use\u002FSKILL.md",[76,79,84],{"categoryNo":77,"categoryName":78,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",{"categoryNo":80,"categoryName":81,"taxonomyType":82,"taxonomyTypeDesc":83},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",{"categoryNo":85,"categoryName":86,"taxonomyType":82,"taxonomyTypeDesc":83},"01JBSEEDCAT000000000000032","设计师",[88,89,90,91,92,93,94,95,96,97,98,99,100,101,102,103,104,105,106,107],"figma use","figma","use","components","create","delete","tool","read","write","edit","context","auto","structure","tokens","bind","Variables","you","inspect","variants","invoke",{"skillNo":109,"name":110,"nameZh":6,"description":111,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":112,"categories":113,"keywords":119,"publishedAt":43},"01M6QQK1AMA90K6DFAVANYQ9X3","figma-use-slides","This skill helps agents use Figma's use_figma MCP tool in the Slides context. Can be used alongside figma-use which has foundational context for using the use_figma tool.","https:\u002F\u002Fgithub.com\u002FSimaDevelopment\u002Fopsiforce\u002Fblob\u002Fmain\u002Ffrontend\u002Fopencode\u002Fpackages\u002Fplugins\u002Ffigma\u002Fskills\u002Ffigma-use-slides\u002FSKILL.md",[114,117,118],{"categoryNo":115,"categoryName":116,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000003","演示文稿",{"categoryNo":77,"categoryName":78,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":85,"categoryName":86,"taxonomyType":82,"taxonomyTypeDesc":83},[88,89,120,121,90,122,94,98,123,124,125,126,127],"figma use slides","slides","MCP","false","which","can","for","figma mcp",{"skillNo":129,"name":130,"nameZh":6,"description":131,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":132,"categories":133,"keywords":136,"publishedAt":43},"01M6QQJTR9QYK297WQ1X792D63","figma-use-motion","Motion \u002F animation context for the `use_figma` MCP tool — animating Figma nodes via manual keyframes, animation styles, easing, and timeline duration. Load alongside figma-use whenever a task involves adding, editing, or inspecting animation on a node.","https:\u002F\u002Fgithub.com\u002FSimaDevelopment\u002Fopsiforce\u002Fblob\u002Fmain\u002Ffrontend\u002Fopencode\u002Fpackages\u002Fplugins\u002Ffigma\u002Fskills\u002Ffigma-use-motion\u002FSKILL.md",[134,135],{"categoryNo":77,"categoryName":78,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":85,"categoryName":86,"taxonomyType":82,"taxonomyTypeDesc":83},[88,89,137,90,138,122,139,94,140,141,98,142,143,144,123,145,146,147,148,126],"figma use motion","motion","Animation","node","editing","Nodes","task","keyframes","via","styles","manual","load",{"skillNo":150,"name":151,"nameZh":6,"description":152,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":153,"categories":154,"keywords":157,"publishedAt":43},"01M6QQJNZ067ZRXZ6A0QB9M6SB","figma-use-figjam","This skill helps agents use Figma's use_figma MCP tool in the FigJam context. Can be used alongside figma-use which has foundational context for using the use_figma tool.","https:\u002F\u002Fgithub.com\u002FSimaDevelopment\u002Fopsiforce\u002Fblob\u002Fmain\u002Ffrontend\u002Fopencode\u002Fpackages\u002Fplugins\u002Ffigma\u002Fskills\u002Ffigma-use-figjam\u002FSKILL.md",[155,156],{"categoryNo":77,"categoryName":78,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":85,"categoryName":86,"taxonomyType":82,"taxonomyTypeDesc":83},[88,89,158,90,122,94,98,123,124,125,126,127],"figma use figjam",{"skillNo":160,"name":161,"nameZh":6,"description":162,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":163,"categories":164,"keywords":166,"publishedAt":43},"01M6QQJ7NEHXPE33VRVHYYM7YE","figma-swiftui","SwiftUI ↔ Figma translation. Use whenever the user mentions Swift, SwiftUI, iOS, iPhone, or iPad — in EITHER direction — translating a Figma design into SwiftUI (design → code), or pushing SwiftUI views \u002F screens \u002F tokens back into a Figma file (code → design). Triggers on phrases like 'implement this Figma design in SwiftUI', 'build this screen in Swift', 'push this SwiftUI view to Figma', 'mirror my Swift code in a Figma file', or whenever a Figma URL appears alongside `.swift` files \u002F an `.xcodeproj`. Routes to a direction-specific reference doc; loads alongside `figma-use` for the code → design path.","https:\u002F\u002Fgithub.com\u002FSimaDevelopment\u002Fopsiforce\u002Fblob\u002Fmain\u002Ffrontend\u002Fopencode\u002Fpackages\u002Fplugins\u002Ffigma\u002Fskills\u002Ffigma-swiftui\u002FSKILL.md",[165],{"categoryNo":85,"categoryName":86,"taxonomyType":82,"taxonomyTypeDesc":83},[89,167,168,33,169,170,171,172,173,174,175,176,177,178,123,179,180,181,182,183],"figma swiftui","swiftui","swift","code","push","doc","files","translation","back","either","iphone","ipad","screen","direction","URL","my","path",{"skillNo":185,"name":186,"nameZh":6,"description":187,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":188,"categories":189,"keywords":191,"publishedAt":43},"01M6QQJ3WZ3M90BZQ5TD305PKN","figma-shaders","**MANDATORY prerequisite** — load this skill before calling `create_shader` or `update_shader`. Use when the user asks to create, author, change, fix, or iterate on a shader effect, shader fill, custom effect, custom fill, or procedural shader in Figma.","https:\u002F\u002Fgithub.com\u002FSimaDevelopment\u002Fopsiforce\u002Fblob\u002Fmain\u002Ffrontend\u002Fopencode\u002Fpackages\u002Fplugins\u002Ffigma\u002Fskills\u002Ffigma-shaders\u002FSKILL.md",[190],{"categoryNo":85,"categoryName":86,"taxonomyType":82,"taxonomyTypeDesc":83},[192,89,193,41,92,194,195,196,197,90,198,199,123,200,148,201,202,203,204],"shaders","figma shaders","update","effect","user","fix","shader","calling","procedural","fill","iterate","custom","author"]