{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "components-animate-card-stroke",
  "title": "Card Stroke",
  "description": "A card component with animated SVG stroke and split-text reveal on hover.",
  "dependencies": [
    "gsap"
  ],
  "files": [
    {
      "path": "registry/components/animate/card-stroke/index.tsx",
      "content": "'use client';\n\nimport { useEffect, useRef } from 'react';\nimport gsap from 'gsap';\nimport { SplitText } from 'gsap/SplitText';\nimport { cn } from '@/lib/utils';\n\ngsap.registerPlugin(SplitText);\n\ntype CardStrokeProps = {\n  title?: string;\n  description?: string;\n  imageSrc?: string;\n  imageAlt?: string;\n  accentStrokeColor?: string;\n  baseStrokeColor?: string;\n  textColor?: string;\n  className?: string;\n};\n\nexport default function CardStroke({\n  title = 'Card Title',\n  description = 'A small two line paragraph passed by prop.',\n  imageSrc = '/templateImages/placeholder.png',\n  imageAlt = 'Card background',\n  accentStrokeColor = '#E0E0E0',\n  baseStrokeColor = '#2B7FFF',\n  textColor = '#F5F5F5',\n  className,\n}: CardStrokeProps) {\n  const cardRef = useRef<HTMLDivElement>(null);\n\n  useEffect(() => {\n    const card = cardRef.current;\n    if (!card) {\n      return;\n    }\n\n    const paths = card.querySelectorAll<SVGPathElement>('.svg-stroke path');\n    const titleElement = card.querySelector<HTMLElement>('.card-title h3');\n    const descriptionElement = card.querySelector<HTMLElement>('.card-title p');\n\n    if (!titleElement || !descriptionElement || paths.length === 0) {\n      return;\n    }\n\n    const splitTitle = SplitText.create(titleElement, {\n      type: 'words',\n      mask: 'words',\n    });\n\n    const splitDescription = SplitText.create(descriptionElement, {\n      type: 'words',\n      mask: 'words',\n    });\n\n    gsap.set(splitTitle.words, { yPercent: 100 });\n    gsap.set(splitDescription.words, { yPercent: 100 });\n\n    paths.forEach((path) => {\n      const length = path.getTotalLength();\n      path.style.strokeDasharray = String(length);\n      path.style.strokeDashoffset = String(length);\n    });\n\n    let timeline: gsap.core.Timeline | null = null;\n\n    const onMouseEnter = () => {\n      timeline?.kill();\n      timeline = gsap.timeline();\n\n      paths.forEach((path) => {\n        timeline?.to(\n          path,\n          {\n            strokeDashoffset: 0,\n            attr: { 'stroke-width': 700 },\n            duration: 1.5,\n            ease: 'power2.inOut',\n          },\n          0,\n        );\n      });\n\n      timeline.to(\n        splitTitle.words,\n        {\n          yPercent: 0,\n          duration: 0.75,\n          ease: 'power3.out',\n          stagger: 0.075,\n        },\n        0.35,\n      );\n\n      timeline.to(\n        splitDescription.words,\n        {\n          yPercent: 0,\n          duration: 0.65,\n          ease: 'power3.out',\n          stagger: 0.03,\n        },\n        0.45,\n      );\n    };\n\n    const onMouseLeave = () => {\n      timeline?.kill();\n      timeline = gsap.timeline();\n\n      paths.forEach((path) => {\n        const length = path.getTotalLength();\n        timeline?.to(\n          path,\n          {\n            strokeDashoffset: length,\n            attr: { 'stroke-width': 200 },\n            duration: 1,\n            ease: 'power2.inOut',\n          },\n          0,\n        );\n      });\n\n      timeline.to(\n        splitDescription.words,\n        {\n          yPercent: 100,\n          duration: 0.35,\n          ease: 'power3.in',\n          stagger: { each: 0.02, from: 'end' },\n        },\n        0,\n      );\n\n      timeline.to(\n        splitTitle.words,\n        {\n          yPercent: 100,\n          duration: 0.5,\n          ease: 'power3.in',\n          stagger: { each: 0.05, from: 'end' },\n        },\n        0.05,\n      );\n    };\n\n    card.addEventListener('mouseenter', onMouseEnter);\n    card.addEventListener('mouseleave', onMouseLeave);\n\n    return () => {\n      card.removeEventListener('mouseenter', onMouseEnter);\n      card.removeEventListener('mouseleave', onMouseLeave);\n      timeline?.kill();\n      splitTitle.revert();\n      splitDescription.revert();\n    };\n  }, []);\n\n  return (\n    <div\n      ref={cardRef}\n      className={cn(\n        'card-container relative aspect-square w-full overflow-hidden rounded-2xl',\n        className,\n      )}\n    >\n      <div className=\"card-img absolute inset-0\">\n        <img src={imageSrc} alt={imageAlt} className=\"size-full object-cover\" />\n      </div>\n\n      <div className=\"svg-stroke svg-stroke-1 pointer-events-none absolute left-1/2 top-1/2 size-full -translate-x-1/2 -translate-y-1/2 scale-150\">\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          fill=\"none\"\n          viewBox=\"0 0 2453 2273\"\n          className=\"size-full\"\n        >\n          <path\n            strokeLinecap=\"round\"\n            strokeWidth=\"200\"\n            stroke={accentStrokeColor}\n            d=\"M227.549 1818.76 C227.549 1818.76 406.016 2207.75 569.049 2130.26 C843.431 1999.85 -264.104 1002.3 227.549 876.262 C552.918 792.849 773.647 2456.11 1342.05 2130.26 C1885.43 1818.76 14.9644 455.772 760.548 137.262 C1342.05 -111.152 1663.5 2266.35 2209.55 1972.76 C2755.6 1679.18 1536.63 384.467 1826.55 137.262 C2013.5 -22.1463 2209.55 381.262 2209.55 381.262\"\n          />\n        </svg>\n      </div>\n\n      <div className=\"svg-stroke svg-stroke-2 pointer-events-none absolute left-1/2 top-1/2 size-full -translate-x-1/2 -translate-y-1/2 scale-150\">\n        <svg\n          xmlns=\"http://www.w3.org/2000/svg\"\n          fill=\"none\"\n          viewBox=\"0 0 2250 2535\"\n          className=\"size-full\"\n        >\n          <path\n            strokeLinecap=\"round\"\n            strokeWidth=\"200\"\n            stroke={baseStrokeColor}\n            d=\"M1661.28 2255.51 C1661.28 2255.51 2311.09 1960.37 2111.78 1817.01 C1944.47 1696.67 718.456 2870.17 499.781 2255.51 C308.969 1719.17 2457.51 1613.83 2111.78 963.512 C1766.05 313.198 427.949 2195.17 132.281 1455.51 C-155.219 736.292 2014.78 891.514 1708.78 252.012 C1437.81 -314.29 369.471 909.169 132.281 566.512 C18.1772 401.672 244.781 193.012 244.781 193.012\"\n          />\n        </svg>\n      </div>\n\n      <div\n        className=\"card-title absolute bottom-8 left-8 z-10 max-w-[22ch]\"\n        style={{ color: textColor }}\n      >\n        <h3 className=\"text-3xl font-bold leading-none tracking-tight sm:text-4xl\">\n          {title}\n        </h3>\n        <p className=\"mt-2 text-xs leading-[1.3] opacity-90 sm:text-sm\">\n          {description}\n        </p>\n      </div>\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/odysseyui/card-stroke.tsx"
    }
  ],
  "type": "registry:ui"
}