{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "glitchy-text",
  "title": "Glitchy Text",
  "description": "Text that glitches like a broken screen, tearing into slices and flickering symbols.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/clover-ui/glitchy-text.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { useReducedMotion } from \"motion/react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport type GlitchyTextTrigger = \"auto\" | \"hover\" | \"always\";\n\nexport type GlitchyTextColor =\n  | \"default\"\n  | \"red\"\n  | \"blue\"\n  | \"green\"\n  | \"purple\"\n  | \"orange\"\n  | \"pink\"\n  | \"yellow\"\n  | \"teal\"\n  | \"cyan\"\n  | \"indigo\"\n  | \"pixel\";\n\nexport interface GlitchyTextProps extends Omit<React.ComponentProps<\"span\">, \"children\"> {\n  children: string;\n  trigger?: GlitchyTextTrigger;\n  interval?: number;\n  duration?: number;\n  color?: GlitchyTextColor;\n  colors?: [string, string];\n  intensity?: number;\n  scramble?: boolean;\n}\n\nconst GLYPHS = \"!<>-_\\\\/[]{}=+*^?#%&@$\";\nconst FRAME = 40;\nconst BLOCKS = 14;\nconst PIXELS = [\n  \"#ef4444\",\n  \"#f97316\",\n  \"#facc15\",\n  \"#22c55e\",\n  \"#22d3ee\",\n  \"#3b82f6\",\n  \"#a855f7\",\n  \"#ec4899\",\n];\n\nconst PALETTES: Record<GlitchyTextColor, [string, string]> = {\n  default: [\"currentColor\", \"currentColor\"],\n  red: [\"#ef4444\", \"#f43f5e\"],\n  blue: [\"#3b82f6\", \"#22d3ee\"],\n  green: [\"#22c55e\", \"#a3e635\"],\n  purple: [\"#a855f7\", \"#d946ef\"],\n  orange: [\"#f97316\", \"#facc15\"],\n  pink: [\"#ec4899\", \"#fb7185\"],\n  yellow: [\"#eab308\", \"#fde047\"],\n  teal: [\"#14b8a6\", \"#5eead4\"],\n  cyan: [\"#06b6d4\", \"#67e8f9\"],\n  indigo: [\"#6366f1\", \"#a78bfa\"],\n  pixel: [\"#ef4444\", \"#22d3ee\"],\n};\n\nconst random = (min: number, max: number) => min + Math.random() * (max - min);\nconst pick = <T,>(list: readonly T[]) => list[Math.floor(Math.random() * list.length)];\n\nfunction scrambled(text: string, amount: number) {\n  const chars = Array.from(text);\n  const spots = chars.map((char, index) => (char.trim() ? index : -1)).filter((i) => i >= 0);\n  if (!spots.length) return text;\n  const count = Math.max(1, Math.round(spots.length * amount));\n  for (let n = 0; n < count; n++) {\n    const spot = spots[Math.floor(Math.random() * spots.length)];\n    chars[spot] = GLYPHS[Math.floor(Math.random() * GLYPHS.length)];\n  }\n  return chars.join(\"\");\n}\n\nfunction GlitchyText({\n  children,\n  trigger = \"auto\",\n  interval = 3000,\n  duration = 450,\n  color = \"default\",\n  colors,\n  intensity = 1,\n  scramble = false,\n  className,\n  onPointerEnter,\n  onPointerLeave,\n  ...props\n}: GlitchyTextProps) {\n  const still = Boolean(useReducedMotion());\n  const [glitched, setGlitched] = React.useState<{ from: string; text: string } | null>(null);\n  const display = glitched?.from === children ? glitched.text : children;\n  const [hovering, setHovering] = React.useState(false);\n  const mainRef = React.useRef<HTMLSpanElement>(null);\n  const redRef = React.useRef<HTMLSpanElement>(null);\n  const blueRef = React.useRef<HTMLSpanElement>(null);\n  const blocksRef = React.useRef<(HTMLSpanElement | null)[]>([]);\n  const pixel = color === \"pixel\";\n  const tint = pixel && !colors;\n  const slices = colors ?? PALETTES[color] ?? PALETTES.default;\n\n  const active = !still && (trigger === \"always\" || (trigger === \"hover\" && hovering));\n  const bursts = !still && trigger === \"auto\";\n\n  React.useEffect(() => {\n    if (!active && !bursts) return;\n\n    let frame = 0;\n    let next = 0;\n    let end = 0;\n    let swing = 1;\n    const layers = [redRef.current, blueRef.current];\n\n    const reset = () => {\n      layers.forEach((layer) => {\n        if (!layer) return;\n        layer.style.opacity = \"0\";\n        layer.style.transform = \"\";\n        layer.style.clipPath = \"\";\n      });\n      blocksRef.current.forEach((block) => {\n        if (block) block.style.opacity = \"0\";\n      });\n      if (mainRef.current) mainRef.current.style.transform = \"\";\n      setGlitched(null);\n    };\n\n    const tick = () => {\n      const reach = 0.12 * intensity;\n      swing = -swing;\n      layers.forEach((layer, index) => {\n        if (!layer) return;\n        const top = random(0, 85);\n        const height = random(6, 40);\n        layer.style.opacity = String(index === 0 ? random(0.5, 0.8) : random(0.25, 0.5));\n        layer.style.clipPath = `inset(${top}% 0 ${Math.max(0, 100 - top - height)}% 0)`;\n        const side = index === 0 ? -1 : 1;\n        layer.style.transform = `translate3d(${(side * random(0.3, 1) + swing * 0.6) * reach}em, ${random(-0.02, 0.02) * intensity}em, 0)`;\n        if (tint) layer.style.color = pick(PIXELS);\n      });\n      if (pixel) {\n        blocksRef.current.forEach((block) => {\n          if (!block) return;\n          if (Math.random() < 0.45) {\n            block.style.opacity = \"0\";\n            return;\n          }\n          const size = 0.12 * Math.max(0.5, intensity);\n          block.style.left = `${random(-2, 98)}%`;\n          block.style.top = `${random(0, 90)}%`;\n          block.style.width = `${size * Math.ceil(random(0, 4))}em`;\n          block.style.height = `${size * Math.ceil(random(0, 2))}em`;\n          block.style.backgroundColor = pick(PIXELS);\n          block.style.opacity = String(random(0.7, 1));\n        });\n      }\n      if (mainRef.current) {\n        const skew = Math.random() < 0.2 ? random(-8, 8) * intensity : 0;\n        const shake = swing * random(0.04, 0.14) * intensity;\n        mainRef.current.style.transform = `translate3d(${shake}em, 0, 0) skewX(${skew}deg)`;\n      }\n      if (scramble) {\n        setGlitched({\n          from: children,\n          text: Math.random() < 0.55 ? scrambled(children, 0.12) : children,\n        });\n      }\n    };\n\n    const burst = () => {\n      end = performance.now() + duration;\n      const run = () => {\n        if (performance.now() >= end) {\n          reset();\n          next = window.setTimeout(burst, interval * random(0.8, 1.2));\n          return;\n        }\n        tick();\n        frame = window.setTimeout(run, FRAME);\n      };\n      run();\n    };\n\n    if (active) {\n      const loop = () => {\n        tick();\n        frame = window.setTimeout(loop, FRAME);\n      };\n      loop();\n    } else {\n      next = window.setTimeout(burst, interval * random(0.4, 1));\n    }\n\n    return () => {\n      window.clearTimeout(frame);\n      window.clearTimeout(next);\n      reset();\n    };\n  }, [active, bursts, children, duration, interval, intensity, scramble, pixel, tint]);\n\n  const layerClass =\n    \"pointer-events-none absolute inset-0 whitespace-pre-wrap opacity-0 will-change-transform select-none\";\n\n  return (\n    <span\n      className={cn(\"relative inline-block\", className)}\n      onPointerEnter={(event) => {\n        onPointerEnter?.(event);\n        setHovering(true);\n      }}\n      onPointerLeave={(event) => {\n        onPointerLeave?.(event);\n        setHovering(false);\n      }}\n      {...props}\n    >\n      <span className=\"sr-only\">{children}</span>\n      <span aria-hidden className=\"relative block\">\n        <span ref={mainRef} className=\"relative block whitespace-pre-wrap will-change-transform\">\n          {display}\n        </span>\n        <span ref={redRef} className={layerClass} style={{ color: slices[0] }}>\n          {display}\n        </span>\n        <span ref={blueRef} className={layerClass} style={{ color: slices[1] }}>\n          {display}\n        </span>\n        {pixel && (\n          <span className=\"pointer-events-none absolute inset-0 select-none\">\n            {Array.from({ length: BLOCKS }, (_, index) => (\n              <span\n                key={index}\n                ref={(block) => {\n                  blocksRef.current[index] = block;\n                }}\n                className=\"absolute opacity-0\"\n              />\n            ))}\n          </span>\n        )}\n      </span>\n    </span>\n  );\n}\n\nexport { GlitchyText };\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}