{
  "name": "squiggly-text",
  "type": "registry:ui",
  "files": [
    {
      "path": "components/ui/squiggly-text.tsx",
      "content": "\"use client\";\n\nimport React, { useId } from \"react\";\nimport { motion, useTime, useTransform } from \"motion/react\";\nimport { cn } from \"@/lib/utils\";\n\nexport interface SquigglyTextProps {\n  /**\n   * The text (or any node) to wrap with the squiggly effect.\n   */\n  children: React.ReactNode;\n  className?: string;\n  style?: React.CSSProperties;\n  /**\n   * Number of distinct displacement frames to cycle through.\n   * Higher = smoother wobble, more SVG filters in the DOM.\n   * @default 5\n   */\n  steps?: number;\n  /**\n   * Time between filter swaps, in milliseconds.\n   * Lower = more frantic; higher = lazier wave.\n   * @default 80\n   */\n  stepDuration?: number;\n  /**\n   * Maximum displacement in px. Bigger = more squiggly.\n   * Pass a single number for a constant scale, or a tuple to alternate\n   * between two values per step (matches the original Lucas Bebber demo).\n   * @default [6, 8]\n   */\n  scale?: number | [number, number];\n  /**\n   * Turbulence base frequency. Lower values produce longer, smoother waves;\n   * higher values produce tighter, jitterier noise.\n   * @default 0.02\n   */\n  baseFrequency?: number;\n  /**\n   * Number of turbulence octaves. Higher = more detailed noise.\n   * @default 3\n   */\n  numOctaves?: number;\n  /**\n   * Render the wrapper as this element type.\n   * @default \"span\"\n   */\n  as?: \"span\" | \"div\";\n}\n\nexport function SquigglyText({\n  children,\n  steps = 5,\n  stepDuration = 80,\n  scale = [6, 8],\n  baseFrequency = 0.02,\n  numOctaves = 3,\n  as = \"span\",\n  className,\n  style,\n}: SquigglyTextProps) {\n  const reactId = useId();\n  // useId can produce \":\" / \"_\" which aren't valid in CSS url(#…) refs.\n  const safeId = reactId.replace(/[:_]/g, \"\");\n  const filterId = (i: number) => `squiggly-${safeId}-${i}`;\n\n  const filters = React.useMemo(\n    () => Array.from({ length: steps }, (_, i) => `url(#${filterId(i)})`),\n    // filterId is stable per render\n    // eslint-disable-next-line react-hooks/exhaustive-deps\n    [steps, safeId],\n  );\n\n  const time = useTime();\n  const filter = useTransform(\n    time,\n    (t) => filters[Math.floor(t / stepDuration) % filters.length],\n  );\n\n  const scaleAt = (i: number) =>\n    Array.isArray(scale) ? scale[i % scale.length] : scale;\n\n  const Wrapper = as === \"div\" ? motion.div : motion.span;\n\n  return (\n    <Wrapper\n      style={{ filter, ...style }}\n      className={cn(\"inline-block\", className)}\n    >\n      <svg\n        aria-hidden\n        className=\"pointer-events-none absolute h-0 w-0 overflow-hidden\"\n        xmlns=\"http://www.w3.org/2000/svg\"\n      >\n        <defs>\n          {Array.from({ length: steps }).map((_, i) => (\n            <filter id={filterId(i)} key={i}>\n              <feTurbulence\n                baseFrequency={baseFrequency}\n                numOctaves={numOctaves}\n                result=\"noise\"\n                seed={i}\n              />\n              <feDisplacementMap\n                in=\"SourceGraphic\"\n                in2=\"noise\"\n                scale={scaleAt(i)}\n              />\n            </filter>\n          ))}\n        </defs>\n      </svg>\n      {children}\n    </Wrapper>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/ui/squiggly-text.tsx"
    }
  ],
  "author": "Manu Arora <hi@manuarora.in>",
  "title": "Squiggly Text"
}