{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "author": "Lopifi (https://lopifi.com)",
  "categories": [
    "cards",
    "finance",
    "accounting"
  ],
  "dependencies": [],
  "description": "The physical card object. 3D flip, pointer tilt.",
  "files": [
    {
      "content": "\"use client\";\n\nimport * as React from \"react\";\nimport { useFinePointer, useReducedMotion } from \"@/hooks/ledger/use-media-query\";\nimport { cn } from \"@/lib/ledger/cn\";\n\nexport interface PaymentMethodCardProps extends Omit<React.ComponentPropsWithoutRef<\"div\">, \"onChange\"> {\n  cvv?: string;\n  expiry: string;\n  holder: string;\n  last4: string;\n  network: \"visa\" | \"mastercard\" | \"amex\";\n  tint?: string;\n}\n\n/**\n * The physical object, at ISO/IEC 7810 ID-1 proportions.\n *\n * 85.60 × 53.98 mm → aspect-ratio 1.586, radius 3.18mm → 3.7% of width, 5.9% of height.\n * A card at 16:9 with a 24px radius reads as a rectangle, not as a card.\n *\n * The pointer tilt is decorative and is licensed only because the card\n * itself is decorative. The same effect on a KpiCard is a review failure.\n */\nexport const PaymentMethodCard = React.forwardRef<HTMLDivElement, PaymentMethodCardProps>(\n  ({ className, holder, last4, expiry, network, cvv, tint = \"oklch(0.34 0.06 258)\", ...props }, ref) => {\n    const [flipped, setFlipped] = React.useState(false);\n    const [tilt, setTilt] = React.useState({ sheen: 0, x: 0, y: 0 });\n    /* Two motions share one transform. The tilt follows the pointer, so it has\n       to land almost at once or the card lags behind the hand; the flip is a\n       turn the reader asked for and wants to watch. Each gets its own curve. */\n    const [turning, setTurning] = React.useState(false);\n    const fine = useFinePointer();\n    const reduced = useReducedMotion();\n\n    const onMove = (e: React.PointerEvent<HTMLDivElement>) => {\n      if (!fine || reduced) return;\n      const rect = e.currentTarget.getBoundingClientRect();\n      const px = (e.clientX - rect.left) / rect.width - 0.5;\n      const py = (e.clientY - rect.top) / rect.height - 0.5;\n      setTilt({ sheen: 0.12, x: -py * 8, y: px * 8 });\n    };\n\n    const onLeave = () => setTilt({ sheen: 0, x: 0, y: 0 });\n\n    return (\n      <div className={cn(\"w-full max-w-[360px] [perspective:1200px]\", className)} ref={ref} {...props}>\n        <div\n          className=\"relative w-full [transform-style:preserve-3d]\"\n          onPointerLeave={onLeave}\n          onPointerMove={onMove}\n          onTransitionEnd={(e) => {\n            if (e.target === e.currentTarget) setTurning(false);\n          }}\n          style={{\n            aspectRatio: \"1.586\",\n            transform: `rotateX(${tilt.x}deg) rotateY(${tilt.y + (flipped ? 180 : 0)}deg)`,\n            transition: reduced\n              ? \"none\"\n              : turning\n                ? \"transform 480ms var(--ease-in-out)\"\n                : \"transform var(--duration-3) var(--ease-out)\",\n          }}\n        >\n          {/* Face */}\n          <div\n            className=\"absolute inset-0 flex flex-col justify-between overflow-hidden p-5 [backface-visibility:hidden]\"\n            style={{\n              background: tint,\n              borderRadius: \"3.7% / 5.9%\",\n              color: \"oklch(0.98 0.005 258)\",\n            }}\n          >\n            <div\n              aria-hidden=\"true\"\n              className=\"pointer-events-none absolute inset-0 transition-opacity duration-2 ease-out\"\n              style={{\n                background:\n                  \"linear-gradient(115deg, transparent 30%, oklch(1 0 0 / 0.5) 50%, transparent 70%)\",\n                opacity: tilt.sheen,\n              }}\n            />\n            <div className=\"flex items-start justify-between\">\n              <div aria-hidden=\"true\" className=\"h-7 w-9 rounded-[3px] bg-[oklch(0.82_0.08_85)]\" />\n              <NetworkMark network={network} />\n            </div>\n            <div>\n              <p className=\"reference-numeric text-title-md\">\n                <span aria-hidden=\"true\">•••• •••• •••• {last4}</span>\n                <span className=\"sr-only\">card ending {last4}</span>\n              </p>\n              <div className=\"mt-3 flex items-end justify-between\">\n                <p className=\"text-label uppercase tracking-[0.06em]\">{holder}</p>\n                <p className=\"reference-numeric text-label\">{expiry}</p>\n              </div>\n            </div>\n          </div>\n\n          {/* Reverse — the CVV is not in the DOM until revealed. */}\n          <div\n            className=\"absolute inset-0 flex flex-col justify-center gap-4 overflow-hidden py-5 [backface-visibility:hidden] [transform:rotateY(180deg)]\"\n            style={{ background: tint, borderRadius: \"3.7% / 5.9%\", color: \"oklch(0.98 0.005 258)\" }}\n          >\n            <div aria-hidden=\"true\" className=\"h-10 w-full bg-[oklch(0.2_0.01_258)]\" />\n            <div className=\"flex items-center justify-end gap-3 px-5\">\n              <span className=\"text-caption opacity-80\">CVV</span>\n              <span className=\"reference-numeric rounded-xs bg-[oklch(1_0_0/0.9)] px-3 py-1 text-label text-[oklch(0.2_0.01_258)]\">\n                {flipped && cvv ? cvv : \"•••\"}\n              </span>\n            </div>\n          </div>\n        </div>\n\n        <button\n          aria-pressed={flipped}\n          className=\"mt-3 text-label font-medium text-accent hover:underline\"\n          onClick={() => {\n            setTurning(!reduced);\n            setFlipped((f) => !f);\n          }}\n          type=\"button\"\n        >\n          {flipped ? \"Hide security code\" : \"Show security code\"}\n        </button>\n      </div>\n    );\n  },\n);\nPaymentMethodCard.displayName = \"PaymentMethodCard\";\n\nfunction NetworkMark({ network }: { network: \"visa\" | \"mastercard\" | \"amex\" }) {\n  if (network === \"mastercard\") {\n    return (\n      <svg aria-label=\"Mastercard\" height=\"24\" role=\"img\" viewBox=\"0 0 40 24\" width=\"40\">\n        <circle cx=\"15\" cy=\"12\" fill=\"oklch(0.7 0.17 40)\" r=\"8\" />\n        <circle cx=\"25\" cy=\"12\" fill=\"oklch(0.75 0.15 75)\" opacity=\"0.85\" r=\"8\" />\n      </svg>\n    );\n  }\n  return (\n    <span className=\"text-label font-semibold uppercase tracking-[0.08em]\">\n      {network === \"amex\" ? \"Amex\" : \"Visa\"}\n    </span>\n  );\n}\n",
      "path": "components/ledger/payment-method-card.tsx",
      "target": "components/ledger/payment-method-card.tsx",
      "type": "registry:component"
    }
  ],
  "name": "payment-method-card",
  "registryDependencies": [
    "@lopifi/theme",
    "@lopifi/cn",
    "@lopifi/use-media-query"
  ],
  "title": "PaymentMethodCard",
  "type": "registry:component"
}
