Source profileQuality 95/100

affaan-m/ECC/skills/motion-advanced/SKILL.md

motion-advanced

Advanced motion patterns for React / 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.

Source repository stars
234,327
Declared platforms
1
Static risk flags
0
Last source update
2026-07-27
Source checked
2026-07-28

Decision brief

What it does—and where it fits

Complex, interactive, and physics-based animation patterns. Requires motion-foundations to be set up first. Use these when motion-patterns is not enough.

Best for

    Not for

    • Tasks that require unconfirmed production actions or broad system permissions.
    • Environments where the pinned source and install steps cannot be inspected.

    Compatibility matrix

    Platform support, with evidence labels

    PlatformStatusEvidenceWhat to check
    CodexNot declaredNo explicit evidencePortability before use
    Claude CodeNot declaredNo explicit evidencePortability before use
    CursorDeclaredSource recordInstall path and trigger
    Gemini CLINot declaredNo explicit evidencePortability before use
    Open the compatibility checker

    Installation

    Inspect first. Install second.

    The source command is displayed only when detected. A safe inspection prompt is always available so your agent can explain every action before execution.

    Source-detected install commandSource
    npx skills add https://github.com/affaan-m/ECC --skill "skills/motion-advanced"
    Safe inspection promptEditorial

    Inspect the Agent Skill "motion-advanced" from https://github.com/affaan-m/ECC/blob/4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38/skills/motion-advanced/SKILL.md at commit 4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38. List every install step, command, network request, credential, file read/write, external action, and rollback step. Explain whether it fits my task. Do not install or execute anything until I approve.

    Workflow

    What the source asks the agent to do

    1. 01

      When to Activate

      Building drag-to-dismiss sheets, swipe gestures, or reorderable lists

      Building drag-to-dismiss sheets, swipe gestures, or reorderable listsAnimating text word-by-word, character-by-character, or as a live counterDrawing SVG paths, morphing icons, or animating circular progress
    2. 02

      Outputs

      Drag interactions: draggable cards, drag-to-dismiss sheets, Reorder.Group lists

      Drag interactions: draggable cards, drag-to-dismiss sheets, Reorder.Group listsGesture hooks: swipe detection, long press, pinch outlineText animation components: word reveal, character typewriter, number counter
    3. 03

      Principles

      Physics-based motion (useSpring, springs.) always feels more natural than duration-based for direct manipulation.

      Physics-based motion (useSpring, springs.) always feels more natural than duration-based for direct manipulation.useMotionValue + useTransform computes derived values without triggering re-renders.useAnimate sequences are imperative and interrupt-safe — calling animate() mid-flight cancels the previous animation automatically.
    4. 04

      Rules

      1. Drag interactions must be tested on touch devices, not just mouse. drag prop works on both but feel and threshold differ. 2. Infinite animations must pause when document.visibilityState === "hidden". Background tabs must not consume GPU/CPU. 3. Swipe threshold must be explici…

      Drag interactions must be tested on touch devices, not just mouse. drag prop works on both but feel and threshold differ.Infinite animations must pause when document.visibilityState === "hidden". Background tabs must not consume GPU/CPU.Swipe threshold must be explicit. Never infer intent from velocity alone; combine offset + velocity checks.
    5. 05

      Decision Guidance

      Review the “Decision Guidance” section in the pinned source before continuing.

      Review and apply the “Decision Guidance” source section.

    Permission review

    Static risk signals and limitations

    No configured static risk pattern was detected

    This is not proof of safety. Runtime behavior, indirect dependencies, and hidden external systems are outside the static scan.

    Evidence record

    Why each signal appears

    EvidenceSourceComputedTestedEditorial
    SignalValueEvidence typeMeaning
    Quality score95/100ComputedDocumentation, specificity, maintenance, and trust rules
    Repository stars234,327SourceRepository attention, not individual Skill quality
    Compatibility1 platformsSourceDeclared in the catalog source record
    Usage guideautomated source guideEditorialGenerated or reviewed according to the visible evidence level

    Pinned source

    Provenance and original SKILL.md

    Repository
    affaan-m/ECC
    Skill path
    skills/motion-advanced/SKILL.md
    Commit
    4e973d3eaf92d97f8d2e2d8abb39d8bdc8711b38
    License
    MIT
    Collected
    2026-07-28
    Default branch
    main
    View the original SKILL.md

    Motion Advanced

    Complex, interactive, and physics-based animation patterns. Requires motion-foundations to be set up first. Use these when motion-patterns is not enough.

    When to Activate

    • Building drag-to-dismiss sheets, swipe gestures, or reorderable lists
    • Animating text word-by-word, character-by-character, or as a live counter
    • Drawing SVG paths, morphing icons, or animating circular progress
    • Writing a custom animation hook (useScrollReveal, magnetic button, cursor follower)
    • Sequencing multi-step animations imperatively with useAnimate
    • Building spinners, shimmer skeletons, pulse indicators, or loading button states

    Outputs

    This skill produces:

    • Drag interactions: draggable cards, drag-to-dismiss sheets, Reorder.Group lists
    • Gesture hooks: swipe detection, long press, pinch outline
    • Text animation components: word reveal, character typewriter, number counter
    • SVG animation: path draw-on, icon morph, stroke progress ring
    • Custom hooks: useScrollReveal, useHoverScale, useNavigationDirection, useInViewOnce
    • Imperative sequences via useAnimate with interrupt-safe async/await
    • Loader components: spinner, shimmer, pulse dot, progress bar, button loading state

    Principles

    • Physics-based motion (useSpring, springs.*) always feels more natural than duration-based for direct manipulation.
    • useMotionValue + useTransform computes derived values without triggering re-renders.
    • useAnimate sequences are imperative and interrupt-safe — calling animate() mid-flight cancels the previous animation automatically.
    • Motion values (useMotionValue, useSpring) are SSR-safe and do not cause hydration errors.

    Rules

    1. Drag interactions must be tested on touch devices, not just mouse. drag prop works on both but feel and threshold differ.
    2. Infinite animations must pause when document.visibilityState === "hidden". Background tabs must not consume GPU/CPU.
    3. Swipe threshold must be explicit. Never infer intent from velocity alone; combine offset + velocity checks.
    4. useAnimate scope ref must be attached to a mounted DOM element. Calling animate() before mount throws silently.
    5. Motion values must not be recreated on render. useMotionValue(0) inside a component body is correct; new MotionValue(0) in a render is not.
    6. All token values are imported from motion-foundations. No inline numbers.
    7. Custom hooks must handle cleanup. Every window.addEventListener needs a matching removeEventListener in the useEffect return.
    8. SVG morphing requires equal path command counts. Paths with different command structures snap instead of interpolating.

    Decision Guidance

    Choosing the right advanced API

    ScenarioAPI
    Drag with physics on releasedrag + dragTransition: springs.release
    Ordered drag-to-reorder listReorder.Group + Reorder.Item
    Dismiss on drag offsetdrag="y" + onDragEnd offset check
    Swipe left/rightdrag="x" + onDragEnd offset check
    Long pressuseLongPress hook
    Value smoothed over timeuseSpring
    Value derived from anotheruseTransform
    Multi-step sequenceuseAnimate with async/await
    One-shot imperative animationanimate() from motion
    Text entering word by wordStagger on inline-block spans
    SVG drawing onpathLength 0 → 1
    SVG morphd attribute tween (equal commands)
    Circular progressstrokeDashoffset tween

    When to use useSpring vs a spring transition

    useSpringtransition: springs.*
    Use forCursor follower, pointer-tracked valuesDiscrete state changes
    UpdatesContinuous, on every frameTriggered by state change
    InterruptSmooth — physics picks up from velocityRestarts from current value

    Core Concepts

    useMotionValue + useTransform

    Reactive computation without re-renders:

    const x = useMotionValue(0)
    const opacity = useTransform(x, [-200, 0, 200], [0, 1, 0])
    // opacity updates every frame as x changes — no setState, no re-render
    

    useAnimate

    Returns [scope, animate]. The scope ref must be attached to a DOM element. animate() calls are interrupt-safe — calling mid-flight cancels the previous run.

    const [scope, animate] = useAnimate()
    
    async function play() {
      await animate(".step-1", { opacity: 1 }, { duration: 0.3 })
      await animate(".step-2", { x: 0 },       { duration: 0.4 })
            animate(".step-3", { scale: 1 },    { duration: 0.25 })  // fire and forget
    }
    
    return <div ref={scope}>...</div>
    

    Code Examples

    Draggable card

    "use client"
    import { motion } from "motion/react"
    import { springs, motionTokens } from "@/lib/motion-tokens"
    
    <motion.div
      drag
      dragConstraints={{ left: -100, right: 100, top: -100, bottom: 100 }}
      dragElastic={0.1}
      whileDrag={{
        scale: motionTokens.scale.pop,
        boxShadow: "0 16px 40px rgba(0,0,0,0.2)",
      }}
      dragTransition={springs.release}
    />
    

    Drag-to-dismiss sheet

    "use client"
    import { motion, useMotionValue, useTransform } from "motion/react"
    
    export function BottomSheet({ onClose }: { onClose: () => void }) {
      const y = useMotionValue(0)
      const opacity = useTransform(y, [0, 200], [1, 0])
    
      return (
        <motion.div
          drag="y"
          dragConstraints={{ top: 0 }}
          style={{ y, opacity }}
          onDragEnd={(_, info) => {
            // Rule 3: combine offset + velocity
            if (info.offset.y > 120 || info.velocity.y > 500) onClose()
          }}
        />
      )
    }
    

    Reorderable list

    "use client"
    import { Reorder } from "motion/react"
    
    export function SortableList() {
      const [items, setItems] = useState(initialItems)
      return (
        <Reorder.Group axis="y" values={items} onReorder={setItems}>
          {items.map((item) => (
            <Reorder.Item key={item.id} value={item}>
              {item.label}
            </Reorder.Item>
          ))}
        </Reorder.Group>
      )
    }
    

    Swipe detection

    "use client"
    import { motion } from "motion/react"
    
    const OFFSET_THRESHOLD  = 50
    const VELOCITY_THRESHOLD = 300
    
    <motion.div
      drag="x"
      dragConstraints={{ left: 0, right: 0 }}
      onDragEnd={(_, info) => {
        const swipedRight = info.offset.x > OFFSET_THRESHOLD  || info.velocity.x > VELOCITY_THRESHOLD
        const swipedLeft  = info.offset.x < -OFFSET_THRESHOLD || info.velocity.x < -VELOCITY_THRESHOLD
        if (swipedRight) onSwipeRight()
        if (swipedLeft)  onSwipeLeft()
      }}
    />
    

    Long press hook

    import { useRef } from "react"
    
    export function useLongPress(callback: () => void, ms = 600) {
      const timerRef = useRef<ReturnType<typeof setTimeout>>()
      return {
        onPointerDown:  () => { timerRef.current = setTimeout(callback, ms) },
        onPointerUp:    () => clearTimeout(timerRef.current),
        onPointerLeave: () => clearTimeout(timerRef.current),
      }
    }
    

    Word-by-word reveal

    "use client"
    import { motion } from "motion/react"
    import { springs } from "@/lib/motion-tokens"
    
    export function AnimatedText({ text }: { text: string }) {
      return (
        <motion.p
          variants={{ visible: { transition: { staggerChildren: 0.05 } } }}
          initial="hidden"
          animate="visible"
        >
          {text.split(" ").map((word, i) => (
            <motion.span
              key={i}
              className="inline-block mr-1"
              variants={{
                hidden:  { opacity: 0, y: 12 },
                visible: { opacity: 1, y: 0, transition: springs.gentle },
              }}
            >
              {word}
            </motion.span>
          ))}
        </motion.p>
      )
    }
    

    Number counter

    "use client"
    import { useRef, useEffect } from "react"
    import { animate } from "motion"
    import { motionTokens } from "@/lib/motion-tokens"
    
    export function Counter({ to }: { to: number }) {
      const nodeRef = useRef<HTMLSpanElement>(null)
    
      useEffect(() => {
        const controls = animate(0, to, {
          duration: motionTokens.duration.crawl,
          ease: motionTokens.easing.smooth,
          onUpdate: (v) => {
            if (nodeRef.current) nodeRef.current.textContent = Math.round(v).toString()
          },
        })
        return controls.stop   // Rule 7: cleanup
      }, [to])
    
      return <span ref={nodeRef} />
    }
    

    SVG path draw-on

    "use client"
    import { motion } from "motion/react"
    import { motionTokens } from "@/lib/motion-tokens"
    
    <motion.path
      d="M 0 100 Q 50 0 100 100"
      initial={{ pathLength: 0, opacity: 0 }}
      animate={{ pathLength: 1, opacity: 1 }}
      transition={{ duration: motionTokens.duration.slow, ease: motionTokens.easing.smooth }}
    />
    

    Stroke progress ring

    "use client"
    import { motion } from "motion/react"
    import { motionTokens } from "@/lib/motion-tokens"
    
    const CIRCUMFERENCE = 2 * Math.PI * 40   // r=40
    
    export function ProgressRing({ progress }: { progress: number }) {
      return (
        <svg width="100" height="100" viewBox="0 0 100 100">
          <circle cx="50" cy="50" r="40" fill="none" stroke="#e5e7eb" strokeWidth="8" />
          <motion.circle
            cx="50" cy="50" r="40"
            fill="none" stroke="#6366f1" strokeWidth="8"
            strokeLinecap="round"
            strokeDasharray={CIRCUMFERENCE}
            animate={{ strokeDashoffset: CIRCUMFERENCE - (progress / 100) * CIRCUMFERENCE }}
            transition={{ duration: motionTokens.duration.normal, ease: motionTokens.easing.smooth }}
            style={{ rotate: -90, transformOrigin: "center" }}
          />
        </svg>
      )
    }
    

    useScrollReveal hook

    "use client"
    import { useRef } from "react"
    import { useScroll, useTransform } from "motion/react"
    import { motionTokens } from "@/lib/motion-tokens"
    
    export function useScrollReveal() {
      const ref = useRef(null)
      const { scrollYProgress } = useScroll({ target: ref, offset: ["start end", "end start"] })
      const opacity = useTransform(scrollYProgress, [0, 0.3], [0, 1])
      const y       = useTransform(scrollYProgress, [0, 0.3], [motionTokens.distance.lg, 0])
      return { ref, style: { opacity, y } }
    }
    
    // Usage
    const { ref, style } = useScrollReveal()
    <motion.section ref={ref} style={style} />
    

    Cursor follower

    "use client"
    import { useEffect } from "react"
    import { motion, useMotionValue, useSpring } from "motion/react"
    import { springs } from "@/lib/motion-tokens"
    
    export function CursorFollower() {
      const x = useMotionValue(-100)
      const y = useMotionValue(-100)
      const sx = useSpring(x, springs.gentle)
      const sy = useSpring(y, springs.gentle)
    
      useEffect(() => {
        const move = (e: MouseEvent) => { x.set(e.clientX); y.set(e.clientY) }
        window.addEventListener("mousemove", move)
        return () => window.removeEventListener("mousemove", move)   // Rule 7
      }, [])
    
      return (
        <motion.div
          className="fixed top-0 left-0 w-6 h-6 rounded-full bg-indigo-500
                     pointer-events-none -translate-x-1/2 -translate-y-1/2 z-50"
          style={{ x: sx, y: sy }}
        />
      )
    }
    

    Shimmer skeleton

    "use client"
    import { useEffect } from "react"
    import { motion, useAnimation } from "motion/react"
    import { motionTokens } from "@/lib/motion-tokens"
    
    export function ShimmerSkeleton({ className = "" }: { className?: string }) {
      const controls = useAnimation()
    
      useEffect(() => {
        const play = () =>
          controls.start({
            x: ["-100%", "100%"],
            transition: {
              repeat: Infinity,
              duration: motionTokens.duration.crawl,
              ease: motionTokens.easing.linear,
            },
          })
    
        const handleVisibility = () => {
          if (document.visibilityState === "hidden") controls.stop()
          else void play()
        }
    
        void play()
        document.addEventListener("visibilitychange", handleVisibility)
        return () => {
          controls.stop()
          document.removeEventListener("visibilitychange", handleVisibility)
        }
      }, [controls])
    
      return (
        <div className={`relative overflow-hidden bg-gray-200 rounded ${className}`}>
          <motion.div
            className="absolute inset-0 bg-gradient-to-r from-transparent via-white/60 to-transparent"
            initial={{ x: "-100%" }}
            animate={controls}
          />
        </div>
      )
    }
    

    Button loading state

    "use client"
    import { motion, AnimatePresence } from "motion/react"
    import { motionTokens, springs } from "@/lib/motion-tokens"
    
    export function LoadingButton({
      loading,
      label,
      onClick,
    }: {
      loading: boolean
      label: string
      onClick: () => void
    }) {
      return (
        <motion.button
          onClick={onClick}
          animate={{ opacity: loading ? 0.7 : 1 }}
          whileTap={loading ? {} : { scale: motionTokens.scale.press }}
          transition={springs.snappy}
          disabled={loading}
        >
          <AnimatePresence mode="wait">
            {loading ? (
              <motion.span
                key="loading"
                initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
                transition={{ duration: motionTokens.duration.fast }}
              >
                …
              </motion.span>
            ) : (
              <motion.span
                key="label"
                initial={{ opacity: 0 }} animate={{ opacity: 1 }} exit={{ opacity: 0 }}
                transition={{ duration: motionTokens.duration.fast }}
              >
                {label}
              </motion.span>
            )}
          </AnimatePresence>
        </motion.button>
      )
    }
    

    Infinite animation with visibility pause

    "use client"
    import { useEffect } from "react"
    import { motion, useAnimation } from "motion/react"
    import { motionTokens } from "@/lib/motion-tokens"
    
    export function PulseDot() {
      const controls = useAnimation()
    
      useEffect(() => {
        const pulse = () =>
          controls.start({
            scale: [1, 1.4, 1],
            opacity: [1, 0.6, 1],
            transition: { repeat: Infinity, duration: motionTokens.duration.crawl },
          })
    
        // Rule 2: pause when tab is hidden
        const handleVisibility = () => {
          if (document.visibilityState === "hidden") controls.stop()
          else void pulse()
        }
    
        void pulse()
        document.addEventListener("visibilitychange", handleVisibility)
        // Rule 7: stop controls and remove listeners on unmount.
        return () => {
          controls.stop()
          document.removeEventListener("visibilitychange", handleVisibility)
        }
      }, [controls])
    
      return <motion.span className="w-2 h-2 rounded-full bg-green-400" animate={controls} />
    }
    

    End-to-End Example

    Drag-to-dismiss sheet with shimmer content, loading state, and reduced motion support — combining useMotionValue, useTransform, useSafeMotion, AnimatePresence, and tokens from motion-foundations:

    "use client"
    import { useState } from "react"
    import { motion, AnimatePresence, useMotionValue, useTransform } from "motion/react"
    import { springs, motionTokens } from "@/lib/motion-tokens"
    import { useSafeMotion } from "@/hooks/use-reduced-motion"
    import { ShimmerSkeleton } from "./shimmer-skeleton"
    
    export function DismissibleSheet({
      isOpen,
      onClose,
      loading,
      children,
    }: {
      isOpen: boolean
      onClose: () => void
      loading: boolean
      children: React.ReactNode
    }) {
      const safe = useSafeMotion(motionTokens.distance.xl)
      const y = useMotionValue(0)
      const opacity = useTransform(y, [0, 200], [1, 0])
    
      return (
        <AnimatePresence>
          {isOpen && (
            <>
              {/* Backdrop */}
              <motion.div
                key="backdrop"
                className="fixed inset-0 bg-black/40"
                initial={{ opacity: 0 }}
                animate={{ opacity: 1 }}
                exit={{ opacity: 0 }}
                onClick={onClose}
              />
    
              {/* Sheet — drag-to-dismiss */}
              <motion.div
                key="sheet"
                className="fixed bottom-0 inset-x-0 rounded-t-2xl bg-white p-6"
                drag="y"
                dragConstraints={{ top: 0 }}
                style={{ y, opacity }}
                onDragEnd={(_, info) => {
                  if (info.offset.y > 120 || info.velocity.y > 500) onClose()
                }}
                initial={safe.initial}
                animate={safe.animate}
                exit={safe.exit}
                transition={springs.gentle}
              >
                {loading ? (
                  <div className="space-y-3">
                    <ShimmerSkeleton className="h-4 w-3/4" />
                    <ShimmerSkeleton className="h-4 w-1/2" />
                    <ShimmerSkeleton className="h-20 w-full" />
                  </div>
                ) : children}
              </motion.div>
            </>
          )}
        </AnimatePresence>
      )
    }
    

    Constraints / Non-Goals

    This skill does not cover:

    • Token and spring definitions → see motion-foundations
    • Standard UI patterns (button, modal, stagger, page transitions) → see motion-patterns
    • CSS-only animations or Tailwind animate-* without motion/react
    • Canvas or WebGL-based animation (Three.js, Pixi, etc.)
    • Full drag-and-drop systems with external state managers (dnd-kit, react-beautiful-dnd)
    • Game-loop or frame-by-frame animation

    Anti-Patterns

    Anti-patternRule violatedFix
    drag tested only on desktopRule 1Test on touch emulator and real device
    animate={{ repeat: Infinity }} with no pauseRule 2Add visibilitychange listener
    onDragEnd checking only offset, not velocityRule 3Check both info.offset and info.velocity
    animate(scope, ...) before useEffectRule 4Call animate() only after mount
    const x = new MotionValue(0) in renderRule 5Use const x = useMotionValue(0)
    transition={{ duration: 1.2 }} inlineRule 6Use motionTokens.duration.crawl
    useEffect without cleanupRule 7Return removeEventListener / controls.stop
    SVG morph between paths with different commandsRule 8Normalize path commands before animating

    Related Skills

    • motion-foundations — defines all tokens, springs, useSafeMotion, and SSR guards imported here. Must be set up before using this skill.
    • motion-patterns — handles standard UI patterns (button, modal, stagger, page transitions, scroll reveals). Use it before reaching for the advanced patterns here.

    Alternatives

    Compare before choosing

    Computed 93234,327

    affaan-m/ECC

    react-performance

    React and Next.js performance optimization patterns adapted from Vercel Engineering's React Best Practices (https://github.com/vercel-labs/agent-skills). Organizes 70+ rules across 8 priority categories — waterfalls, bundle size, server-side, client fetching, re-render, rendering, JS micro-perf, advanced. Use when writing, reviewing, or refactoring React/Next.js code for performance.

    Computed 90234,327

    affaan-m/ECC

    motion-patterns

    Production-ready animation patterns for React / Next.js — button, modal, toast, stagger, page transitions, exit animations, scroll, and layout — built on motion-foundations tokens and springs.

    Computed 9037,126

    github/awesome-copilot

    markstream-install

    Install and configure Markstream streaming Markdown renderers for Vue, React, Svelte, Angular, Nuxt, and Vue 2 applications. Use for package selection, minimal peer dependencies, CSS order, SSR boundaries, streaming mode, and renderer setup.

    Computed 8929,549

    vercel-labs/agent-skills

    vercel-react-view-transitions

    Guide for implementing smooth, native-feeling animations using React's View Transition API (`<ViewTransition>` component, `addTransitionType`, and CSS view transition pseudo-elements). Use this skill whenever the user wants to add page transitions, animate route changes, create shared element animations, animate enter/exit of components, animate list reorder, implement directional (forward/back) navigation animations, or integrate view transitions in Next.js. Also use when the user mentions view