{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "product-image",
  "type": "registry:ui",
  "title": "Product image",
  "description": "Image on the surface token with shimmer, fade-in, hover zoom and optional hover swap.",
  "author": "StoreKit <hello@storekit.dev>",
  "categories": [
    "ui"
  ],
  "dependencies": [],
  "registryDependencies": [
    "https://storekit-eight.vercel.app/r/cn.json"
  ],
  "files": [
    {
      "path": "ui/product-image.tsx",
      "content": "\"use client\"\n\nimport { useEffect, useRef, useState } from \"react\"\nimport { cn } from \"@/components/storekit/lib/cn\"\n\n/* Product image on the surface token with a fade-in on load and an optional\n   hover swap to a second image. Plain <img> so it works outside Next too. */\n\nexport function ProductImage({\n  src,\n  hoverSrc,\n  alt,\n  className,\n  aspect = \"square\",\n  fit = \"contain\",\n  zoomOnHover = true,\n}: {\n  src: string\n  hoverSrc?: string\n  alt: string\n  className?: string\n  aspect?: \"square\" | \"portrait\" | \"wide\"\n  fit?: \"contain\" | \"cover\"\n  zoomOnHover?: boolean\n}) {\n  const [loaded, setLoaded] = useState(false)\n  const ref = useRef<HTMLImageElement>(null)\n  // Cached images can finish before React attaches onLoad; check on mount.\n  useEffect(() => {\n    if (ref.current?.complete && ref.current.naturalWidth > 0) setLoaded(true)\n  }, [src])\n  const ratio = { square: \"aspect-square\", portrait: \"aspect-[4/5]\", wide: \"aspect-[16/10]\" }[aspect]\n  return (\n    <div className={cn(\"group/img relative overflow-hidden bg-sk-surface\", ratio, className)}>\n      <img\n        ref={ref}\n        src={src}\n        alt={alt}\n        loading=\"lazy\"\n        onLoad={() => setLoaded(true)}\n        onError={() => setLoaded(true)}\n        className={cn(\n          \"absolute inset-0 h-full w-full transition-[opacity,transform] duration-700 ease-sk-out\",\n          fit === \"contain\" ? \"object-contain p-[8%]\" : \"object-cover\",\n          loaded ? \"opacity-100\" : \"opacity-0\",\n          zoomOnHover && \"group-hover/img:scale-[1.04]\",\n          hoverSrc && \"group-hover/img:opacity-0\"\n        )}\n      />\n      {hoverSrc ? (\n        <img\n          src={hoverSrc}\n          alt=\"\"\n          aria-hidden\n          loading=\"lazy\"\n          className={cn(\"absolute inset-0 h-full w-full opacity-0 transition-opacity duration-500 group-hover/img:opacity-100\", fit === \"contain\" ? \"object-contain p-[8%]\" : \"object-cover\")}\n        />\n      ) : null}\n      {!loaded ? <div className=\"absolute inset-0 animate-sk-shimmer bg-[linear-gradient(90deg,transparent,rgb(var(--sk-ink)/0.05),transparent)] bg-[length:200%_100%]\" /> : null}\n    </div>\n  )\n}\n",
      "type": "registry:ui",
      "target": "components/storekit/ui/product-image.tsx"
    }
  ],
  "meta": {
    "section": "ui",
    "previewHeight": 260
  }
}