{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scramble-text",
  "type": "registry:component",
  "title": "ScrambleText",
  "description": "Decrypts its text from random glyphs to the target when it scrolls into view. rAF-stepped, zero deps. Use a monospace container to avoid reflow.",
  "dependencies": [],
  "registryDependencies": [],
  "tier": "free",
  "files": [
    {
      "path": "components/prism/ScrambleText.tsx",
      "content": "\"use client\";\n\nimport { createElement, useEffect, useRef, useState, type ElementType } from \"react\";\n\n/**\n * ScrambleText — decrypts its text from random glyphs to the target when it\n * scrolls into view. rAF-stepped, no deps. Use a monospace/tabular container to\n * avoid per-frame reflow. Honors prefers-reduced-motion (shows final text).\n */\nconst GLYPHS = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#%&@$*<>/\\\\\";\n\nexport function ScrambleText({\n  text,\n  className = \"\",\n  duration = 1.1,\n  as: Tag = \"span\",\n}: {\n  text: string;\n  className?: string;\n  /** seconds */\n  duration?: number;\n  as?: \"span\" | \"h1\" | \"h2\" | \"h3\" | \"p\";\n}) {\n  const ref = useRef<HTMLSpanElement>(null);\n  const [display, setDisplay] = useState(text);\n\n  useEffect(() => {\n    const el = ref.current;\n    if (!el) return;\n    if (window.matchMedia(\"(prefers-reduced-motion: reduce)\").matches) {\n      setDisplay(text);\n      return;\n    }\n\n    let raf = 0;\n    let start = 0;\n    const step = (now: number) => {\n      if (!start) start = now;\n      const p = Math.min((now - start) / (duration * 1000), 1);\n      const revealed = Math.floor(p * text.length);\n      let out = \"\";\n      for (let i = 0; i < text.length; i++) {\n        if (i < revealed || text[i] === \" \") out += text[i];\n        else out += GLYPHS[Math.floor(Math.random() * GLYPHS.length)];\n      }\n      setDisplay(out);\n      if (p < 1) raf = requestAnimationFrame(step);\n      else setDisplay(text);\n    };\n\n    const io = new IntersectionObserver(\n      (entries) => {\n        if (entries[0].isIntersecting) {\n          raf = requestAnimationFrame(step);\n          io.disconnect();\n        }\n      },\n      { threshold: 0.5 }\n    );\n    io.observe(el);\n\n    return () => {\n      cancelAnimationFrame(raf);\n      io.disconnect();\n    };\n  }, [text, duration]);\n\n  const Comp = Tag as ElementType;\n  return createElement(\n    Comp,\n    { ref, className, style: { fontVariantNumeric: \"tabular-nums\" }, \"aria-label\": text },\n    display\n  );\n}\n",
      "type": "registry:component",
      "target": "components/prism/ScrambleText.tsx"
    }
  ]
}