{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "plate",
  "title": "Plate",
  "author": "Visant Labs <visantsupply@gmail.com>",
  "description": "A placa onde a foto de catálogo senta. Resolve o recorte branco sobre fundo colorido.",
  "registryDependencies": [
    "utils"
  ],
  "files": [
    {
      "path": "registry/visant/ui/plate.tsx",
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { cn } from \"@/lib/utils\";\n\n/**\n * Plate — a placa onde a foto de catálogo senta.\n *\n * Colhida de um concept de e-commerce, e resolve um problema chato de verdade:\n * **foto de catálogo vem recortada sobre branco.** Jogada num fundo colorido ou\n * escuro, ela aparece como um retângulo branco duro — o defeito que a auditoria\n * chamou de \"duas placas concorrentes\" quando metade da página usava recorte e\n * a outra metade não.\n *\n * A saída não é recortar de novo nem pedir foto nova ao cliente: é multiplicar a\n * foto sobre uma placa clara. O branco do arquivo vira a placa, os pixels\n * escuros ficam. Uma placa só na página inteira, e a foto casa com o fundo sem\n * ninguém abrir o Photoshop.\n *\n * `isolation: isolate` é obrigatório: sem ele o `multiply` sangra para o que\n * estiver atrás da placa.\n *\n * ── Três coisas que faltavam, e a primeira era um bug ──────────────────────\n *\n * 1. **`duration-500` no zoom do hover.** Meio segundo num gesto de ponteiro é\n *    quase o dobro do teto da casa (300ms), e estava cravado em número em vez de\n *    token. Virou `--dur-base`.\n * 2. **O multiply era incondicional, e ele só serve pra UM tipo de arquivo.**\n *    `multiply` escurece tudo que não é branco. Numa foto de cena — produto no\n *    ambiente, fundo colorido, still com sombra — a placa não \"some\": ela come a\n *    imagem inteira e devolve uma mancha suja. Ou seja, a peça que existe pra\n *    salvar o recorte sobre branco estragava toda foto que não fosse recorte, e\n *    não tinha saída. `blend=\"normal\"` é a saída, e o default segue `multiply`.\n * 3. **O produto flutuava.** Recorte sem sombra de contato paira no meio da\n *    caixa — é o que faz catálogo montado parecer colagem. `ground` desenha o\n *    chão: uma elipse curta e larga logo abaixo do produto. É a diferença entre\n *    \"imagem dentro de um quadrado\" e \"objeto pousado numa superfície\", e custa\n *    um gradiente.\n *\n * `badge` existe porque a etiqueta (esgotado, novo, -30%) sempre acaba sendo\n * escrita por fora com `absolute` e um `z-index` chutado por quem consome. O\n * canto é território da placa: ela já tem `isolate` e `overflow-hidden`.\n *\n * ── E faltava a placa ser uma placa ────────────────────────────────────────\n *\n * Duas correções que só aparecem quando a peça é usada de verdade:\n *\n * 4. **No tema escuro o multiply comia o produto.** A placa pintava `--muted`,\n *    que é escuro no escuro. `multiply` sobre fundo escuro escurece TUDO: o\n *    branco do arquivo vira o cinza da placa e o produto some junto. Ou seja, a\n *    peça que existe pra resolver recorte sobre branco só funcionava em metade\n *    dos temas. `surface=\"photo\"` pinta a ilha clara que o multiply exige, nos\n *    dois temas, por `--plate-photo` — quem instala aponta o token pro papel da\n *    marca e a página inteira acompanha. Segue desligado por padrão: a placa\n *    já publicada não pode trocar de cor sozinha.\n * 5. **O chão era estático.** Sombra de contato que não responde ao ponteiro\n *    denuncia que é desenho, não apoio. No hover do card ela espalha e clareia\n *    junto com o zoom da foto — o produto levanta um dedo da superfície. É o\n *    mesmo gesto do zoom (nada muda de tamanho na página) e ele para inteiro\n *    em `prefers-reduced-motion`.\n */\n\nexport interface PlateProps extends React.HTMLAttributes<HTMLElement> {\n  children: React.ReactNode;\n  /** Proporção da placa. Produto alto (stick, garrafa) pede 4/5 ou 3/4. */\n  ratio?: string;\n  /** Respiro entre o produto e a borda da placa. */\n  inset?: number;\n  /** Cresce um degrau no hover do card em volta. */\n  zoomOnGroupHover?: boolean;\n  as?: \"div\" | \"span\";\n  /**\n   * Como a foto se mistura com a placa.\n   *\n   * `\"multiply\"` (padrão) para recorte sobre branco — o branco vira placa.\n   * `\"normal\"` para foto de cena, still com fundo próprio ou PNG com alpha, que\n   * o multiply destruiria.\n   */\n  blend?: \"multiply\" | \"normal\";\n  /**\n   * Sombra de contato na base da placa. Faz o recorte pousar em vez de flutuar.\n   *\n   * Ela mora na base da PLACA, não na base do produto — a placa não tem como\n   * saber onde a silhueta termina dentro de um `object-contain`. Vale pro\n   * recorte de catálogo, que por convenção enquadra o produto ocupando o frame.\n   * Produto pequeno e centralizado deixa a sombra solta embaixo; nesse caso o\n   * conserto é o enquadramento da foto, não ligar o `ground`.\n   */\n  ground?: boolean;\n  /** Etiqueta no canto superior esquerdo: \"novo\", \"esgotado\", \"-30%\". */\n  badge?: React.ReactNode;\n  /**\n   * A superfície da placa.\n   *\n   * `\"muted\"` (padrão) segue o tema, e serve pra placa que é só moldura.\n   * `\"photo\"` é a ilha clara nos dois temas — condição de existência do\n   * `multiply`, que precisa de fundo claro pra devolver o produto. Redefina\n   * `--plate-photo` pra casar com o papel da marca.\n   */\n  surface?: \"muted\" | \"photo\";\n}\n\nexport function Plate({\n  children,\n  ratio = \"4 / 5\",\n  inset = 20,\n  zoomOnGroupHover = true,\n  as: Tag = \"div\",\n  blend = \"multiply\",\n  ground = false,\n  badge,\n  surface = \"muted\",\n  className,\n  style,\n  ...props\n}: PlateProps) {\n  return (\n    <Tag\n      className={cn(\n        \"relative block overflow-hidden [isolation:isolate]\",\n        surface === \"muted\" && \"bg-[var(--muted)]\",\n        // A foto é filha direta; o blend e o zoom vivem aqui para o consumidor\n        // só precisar passar um <img> ou um next/image.\n        \"[&_img]:relative [&_img]:z-[1] [&_img]:block [&_img]:size-full [&_img]:object-contain\",\n        blend === \"multiply\" && \"[&_img]:mix-blend-multiply\",\n        /* Zoom de FOTO dentro de moldura com `overflow-hidden` — exceção\n           deliberada à regra de não escalar no hover. O que a regra proíbe é o\n           card inteiro inchar sob o cursor; aqui nada muda de tamanho na página,\n           só o enquadramento da imagem dentro de uma caixa que não se mexe. */\n        \"[&_img]:transition-transform [&_img]:[transition-duration:var(--dur-base,200ms)] [&_img]:ease-[var(--ease-out)]\",\n        zoomOnGroupHover && \"[.group:hover_&_img]:scale-[1.045]\",\n        className,\n      )}\n      style={{\n        aspectRatio: ratio,\n        padding: `${inset}px`,\n        /* A ilha clara vai em `style`, e não em classe: o fallback é uma função\n           de cor com vírgulas dentro de um `var()`, o tipo de valor arbitrário\n           que o Tailwind às vezes recusa em silêncio — e recusar em silêncio,\n           aqui, é a placa voltando a comer o produto no tema escuro. Claro de\n           propósito nos dois temas: é a condição do `multiply`, não um gosto. */\n        ...(surface === \"photo\"\n          ? { background: \"var(--plate-photo, oklch(0.97 0 0))\" }\n          : null),\n        ...style,\n      }}\n      {...props}\n    >\n      {ground && (\n        /*\n         * O chão fica ATRÁS da foto (`z-0` contra o `z-[1]` da imagem) e por\n         * baixo do `inset`, encostado na base da placa. Elipse larga e baixa,\n         * porque sombra de contato é larga e baixa — um círculo desenharia uma\n         * bola de luz e leria como brilho, não como apoio.\n         *\n         * `color-mix` sobre `--foreground`: no tema escuro a mesma regra produz\n         * uma sombra clara, que é o comportamento certo, e nenhum hex entra aqui.\n         */\n        <div\n          aria-hidden=\"true\"\n          /* No hover do card a elipse espalha e afina: é o produto levantando\n             um dedo da superfície, o mesmo instante em que a foto dá o zoom.\n             Só `transform` e `opacity` animam — as duas propriedades que o\n             compositor resolve sem relayout. */\n          className={cn(\n            \"pointer-events-none absolute inset-x-0 bottom-0 z-0 h-[22%] origin-bottom\",\n            \"transition-[transform,opacity] [transition-duration:var(--dur-base,200ms)] ease-[var(--ease-out)]\",\n            \"[.group:hover_&]:scale-x-[1.14] [.group:hover_&]:opacity-70\",\n            \"motion-reduce:transition-none motion-reduce:[.group:hover_&]:scale-x-100 motion-reduce:[.group:hover_&]:opacity-100\",\n          )}\n          style={{\n            background:\n              \"radial-gradient(ellipse 46% 52% at 50% 100%, color-mix(in oklch, var(--foreground) 22%, transparent), transparent 70%)\",\n          }}\n        />\n      )}\n\n      {children}\n\n      {badge && (\n        // `z-[2]`: acima da foto e do chão. Sem a ordem explícita, o\n        // `mix-blend-multiply` da imagem come a etiqueta em fundo claro.\n        <div className=\"absolute left-0 top-0 z-[2] m-2\">{badge}</div>\n      )}\n    </Tag>\n  );\n}\n",
      "type": "registry:ui"
    }
  ],
  "meta": {
    "uso": {
      "quando": "Assentar foto de produto recortada sobre branco num fundo colorido de loja, sem o retângulo branco",
      "evite": "Foto com fundo próprio ou editorial: photo-frame; item sem foto nenhuma, cover-art"
    }
  },
  "categories": [
    "conteudo",
    "produto",
    "foto"
  ],
  "type": "registry:ui"
}