{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "echo-text",
  "title": "EchoText",
  "description": "Layered stroke text that follows the pointer for a simple motion trail effect.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/components/echo-text.tsx",
      "content": "\"use client\";\n\n/**\n * Layered stroke text that follows pointer movement. Content is the `text` string only\n * (not arbitrary children) so layers stay plain text. Depends on `cn` from registry `utils`.\n */\nimport * as React from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\nexport type EchoTextProps = React.ComponentProps<\"div\"> & {\n  text?: string;\n  fontClassName?: string;\n};\n\nexport function EchoText({\n  text = \"text\",\n  className,\n  fontClassName,\n  \"aria-label\": ariaLabel,\n  role = \"img\",\n  onPointerMove,\n  onPointerLeave,\n  ...props\n}: EchoTextProps) {\n  const [offset, setOffset] = React.useState({ x: 0, y: 0 });\n\n  const handlePointerMove = (event: React.PointerEvent<HTMLDivElement>) => {\n    onPointerMove?.(event);\n\n    if (event.defaultPrevented) {\n      return;\n    }\n\n    const rect = event.currentTarget.getBoundingClientRect();\n    const nx = (event.clientX - rect.left - rect.width / 2) / (rect.width / 2);\n    const ny = (event.clientY - rect.top - rect.height / 2) / (rect.height / 2);\n\n    setOffset({ x: -nx, y: -ny });\n  };\n\n  const handlePointerLeave = (event: React.PointerEvent<HTMLDivElement>) => {\n    onPointerLeave?.(event);\n\n    if (event.defaultPrevented) {\n      return;\n    }\n\n    setOffset({ x: 0, y: 0 });\n  };\n\n  const layerClassName = cn(\n    \"text-foreground pointer-events-none absolute inset-0 text-[180px] leading-none font-medium transition-transform duration-300 ease-out\",\n    fontClassName,\n  );\n\n  const baseClassName = cn(\n    \"text-foreground relative text-[180px] leading-none font-medium\",\n    fontClassName,\n  );\n\n  return (\n    <div\n      aria-label={ariaLabel ?? text}\n      data-slot=\"echo-text\"\n      role={role}\n      className={cn(\"relative select-none\", className)}\n      onPointerMove={handlePointerMove}\n      onPointerLeave={handlePointerLeave}\n      {...props}\n    >\n      <p\n        aria-hidden\n        className={layerClassName}\n        style={{\n          WebkitTextStroke: \"2px currentColor\",\n          WebkitTextFillColor: \"transparent\",\n          transform: `translate(${offset.x * 32}px, ${offset.y * 32}px)`,\n        }}\n      >\n        {text}\n      </p>\n      <p\n        aria-hidden\n        className={layerClassName}\n        style={{\n          WebkitTextStroke: \"2px currentColor\",\n          WebkitTextFillColor: \"transparent\",\n          transform: `translate(${offset.x * 16}px, ${offset.y * 16}px)`,\n        }}\n      >\n        {text}\n      </p>\n      <p aria-hidden className={baseClassName}>\n        {text}\n      </p>\n    </div>\n  );\n}\n",
      "type": "registry:component"
    }
  ],
  "type": "registry:component"
}