{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "scramble-text",
  "title": "Scramble Text",
  "description": "Scrambles characters then resolves them into the final text.",
  "dependencies": [
    "@vllnt/ui@^0.2.1"
  ],
  "registryDependencies": [],
  "files": [
    {
      "path": "registry/default/scramble-text/scramble-text.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nimport { cn } from \"@vllnt/ui\";\n\n/** Props for {@link ScrambleText}. */\nexport type ScrambleTextProps = React.ComponentPropsWithoutRef<\"span\"> & {\n  /** Milliseconds for the full resolve. Defaults to `1200`. */\n  duration?: number;\n  /** Pool of glyphs used while scrambling. */\n  scrambleCharacters?: string;\n  /** Final resolved text. */\n  text: string;\n};\n\nfunction usePrefersReducedMotion(): boolean {\n  const [reduced, setReduced] = React.useState(false);\n\n  React.useEffect(() => {\n    if (\n      typeof window === \"undefined\" ||\n      typeof window.matchMedia !== \"function\"\n    ) {\n      return;\n    }\n\n    const query = window.matchMedia(\"(prefers-reduced-motion: reduce)\");\n    const onChange = (): void => {\n      setReduced(query.matches);\n    };\n\n    onChange();\n    query.addEventListener(\"change\", onChange);\n\n    return () => {\n      query.removeEventListener(\"change\", onChange);\n    };\n  }, []);\n\n  return reduced;\n}\n\nfunction scramble(text: string, revealed: number, pool: string): string {\n  const characters = text.match(/[\\s\\S]/gu) ?? [];\n  return characters\n    .map((character, index) => {\n      if (index < revealed || character === \" \") {\n        return character;\n      }\n      return pool.charAt(Math.floor(Math.random() * pool.length));\n    })\n    .join(\"\");\n}\n\nconst defaultPool = \"ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789\";\n\n/**\n * Resolves text left-to-right out of randomized glyphs on mount.\n *\n * Respects `prefers-reduced-motion`: the final text shows at once.\n *\n * @example\n * ```tsx\n * <ScrambleText text=\"DECRYPTED\" />\n * ```\n */\nexport const ScrambleText = ({\n  className,\n  duration = 1200,\n  ref,\n  scrambleCharacters = defaultPool,\n  text,\n  ...props\n}: ScrambleTextProps & { ref?: React.Ref<HTMLSpanElement> }) => {\n  const reduced = usePrefersReducedMotion();\n  const [display, setDisplay] = React.useState(text);\n\n  React.useEffect(() => {\n    if (reduced) {\n      setDisplay(text);\n      return;\n    }\n\n    const steps = Math.max(text.length, 1);\n    const tick = duration / steps;\n    let revealed = 0;\n    const timer = setInterval(() => {\n      revealed += 1;\n      setDisplay(scramble(text, revealed, scrambleCharacters));\n      if (revealed >= text.length) {\n        clearInterval(timer);\n      }\n    }, tick);\n\n    return () => {\n      clearInterval(timer);\n    };\n  }, [duration, reduced, scrambleCharacters, text]);\n\n  return (\n    <span\n      aria-label={text}\n      className={cn(\"font-mono\", className)}\n      ref={ref}\n      {...props}\n    >\n      <span aria-hidden=\"true\">{display}</span>\n    </span>\n  );\n};\nScrambleText.displayName = \"ScrambleText\";\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component",
  "version": "0.2.1",
  "stability": "stable"
}
