{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "lens",
  "title": "Lens",
  "description": "Circular magnifier that follows the pointer or arrow keys to zoom into an image.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "src/components/velora/lens.tsx",
      "content": "\"use client\";\n\nimport { useRef, useState } from \"react\";\n\nimport { cn } from \"@/lib/utils\";\n\ninterface LensProps extends React.HTMLAttributes<HTMLDivElement> {\n  /** The content to magnify, usually an `<img>` that fills the box */\n  children: React.ReactNode;\n  /** How many times the lens magnifies */\n  zoomFactor?: number;\n  /** Lens radius in px */\n  radius?: number;\n  /** Pin the lens open at this spot (percentages of width and height) and ignore the pointer — handy for touch screens and product callouts */\n  staticPosition?: { x: number; y: number };\n}\n\n/**\n * A circular magnifier that follows the pointer over an image (or any\n * content). Keyboard users can focus it, move the lens with the arrow keys\n * (Shift for bigger steps) and hide it with Escape. The magnified copy is\n * aria-hidden and inert.\n */\nexport function Lens({\n  children,\n  zoomFactor = 2,\n  radius = 80,\n  staticPosition,\n  className,\n  style,\n  ...props\n}: LensProps) {\n  const ref = useRef<HTMLDivElement>(null);\n  const start = staticPosition ?? { x: 50, y: 50 };\n  // Position lives in CSS variables, so pointer moves never re-render.\n  const pos = useRef(start);\n  const [shown, setShown] = useState(false);\n  const visible = shown || !!staticPosition;\n\n  const moveTo = (x: number, y: number) => {\n    pos.current = { x: Math.min(100, Math.max(0, x)), y: Math.min(100, Math.max(0, y)) };\n    ref.current?.style.setProperty(\"--x\", `${pos.current.x}%`);\n    ref.current?.style.setProperty(\"--y\", `${pos.current.y}%`);\n    setShown(true);\n  };\n\n  const track = (event: React.PointerEvent<HTMLDivElement>) => {\n    if (staticPosition) return;\n    const rect = event.currentTarget.getBoundingClientRect();\n    moveTo(\n      ((event.clientX - rect.left) / rect.width) * 100,\n      ((event.clientY - rect.top) / rect.height) * 100\n    );\n  };\n\n  const keys: Record<string, [number, number]> = {\n    ArrowLeft: [-1, 0],\n    ArrowRight: [1, 0],\n    ArrowUp: [0, -1],\n    ArrowDown: [0, 1],\n  };\n\n  return (\n    <div\n      ref={ref}\n      tabIndex={0}\n      data-slot=\"lens\"\n      onPointerMove={track}\n      onPointerDown={track}\n      onPointerLeave={(event) => event.pointerType === \"mouse\" && setShown(false)}\n      onFocus={(event) => event.target.matches(\":focus-visible\") && setShown(true)}\n      onBlur={() => setShown(false)}\n      onKeyDown={(event) => {\n        if (event.key === \"Escape\") setShown(false);\n        const step = keys[event.key];\n        if (!step || staticPosition) return;\n        event.preventDefault();\n        const size = event.shiftKey ? 12 : 4;\n        moveTo(pos.current.x + step[0] * size, pos.current.y + step[1] * size);\n      }}\n      className={cn(\n        \"relative overflow-hidden rounded-2xl focus-visible:outline-2 focus-visible:outline-offset-2 focus-visible:outline-ring\",\n        !staticPosition && \"cursor-crosshair\",\n        className\n      )}\n      style={\n        {\n          \"--x\": `${start.x}%`,\n          \"--y\": `${start.y}%`,\n          \"--r\": `${radius}px`,\n          ...style,\n        } as React.CSSProperties\n      }\n      {...props}\n    >\n      {children}\n      <div\n        aria-hidden\n        inert\n        className={cn(\n          \"pointer-events-none absolute inset-0 bg-background transition-[opacity,clip-path,transform-origin] duration-150 ease-out motion-reduce:transition-none\",\n          !visible && \"opacity-0\"\n        )}\n        style={{\n          transform: `scale(${zoomFactor})`,\n          transformOrigin: \"var(--x) var(--y)\",\n          clipPath: `circle(calc(var(--r) / ${zoomFactor}) at var(--x) var(--y))`,\n        }}\n      >\n        {children}\n      </div>\n      <div\n        aria-hidden\n        className={cn(\n          \"pointer-events-none absolute top-(--y) left-(--x) size-[calc(var(--r)*2)] -translate-1/2 rounded-full border-2 border-white/80 shadow-[0_10px_40px_-8px_rgb(0_0_0/0.45)] ring-1 ring-black/10 transition-[opacity,scale,top,left] duration-150 ease-out motion-reduce:transition-none\",\n          !visible && \"scale-90 opacity-0\"\n        )}\n      />\n    </div>\n  );\n}\n",
      "type": "registry:ui",
      "target": "components/velora/lens.tsx"
    }
  ],
  "type": "registry:ui"
}