[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6JD9C9RFPRK9CQ7YWG191Z2":3,"skill-same:01M6JD9C9RFPRK9CQ7YWG191Z2":83},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":15,"categories":16,"keywords":30,"publishedAt":51,"linkOnly":52,"markdownContent":53,"markdownBody":54,"frontMatter":55,"refName":61,"commitSha":62,"skillDirectory":63,"forksCount":64,"repositoryHtmlUrl":65,"repositoryHomepageUrl":66,"ownerHtmlUrl":67,"ownerBlogUrl":6,"ownerDescription":6,"updatedAt":68,"install":69,"resources":82},"01M6JD9C9RFPRK9CQ7YWG191Z2","motion-advanced",null,"Advanced motion patterns for React \u002F Next.js — drag & drop, gestures, text animations, SVG path drawing, custom hooks, imperative sequences (useAnimate), loaders, and the full API decision tree. Requires motion-foundations.","S3YED\u002Fappie-kit","S3YED","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F62743867?v=4","01M6JBKBRRVP6KMS0VWZ27T3R6","01M6JBKBX0KG30284PW1DBYJT1",14,"MIT","https:\u002F\u002Fgithub.com\u002FS3YED\u002Fappie-kit\u002Fblob\u002Fmaster\u002Fskills\u002Fecc\u002Fmotion-advanced\u002FSKILL.md",[17,22,25],{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000009","前端开发","SCENARIO","使用场景",{"categoryNo":23,"categoryName":24,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000016","图像与多媒体",{"categoryNo":26,"categoryName":27,"taxonomyType":28,"taxonomyTypeDesc":29},"01JBSEEDCAT000000000000030","开发工程师","PROFESSION","适用职业",[31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48,49,50],"motion advanced","advanced","motion","next","path","tree","foundations","drag","SVG","full","drop","frontend","patterns","hooks","decision","text","Animation","api","react","gestures","2026-11-10 10:12:30",false,"---\nname: motion-advanced\ndescription: Advanced motion patterns for React \u002F Next.js — drag & drop, gestures, text animations, SVG path drawing, custom hooks, imperative sequences (useAnimate), loaders, and the full API decision tree. Requires motion-foundations.\nversion: 1.0\ntags: [motion, animation, advanced, gestures, svg]\ncategory: frontend\nauthor: jeff\n---\n\n# Motion Advanced\n\nComplex, interactive, and physics-based animation patterns.\nRequires `motion-foundations` to be set up first.\nUse these when `motion-patterns` is not enough.\n\n## When to Activate\n\n- Building drag-to-dismiss sheets, swipe gestures, or reorderable lists\n- Animating text word-by-word, character-by-character, or as a live counter\n- Drawing SVG paths, morphing icons, or animating circular progress\n- Writing a custom animation hook (`useScrollReveal`, magnetic button, cursor follower)\n- Sequencing multi-step animations imperatively with `useAnimate`\n- Building spinners, shimmer skeletons, pulse indicators, or loading button states\n\n## Outputs\n\nThis skill produces:\n\n- Drag interactions: draggable cards, drag-to-dismiss sheets, `Reorder.Group` lists\n- Gesture hooks: swipe detection, long press, pinch outline\n- Text animation components: word reveal, character typewriter, number counter\n- SVG animation: path draw-on, icon morph, stroke progress ring\n- Custom hooks: `useScrollReveal`, `useHoverScale`, `useNavigationDirection`, `useInViewOnce`\n- Imperative sequences via `useAnimate` with interrupt-safe `async\u002Fawait`\n- Loader components: spinner, shimmer, pulse dot, progress bar, button loading state\n\n## Principles\n\n- Physics-based motion (`useSpring`, `springs.*`) always feels more natural than duration-based for direct manipulation.\n- `useMotionValue` + `useTransform` computes derived values without triggering re-renders.\n- `useAnimate` sequences are imperative and interrupt-safe — calling `animate()` mid-flight cancels the previous animation automatically.\n- Motion values (`useMotionValue`, `useSpring`) are SSR-safe and do not cause hydration errors.\n\n## Rules\n\n1. **Drag interactions must be tested on touch devices**, not just mouse. `drag` prop works on both but feel and threshold differ.\n2. **Infinite animations must pause when `document.visibilityState === \"hidden\"`.** Background tabs must not consume GPU\u002FCPU.\n3. **Swipe threshold must be explicit.** Never infer intent from velocity alone; combine `offset` + `velocity` checks.\n4. **`useAnimate` scope ref must be attached to a mounted DOM element.** Calling `animate()` before mount throws silently.\n5. **Motion values must not be recreated on render.** `useMotionValue(0)` inside a component body is correct; `new MotionValue(0)` in a render is not.\n6. **All token values are imported from `motion-foundations`.** No inline numbers.\n7. **Custom hooks must handle cleanup.** Every `window.addEventListener` needs a matching `removeEventListener` in the `useEffect` return.\n8. **SVG morphing requires equal path command counts.** Paths with different command structures snap instead of interpolating.\n\n## Decision Guidance\n\n### Choosing the right advanced API\n\n| Scenario | API |\n| ------------------------------ | -------------------------------- |\n| Drag with physics on release | `drag` + `dragTransition: springs.release` |\n| Ordered drag-to-reorder list | `Reorder.Group` + `Reorder.Item` |\n| Dismiss on drag offset | `drag=\"y\"` + `onDragEnd` offset check |\n| Swipe left\u002Fright | `drag=\"x\"` + `onDragEnd` offset check |\n| Long press | `useLongPress` hook |\n| Value smoothed over time | `useSpring` |\n| Value derived from another | `useTransform` |\n| Multi-step sequence | `useAnimate` with `async\u002Fawait` |\n| One-shot imperative animation | `animate()` from `motion` |\n| Text entering word by word | Stagger on `inline-block` spans |\n| SVG drawing on | `pathLength` 0 → 1 |\n| SVG morph | `d` attribute tween (equal commands) |\n| Circular progress | `strokeDashoffset` tween |\n\n### When to use `useSpring` vs a spring transition\n\n| | `useSpring` | `transition: springs.*` |\n| -------------- | ---------------------------------------- | ----------------------- |\n| Use for | Cursor follower, pointer-tracked values | Discrete state changes |\n| Updates | Continuous, on every frame | Triggered by state change |\n| Interrupt | Smooth — physics picks up from velocity | Restarts from current value |\n\n## Core Concepts\n\n### useMotionValue + useTransform\n\nReactive computation without re-renders:\n\n```tsx\nconst x = useMotionValue(0)\nconst opacity = useTransform(x, [-200, 0, 200], [0, 1, 0])\n\u002F\u002F opacity updates every frame as x changes — no setState, no re-render\n```\n\n### useAnimate\n\nReturns `[scope, animate]`. The scope ref must be attached to a DOM element.\n`animate()` calls are interrupt-safe — calling mid-flight cancels the previous run.\n\n```tsx\nconst [scope, animate] = useAnimate()\n\nasync function play() {\n  await animate(\".step-1\", { opacity: 1 }, { duration: 0.3 })\n  await animate(\".step-2\", { x: 0 },       { duration: 0.4 })\n        animate(\".step-3\", { scale: 1 },    { duration: 0.25 })  \u002F\u002F fire and forget\n}\n\nreturn \u003Cdiv ref={scope}>...\u003C\u002Fdiv>\n```\n\n## Code Examples\n\n### Draggable card\n\n```tsx\n\"use client\"\nimport { motion } from \"motion\u002Freact\"\nimport { springs, motionTokens } from \"@\u002Flib\u002Fmotion-tokens\"\n\n\u003Cmotion.div\n  drag\n  dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}\n  dragElastic={0.1}\n  whileDrag={{\n    scale: motionTokens.scale.pop,\n    boxShadow: \"0 16px 40px rgba(0,0,0,0.2)\",\n  }}\n  dragTransition={springs.release}\n\u002F>\n```\n\n### Drag-to-dismiss sheet\n\n```tsx\n\"use client\"\nimport { motion, useMotionValue, useTransform } from \"motion\u002Freact\"\n\nexport function BottomSheet({ onClose }: { onClose: () => void }) {\n  const y = useMotionValue(0)\n  const opacity = useTransform(y, [0, 200], [1, 0])\n\n  return (\n    \u003Cmotion.div\n      drag=\"y\"\n      dragConstraints={{ top: 0 }}\n      style={{ y, opacity }}\n      onDragEnd={(_, info) => {\n        \u002F\u002F Rule 3: combine offset + velocity\n        if (info.offset.y > 120 || info.velocity.y > 500) onClose()\n      }}\n    \u002F>\n  )\n}\n```\n\n### Reorderable list\n\n```tsx\n\"use client\"\nimport { Reorder } from \"motion\u002Freact\"\n\nexport function SortableList() {\n  const [items, setItems] = useState(initialItems)\n  return (\n    \u003CReorder.Group axis=\"y\" values={items} onReorder={setItems}>\n      {items.map((item) => (\n        \u003CReorder.Item key={item.id} value={item}>\n          {item.label}\n        \u003C\u002FReorder.Item>\n      ))}\n    \u003C\u002FReorder.Group>\n  )\n}\n```\n\n### Swipe detection\n\n```tsx\n\"use client\"\nimport { motion } from \"motion\u002Freact\"\n\nconst OFFSET_THRESHOLD  = 50\nconst VELOCITY_THRESHOLD = 300\n\n\u003Cmotion.div\n  drag=\"x\"\n  dragConstraints={{ left: 0, right: 0 }}\n  onDragEnd={(_, info) => {\n    const swipedRight = info.offset.x > OFFSET_THRESHOLD  || info.velocity.x > VELOCITY_THRESHOLD\n    const swipedLeft  = info.offset.x \u003C -OFFSET_THRESHOLD || info.velocity.x \u003C -VELOCITY_THRESHOLD\n    if (swipedRight) onSwipeRight()\n    if (swipedLeft)  onSwipeLeft()\n  }}\n\u002F>\n```\n\n### Long press hook\n\n```tsx\nimport { useRef } from \"react\"\n\nexport function useLongPress(callback: () => void, ms = 600) {\n  const timerRef = useRef\u003CReturnType\u003Ctypeof setTimeout>>()\n  return {\n    onPointerDown:  () => { timerRef.current = setTimeout(callback, ms) },\n    onPointerUp:    () => clearTimeout(timerRef.current),\n    onPointerLeave: () => clearTimeout(timerRef.current),\n  }\n}\n```\n\n### Word-by-word reveal\n\n```tsx\n\"use client\"\nimport { motion } from \"motion\u002Freact\"\nimport { springs } from \"@\u002Flib\u002Fmotion-tokens\"\n\nexport function AnimatedText({ text }: { text: string }) {\n  return (\n    \u003Cmotion.p\n      variants={{ visible: { transition: { staggerChildren: 0.05 } } }}\n      initial=\"hidden\"\n      animate=\"visible\"\n    >\n      {text.split(\" \").map((word, i) => (\n        \u003Cmotion.span\n          key={i}\n          className=\"inline-block mr-1\"\n          variants={{\n            hidden:  { opacity: 0, y: 12 },\n            visible: { opacity: 1, y: 0, transition: springs.gentle },\n          }}\n        >\n          {word}\n        \u003C\u002Fmotion.span>\n      ))}\n    \u003C\u002Fmotion.p>\n  )\n}\n```\n\n### Number counter\n\n```tsx\n\"use client\"\nimport { useRef, useEffect } from \"react\"\nimport { animate } from \"motion\"\nimport { motionTokens } from \"@\u002Flib\u002Fmotion-tokens\"\n\nexport function Counter({ to }: { to: number }) {\n  const nodeRef = useRef\u003CHTMLSpanElement>(null)\n\n  useEffect(() => {\n    const controls = animate(0, to, {\n      duration: motionTokens.duration.crawl,\n      ease: motionTokens.easing.smooth,\n      onUpdate: (v) => {\n        if (nodeRef.current) nodeRef.current.textContent = Math.round(v).toString()\n      },\n    })\n    return controls.stop   \u002F\u002F Rule 7: cleanup\n  }, [to])\n\n  return \u003Cspan ref={nodeRef} \u002F>\n}\n```\n\n### SVG path draw-on\n\n```tsx\n\"use client\"\nimport { motion } from \"motion\u002Freact\"\nimport { motionTokens } from \"@\u002Flib\u002Fmotion-tokens\"\n\n\u003Cmotion.path\n  d=\"M 0 100 Q 50 0 100 100\"\n  initial={{ pathLength: 0, opacity: 0 }}\n  animate={{ pathLength: 1, opacity: 1 }}\n  transition={{ duration: motionTokens.duration.slow, ease: motionTokens.easing.smooth }}\n\u002F>\n```\n\n### Stroke progress ring\n\n```tsx\n\"use client\"\nimport { motion } from \"motion\u002Freact\"\nimport { motionTokens } from \"@\u002Flib\u002Fmotion-tokens\"\n\nconst CIRCUMFERENCE = 2 * Math.PI * 40   \u002F\u002F r=40\n\nexport function ProgressRing({ progress }: { progress: number }) {\n  return (\n    \u003Csvg width=\"100\" height=\"100\" viewBox=\"0 0 100 100\">\n      \u003Ccircle cx=\"50\" cy=\"50\" r=\"40\" fill=\"none\" stroke=\"#e5e7eb\" strokeWidth=\"8\" \u002F>\n      \u003Cmotion.circle\n        cx=\"50\" cy=\"50\" r=\"40\"\n        fill=\"none\" stroke=\"#6366f1\" strokeWidth=\"8\"\n        strokeLinecap=\"round\"\n        strokeDasharray={CIRCUMFERENCE}\n        animate={{ strokeDashoffset: CIRCUMFERENCE - (progress \u002F 100) * CIRCUMFERENCE }}\n        transition={{ duration: motionTokens.duration.normal, ease: motionTokens.easing.smooth }}\n        style={{ rotate: -90, transformOrigin: \"center\" }}\n      \u002F>\n    \u003C\u002Fsvg>\n  )\n}\n```\n\n### useScrollReveal hook\n\n```tsx\n\"use client\"\nimport { useRef } from \"react\"\nimport { useScroll, useTransform } from \"motion\u002Freact\"\nimport { motionTokens } from \"@\u002Flib\u002Fmotion-tokens\"\n\nexport function useScrollReveal() {\n  const ref = useRef(null)\n  const { scrollYProgress } = useScroll({ target: ref, offset: [\"start end\", \"end start\"] })\n  const opacity = useTransform(scrollYProgress, [0, 0.3], [0, 1])\n  const y       = useTransform(scrollYProgress, [0, 0.3], [motionTokens.distance.lg, 0])\n  return { ref, style: { opacity, y } }\n}\n\n\u002F\u002F Usage\nconst { ref, style } = useScrollReveal()\n\u003Cmotion.section ref={ref} style={style} \u002F>\n```\n\n### Cursor follower\n\n```tsx\n\"use client\"\nimport { useEffect } from \"react\"\nimport { motion, useMotionValue, useSpring } from \"motion\u002Freact\"\nimport { springs } from \"@\u002Flib\u002Fmotion-tokens\"\n\nexport function CursorFollower() {\n  const x = useMotionValue(-100)\n  const y = useMotionValue(-100)\n  const sx = useSpring(x, springs.gentle)\n  const sy = useSpring(y, springs.gentle)\n\n  useEffect(() => {\n    const move = (e: MouseEvent) => { x.set(e.clientX); y.set(e.clientY) }\n    window.addEventListener(\"mousemove\", move)\n    return () => window.removeEventListener(\"mousemove\", move)   \u002F\u002F Rule 7\n  }, [])\n\n  return (\n    \u003Cmotion.div\n      className=\"fixed top-0 left-0 w-6 h-6 rounded-full bg-indigo-500\n                 pointer-events-none -translate-x-1\u002F2 -translate-y-1\u002F2 z-50\"\n      style={{ x: sx, y: sy }}\n    \u002F>\n  )\n}\n```\n\n### Shimmer skeleton\n\n```tsx\n\"use client\"\nimport { useEffect } from \"react\"\nimport { motion, useAnimation } from \"motion\u002Freact\"\nimport { motionTokens } from \"@\u002Flib\u002Fmotion-tokens\"\n\nexport function ShimmerSkeleton({ className = \"\" }: { className?: string }) {\n  const controls = useAnimation()\n\n  useEffect(() => {\n    const play = () =>\n      controls.start({\n        x: [\"-100%\", \"100%\"],\n        transition: {\n          repeat: Infinity,\n          duration: motionTokens.duration.crawl,\n          ease: motionTokens.easing.linear,\n        },\n      })\n\n    const handleVisibility = () => {\n      if (document.visibilityState === \"hidden\") controls.stop()\n      else void play()\n    }\n\n    void play()\n    document.addEventListener(\"visibilitychange\", handleVisibility)\n    return () => {\n      controls.stop()\n      document.removeEventListener(\"visibilitychange\", handleVisibility)\n    }\n  }, [controls])\n\n  return (\n    \u003Cdiv className={`relative overflow-hidden bg-gray-200 rounded ${className}`}>\n      \u003Cmotion.div\n        className=\"absolute inset-0 bg-gradient-to-r from-transparent via-white\u002F60 to-transparent\"\n        initial={{ x: \"-100%\" }}\n        animate={controls}\n      \u002F>\n    \u003C\u002Fdiv>\n  )\n}\n```\n\n### Button loading state\n\n```tsx\n\"use client\"\nimport { motion, AnimatePresence } from \"motion\u002Freact\"\nimport { motionTokens, springs } from \"@\u002Flib\u002Fmotion-tokens\"\n\nexport function LoadingButton({\n  loading,\n  label,\n  onClick,\n}: {\n  loading: boolean\n  label: string\n  onClick: () => void\n}) {\n  return (\n    \u003Cmotion.button\n      onClick={onClick}\n      animate={{ opacity: loading ? 0.7 : 1 }}\n      whileTap={loading ? {} : { scale: motionTokens.scale.press }}\n      transition={springs.snappy}\n      disabled={loading}\n    >\n      \u003CAnimatePresence mode=\"wait\">\n        {loading ? (\n          \u003Cmotion.span\n            key=\"loading\"\n            initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}\n            transition={{ duration: motionTokens.duration.fast }}\n          >\n            …\n          \u003C\u002Fmotion.span>\n        ) : (\n          \u003Cmotion.span\n            key=\"label\"\n            initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}\n            transition={{ duration: motionTokens.duration.fast }}\n          >\n            {label}\n          \u003C\u002Fmotion.span>\n        )}\n      \u003C\u002FAnimatePresence>\n    \u003C\u002Fmotion.button>\n  )\n}\n```\n\n### Infinite animation with visibility pause\n\n```tsx\n\"use client\"\nimport { useEffect } from \"react\"\nimport { motion, useAnimation } from \"motion\u002Freact\"\nimport { motionTokens } from \"@\u002Flib\u002Fmotion-tokens\"\n\nexport function PulseDot() {\n  const controls = useAnimation()\n\n  useEffect(() => {\n    const pulse = () =>\n      controls.start({\n        scale: [1, 1.4, 1],\n        opacity: [1, 0.6, 1],\n        transition: { repeat: Infinity, duration: motionTokens.duration.crawl },\n      })\n\n    \u002F\u002F Rule 2: pause when tab is hidden\n    const handleVisibility = () => {\n      if (document.visibilityState === \"hidden\") controls.stop()\n      else void pulse()\n    }\n\n    void pulse()\n    document.addEventListener(\"visibilitychange\", handleVisibility)\n    \u002F\u002F Rule 7: stop controls and remove listeners on unmount.\n    return () => {\n      controls.stop()\n      document.removeEventListener(\"visibilitychange\", handleVisibility)\n    }\n  }, [controls])\n\n  return \u003Cmotion.span className=\"w-2 h-2 rounded-full bg-green-400\" animate={controls} \u002F>\n}\n```\n\n## End-to-End Example\n\nDrag-to-dismiss sheet with shimmer content, loading state, and reduced motion\nsupport — combining `useMotionValue`, `useTransform`, `useSafeMotion`,\n`AnimatePresence`, and tokens from `motion-foundations`:\n\n```tsx\n\"use client\"\nimport { useState } from \"react\"\nimport { motion, AnimatePresence, useMotionValue, useTransform } from \"motion\u002Freact\"\nimport { springs, motionTokens } from \"@\u002Flib\u002Fmotion-tokens\"\nimport { useSafeMotion } from \"@\u002Fhooks\u002Fuse-reduced-motion\"\nimport { ShimmerSkeleton } from \".\u002Fshimmer-skeleton\"\n\nexport function DismissibleSheet({\n  isOpen,\n  onClose,\n  loading,\n  children,\n}: {\n  isOpen: boolean\n  onClose: () => void\n  loading: boolean\n  children: React.ReactNode\n}) {\n  const safe = useSafeMotion(motionTokens.distance.xl)\n  const y = useMotionValue(0)\n  const opacity = useTransform(y, [0, 200], [1, 0])\n\n  return (\n    \u003CAnimatePresence>\n      {isOpen && (\n        \u003C>\n          {\u002F* Backdrop *\u002F}\n          \u003Cmotion.div\n            key=\"backdrop\"\n            className=\"fixed inset-0 bg-black\u002F40\"\n            initial={{ opacity: 0 }}\n            animate={{ opacity: 1 }}\n            exit={{ opacity: 0 }}\n            onClick={onClose}\n          \u002F>\n\n          {\u002F* Sheet — drag-to-dismiss *\u002F}\n          \u003Cmotion.div\n            key=\"sheet\"\n            className=\"fixed bottom-0 inset-x-0 rounded-t-2xl bg-white p-6\"\n            drag=\"y\"\n            dragConstraints={{ top: 0 }}\n            style={{ y, opacity }}\n            onDragEnd={(_, info) => {\n              if (info.offset.y > 120 || info.velocity.y > 500) onClose()\n            }}\n            initial={safe.initial}\n            animate={safe.animate}\n            exit={safe.exit}\n            transition={springs.gentle}\n          >\n            {loading ? (\n              \u003Cdiv className=\"space-y-3\">\n                \u003CShimmerSkeleton className=\"h-4 w-3\u002F4\" \u002F>\n                \u003CShimmerSkeleton className=\"h-4 w-1\u002F2\" \u002F>\n                \u003CShimmerSkeleton className=\"h-20 w-full\" \u002F>\n              \u003C\u002Fdiv>\n            ) : children}\n          \u003C\u002Fmotion.div>\n        \u003C\u002F>\n      )}\n    \u003C\u002FAnimatePresence>\n  )\n}\n```\n\n## Constraints \u002F Non-Goals\n\nThis skill does **not** cover:\n\n- Token and spring definitions → see `motion-foundations`\n- Standard UI patterns (button, modal, stagger, page transitions) → see `motion-patterns`\n- CSS-only animations or Tailwind `animate-*` without `motion\u002Freact`\n- Canvas or WebGL-based animation (Three.js, Pixi, etc.)\n- Full drag-and-drop systems with external state managers (dnd-kit, react-beautiful-dnd)\n- Game-loop or frame-by-frame animation\n\n## Anti-Patterns\n\n| Anti-pattern | Rule violated | Fix |\n| ---------------------------------------------- | ------- | ------------------------------------------------ |\n| `drag` tested only on desktop | Rule 1 | Test on touch emulator and real device |\n| `animate={{ repeat: Infinity }}` with no pause | Rule 2 | Add `visibilitychange` listener |\n| `onDragEnd` checking only offset, not velocity | Rule 3 | Check both `info.offset` and `info.velocity` |\n| `animate(scope, ...)` before `useEffect` | Rule 4 | Call `animate()` only after mount |\n| `const x = new MotionValue(0)` in render | Rule 5 | Use `const x = useMotionValue(0)` |\n| `transition={{ duration: 1.2 }}` inline | Rule 6 | Use `motionTokens.duration.crawl` |\n| `useEffect` without cleanup | Rule 7 | Return `removeEventListener` \u002F `controls.stop` |\n| SVG morph between paths with different commands | Rule 8 | Normalize path commands before animating |\n\n## Related Skills\n\n- **`motion-foundations`** — defines all tokens, springs, `useSafeMotion`, and SSR guards imported here. Must be set up before using this skill.\n- **`motion-patterns`** — handles standard UI patterns (button, modal, stagger, page transitions, scroll reveals). Use it before reaching for the advanced patterns here.\n","# Motion Advanced\n\nComplex, interactive, and physics-based animation patterns.\nRequires `motion-foundations` to be set up first.\nUse these when `motion-patterns` is not enough.\n\n## When to Activate\n\n- Building drag-to-dismiss sheets, swipe gestures, or reorderable lists\n- Animating text word-by-word, character-by-character, or as a live counter\n- Drawing SVG paths, morphing icons, or animating circular progress\n- Writing a custom animation hook (`useScrollReveal`, magnetic button, cursor follower)\n- Sequencing multi-step animations imperatively with `useAnimate`\n- Building spinners, shimmer skeletons, pulse indicators, or loading button states\n\n## Outputs\n\nThis skill produces:\n\n- Drag interactions: draggable cards, drag-to-dismiss sheets, `Reorder.Group` lists\n- Gesture hooks: swipe detection, long press, pinch outline\n- Text animation components: word reveal, character typewriter, number counter\n- SVG animation: path draw-on, icon morph, stroke progress ring\n- Custom hooks: `useScrollReveal`, `useHoverScale`, `useNavigationDirection`, `useInViewOnce`\n- Imperative sequences via `useAnimate` with interrupt-safe `async\u002Fawait`\n- Loader components: spinner, shimmer, pulse dot, progress bar, button loading state\n\n## Principles\n\n- Physics-based motion (`useSpring`, `springs.*`) always feels more natural than duration-based for direct manipulation.\n- `useMotionValue` + `useTransform` computes derived values without triggering re-renders.\n- `useAnimate` sequences are imperative and interrupt-safe — calling `animate()` mid-flight cancels the previous animation automatically.\n- Motion values (`useMotionValue`, `useSpring`) are SSR-safe and do not cause hydration errors.\n\n## Rules\n\n1. **Drag interactions must be tested on touch devices**, not just mouse. `drag` prop works on both but feel and threshold differ.\n2. **Infinite animations must pause when `document.visibilityState === \"hidden\"`.** Background tabs must not consume GPU\u002FCPU.\n3. **Swipe threshold must be explicit.** Never infer intent from velocity alone; combine `offset` + `velocity` checks.\n4. **`useAnimate` scope ref must be attached to a mounted DOM element.** Calling `animate()` before mount throws silently.\n5. **Motion values must not be recreated on render.** `useMotionValue(0)` inside a component body is correct; `new MotionValue(0)` in a render is not.\n6. **All token values are imported from `motion-foundations`.** No inline numbers.\n7. **Custom hooks must handle cleanup.** Every `window.addEventListener` needs a matching `removeEventListener` in the `useEffect` return.\n8. **SVG morphing requires equal path command counts.** Paths with different command structures snap instead of interpolating.\n\n## Decision Guidance\n\n### Choosing the right advanced API\n\n| Scenario | API |\n| ------------------------------ | -------------------------------- |\n| Drag with physics on release | `drag` + `dragTransition: springs.release` |\n| Ordered drag-to-reorder list | `Reorder.Group` + `Reorder.Item` |\n| Dismiss on drag offset | `drag=\"y\"` + `onDragEnd` offset check |\n| Swipe left\u002Fright | `drag=\"x\"` + `onDragEnd` offset check |\n| Long press | `useLongPress` hook |\n| Value smoothed over time | `useSpring` |\n| Value derived from another | `useTransform` |\n| Multi-step sequence | `useAnimate` with `async\u002Fawait` |\n| One-shot imperative animation | `animate()` from `motion` |\n| Text entering word by word | Stagger on `inline-block` spans |\n| SVG drawing on | `pathLength` 0 → 1 |\n| SVG morph | `d` attribute tween (equal commands) |\n| Circular progress | `strokeDashoffset` tween |\n\n### When to use `useSpring` vs a spring transition\n\n| | `useSpring` | `transition: springs.*` |\n| -------------- | ---------------------------------------- | ----------------------- |\n| Use for | Cursor follower, pointer-tracked values | Discrete state changes |\n| Updates | Continuous, on every frame | Triggered by state change |\n| Interrupt | Smooth — physics picks up from velocity | Restarts from current value |\n\n## Core Concepts\n\n### useMotionValue + useTransform\n\nReactive computation without re-renders:\n\n```tsx\nconst x = useMotionValue(0)\nconst opacity = useTransform(x, [-200, 0, 200], [0, 1, 0])\n\u002F\u002F opacity updates every frame as x changes — no setState, no re-render\n```\n\n### useAnimate\n\nReturns `[scope, animate]`. The scope ref must be attached to a DOM element.\n`animate()` calls are interrupt-safe — calling mid-flight cancels the previous run.\n\n```tsx\nconst [scope, animate] = useAnimate()\n\nasync function play() {\n  await animate(\".step-1\", { opacity: 1 }, { duration: 0.3 })\n  await animate(\".step-2\", { x: 0 },       { duration: 0.4 })\n        animate(\".step-3\", { scale: 1 },    { duration: 0.25 })  \u002F\u002F fire and forget\n}\n\nreturn \u003Cdiv ref={scope}>...\u003C\u002Fdiv>\n```\n\n## Code Examples\n\n### Draggable card\n\n```tsx\n\"use client\"\nimport { motion } from \"motion\u002Freact\"\nimport { springs, motionTokens } from \"@\u002Flib\u002Fmotion-tokens\"\n\n\u003Cmotion.div\n  drag\n  dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}\n  dragElastic={0.1}\n  whileDrag={{\n    scale: motionTokens.scale.pop,\n    boxShadow: \"0 16px 40px rgba(0,0,0,0.2)\",\n  }}\n  dragTransition={springs.release}\n\u002F>\n```\n\n### Drag-to-dismiss sheet\n\n```tsx\n\"use client\"\nimport { motion, useMotionValue, useTransform } from \"motion\u002Freact\"\n\nexport function BottomSheet({ onClose }: { onClose: () => void }) {\n  const y = useMotionValue(0)\n  const opacity = useTransform(y, [0, 200], [1, 0])\n\n  return (\n    \u003Cmotion.div\n      drag=\"y\"\n      dragConstraints={{ top: 0 }}\n      style={{ y, opacity }}\n      onDragEnd={(_, info) => {\n        \u002F\u002F Rule 3: combine offset + velocity\n        if (info.offset.y > 120 || info.velocity.y > 500) onClose()\n      }}\n    \u002F>\n  )\n}\n```\n\n### Reorderable list\n\n```tsx\n\"use client\"\nimport { Reorder } from \"motion\u002Freact\"\n\nexport function SortableList() {\n  const [items, setItems] = useState(initialItems)\n  return (\n    \u003CReorder.Group axis=\"y\" values={items} onReorder={setItems}>\n      {items.map((item) => (\n        \u003CReorder.Item key={item.id} value={item}>\n          {item.label}\n        \u003C\u002FReorder.Item>\n      ))}\n    \u003C\u002FReorder.Group>\n  )\n}\n```\n\n### Swipe detection\n\n```tsx\n\"use client\"\nimport { motion } from \"motion\u002Freact\"\n\nconst OFFSET_THRESHOLD  = 50\nconst VELOCITY_THRESHOLD = 300\n\n\u003Cmotion.div\n  drag=\"x\"\n  dragConstraints={{ left: 0, right: 0 }}\n  onDragEnd={(_, info) => {\n    const swipedRight = info.offset.x > OFFSET_THRESHOLD  || info.velocity.x > VELOCITY_THRESHOLD\n    const swipedLeft  = info.offset.x \u003C -OFFSET_THRESHOLD || info.velocity.x \u003C -VELOCITY_THRESHOLD\n    if (swipedRight) onSwipeRight()\n    if (swipedLeft)  onSwipeLeft()\n  }}\n\u002F>\n```\n\n### Long press hook\n\n```tsx\nimport { useRef } from \"react\"\n\nexport function useLongPress(callback: () => void, ms = 600) {\n  const timerRef = useRef\u003CReturnType\u003Ctypeof setTimeout>>()\n  return {\n    onPointerDown:  () => { timerRef.current = setTimeout(callback, ms) },\n    onPointerUp:    () => clearTimeout(timerRef.current),\n    onPointerLeave: () => clearTimeout(timerRef.current),\n  }\n}\n```\n\n### Word-by-word reveal\n\n```tsx\n\"use client\"\nimport { motion } from \"motion\u002Freact\"\nimport { springs } from \"@\u002Flib\u002Fmotion-tokens\"\n\nexport function AnimatedText({ text }: { text: string }) {\n  return (\n    \u003Cmotion.p\n      variants={{ visible: { transition: { staggerChildren: 0.05 } } }}\n      initial=\"hidden\"\n      animate=\"visible\"\n    >\n      {text.split(\" \").map((word, i) => (\n        \u003Cmotion.span\n          key={i}\n          className=\"inline-block mr-1\"\n          variants={{\n            hidden:  { opacity: 0, y: 12 },\n            visible: { opacity: 1, y: 0, transition: springs.gentle },\n          }}\n        >\n          {word}\n        \u003C\u002Fmotion.span>\n      ))}\n    \u003C\u002Fmotion.p>\n  )\n}\n```\n\n### Number counter\n\n```tsx\n\"use client\"\nimport { useRef, useEffect } from \"react\"\nimport { animate } from \"motion\"\nimport { motionTokens } from \"@\u002Flib\u002Fmotion-tokens\"\n\nexport function Counter({ to }: { to: number }) {\n  const nodeRef = useRef\u003CHTMLSpanElement>(null)\n\n  useEffect(() => {\n    const controls = animate(0, to, {\n      duration: motionTokens.duration.crawl,\n      ease: motionTokens.easing.smooth,\n      onUpdate: (v) => {\n        if (nodeRef.current) nodeRef.current.textContent = Math.round(v).toString()\n      },\n    })\n    return controls.stop   \u002F\u002F Rule 7: cleanup\n  }, [to])\n\n  return \u003Cspan ref={nodeRef} \u002F>\n}\n```\n\n### SVG path draw-on\n\n```tsx\n\"use client\"\nimport { motion } from \"motion\u002Freact\"\nimport { motionTokens } from \"@\u002Flib\u002Fmotion-tokens\"\n\n\u003Cmotion.path\n  d=\"M 0 100 Q 50 0 100 100\"\n  initial={{ pathLength: 0, opacity: 0 }}\n  animate={{ pathLength: 1, opacity: 1 }}\n  transition={{ duration: motionTokens.duration.slow, ease: motionTokens.easing.smooth }}\n\u002F>\n```\n\n### Stroke progress ring\n\n```tsx\n\"use client\"\nimport { motion } from \"motion\u002Freact\"\nimport { motionTokens } from \"@\u002Flib\u002Fmotion-tokens\"\n\nconst CIRCUMFERENCE = 2 * Math.PI * 40   \u002F\u002F r=40\n\nexport function ProgressRing({ progress }: { progress: number }) {\n  return (\n    \u003Csvg width=\"100\" height=\"100\" viewBox=\"0 0 100 100\">\n      \u003Ccircle cx=\"50\" cy=\"50\" r=\"40\" fill=\"none\" stroke=\"#e5e7eb\" strokeWidth=\"8\" \u002F>\n      \u003Cmotion.circle\n        cx=\"50\" cy=\"50\" r=\"40\"\n        fill=\"none\" stroke=\"#6366f1\" strokeWidth=\"8\"\n        strokeLinecap=\"round\"\n        strokeDasharray={CIRCUMFERENCE}\n        animate={{ strokeDashoffset: CIRCUMFERENCE - (progress \u002F 100) * CIRCUMFERENCE }}\n        transition={{ duration: motionTokens.duration.normal, ease: motionTokens.easing.smooth }}\n        style={{ rotate: -90, transformOrigin: \"center\" }}\n      \u002F>\n    \u003C\u002Fsvg>\n  )\n}\n```\n\n### useScrollReveal hook\n\n```tsx\n\"use client\"\nimport { useRef } from \"react\"\nimport { useScroll, useTransform } from \"motion\u002Freact\"\nimport { motionTokens } from \"@\u002Flib\u002Fmotion-tokens\"\n\nexport function useScrollReveal() {\n  const ref = useRef(null)\n  const { scrollYProgress } = useScroll({ target: ref, offset: [\"start end\", \"end start\"] })\n  const opacity = useTransform(scrollYProgress, [0, 0.3], [0, 1])\n  const y       = useTransform(scrollYProgress, [0, 0.3], [motionTokens.distance.lg, 0])\n  return { ref, style: { opacity, y } }\n}\n\n\u002F\u002F Usage\nconst { ref, style } = useScrollReveal()\n\u003Cmotion.section ref={ref} style={style} \u002F>\n```\n\n### Cursor follower\n\n```tsx\n\"use client\"\nimport { useEffect } from \"react\"\nimport { motion, useMotionValue, useSpring } from \"motion\u002Freact\"\nimport { springs } from \"@\u002Flib\u002Fmotion-tokens\"\n\nexport function CursorFollower() {\n  const x = useMotionValue(-100)\n  const y = useMotionValue(-100)\n  const sx = useSpring(x, springs.gentle)\n  const sy = useSpring(y, springs.gentle)\n\n  useEffect(() => {\n    const move = (e: MouseEvent) => { x.set(e.clientX); y.set(e.clientY) }\n    window.addEventListener(\"mousemove\", move)\n    return () => window.removeEventListener(\"mousemove\", move)   \u002F\u002F Rule 7\n  }, [])\n\n  return (\n    \u003Cmotion.div\n      className=\"fixed top-0 left-0 w-6 h-6 rounded-full bg-indigo-500\n                 pointer-events-none -translate-x-1\u002F2 -translate-y-1\u002F2 z-50\"\n      style={{ x: sx, y: sy }}\n    \u002F>\n  )\n}\n```\n\n### Shimmer skeleton\n\n```tsx\n\"use client\"\nimport { useEffect } from \"react\"\nimport { motion, useAnimation } from \"motion\u002Freact\"\nimport { motionTokens } from \"@\u002Flib\u002Fmotion-tokens\"\n\nexport function ShimmerSkeleton({ className = \"\" }: { className?: string }) {\n  const controls = useAnimation()\n\n  useEffect(() => {\n    const play = () =>\n      controls.start({\n        x: [\"-100%\", \"100%\"],\n        transition: {\n          repeat: Infinity,\n          duration: motionTokens.duration.crawl,\n          ease: motionTokens.easing.linear,\n        },\n      })\n\n    const handleVisibility = () => {\n      if (document.visibilityState === \"hidden\") controls.stop()\n      else void play()\n    }\n\n    void play()\n    document.addEventListener(\"visibilitychange\", handleVisibility)\n    return () => {\n      controls.stop()\n      document.removeEventListener(\"visibilitychange\", handleVisibility)\n    }\n  }, [controls])\n\n  return (\n    \u003Cdiv className={`relative overflow-hidden bg-gray-200 rounded ${className}`}>\n      \u003Cmotion.div\n        className=\"absolute inset-0 bg-gradient-to-r from-transparent via-white\u002F60 to-transparent\"\n        initial={{ x: \"-100%\" }}\n        animate={controls}\n      \u002F>\n    \u003C\u002Fdiv>\n  )\n}\n```\n\n### Button loading state\n\n```tsx\n\"use client\"\nimport { motion, AnimatePresence } from \"motion\u002Freact\"\nimport { motionTokens, springs } from \"@\u002Flib\u002Fmotion-tokens\"\n\nexport function LoadingButton({\n  loading,\n  label,\n  onClick,\n}: {\n  loading: boolean\n  label: string\n  onClick: () => void\n}) {\n  return (\n    \u003Cmotion.button\n      onClick={onClick}\n      animate={{ opacity: loading ? 0.7 : 1 }}\n      whileTap={loading ? {} : { scale: motionTokens.scale.press }}\n      transition={springs.snappy}\n      disabled={loading}\n    >\n      \u003CAnimatePresence mode=\"wait\">\n        {loading ? (\n          \u003Cmotion.span\n            key=\"loading\"\n            initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}\n            transition={{ duration: motionTokens.duration.fast }}\n          >\n            …\n          \u003C\u002Fmotion.span>\n        ) : (\n          \u003Cmotion.span\n            key=\"label\"\n            initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}\n            transition={{ duration: motionTokens.duration.fast }}\n          >\n            {label}\n          \u003C\u002Fmotion.span>\n        )}\n      \u003C\u002FAnimatePresence>\n    \u003C\u002Fmotion.button>\n  )\n}\n```\n\n### Infinite animation with visibility pause\n\n```tsx\n\"use client\"\nimport { useEffect } from \"react\"\nimport { motion, useAnimation } from \"motion\u002Freact\"\nimport { motionTokens } from \"@\u002Flib\u002Fmotion-tokens\"\n\nexport function PulseDot() {\n  const controls = useAnimation()\n\n  useEffect(() => {\n    const pulse = () =>\n      controls.start({\n        scale: [1, 1.4, 1],\n        opacity: [1, 0.6, 1],\n        transition: { repeat: Infinity, duration: motionTokens.duration.crawl },\n      })\n\n    \u002F\u002F Rule 2: pause when tab is hidden\n    const handleVisibility = () => {\n      if (document.visibilityState === \"hidden\") controls.stop()\n      else void pulse()\n    }\n\n    void pulse()\n    document.addEventListener(\"visibilitychange\", handleVisibility)\n    \u002F\u002F Rule 7: stop controls and remove listeners on unmount.\n    return () => {\n      controls.stop()\n      document.removeEventListener(\"visibilitychange\", handleVisibility)\n    }\n  }, [controls])\n\n  return \u003Cmotion.span className=\"w-2 h-2 rounded-full bg-green-400\" animate={controls} \u002F>\n}\n```\n\n## End-to-End Example\n\nDrag-to-dismiss sheet with shimmer content, loading state, and reduced motion\nsupport — combining `useMotionValue`, `useTransform`, `useSafeMotion`,\n`AnimatePresence`, and tokens from `motion-foundations`:\n\n```tsx\n\"use client\"\nimport { useState } from \"react\"\nimport { motion, AnimatePresence, useMotionValue, useTransform } from \"motion\u002Freact\"\nimport { springs, motionTokens } from \"@\u002Flib\u002Fmotion-tokens\"\nimport { useSafeMotion } from \"@\u002Fhooks\u002Fuse-reduced-motion\"\nimport { ShimmerSkeleton } from \".\u002Fshimmer-skeleton\"\n\nexport function DismissibleSheet({\n  isOpen,\n  onClose,\n  loading,\n  children,\n}: {\n  isOpen: boolean\n  onClose: () => void\n  loading: boolean\n  children: React.ReactNode\n}) {\n  const safe = useSafeMotion(motionTokens.distance.xl)\n  const y = useMotionValue(0)\n  const opacity = useTransform(y, [0, 200], [1, 0])\n\n  return (\n    \u003CAnimatePresence>\n      {isOpen && (\n        \u003C>\n          {\u002F* Backdrop *\u002F}\n          \u003Cmotion.div\n            key=\"backdrop\"\n            className=\"fixed inset-0 bg-black\u002F40\"\n            initial={{ opacity: 0 }}\n            animate={{ opacity: 1 }}\n            exit={{ opacity: 0 }}\n            onClick={onClose}\n          \u002F>\n\n          {\u002F* Sheet — drag-to-dismiss *\u002F}\n          \u003Cmotion.div\n            key=\"sheet\"\n            className=\"fixed bottom-0 inset-x-0 rounded-t-2xl bg-white p-6\"\n            drag=\"y\"\n            dragConstraints={{ top: 0 }}\n            style={{ y, opacity }}\n            onDragEnd={(_, info) => {\n              if (info.offset.y > 120 || info.velocity.y > 500) onClose()\n            }}\n            initial={safe.initial}\n            animate={safe.animate}\n            exit={safe.exit}\n            transition={springs.gentle}\n          >\n            {loading ? (\n              \u003Cdiv className=\"space-y-3\">\n                \u003CShimmerSkeleton className=\"h-4 w-3\u002F4\" \u002F>\n                \u003CShimmerSkeleton className=\"h-4 w-1\u002F2\" \u002F>\n                \u003CShimmerSkeleton className=\"h-20 w-full\" \u002F>\n              \u003C\u002Fdiv>\n            ) : children}\n          \u003C\u002Fmotion.div>\n        \u003C\u002F>\n      )}\n    \u003C\u002FAnimatePresence>\n  )\n}\n```\n\n## Constraints \u002F Non-Goals\n\nThis skill does **not** cover:\n\n- Token and spring definitions → see `motion-foundations`\n- Standard UI patterns (button, modal, stagger, page transitions) → see `motion-patterns`\n- CSS-only animations or Tailwind `animate-*` without `motion\u002Freact`\n- Canvas or WebGL-based animation (Three.js, Pixi, etc.)\n- Full drag-and-drop systems with external state managers (dnd-kit, react-beautiful-dnd)\n- Game-loop or frame-by-frame animation\n\n## Anti-Patterns\n\n| Anti-pattern | Rule violated | Fix |\n| ---------------------------------------------- | ------- | ------------------------------------------------ |\n| `drag` tested only on desktop | Rule 1 | Test on touch emulator and real device |\n| `animate={{ repeat: Infinity }}` with no pause | Rule 2 | Add `visibilitychange` listener |\n| `onDragEnd` checking only offset, not velocity | Rule 3 | Check both `info.offset` and `info.velocity` |\n| `animate(scope, ...)` before `useEffect` | Rule 4 | Call `animate()` only after mount |\n| `const x = new MotionValue(0)` in render | Rule 5 | Use `const x = useMotionValue(0)` |\n| `transition={{ duration: 1.2 }}` inline | Rule 6 | Use `motionTokens.duration.crawl` |\n| `useEffect` without cleanup | Rule 7 | Return `removeEventListener` \u002F `controls.stop` |\n| SVG morph between paths with different commands | Rule 8 | Normalize path commands before animating |\n\n## Related Skills\n\n- **`motion-foundations`** — defines all tokens, springs, `useSafeMotion`, and SSR guards imported here. Must be set up before using this skill.\n- **`motion-patterns`** — handles standard UI patterns (button, modal, stagger, page transitions, scroll reveals). Use it before reaching for the advanced patterns here.\n",{"name":5,"tags":56,"author":59,"version":60,"category":42,"description":7},[33,57,32,50,58],"animation","svg","jeff",1,"master","8ec37df6d71fb664ea19d32c079e7d3ff931a8e0","skills\u002Fecc\u002Fmotion-advanced",3,"https:\u002F\u002Fgithub.com\u002FS3YED\u002Fappie-kit","https:\u002F\u002Fweblyfe.ai","https:\u002F\u002Fgithub.com\u002FS3YED","2026-08-27 01:54:02",{"source":8,"skillName":5,"commands":70,"prompt":80,"zipAvailable":81},[71,74,77],{"tool":72,"command":73},"npx","npx skills add S3YED\u002Fappie-kit --skill motion-advanced",{"tool":75,"command":76},"bunx","bunx skills add S3YED\u002Fappie-kit --skill motion-advanced",{"tool":78,"command":79},"pnpm","pnpm dlx skills add S3YED\u002Fappie-kit --skill motion-advanced","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FS3YED\u002Fappie-kit\u002Fblob\u002Fmaster\u002Fskills\u002Fecc\u002Fmotion-advanced\u002FSKILL.md",true,[],[84,116,149,175,205,230],{"skillNo":85,"name":86,"nameZh":6,"description":87,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":88,"categories":89,"keywords":97,"publishedAt":51},"01M6JFD4M6N4DSGC7TXEJ5R3J2","ai-music","AI music generation and audio tools: HeartMuLa lyrics-to-music generation, songwriting craft (+ Suno prompts), and audio spectrogram\u002Ffeature visualization.","https:\u002F\u002Fgithub.com\u002FS3YED\u002Fappie-kit\u002Fblob\u002Fmaster\u002Fskills\u002Fwethlete\u002Fai-music\u002FSKILL.md",[90,93,94],{"categoryNo":91,"categoryName":92,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000013","分析与可视化",{"categoryNo":23,"categoryName":24,"taxonomyType":20,"taxonomyTypeDesc":21},{"categoryNo":95,"categoryName":96,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000023","Skill与智能体开发",[98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115],"music","ai music","humanizer","visualization","Tools","Audio","hermes","hermes agent","songwriting","suno","lyrics","prompts","heartmula","craft","generation","feature","ai music generation","music generation",{"skillNo":117,"name":118,"nameZh":6,"description":119,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":120,"categories":121,"keywords":128,"publishedAt":51},"01M6JFCZE4XNFQFMNZDHKHT5V5","agentic-webdesign-design-system-ingestion","Use v0 Design Systems 2.0, Bolt Design System Agents, or Lovable to generate production UI from a client's real design system components — not generic Tailwind. Use when building a client website where brand consistency matters.","https:\u002F\u002Fgithub.com\u002FS3YED\u002Fappie-kit\u002Fblob\u002Fmaster\u002Fskills\u002Fweb-design\u002Fagentic-webdesign-design-system-ingestion\u002FSKILL.md",[122,125],{"categoryNo":123,"categoryName":124,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000015","视觉设计",{"categoryNo":126,"categoryName":127,"taxonomyType":28,"taxonomyTypeDesc":29},"01JBSEEDCAT000000000000032","设计师",[129,130,131,132,133,134,135,136,137,138,139,140,141,142,143,144,145,146,147,148],"design","agentic","design system","system","ingestion","webdesign","agentic webdesign design system ingestion","components","generate","design systems","website","ui","Tailwind","bolt","lovable","v0","design-system","wed","design system components","appie",{"skillNo":150,"name":151,"nameZh":6,"description":152,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":153,"categories":154,"keywords":157,"publishedAt":51},"01M6JFCVPX3913CFAS6QQJ33ZB","agentic-webdesign-agent-elements","Use 21st.dev Agent Elements — 25 shadcn\u002Fui components purpose-built for AI agent UIs. Tool cards (Bash, Edit, Search, Plan, Subagent, MCP), chat, input, streaming. Open source, shadcn-registry install, no runtime lock-in.","https:\u002F\u002Fgithub.com\u002FS3YED\u002Fappie-kit\u002Fblob\u002Fmaster\u002Fskills\u002Fweb-design\u002Fagentic-webdesign-agent-elements\u002FSKILL.md",[155,156],{"categoryNo":95,"categoryName":96,"taxonomyType":20,"taxonomyTypeDesc":21},{"categoryNo":126,"categoryName":127,"taxonomyType":28,"taxonomyTypeDesc":29},[130,158,134,159,160,161,136,162,163,164,165,166,167,168,169,170,171,172,173,174],"elements","agentic webdesign agent elements","MCP","ai agent","registry","search","streaming","open source","open","Bash","plan","subagent","edit","install","input","lock","source",{"skillNo":176,"name":177,"nameZh":6,"description":178,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":179,"categories":180,"keywords":185,"publishedAt":51},"01M6JFCQC7H65M9QJ6TRE71BS1","vercel-deployment","Deploy static sites and frontends to Vercel — handles both CLI and REST API paths, including the CLI token sanitization workaround.","https:\u002F\u002Fgithub.com\u002FS3YED\u002Fappie-kit\u002Fblob\u002Fmaster\u002Fskills\u002Fsoftware-development\u002Fvercel-deployment\u002FSKILL.md",[181,184],{"categoryNo":182,"categoryName":183,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000011","运维与部署",{"categoryNo":95,"categoryName":96,"taxonomyType":20,"taxonomyTypeDesc":21},[186,187,188,189,190,191,192,193,194,195,48,196,197,198,199,200,201,202,203,204],"deployment","vercel deployment","vercel","macos","cli","linux","windows","deploy","vercel deploy","REST","site","deploy to vercel","token","rest api","host","sites","paths","sanitization","static site",{"skillNo":206,"name":207,"nameZh":6,"description":208,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":209,"categories":210,"keywords":212,"publishedAt":51},"01M6JFCM4SMTXBM5A01BK2YQG1","software-quality-workflows","Umbrella workflow for TDD, debugging, code simplification, spikes, pre-commit review, and debugger-assisted investigation.","https:\u002F\u002Fgithub.com\u002FS3YED\u002Fappie-kit\u002Fblob\u002Fmaster\u002Fskills\u002Fsoftware-development\u002Fsoftware-quality-workflows\u002FSKILL.md",[211],{"categoryNo":95,"categoryName":96,"taxonomyType":20,"taxonomyTypeDesc":21},[213,214,215,216,217,218,219,220,221,222,223,224,225,226,227,228,229],"workflows","quality","software","software quality workflows","review","tdd","workflow","commit","debugging","code","debugger","pre-commit","investigation","code simplification","user","simplification","for",{"skillNo":231,"name":232,"nameZh":6,"description":233,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":6,"repositoryNo":12,"starsCount":13,"licenseSpdxId":14,"licenseFileUrl":6,"sourceHtmlUrl":234,"categories":235,"keywords":241,"publishedAt":51},"01M6JFCGD0M6CEATY01W7YN6DB","react-component-refactoring","Safely refactor large React components into smaller, maintainable pieces using proven patterns","https:\u002F\u002Fgithub.com\u002FS3YED\u002Fappie-kit\u002Fblob\u002Fmaster\u002Fskills\u002Fsoftware-development\u002Freact-component-refactoring\u002FSKILL.md",[236,239,240],{"categoryNo":237,"categoryName":238,"taxonomyType":20,"taxonomyTypeDesc":21},"01JBSEEDCAT000000000000007","代码审查",{"categoryNo":18,"categoryName":19,"taxonomyType":20,"taxonomyTypeDesc":21},{"categoryNo":95,"categoryName":96,"taxonomyType":20,"taxonomyTypeDesc":21},[49,242,243,244,245,246,42,247,189,248,249,136,250,192,251,222,252,253,43,254],"component","refactoring","component refactoring","react component","react component refactoring","writing","safe","typescript","react components","modular","refactor","simplify","test-driven-development"]