{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "loader-glitch-text",
  "title": "Glitch Text Loader",
  "description": "Text with RGB channel splitting, clip-path distortion, and character scrambling",
  "dependencies": [],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/blocks/loaders/loader-glitch-text/components/elements/loader-glitch-text.tsx",
      "content": "\"use client\";\n\nimport { useCallback, useEffect, useRef, useState } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nconst GLITCH_CHARS = \"!@#$%^&*<>{}[]|/\\\\~\";\n\ntype LoaderGlitchTextProps = {\n  text?: string;\n  intensity?: \"subtle\" | \"medium\" | \"heavy\";\n  className?: string;\n};\n\nexport type { LoaderGlitchTextProps };\n\nexport function LoaderGlitchText({\n  text = \"LOADING\",\n  intensity = \"medium\",\n  className,\n}: LoaderGlitchTextProps) {\n  const [displayText, setDisplayText] = useState(text);\n  const textRef = useRef(text);\n  const rafRef = useRef<number>(0);\n  const lastScrambleRef = useRef<number>(0);\n  const scrambleTimeoutRef = useRef<ReturnType<typeof setTimeout>>(undefined);\n\n  textRef.current = text;\n\n  const intensityConfig = {\n    subtle: {\n      translateScale: 1,\n      duration: \"4s\",\n      interval: 300,\n      scrambleChars: 1,\n    },\n    medium: {\n      translateScale: 1,\n      duration: \"2s\",\n      interval: 150,\n      scrambleChars: 2,\n    },\n    heavy: {\n      translateScale: 1.5,\n      duration: \"1s\",\n      interval: 80,\n      scrambleChars: 3,\n    },\n  }[intensity];\n\n  const scrambleText = useCallback(() => {\n    const chars = textRef.current.split(\"\");\n    const indices = new Set<number>();\n\n    while (\n      indices.size < Math.min(intensityConfig.scrambleChars, chars.length)\n    ) {\n      indices.add(Math.floor(Math.random() * chars.length));\n    }\n\n    const scrambled = chars.map((char, i) =>\n      indices.has(i) && char !== \" \"\n        ? GLITCH_CHARS[Math.floor(Math.random() * GLITCH_CHARS.length)]\n        : char,\n    );\n\n    setDisplayText(scrambled.join(\"\"));\n\n    scrambleTimeoutRef.current = setTimeout(\n      () => {\n        setDisplayText(textRef.current);\n      },\n      50 + Math.random() * 50,\n    );\n  }, [intensityConfig.scrambleChars]);\n\n  useEffect(() => {\n    setDisplayText(text);\n  }, [text]);\n\n  useEffect(() => {\n    let running = true;\n\n    const animate = (timestamp: number) => {\n      if (!running) return;\n\n      if (timestamp - lastScrambleRef.current >= intensityConfig.interval) {\n        lastScrambleRef.current = timestamp;\n        scrambleText();\n      }\n\n      rafRef.current = requestAnimationFrame(animate);\n    };\n\n    rafRef.current = requestAnimationFrame(animate);\n\n    return () => {\n      running = false;\n      if (rafRef.current) {\n        cancelAnimationFrame(rafRef.current);\n      }\n      if (scrambleTimeoutRef.current) {\n        clearTimeout(scrambleTimeoutRef.current);\n      }\n    };\n  }, [intensityConfig.interval, scrambleText]);\n\n  return (\n    <>\n      <style>{`\n        @keyframes glitch-clip-1 {\n          0% { clip-path: inset(40% 0 61% 0); transform: translate(calc(-2px * var(--translate-scale)), calc(-1px * var(--translate-scale))); }\n          20% { clip-path: inset(92% 0 1% 0); transform: translate(calc(1px * var(--translate-scale)), calc(2px * var(--translate-scale))); }\n          40% { clip-path: inset(43% 0 1% 0); transform: translate(calc(-1px * var(--translate-scale)), calc(3px * var(--translate-scale))); }\n          60% { clip-path: inset(25% 0 58% 0); transform: translate(calc(3px * var(--translate-scale)), calc(-1px * var(--translate-scale))); }\n          80% { clip-path: inset(54% 0 7% 0); transform: translate(calc(-2px * var(--translate-scale)), calc(2px * var(--translate-scale))); }\n          100% { clip-path: inset(58% 0 43% 0); transform: translate(calc(2px * var(--translate-scale)), calc(-2px * var(--translate-scale))); }\n        }\n\n        @keyframes glitch-clip-2 {\n          0% { clip-path: inset(65% 0 13% 0); transform: translate(calc(2px * var(--translate-scale)), calc(1px * var(--translate-scale))); }\n          20% { clip-path: inset(79% 0 14% 0); transform: translate(calc(-3px * var(--translate-scale)), 0px); }\n          40% { clip-path: inset(32% 0 52% 0); transform: translate(calc(1px * var(--translate-scale)), calc(-2px * var(--translate-scale))); }\n          60% { clip-path: inset(8% 0 76% 0); transform: translate(calc(-1px * var(--translate-scale)), calc(3px * var(--translate-scale))); }\n          80% { clip-path: inset(71% 0 2% 0); transform: translate(calc(3px * var(--translate-scale)), calc(1px * var(--translate-scale))); }\n          100% { clip-path: inset(15% 0 62% 0); transform: translate(calc(-2px * var(--translate-scale)), calc(-1px * var(--translate-scale))); }\n        }\n      `}</style>\n      <output\n        data-slot=\"loader-glitch-text\"\n        aria-live=\"polite\"\n        aria-label={text}\n        className={cn(\"relative inline-block font-mono\", className)}\n        style={\n          {\n            \"--translate-scale\": intensityConfig.translateScale,\n          } as React.CSSProperties\n        }\n      >\n        <span className=\"sr-only\">{text}</span>\n        <span aria-hidden=\"true\" className=\"relative text-foreground\">\n          {displayText}\n        </span>\n        <span\n          aria-hidden=\"true\"\n          className=\"absolute inset-0 text-foreground/70\"\n          style={{\n            color: \"oklch(0.65 0.2 25)\",\n            animation: `glitch-clip-1 ${intensityConfig.duration} infinite linear alternate-reverse`,\n          }}\n        >\n          {displayText}\n        </span>\n        <span\n          aria-hidden=\"true\"\n          className=\"absolute inset-0 text-foreground/70\"\n          style={{\n            color: \"oklch(0.7 0.15 200)\",\n            animation: `glitch-clip-2 ${intensityConfig.duration} infinite linear alternate-reverse`,\n          }}\n        >\n          {displayText}\n        </span>\n        <span\n          aria-hidden=\"true\"\n          className=\"pointer-events-none absolute inset-0\"\n          style={{\n            background:\n              \"repeating-linear-gradient(0deg, transparent, transparent 2px, oklch(0 0 0 / 0.03) 2px, oklch(0 0 0 / 0.03) 4px)\",\n          }}\n        />\n      </output>\n    </>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "docs": "Cyberpunk-style glitch text loader with channel split, scan-line overlay, and random character scramble. Intensity levels: subtle, medium, heavy.",
  "categories": [
    "loaders"
  ],
  "type": "registry:block"
}