{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "electric-text",
  "type": "registry:ui",
  "title": "Electric Text",
  "description": "Animated electric text with glowing, distorted outline-solid for dynamic neon effects.",
  "author": "Ahdeetai <https://aditya.is-cool.dev>",
  "registryDependencies": [],
  "dependencies": ["clsx", "tailwind-merge"],
  "files": [
    {
      "type": "registry:ui",
      "path": "components/ui/electric-text.tsx",
      "content": "'use client';\n\nimport React, {\n  useCallback,\n  useEffect,\n  useId,\n  useLayoutEffect,\n  useRef,\n  useState,\n} from 'react';\nimport { cn } from '@/lib/utils';\n\ntype ElectricTextProps = {\n  children: React.ReactNode;\n  color?: string;\n  speed?: number;\n  chaos?: number;\n  strokeWidth?: number;\n  className?: string;\n  glowIntensity?: 'low' | 'medium' | 'high';\n  fillColor?: string;\n  showFill?: boolean;\n};\n\nfunction hexToRgba(hex: string, alpha = 1): string {\n  if (!hex) return `rgba(0,0,0,${alpha})`;\n  let h = hex.replace('#', '');\n  if (h.length === 3) {\n    h = h\n      .split('')\n      .map((c) => c + c)\n      .join('');\n  }\n  const int = parseInt(h, 16);\n  const r = (int >> 16) & 255;\n  const g = (int >> 8) & 255;\n  const b = int & 255;\n  return `rgba(${r}, ${g}, ${b}, ${alpha})`;\n}\n\nconst ElectricText: React.FC<ElectricTextProps> = ({\n  children,\n  color = '#5227FF',\n  speed = 1,\n  chaos = 1,\n  strokeWidth = 2,\n  className = '',\n  glowIntensity = 'medium',\n  fillColor = 'rgba(0,0,0,0.8)',\n  showFill = true,\n}) => {\n  const rawId = useId().replace(/[:]/g, '');\n  const filterId = `text-turbulent-${rawId}`;\n  const svgRef = useRef<SVGSVGElement | null>(null);\n  const measureRef = useRef<HTMLDivElement | null>(null);\n  const [svgFontSize, setSvgFontSize] = useState(120);\n\n  const updateAnim = useCallback(() => {\n    const svg = svgRef.current;\n    if (!svg) return;\n\n    const dyAnims = Array.from(\n      svg.querySelectorAll<SVGAnimateElement>(\n        'feOffset > animate[attributeName=\"dy\"]',\n      ),\n    );\n    const dxAnims = Array.from(\n      svg.querySelectorAll<SVGAnimateElement>(\n        'feOffset > animate[attributeName=\"dx\"]',\n      ),\n    );\n\n    const baseDur = 6;\n    const dur = Math.max(0.001, baseDur / (speed || 1));\n    [...dyAnims, ...dxAnims].forEach((a) => a.setAttribute('dur', `${dur}s`));\n\n    const disp = svg.querySelector('feDisplacementMap');\n    if (disp) disp.setAttribute('scale', String(30 * (chaos || 1)));\n\n    requestAnimationFrame(() => {\n      [...dyAnims, ...dxAnims].forEach((a: SVGAnimateElement) => {\n        if (typeof a.beginElement === 'function') {\n          try {\n            a.beginElement();\n          } catch {}\n        }\n      });\n    });\n  }, [speed, chaos]);\n\n  useEffect(() => {\n    updateAnim();\n  }, [updateAnim]);\n\n  useLayoutEffect(() => {\n    if (measureRef.current) {\n      const computedStyle = window.getComputedStyle(measureRef.current);\n      const fontSize = parseFloat(computedStyle.fontSize);\n      const scaledSize = Math.max(60, Math.min(200, fontSize * 3));\n      setSvgFontSize(scaledSize);\n    }\n    setTimeout(updateAnim, 10);\n  }, [children, strokeWidth, className, updateAnim]);\n\n  const glowSettings = {\n    low: {\n      blur1: 10,\n      blur2: 2,\n      blur3: 1,\n      opacity1: 0.2,\n      opacity2: 0.5,\n      opacity3: 0.6,\n    },\n    medium: {\n      blur1: 20,\n      blur2: 4,\n      blur3: 2,\n      opacity1: 0.3,\n      opacity2: 0.7,\n      opacity3: 0.8,\n    },\n    high: {\n      blur1: 32,\n      blur2: 8,\n      blur3: 4,\n      opacity1: 0.5,\n      opacity2: 0.9,\n      opacity3: 1,\n    },\n  }[glowIntensity];\n\n  return (\n    <div className={cn('relative inline-block', className)}>\n      <div\n        ref={measureRef}\n        className='absolute opacity-0 pointer-events-none whitespace-nowrap'\n      >\n        {children}\n      </div>\n      <svg\n        ref={svgRef}\n        viewBox='0 0 1000 300'\n        className='w-full h-full block'\n        style={{ overflow: 'visible' }}\n        preserveAspectRatio='xMidYMid meet'\n      >\n        <defs>\n          <filter\n            id={filterId}\n            colorInterpolationFilters='sRGB'\n            x='-50%'\n            y='-50%'\n            width='200%'\n            height='200%'\n          >\n            <feTurbulence\n              type='turbulence'\n              baseFrequency='0.02'\n              numOctaves='10'\n              result='noise1'\n              seed='1'\n            />\n            <feOffset in='noise1' dx='0' dy='0' result='offsetNoise1'>\n              <animate\n                attributeName='dy'\n                values='700; 0'\n                dur='6s'\n                repeatCount='indefinite'\n                calcMode='linear'\n              />\n            </feOffset>\n\n            <feTurbulence\n              type='turbulence'\n              baseFrequency='0.02'\n              numOctaves='10'\n              result='noise2'\n              seed='1'\n            />\n            <feOffset in='noise2' dx='0' dy='0' result='offsetNoise2'>\n              <animate\n                attributeName='dy'\n                values='0; -700'\n                dur='6s'\n                repeatCount='indefinite'\n                calcMode='linear'\n              />\n            </feOffset>\n\n            <feTurbulence\n              type='turbulence'\n              baseFrequency='0.02'\n              numOctaves='10'\n              result='noise3'\n              seed='2'\n            />\n            <feOffset in='noise3' dx='0' dy='0' result='offsetNoise3'>\n              <animate\n                attributeName='dx'\n                values='490; 0'\n                dur='6s'\n                repeatCount='indefinite'\n                calcMode='linear'\n              />\n            </feOffset>\n\n            <feTurbulence\n              type='turbulence'\n              baseFrequency='0.02'\n              numOctaves='10'\n              result='noise4'\n              seed='2'\n            />\n            <feOffset in='noise4' dx='0' dy='0' result='offsetNoise4'>\n              <animate\n                attributeName='dx'\n                values='0; -490'\n                dur='6s'\n                repeatCount='indefinite'\n                calcMode='linear'\n              />\n            </feOffset>\n\n            <feComposite in='offsetNoise1' in2='offsetNoise2' result='part1' />\n            <feComposite in='offsetNoise3' in2='offsetNoise4' result='part2' />\n            <feBlend\n              in='part1'\n              in2='part2'\n              mode='color-dodge'\n              result='combinedNoise'\n            />\n            <feDisplacementMap\n              in='SourceGraphic'\n              in2='combinedNoise'\n              scale='30'\n              xChannelSelector='R'\n              yChannelSelector='B'\n            />\n          </filter>\n        </defs>\n\n        {showFill && (\n          <>\n            <text\n              x='500'\n              y='150'\n              dominantBaseline='central'\n              textAnchor='middle'\n              stroke={hexToRgba(color, glowSettings.opacity1)}\n              strokeWidth={strokeWidth * 3}\n              fill='none'\n              fontSize={svgFontSize}\n              fontWeight='bold'\n              style={{ filter: `blur(${glowSettings.blur1}px)` }}\n            >\n              {children}\n            </text>\n\n            <text\n              x='500'\n              y='150'\n              dominantBaseline='central'\n              textAnchor='middle'\n              stroke={hexToRgba(color, 0.6)}\n              strokeWidth={strokeWidth * 1.5}\n              fill='none'\n              fontSize={svgFontSize}\n              fontWeight='bold'\n              style={{\n                filter: `blur(${glowSettings.blur2}px)`,\n                opacity: glowSettings.opacity2,\n              }}\n            >\n              {children}\n            </text>\n          </>\n        )}\n\n        <text\n          x='500'\n          y='150'\n          dominantBaseline='central'\n          textAnchor='middle'\n          stroke={color}\n          strokeWidth={strokeWidth}\n          fill='none'\n          fontSize={svgFontSize}\n          fontWeight='bold'\n          filter={`url(#${filterId})`}\n        >\n          {children}\n        </text>\n\n        {showFill && (\n          <>\n            <text\n              x='500'\n              y='150'\n              dominantBaseline='central'\n              textAnchor='middle'\n              stroke={color}\n              strokeWidth={strokeWidth * 0.5}\n              fill='none'\n              fontSize={svgFontSize}\n              fontWeight='bold'\n              style={{\n                filter: `blur(${glowSettings.blur3}px)`,\n                opacity: glowSettings.opacity3,\n              }}\n            >\n              {children}\n            </text>\n\n            <text\n              x='500'\n              y='150'\n              dominantBaseline='central'\n              textAnchor='middle'\n              fill={fillColor}\n              fontSize={svgFontSize}\n              fontWeight='bold'\n            >\n              {children}\n            </text>\n          </>\n        )}\n      </svg>\n    </div>\n  );\n};\n\nexport { ElectricText };\n"
    },
    {
      "type": "registry:lib",
      "path": "lib/utils.ts",
      "content": "import { clsx, type ClassValue } from \"clsx\";\nimport { twMerge } from \"tailwind-merge\";\n\nexport function cn(...inputs: ClassValue[]) {\n  return twMerge(clsx(inputs));\n}"
    }
  ]
}
