{
  "title": "Mouse Wave Text",
  "description": "Creates a wave-like bounce effect on text as the mouse moves across",
  "name": "motion-mouse-wave-text",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [],
  "type": "registry:ui",
  "files": [
    {
      "path": "components/paceui/motion/mouse-wave-text.tsx",
      "type": "registry:ui",
      "content": "import { type ReactNode, useEffect, useRef, useState } from \"react\";\n\nimport {\n    type AnimationPlaybackControls,\n    type HTMLMotionProps,\n    MotionValue,\n    type Target,\n    animate,\n    motion,\n    useAnimation,\n    useMotionValue,\n    useTransform,\n} from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\n\ntype MouseWaveTextProps = {\n    children: ReactNode;\n    className?: string;\n    shadowClassName?: string;\n    textClassName?: string;\n    wrapperTweenVars?: Target;\n} & Omit<HTMLMotionProps<\"div\">, \"children\" | \"initial\">;\n\nconst TOTAL_DURATION = 1.5;\nconst STAGGER_AMOUNT = 1;\nconst ANIM_DURATION = 0.25;\n\nconst Slice = ({\n    index,\n    total,\n    progress,\n    textContent,\n    height,\n}: {\n    index: number;\n    total: number;\n    progress: MotionValue<number>;\n    textContent: string;\n    height: number;\n}) => {\n    const easeInOutCubic = (x: number) => (x < 0.5 ? 4 * x * x * x : 1 - Math.pow(-2 * x + 2, 3) / 2);\n\n    const sineInOut = (x: number) => -(Math.cos(Math.PI * x) - 1) / 2;\n\n    const y = useTransform(progress, (p: number) => {\n        const t = p * TOTAL_DURATION;\n        const start = total > 1 ? (index / (total - 1)) * STAGGER_AMOUNT : 0;\n        const localT = t - start;\n\n        if (localT < 0 || localT > ANIM_DURATION * 2) return -height;\n\n        let yAnim = 0;\n        if (localT <= ANIM_DURATION) {\n            const normalized = localT / ANIM_DURATION;\n            yAnim = -30 * easeInOutCubic(normalized);\n        } else {\n            const normalized = (localT - ANIM_DURATION) / ANIM_DURATION;\n            yAnim = -30 * (1 - sineInOut(normalized));\n        }\n\n        return -height + yAnim;\n    });\n\n    return (\n        <motion.div\n            style={{\n                position: \"absolute\",\n                width: 4,\n                height: height,\n                x: index * 2,\n                y,\n                textIndent: -index * 2,\n                overflow: \"hidden\",\n            }}>\n            {textContent}\n        </motion.div>\n    );\n};\n\nexport const MouseWaveText = ({\n    children,\n    textClassName,\n    className,\n    shadowClassName,\n    wrapperTweenVars,\n    ...props\n}: MouseWaveTextProps) => {\n    const shadowRef = useRef<HTMLParagraphElement | null>(null);\n\n    const [sliceData, setSliceData] = useState<{\n        numSlices: number;\n        height: number;\n        text: string;\n    } | null>(null);\n\n    const progress = useMotionValue(0.9);\n    const wrapperX = useTransform(progress, [0, 1], [15, -15]);\n    const wrapperY = useTransform(progress, [0, 1], [-15, 15]);\n\n    const textControls = useAnimation();\n    const shadowControls = useAnimation();\n\n    useEffect(() => {\n        if (!shadowRef.current) return;\n        const bb = shadowRef.current.getBoundingClientRect();\n        setSliceData({\n            numSlices: Math.floor(bb.width * 0.55) + 1,\n            height: bb.height,\n            text: shadowRef.current.textContent || \"\",\n        });\n    }, [children]);\n\n    useEffect(() => {\n        if (!sliceData) return;\n\n        let anim: AnimationPlaybackControls;\n        let isCancelled = false;\n\n        const sequence = async () => {\n            anim = animate(progress, 0.1, { duration: 1.5, ease: \"easeInOut\" });\n            await anim;\n            if (!isCancelled) {\n                anim = animate(progress, 0.4, {\n                    duration: 4,\n                    type: \"spring\",\n                    bounce: 0.5,\n                });\n            }\n        };\n\n        sequence();\n\n        const onMove = (e: MouseEvent) => {\n            isCancelled = true;\n            if (anim) anim.stop();\n            const xp = e.clientX / window.innerWidth;\n            anim = animate(progress, xp, { duration: 0.5, ease: \"easeOut\" });\n        };\n\n        const onMouseDown = () => {\n            textControls.start({ y: -25, transition: { duration: 0.2 } });\n            shadowControls.start({\n                filter: \"blur(2px)\",\n                opacity: 0.85,\n                scale: 0.96,\n                transformOrigin: \"45px 99px\",\n                transition: { duration: 0.2 },\n            });\n        };\n\n        const onMouseUp = () => {\n            textControls.start({ y: 0, transition: { type: \"spring\", bounce: 0.5 } });\n            shadowControls.start({\n                filter: \"blur(0px)\",\n                opacity: 1,\n                scale: 1,\n                transition: { type: \"spring\", bounce: 0.5 },\n            });\n        };\n\n        window.addEventListener(\"pointermove\", onMove);\n        window.addEventListener(\"mousedown\", onMouseDown);\n        window.addEventListener(\"mouseup\", onMouseUp);\n\n        return () => {\n            isCancelled = true;\n            if (anim) anim.stop();\n            textControls.stop();\n            shadowControls.stop();\n            window.removeEventListener(\"pointermove\", onMove);\n            window.removeEventListener(\"mousedown\", onMouseDown);\n            window.removeEventListener(\"mouseup\", onMouseUp);\n        };\n    }, [sliceData, progress, textControls, shadowControls]);\n\n    return (\n        <motion.div\n            {...props}\n            className={cn(\"whitespace-nowrap\", className)}\n            style={{ x: wrapperX, y: wrapperY, ...props.style }}\n            initial={wrapperTweenVars}>\n            <motion.p ref={shadowRef} className={shadowClassName} animate={shadowControls}>\n                {children}\n            </motion.p>\n            <motion.div\n                aria-disabled=\"true\"\n                className={textClassName}\n                animate={textControls}\n                style={{ position: \"relative\" }}>\n                {sliceData &&\n                    Array.from({ length: sliceData.numSlices }).map((_, i) => (\n                        <Slice\n                            key={i}\n                            index={i}\n                            total={sliceData.numSlices}\n                            progress={progress}\n                            textContent={sliceData.text}\n                            height={sliceData.height}\n                        />\n                    ))}\n            </motion.div>\n        </motion.div>\n    );\n};\n"
    }
  ],
  "mcp": {
    "description": "Provides refined UI interactions and accessible controls. Creates a wave-like bounce effect on text as the mouse moves across Configurable props include: {; children: ReactNode;; className?: string;; shadowClassName?: string;. Relies on motion. Optimal for building reusable interface elements and user input flows.",
    "tags": [
      "component",
      "motion",
      "animation"
    ]
  }
}