{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "pointer",
  "type": "registry:component",
  "title": "Pointer",
  "description": "A controllable on-screen pointer that moves to coordinates and shows thoughts.",
  "dependencies": [
    "lucide-react"
  ],
  "files": [
    {
      "path": "registry/new-york/blocks/pointer/pointer.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { MousePointer2 } from \"lucide-react\";\n\ntype PointerLayerContextValue = {\n  getRect: () => DOMRect | null;\n} | null;\n\nconst PointerLayerContext = React.createContext<PointerLayerContextValue>(null);\n\nexport interface PointerLayerProps extends React.HTMLAttributes<HTMLDivElement> {}\n\nexport function PointerLayer({ className, children, ...props }: PointerLayerProps) {\n  const ref = React.useRef<HTMLDivElement | null>(null);\n\n  const getRect = React.useCallback((): DOMRect | null => {\n    const el = ref.current;\n    if (!el) return null;\n    return el.getBoundingClientRect();\n  }, []);\n\n  return (\n    <PointerLayerContext.Provider value={{ getRect }}>\n      <div ref={ref} className={`relative w-full h-full ${className ?? \"\"}`} {...props}>\n        {children}\n      </div>\n    </PointerLayerContext.Provider>\n  );\n}\n\nfunction useLayerBounds() {\n  const ctx = React.useContext(PointerLayerContext);\n  return ctx?.getRect ?? (() => null);\n}\n\nexport interface PointerProps extends React.HTMLAttributes<HTMLDivElement> {\n  x: number;\n  y: number;\n  thoughts?: string | null;\n  clampToLayer?: boolean;\n}\n\nexport function Pointer({ x, y, thoughts, clampToLayer = true, className, ...props }: PointerProps) {\n  const getRect = useLayerBounds();\n\n  const { clampedX, clampedY } = React.useMemo(() => {\n    const rect = getRect();\n    if (!clampToLayer || !rect) return { clampedX: x, clampedY: y };\n    const maxX = Math.max(0, rect.width - 20);\n    const maxY = Math.max(0, rect.height - 20);\n    return {\n      clampedX: Math.min(Math.max(0, x), maxX),\n      clampedY: Math.min(Math.max(0, y), maxY),\n    };\n  }, [x, y, clampToLayer, getRect]);\n\n  const showThoughts = Boolean(thoughts && thoughts.trim().length > 0);\n\n  return (\n    <div\n      className={`absolute pointer-events-none transition-all duration-1000 ease-in-out ${className ?? \"\"}`}\n      style={{ left: clampedX, top: clampedY }}\n      aria-live=\"polite\"\n      {...props}\n    >\n      <MousePointer2 className=\"text-primary drop-shadow-sm size-9 stroke-[1px] fill-primary/60\" />\n\n      {/* Thoughts bubble (anchored near bottom-right of the cursor) */}\n      <div\n        className={`absolute z-[5] select-text top-6 left-6 inline-flex items-center rounded-xl border border-border bg-card text-card-foreground shadow-md px-3 py-1.5 text-xs whitespace-nowrap transition-opacity duration-200 ease-out ${\n          showThoughts ? \"opacity-100\" : \"opacity-0\"\n        }`}\n        aria-hidden={!showThoughts}\n      >\n        {thoughts}\n      </div>\n    </div>\n  );\n}\n\nexport type MoveToArgs = { x: number; y: number; thoughts?: string | null };\n\nexport function usePointer(initial: { x?: number; y?: number; thoughts?: string | null } = {}) {\n  const [x, setX] = React.useState<number>(initial.x ?? 24);\n  const [y, setY] = React.useState<number>(initial.y ?? 24);\n  const [thoughts, setThoughts] = React.useState<string | null>(initial.thoughts ?? null);\n\n  const moveTo = React.useCallback((args: MoveToArgs) => {\n    setX(args.x);\n    setY(args.y);\n    if (typeof args.thoughts !== \"undefined\") setThoughts(args.thoughts);\n  }, []);\n\n  return { x, y, thoughts, moveTo, setThoughts } as const;\n}\n\nexport default Pointer;\n\n\n",
      "type": "registry:component"
    }
  ]
}