{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "product-art",
  "title": "Product Art",
  "author": "Ballmac <https://ui.ballmac.com>",
  "description": "Soft square placeholders with a simple object drawn in a chart color, for product grids, carts and detail pages before real photos exist, or as the fallback when a photo fails to load. Theme tokens only.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/ballmac/components/product-art.tsx",
      "content": "// Ballmac UI: Product Art. https://ui.ballmac.com/components/product-art\nimport * as React from \"react\"\n\nimport { cn } from \"@/lib/utils\"\n\ntype ProductArtKind = \"speaker\" | \"mug\" | \"bag\" | \"lamp\" | \"bottle\" | \"headphones\"\ntype ProductArtTone = 1 | 2 | 3 | 4 | 5\n\ntype ProductArtProps = Omit<React.ComponentProps<\"div\">, \"children\"> & {\n  /** Which object to draw. */\n  kind: ProductArtKind\n  /** Which chart color tints the background and the object. */\n  tone?: ProductArtTone\n  /** Describes the product for screen readers. Leave it out when the name is written beside the picture. */\n  label?: string\n}\n\nconst SHAPES: Record<ProductArtKind, React.ReactNode> = {\n  speaker: (\n    <>\n      <rect x=\"62\" y=\"34\" width=\"76\" height=\"132\" rx=\"18\" fill=\"currentColor\" opacity=\"0.9\" />\n      <circle cx=\"100\" cy=\"76\" r=\"17\" fill=\"var(--card)\" opacity=\"0.9\" />\n      <circle cx=\"100\" cy=\"76\" r=\"7\" fill=\"currentColor\" opacity=\"0.55\" />\n      <circle cx=\"100\" cy=\"128\" r=\"25\" fill=\"var(--card)\" opacity=\"0.9\" />\n      <circle cx=\"100\" cy=\"128\" r=\"12\" fill=\"currentColor\" opacity=\"0.55\" />\n    </>\n  ),\n  mug: (\n    <>\n      <path d=\"M58 64h72v68a24 24 0 0 1-24 24H82a24 24 0 0 1-24-24V64Z\" fill=\"currentColor\" opacity=\"0.9\" />\n      <path d=\"M130 80h12a18 18 0 0 1 0 36h-12\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"11\" opacity=\"0.9\" />\n      <ellipse cx=\"94\" cy=\"64\" rx=\"36\" ry=\"7\" fill=\"var(--card)\" opacity=\"0.55\" />\n    </>\n  ),\n  bag: (\n    <>\n      <path d=\"M52 78h96l9 84a10 10 0 0 1-10 11H53a10 10 0 0 1-10-11l9-84Z\" fill=\"currentColor\" opacity=\"0.9\" />\n      <path d=\"M76 78V66a24 24 0 0 1 48 0v12\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"9\" opacity=\"0.9\" />\n      <rect x=\"86\" y=\"108\" width=\"28\" height=\"20\" rx=\"5\" fill=\"var(--card)\" opacity=\"0.7\" />\n    </>\n  ),\n  lamp: (\n    <>\n      <path d=\"M70 40h60l22 62H48l22-62Z\" fill=\"currentColor\" opacity=\"0.9\" />\n      <rect x=\"96\" y=\"102\" width=\"8\" height=\"52\" fill=\"currentColor\" opacity=\"0.7\" />\n      <ellipse cx=\"100\" cy=\"162\" rx=\"30\" ry=\"7\" fill=\"currentColor\" opacity=\"0.9\" />\n      <path d=\"M70 40h60\" stroke=\"var(--card)\" strokeWidth=\"3\" opacity=\"0.5\" />\n    </>\n  ),\n  bottle: (\n    <>\n      <rect x=\"88\" y=\"28\" width=\"24\" height=\"20\" rx=\"5\" fill=\"currentColor\" opacity=\"0.7\" />\n      <path d=\"M84 48h32l8 22v84a14 14 0 0 1-14 14H90a14 14 0 0 1-14-14V70l8-22Z\" fill=\"currentColor\" opacity=\"0.9\" />\n      <rect x=\"82\" y=\"96\" width=\"36\" height=\"34\" rx=\"6\" fill=\"var(--card)\" opacity=\"0.8\" />\n    </>\n  ),\n  headphones: (\n    <>\n      <path d=\"M52 112V96a48 48 0 0 1 96 0v16\" fill=\"none\" stroke=\"currentColor\" strokeWidth=\"10\" opacity=\"0.9\" strokeLinecap=\"round\" />\n      <rect x=\"40\" y=\"104\" width=\"30\" height=\"52\" rx=\"14\" fill=\"currentColor\" opacity=\"0.9\" />\n      <rect x=\"130\" y=\"104\" width=\"30\" height=\"52\" rx=\"14\" fill=\"currentColor\" opacity=\"0.9\" />\n      <rect x=\"47\" y=\"114\" width=\"12\" height=\"32\" rx=\"6\" fill=\"var(--card)\" opacity=\"0.5\" />\n      <rect x=\"141\" y=\"114\" width=\"12\" height=\"32\" rx=\"6\" fill=\"var(--card)\" opacity=\"0.5\" />\n    </>\n  ),\n}\n\n/**\n * A soft square with a simple object drawn in a chart color, for product grids and detail pages before real photos exist (or as the fallback when one fails\n * to load). It uses theme tokens only, fills whatever width it is given, and is hidden from screen readers unless it has a label.\n */\nfunction ProductArt({ kind, tone = 1, label, className, style, ...props }: ProductArtProps) {\n  return (\n    <div\n      data-slot=\"product-art\"\n      role={label ? \"img\" : undefined}\n      aria-label={label}\n      aria-hidden={label ? undefined : true}\n      className={cn(\"relative aspect-square w-full overflow-hidden rounded-xl\", className)}\n      style={{ background: `color-mix(in oklch, var(--chart-${tone}) 16%, var(--card))`, color: `color-mix(in oklch, var(--chart-${tone}) 78%, var(--foreground))`, ...style }}\n      {...props}\n    >\n      <svg viewBox=\"0 0 200 200\" className=\"absolute inset-0 size-full\" aria-hidden=\"true\">\n        <ellipse cx=\"100\" cy=\"172\" rx=\"52\" ry=\"7\" fill=\"currentColor\" opacity=\"0.14\" />\n        {SHAPES[kind]}\n      </svg>\n    </div>\n  )\n}\n\nexport { ProductArt, type ProductArtKind, type ProductArtProps, type ProductArtTone }\n",
      "type": "registry:ui",
      "target": "@components/ballmac/product-art.tsx"
    }
  ],
  "meta": {
    "schema": 1,
    "tier": "free",
    "tags": [
      "ecommerce",
      "product",
      "placeholder",
      "illustration",
      "image",
      "shop",
      "fallback"
    ],
    "version": "1.0.0",
    "updated": "2026-10-25",
    "tests": true,
    "ai": {
      "summary": "kind is 'speaker' | 'mug' | 'bag' | 'lamp' | 'bottle' | 'headphones'; tone is 1 to 5 (the chart color that tints the background and the object). It fills the width it is given as a square. Pass label to describe it as an image; without a label it is hidden from screen readers.",
      "whenToUse": [
        "Product cards and galleries in demos, templates and loading states",
        "A fallback when a product photo is missing"
      ],
      "whenNotToUse": [
        "Real product photography; use an image",
        "Icons; use lucide-react"
      ],
      "composesWith": [
        "ecommerce-1",
        "ecommerce-2",
        "ecommerce-3",
        "ecommerce-4"
      ],
      "a11y": [
        {
          "keys": "Image",
          "action": "Decorative by default (aria-hidden); with label it is role=img with that name"
        }
      ],
      "customization": [
        "kind and tone",
        "label",
        "className for the shape"
      ]
    },
    "statesNone": "A static illustration: nothing to load or fail.",
    "examples": [
      "product-art-demo",
      "product-art-tones"
    ],
    "url": "https://ui.ballmac.com/components/product-art"
  },
  "categories": [
    "marketing"
  ],
  "type": "registry:ui"
}