{
  "name": "shutter-text",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "ui/shutter-text.tsx",
      "content": "\"use client\";\n\nimport { AnimatePresence, motion, useInView } from \"framer-motion\";\nimport {\n  type HTMLAttributes,\n  useCallback,\n  useEffect,\n  useRef,\n  useState,\n} from \"react\";\n\ninterface ShutterTextProps extends HTMLAttributes<HTMLDivElement> {\n  text?: string;\n  trigger?: \"auto\" | \"scroll\" | \"click\" | \"hover\";\n}\n\nexport default function ShutterText({\n  text = \"IMMERSE\",\n  trigger = \"auto\",\n  className = \"\",\n  ...props\n}: ShutterTextProps) {\n  const [count, setCount] = useState(0);\n  const [active, setActive] = useState(\n    trigger === \"auto\" || trigger === \"click\" || trigger === \"hover\",\n  );\n  const [animating, setAnimating] = useState(trigger === \"auto\");\n  const ref = useRef<HTMLDivElement>(null);\n  const isInView = useInView(ref, { once: false, amount: 0.5 });\n  const characters = text.split(\"\");\n\n  // Handle scroll trigger\n  useEffect(() => {\n    if (trigger === \"scroll\" && isInView) {\n      setActive(true);\n      setAnimating(true);\n      setCount((c) => c + 1);\n    }\n    if (trigger === \"scroll\" && !isInView) {\n      setActive(false);\n      setAnimating(false);\n    }\n  }, [trigger, isInView]);\n\n  // Handle auto trigger – animate once on mount\n  useEffect(() => {\n    if (trigger === \"auto\") {\n      setActive(true);\n      setAnimating(true);\n      setCount((c) => c + 1);\n    }\n  }, [trigger]);\n\n  const handleClick = useCallback(() => {\n    if (trigger === \"click\") {\n      setAnimating(true);\n      setCount((c) => c + 1);\n    }\n  }, [trigger]);\n\n  const handleMouseEnter = useCallback(() => {\n    if (trigger === \"hover\") {\n      setAnimating(true);\n      setCount((c) => c + 1);\n    }\n  }, [trigger]);\n\n  const handleMouseLeave = useCallback(() => {\n    if (trigger === \"hover\") {\n      setAnimating(false);\n    }\n  }, [trigger]);\n\n  return (\n    <div\n      ref={ref}\n      role=\"button\"\n      tabIndex={0}\n      onClick={handleClick}\n      onMouseEnter={handleMouseEnter}\n      onMouseLeave={handleMouseLeave}\n      className={`relative inline-flex flex-wrap items-center justify-center ${className}`}\n      {...props}\n    >\n      <AnimatePresence mode=\"wait\">\n        {animating ? (\n          <motion.span\n            key={count}\n            className=\"flex flex-wrap items-center justify-center\"\n          >\n            {characters.map((char, i) => (\n              <span\n                key={i}\n                className=\"relative inline-block overflow-hidden px-[0.1vw]\"\n              >\n                {/* Main Character */}\n                <motion.span\n                  initial={{ opacity: 0, filter: \"blur(10px)\" }}\n                  animate={{ opacity: 1, filter: \"blur(0px)\" }}\n                  transition={{ delay: i * 0.04 + 0.3, duration: 0.8 }}\n                  className=\"inline-block font-black text-zinc-900 leading-none tracking-tighter dark:text-white\"\n                >\n                  {char === \" \" ? \"\\u00A0\" : char}\n                </motion.span>\n\n                {/* Top Slice Layer */}\n                <motion.span\n                  initial={{ x: \"-100%\", opacity: 0 }}\n                  animate={{ x: \"100%\", opacity: [0, 1, 0] }}\n                  transition={{\n                    duration: 0.7,\n                    delay: i * 0.04,\n                    ease: \"easeInOut\",\n                  }}\n                  className=\"pointer-events-none absolute inset-0 z-10 inline-block font-black text-indigo-600 leading-none dark:text-emerald-400\"\n                  style={{ clipPath: \"polygon(0 0, 100% 0, 100% 35%, 0 35%)\" }}\n                >\n                  {char}\n                </motion.span>\n\n                {/* Middle Slice Layer */}\n                <motion.span\n                  initial={{ x: \"100%\", opacity: 0 }}\n                  animate={{ x: \"-100%\", opacity: [0, 1, 0] }}\n                  transition={{\n                    duration: 0.7,\n                    delay: i * 0.04 + 0.1,\n                    ease: \"easeInOut\",\n                  }}\n                  className=\"pointer-events-none absolute inset-0 z-10 inline-block font-black text-zinc-800 leading-none dark:text-zinc-200\"\n                  style={{\n                    clipPath: \"polygon(0 35%, 100% 35%, 100% 65%, 0 65%)\",\n                  }}\n                >\n                  {char}\n                </motion.span>\n\n                {/* Bottom Slice Layer */}\n                <motion.span\n                  initial={{ x: \"-100%\", opacity: 0 }}\n                  animate={{ x: \"100%\", opacity: [0, 1, 0] }}\n                  transition={{\n                    duration: 0.7,\n                    delay: i * 0.04 + 0.2,\n                    ease: \"easeInOut\",\n                  }}\n                  className=\"pointer-events-none absolute inset-0 z-10 inline-block font-black text-indigo-600 leading-none dark:text-emerald-400\"\n                  style={{\n                    clipPath: \"polygon(0 65%, 100% 65%, 100% 100%, 0 100%)\",\n                  }}\n                >\n                  {char}\n                </motion.span>\n              </span>\n            ))}\n          </motion.span>\n        ) : (\n          <span className=\"flex flex-wrap items-center justify-center\">\n            {characters.map((char, i) => (\n              <span\n                key={i}\n                className=\"relative inline-block overflow-hidden px-[0.1vw]\"\n              >\n                <span className=\"inline-block font-black text-zinc-900 leading-none tracking-tighter dark:text-white\">\n                  {char === \" \" ? \"\\u00A0\" : char}\n                </span>\n              </span>\n            ))}\n          </span>\n        )}\n      </AnimatePresence>\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": ""
    }
  ],
  "type": "registry:ui"
}