{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "postage-stamp",
  "type": "registry:ui",
  "title": "Postage Stamp",
  "description": "A perforated stamp with an optional inked postmark.",
  "files": [
    {
      "path": "ui/postage-stamp.tsx",
      "type": "registry:ui",
      "content": "import * as React from \"react\";\n\nexport interface PostageStampProps extends React.HTMLAttributes<HTMLDivElement> {\n  src?: string;\n  alt?: string;\n  /** Denomination, e.g. \"42¢\" */\n  value?: string;\n  /** Small text along the bottom, e.g. a country or brand */\n  label?: string;\n  /** Frame color behind the artwork */\n  frame?: string;\n  /** Add a cancellation postmark. Pass an object to customise its text. */\n  postmark?: boolean | { city?: string; date?: string };\n  rotate?: number;\n  width?: number;\n  /** Custom artwork instead of an image */\n  children?: React.ReactNode;\n}\n\nconst r = 5; // perforation hole radius\nconst PERF = `radial-gradient(${r}px, #0000 98%, #000) round ${-1.5 * r}px ${-1.5 * r}px / ${3 * r}px ${3 * r}px, linear-gradient(#000 0 0) no-repeat 50% / calc(100% - ${3 * r}px) calc(100% - ${3 * r}px)`;\n\nconst NOISE =\n  \"url(\\\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='120' height='120'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.9' numOctaves='2' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 .08 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>\\\")\";\n\nfunction Postmark({ city, date, id }: { city: string; date: string; id: string }) {\n  return (\n    <svg viewBox=\"0 0 220 100\" className=\"h-full w-full\" aria-hidden>\n      <defs>\n        <path id={`${id}-arc`} d=\"M 22 50 A 28 28 0 1 1 78 50 A 28 28 0 1 1 22 50\" />\n        <filter id={`${id}-ink`}>\n          <feTurbulence type=\"fractalNoise\" baseFrequency=\"0.9\" numOctaves=\"2\" result=\"t\" />\n          <feColorMatrix in=\"t\" values=\"0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  -2.5 0 0 0 1.9\" result=\"m\" />\n          <feComposite in=\"SourceGraphic\" in2=\"m\" operator=\"in\" />\n        </filter>\n      </defs>\n      <g fill=\"none\" stroke=\"currentColor\" filter={`url(#${id}-ink)`}>\n        <circle cx=\"50\" cy=\"50\" r=\"40\" strokeWidth=\"2.5\" />\n        <circle cx=\"50\" cy=\"50\" r=\"22\" strokeWidth=\"1.2\" />\n        <text fill=\"currentColor\" stroke=\"none\" fontSize=\"10\" letterSpacing=\"2\" fontFamily=\"var(--font-type, ui-monospace, monospace)\">\n          <textPath href={`#${id}-arc`}>{city.toUpperCase()} · {city.toUpperCase()} ·</textPath>\n        </text>\n        <text x=\"50\" y=\"54\" textAnchor=\"middle\" fill=\"currentColor\" stroke=\"none\" fontSize=\"10\" fontFamily=\"var(--font-type, ui-monospace, monospace)\">\n          {date}\n        </text>\n        {[30, 42, 54, 66].map((y) => (\n          <path key={y} d={`M 96 ${y} q 15 -7 30 0 t 30 0 t 30 0 t 30 0`} strokeWidth=\"2.6\" />\n        ))}\n      </g>\n    </svg>\n  );\n}\n\nexport function PostageStamp({\n  src,\n  alt = \"\",\n  value = \"42¢\",\n  label = \"EPHEMERA POST\",\n  frame = \"#f7f1e3\",\n  postmark = false,\n  rotate = 0,\n  width = 160,\n  children,\n  className = \"\",\n  style,\n  ...props\n}: PostageStampProps) {\n  const id = React.useId().replace(/:/g, \"\");\n  const pm = typeof postmark === \"object\" ? postmark : {};\n\n  return (\n    <div\n      className={`relative inline-block align-top ${className}`}\n      style={{ transform: `rotate(${rotate}deg)`, width, maxWidth: \"100%\", ...style }}\n      {...props}\n    >\n      <div style={{ filter: \"drop-shadow(0 3px 5px rgb(0 0 0 / .18)) drop-shadow(0 0 .5px rgb(0 0 0 / .3))\" }}>\n        <div style={{ padding: 2 * r, background: `${NOISE}, #fdfbf5`, mask: PERF, WebkitMask: PERF }}>\n          <div className=\"relative p-[7px]\" style={{ background: frame }}>\n            <div className=\"relative aspect-[4/5] overflow-hidden\" style={{ background: \"#d9cdb4\" }}>\n              {children ??\n                (src && (\n                  // eslint-disable-next-line @next/next/no-img-element\n                  <img src={src} alt={alt} draggable={false} className=\"absolute inset-0 h-full w-full object-cover\" />\n                ))}\n              <div className=\"pointer-events-none absolute inset-0\" style={{ boxShadow: \"inset 0 0 0 1px rgb(0 0 0 / .12)\" }} />\n            </div>\n            <div className=\"mt-1.5 flex items-baseline justify-between gap-2\" style={{ color: \"#3a3226\" }}>\n              <span\n                className=\"truncate text-[8.5px] tracking-[0.18em]\"\n                style={{ fontFamily: \"var(--font-type, ui-monospace, monospace)\" }}\n              >\n                {label}\n              </span>\n              <span className=\"text-[20px] font-bold leading-none\" style={{ fontFamily: \"var(--font-display, Georgia, serif)\" }}>\n                {value}\n              </span>\n            </div>\n          </div>\n        </div>\n      </div>\n      {postmark && (\n        <div\n          className=\"pointer-events-none absolute\"\n          style={{\n            width: \"135%\",\n            aspectRatio: \"220 / 100\",\n            top: \"18%\",\n            left: \"-42%\",\n            transform: \"rotate(-12deg)\",\n            color: \"rgb(30 30 45 / .72)\",\n            mixBlendMode: \"multiply\",\n          }}\n        >\n          <Postmark id={id} city={pm.city ?? \"Paris\"} date={pm.date ?? \"12 OCT 26\"} />\n        </div>\n      )}\n    </div>\n  );\n}\n"
    }
  ],
  "categories": [
    "extras"
  ]
}