{
  "name": "text-reveal-card",
  "type": "registry:ui",
  "dependencies": [
    "motion"
  ],
  "files": [
    {
      "path": "components/ui/text-reveal-card.tsx",
      "content": "\"use client\";\nimport React, { useEffect, useRef, useState, memo } from \"react\";\nimport { motion } from \"motion/react\";\nimport { twMerge } from \"tailwind-merge\";\nimport { cn } from \"@/lib/utils\";\n\nexport const TextRevealCard = ({\n  text,\n  revealText,\n  children,\n  className,\n}: {\n  text: string;\n  revealText: string;\n  children?: React.ReactNode;\n  className?: string;\n}) => {\n  const [widthPercentage, setWidthPercentage] = useState(0);\n  const cardRef = useRef<HTMLDivElement | any>(null);\n  const [left, setLeft] = useState(0);\n  const [localWidth, setLocalWidth] = useState(0);\n  const [isMouseOver, setIsMouseOver] = useState(false);\n\n  useEffect(() => {\n    if (cardRef.current) {\n      const { left, width: localWidth } =\n        cardRef.current.getBoundingClientRect();\n      setLeft(left);\n      setLocalWidth(localWidth);\n    }\n  }, []);\n\n  function mouseMoveHandler(event: any) {\n    event.preventDefault();\n\n    const { clientX } = event;\n    if (cardRef.current) {\n      const relativeX = clientX - left;\n      setWidthPercentage((relativeX / localWidth) * 100);\n    }\n  }\n\n  function mouseLeaveHandler() {\n    setIsMouseOver(false);\n    setWidthPercentage(0);\n  }\n  function mouseEnterHandler() {\n    setIsMouseOver(true);\n  }\n  function touchMoveHandler(event: React.TouchEvent<HTMLDivElement>) {\n    event.preventDefault();\n    const clientX = event.touches[0]!.clientX;\n    if (cardRef.current) {\n      const relativeX = clientX - left;\n      setWidthPercentage((relativeX / localWidth) * 100);\n    }\n  }\n\n  const rotateDeg = (widthPercentage - 50) * 0.1;\n  return (\n    <div\n      onMouseEnter={mouseEnterHandler}\n      onMouseLeave={mouseLeaveHandler}\n      onMouseMove={mouseMoveHandler}\n      onTouchStart={mouseEnterHandler}\n      onTouchEnd={mouseLeaveHandler}\n      onTouchMove={touchMoveHandler}\n      ref={cardRef}\n      className={cn(\n        \"bg-[#1d1c20] border border-white/[0.08] w-[40rem] rounded-lg p-8 relative overflow-hidden\",\n        className\n      )}\n    >\n      {children}\n\n      <div className=\"h-40  relative flex items-center overflow-hidden\">\n        <motion.div\n          style={{\n            width: \"100%\",\n          }}\n          animate={\n            isMouseOver\n              ? {\n                  opacity: widthPercentage > 0 ? 1 : 0,\n                  clipPath: `inset(0 ${100 - widthPercentage}% 0 0)`,\n                }\n              : {\n                  clipPath: `inset(0 ${100 - widthPercentage}% 0 0)`,\n                }\n          }\n          transition={isMouseOver ? { duration: 0 } : { duration: 0.4 }}\n          className=\"absolute bg-[#1d1c20] z-20  will-change-transform\"\n        >\n          <p\n            style={{\n              textShadow: \"4px 4px 15px rgba(0,0,0,0.5)\",\n            }}\n            className=\"text-base sm:text-[3rem] py-10 font-bold text-white bg-clip-text text-transparent bg-gradient-to-b from-white to-neutral-300\"\n          >\n            {revealText}\n          </p>\n        </motion.div>\n        <motion.div\n          animate={{\n            left: `${widthPercentage}%`,\n            rotate: `${rotateDeg}deg`,\n            opacity: widthPercentage > 0 ? 1 : 0,\n          }}\n          transition={isMouseOver ? { duration: 0 } : { duration: 0.4 }}\n          className=\"h-40 w-[8px] bg-gradient-to-b from-transparent via-neutral-800 to-transparent absolute z-50 will-change-transform\"\n        ></motion.div>\n\n        <div className=\" overflow-hidden [mask-image:linear-gradient(to_bottom,transparent,white,transparent)]\">\n          <p className=\"text-base sm:text-[3rem] py-10 font-bold bg-clip-text text-transparent bg-[#323238]\">\n            {text}\n          </p>\n          <MemoizedStars />\n        </div>\n      </div>\n    </div>\n  );\n};\n\nexport const TextRevealCardTitle = ({\n  children,\n  className,\n}: {\n  children: React.ReactNode;\n  className?: string;\n}) => {\n  return (\n    <h2 className={twMerge(\"text-white text-lg mb-2\", className)}>\n      {children}\n    </h2>\n  );\n};\n\nexport const TextRevealCardDescription = ({\n  children,\n  className,\n}: {\n  children: React.ReactNode;\n  className?: string;\n}) => {\n  return (\n    <p className={twMerge(\"text-[#a9a9a9] text-sm\", className)}>{children}</p>\n  );\n};\n\nconst Stars = () => {\n  const randomMove = () => Math.random() * 4 - 2;\n  const randomOpacity = () => Math.random();\n  const random = () => Math.random();\n  return (\n    <div className=\"absolute inset-0\">\n      {[...Array(80)].map((_, i) => (\n        <motion.span\n          key={`star-${i}`}\n          animate={{\n            top: `calc(${random() * 100}% + ${randomMove()}px)`,\n            left: `calc(${random() * 100}% + ${randomMove()}px)`,\n            opacity: randomOpacity(),\n            scale: [1, 1.2, 0],\n          }}\n          transition={{\n            duration: random() * 10 + 20,\n            repeat: Infinity,\n            ease: \"linear\",\n          }}\n          style={{\n            position: \"absolute\",\n            top: `${random() * 100}%`,\n            left: `${random() * 100}%`,\n            width: `2px`,\n            height: `2px`,\n            backgroundColor: \"white\",\n            borderRadius: \"50%\",\n            zIndex: 1,\n          }}\n          className=\"inline-block\"\n        ></motion.span>\n      ))}\n    </div>\n  );\n};\n\nexport const MemoizedStars = memo(Stars);\n",
      "type": "registry:ui",
      "target": "components/ui/text-reveal-card.tsx"
    }
  ],
  "author": "Manu Arora <hi@manuarora.in>",
  "title": "Text Reveal Card"
}