{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "ticket",
  "type": "registry:ui",
  "title": "Ticket",
  "description": "An admission ticket with a perforated stub you can tear off.",
  "files": [
    {
      "path": "ui/ticket.tsx",
      "type": "registry:ui",
      "content": "\"use client\";\n\nimport * as React from \"react\";\n\nexport interface TicketProps extends Omit<React.HTMLAttributes<HTMLDivElement>, \"title\"> {\n  title: React.ReactNode;\n  eyebrow?: string;\n  details?: { label: string; value: string }[];\n  admit?: string;\n  serial?: string;\n  /** Accent color for the side band and stub. */\n  accent?: string;\n  paper?: string;\n  /** Let people drag the stub to tear it off. */\n  tearable?: boolean;\n  onTear?: () => void;\n  /** Custom main body. Replaces title/eyebrow/details. */\n  children?: React.ReactNode;\n}\n\nconst NOISE =\n  \"url(\\\"data:image/svg+xml;utf8,<svg xmlns='http://www.w3.org/2000/svg' width='160' height='160'><filter id='n'><feTurbulence type='fractalNoise' baseFrequency='.8' numOctaves='3' stitchTiles='stitch'/><feColorMatrix values='0 0 0 0 0  0 0 0 0 0  0 0 0 0 0  0 0 0 .1 0'/></filter><rect width='100%' height='100%' filter='url(%23n)'/></svg>\\\")\";\n\nconst R = 13; // notch radius\nconst notches = (side: \"left\" | \"right\") => {\n  const x = side === \"right\" ? \"100%\" : \"0\";\n  return `radial-gradient(circle at ${x} 0, #0000 ${R}px, #000 ${R + 0.5}px) top / 100% 51% no-repeat, radial-gradient(circle at ${x} 100%, #0000 ${R}px, #000 ${R + 0.5}px) bottom / 100% 51% no-repeat`;\n};\n\nconst TEAR_AT = 90;\n\nexport function Ticket({\n  title,\n  eyebrow,\n  details,\n  admit = \"ADMIT ONE\",\n  serial = \"Nº 004217\",\n  accent = \"#c2410c\",\n  paper = \"#f4ead5\",\n  tearable = true,\n  onTear,\n  children,\n  className = \"\",\n  style,\n  ...props\n}: TicketProps) {\n  const [dx, setDx] = React.useState(0);\n  const [dragging, setDragging] = React.useState(false);\n  const [torn, setTorn] = React.useState(false);\n  const start = React.useRef(0);\n\n  const tear = React.useCallback(() => {\n    setTorn(true);\n    onTear?.();\n  }, [onTear]);\n\n  const onPointerDown = (e: React.PointerEvent) => {\n    if (!tearable || torn) return;\n    e.stopPropagation();\n    (e.currentTarget as HTMLElement).setPointerCapture(e.pointerId);\n    start.current = e.clientX;\n    setDragging(true);\n  };\n  const onPointerMove = (e: React.PointerEvent) => {\n    if (!dragging) return;\n    const d = Math.max(0, e.clientX - start.current);\n    if (d > TEAR_AT) {\n      setDragging(false);\n      tear();\n    } else setDx(d);\n  };\n  const onPointerUp = () => {\n    setDragging(false);\n    if (!torn) setDx(0);\n  };\n\n  // resistance: the stub strains before it gives\n  const pull = dx * 0.32;\n  const stubTransform = torn\n    ? \"translate(70px, 160px) rotate(24deg)\"\n    : `translateX(${pull}px) rotate(${pull * 0.12}deg)`;\n\n  const surface: React.CSSProperties = {\n    background: `${NOISE}, ${paper}`,\n    color: \"#2a2520\",\n  };\n\n  return (\n    <div\n      className={`relative flex w-full max-w-[560px] select-none ${className}`}\n      style={{ filter: \"drop-shadow(0 8px 14px rgb(0 0 0 / .16)) drop-shadow(0 1px 1px rgb(0 0 0 / .15))\", ...style }}\n      {...props}\n    >\n      {/* main */}\n      <div\n        className=\"relative flex min-h-[190px] flex-1 overflow-hidden rounded-l-[10px]\"\n        style={{ ...surface, mask: notches(\"right\"), WebkitMask: notches(\"right\") }}\n      >\n        <div\n          className=\"flex w-9 shrink-0 items-center justify-center\"\n          style={{ background: accent, color: paper }}\n        >\n          <span\n            className=\"whitespace-nowrap text-[11px] font-bold tracking-[0.35em]\"\n            style={{ transform: \"rotate(-90deg)\", fontFamily: \"var(--font-type, ui-monospace, monospace)\" }}\n          >\n            {admit}\n          </span>\n        </div>\n        <div className=\"flex min-w-0 flex-1 flex-col justify-between p-5 pr-6\">\n          {children ?? (\n            <>\n              <div>\n                {eyebrow && (\n                  <div\n                    className=\"text-[11px] uppercase tracking-[0.25em]\"\n                    style={{ color: accent, fontFamily: \"var(--font-type, ui-monospace, monospace)\" }}\n                  >\n                    {eyebrow}\n                  </div>\n                )}\n                <div\n                  className=\"mt-1 text-[30px] leading-[1.05]\"\n                  style={{ fontFamily: \"var(--font-display, Georgia, serif)\", fontWeight: 600 }}\n                >\n                  {title}\n                </div>\n              </div>\n              {details && (\n                <div className=\"mt-4 flex flex-wrap gap-x-6 gap-y-2\">\n                  {details.map((d) => (\n                    <div key={d.label}>\n                      <div\n                        className=\"text-[9.5px] uppercase tracking-[0.2em] opacity-60\"\n                        style={{ fontFamily: \"var(--font-type, ui-monospace, monospace)\" }}\n                      >\n                        {d.label}\n                      </div>\n                      <div className=\"text-[14px] font-semibold\">{d.value}</div>\n                    </div>\n                  ))}\n                </div>\n              )}\n            </>\n          )}\n        </div>\n      </div>\n\n      {/* stub */}\n      <div\n        role={tearable ? \"button\" : undefined}\n        tabIndex={tearable && !torn ? 0 : undefined}\n        aria-label={tearable ? \"Tear off stub\" : undefined}\n        data-no-drag\n        onPointerDown={onPointerDown}\n        onPointerMove={onPointerMove}\n        onPointerUp={onPointerUp}\n        onPointerCancel={onPointerUp}\n        onKeyDown={(e) => {\n          if (tearable && !torn && (e.key === \"Enter\" || e.key === \" \")) {\n            e.preventDefault();\n            tear();\n          }\n        }}\n        className={`group relative flex w-[26%] min-w-[104px] shrink-0 flex-col items-center justify-between rounded-r-[10px] px-2 py-4 outline-none focus-visible:ring-2 focus-visible:ring-offset-2 ${\n          tearable && !torn ? \"cursor-grab touch-none active:cursor-grabbing\" : \"\"\n        }`}\n        style={{\n          ...surface,\n          mask: notches(\"left\"),\n          WebkitMask: notches(\"left\"),\n          transform: stubTransform,\n          transformOrigin: \"0% 100%\",\n          opacity: torn ? 0 : 1,\n          transition: dragging\n            ? \"none\"\n            : torn\n              ? \"transform .8s cubic-bezier(.5,0,.75,0), opacity .5s .3s\"\n              : \"transform .45s cubic-bezier(.34,1.56,.64,1)\",\n        }}\n      >\n        {/* perforation */}\n        <div\n          className=\"absolute inset-y-3 left-0 w-[2px]\"\n          style={{ background: `radial-gradient(circle, ${accent}88 1px, transparent 1.5px) 0 0 / 2px 7px repeat-y` }}\n        />\n        <div\n          className=\"text-[10px] font-bold tracking-[0.3em]\"\n          style={{ color: accent, fontFamily: \"var(--font-type, ui-monospace, monospace)\" }}\n        >\n          {admit.split(\" \")[0]}\n        </div>\n        <div\n          className=\"whitespace-nowrap text-[13px] tracking-[0.15em]\"\n          style={{ transform: \"rotate(-90deg)\", fontFamily: \"var(--font-type, ui-monospace, monospace)\" }}\n        >\n          {serial}\n        </div>\n        <div\n          className=\"text-[9px] uppercase tracking-[0.2em] opacity-50 transition-opacity group-hover:opacity-90\"\n          style={{ fontFamily: \"var(--font-type, ui-monospace, monospace)\" }}\n        >\n          {tearable ? \"tear →\" : \"stub\"}\n        </div>\n      </div>\n    </div>\n  );\n}\n"
    }
  ],
  "categories": [
    "extras"
  ]
}