{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "hyper-text",
  "type": "registry:component",
  "title": "Hyper text",
  "description": "Text that decodes itself: every character cycles through random glyphs and locks into place from left to right.",
  "dependencies": [
    "motion"
  ],
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/premium/hyper-text.tsx",
      "type": "registry:ui",
      "target": "components/ui/hyper-text.tsx",
      "content": "\"use client\"\nimport * as React from \"react\"\nimport { useInView, useReducedMotion } from \"motion/react\"\nimport { cn } from \"@/lib/utils\"\n\n/**\n * Text that decodes itself: every character cycles through random glyphs and locks into place from left to right.\n * A violet scanning head marks the letter about to settle. It runs once\n * when it scrolls into view and again on hover, set in monospace so nothing\n * shifts while it runs. Screen readers only ever get the final text, and\n * reduced motion shows it at once.\n */\ntype HyperTextProps = {\n  text?: string\n  /** Milliseconds for the whole line to settle. */\n  duration?: number\n  /** The pool of glyphs characters cycle through before they lock. */\n  characters?: string\n  /** When to run: the first time it is in view, on hover, or both. */\n  trigger?: \"mount\" | \"hover\" | \"both\"\n  className?: string\n}\n\n/** How much of the run the first character spends scrambling before anything locks. */\nconst LEAD = 0.25\n\nfunction HyperText({ text = \"Close the books in 24 hours\", duration = 1100, characters = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789#%&*+<=>?@\", trigger = \"both\", className }: HyperTextProps) {\n  const reduce = useReducedMotion()\n  const ref = React.useRef<HTMLSpanElement>(null)\n  const inView = useInView(ref, { once: true, amount: 0.6 })\n  // How many characters have locked; everything past it is still scrambling. -1 means settled.\n  const [head, setHead] = React.useState(-1)\n  const [glyphs, setGlyphs] = React.useState<string[]>([])\n  const raf = React.useRef(0)\n  const chars = React.useMemo(() => Array.from(text), [text])\n  const pool = characters.length ? characters : \"#\"\n\n  const run = React.useCallback(() => {\n    if (reduce) return\n    window.cancelAnimationFrame(raf.current)\n    const n = chars.length\n    const start = performance.now()\n    let tick = 0\n    const roll = () => setGlyphs(chars.map((c) => (c === \" \" ? \" \" : pool[Math.floor(Math.random() * pool.length)])))\n    roll()\n    const step = (now: number) => {\n      const p = Math.min(1, (now - start) / Math.max(1, duration))\n      // Characters lock one after another from the left, after a short lead where everything churns.\n      const locked = p >= 1 ? n : Math.floor(Math.max(0, (p - LEAD) / (1 - LEAD)) * (n + 1))\n      // A glyph changes every third frame: fast enough to read as churn, slow enough to read at all.\n      if (tick++ % 3 === 0) roll()\n      setHead(locked >= n ? -1 : locked)\n      if (p < 1) raf.current = window.requestAnimationFrame(step)\n    }\n    raf.current = window.requestAnimationFrame(step)\n  }, [chars, pool, duration, reduce])\n\n  React.useEffect(() => () => window.cancelAnimationFrame(raf.current), [])\n\n  React.useEffect(() => {\n    if (inView && trigger !== \"hover\") run()\n  }, [inView, trigger, run])\n\n  // Each word keeps its trailing space and wraps as a unit, so lines break where the real text would.\n  const words: { start: number; chars: string[] }[] = []\n  chars.forEach((c, i) => {\n    if (!words.length || chars[i - 1] === \" \") words.push({ start: i, chars: [] })\n    words[words.length - 1].chars.push(c)\n  })\n\n  return (\n    <span\n      ref={ref}\n      data-slot=\"hyper-text\"\n      onPointerEnter={(e) => {\n        if (trigger !== \"mount\" && e.pointerType !== \"touch\") run()\n      }}\n      className={cn(\"mx-auto block w-fit max-w-full font-mono text-2xl font-medium tracking-[-0.01em] text-fg sm:text-3xl\", className)}\n    >\n      <span className=\"sr-only\">{text}</span>\n      <span aria-hidden>\n        {words.map((w, wi) => (\n          <span key={wi} className=\"inline-block whitespace-pre\">\n            {w.chars.map((c, ci) => {\n              const i = w.start + ci\n              const settled = reduce || head === -1 || i < head || c === \" \"\n              return (\n                <span key={ci} className={cn(\"inline-block\", !settled && (i === head ? \"text-accent-fg\" : \"text-fg-subtle\"))}>\n                  {settled ? c : (glyphs[i] ?? c)}\n                </span>\n              )\n            })}\n          </span>\n        ))}\n      </span>\n    </span>\n  )\n}\n\nexport { HyperText }\nexport type { HyperTextProps }\n"
    }
  ]
}
