{
  "$schema": "https://ui.shadcn.com/schema/registry-item.json",
  "name": "playing-card",
  "title": "Playing cards",
  "description": "Four-color playing cards: full-size cards, mini cards, hole cards and a five-slot board.",
  "registryDependencies": [
    "https://www.pokerstudy.ai/r/poker-core.json"
  ],
  "files": [
    {
      "path": "src/components/pokerstudy/playing-card.tsx",
      "content": "import type { CSSProperties } from \"react\";\nimport { suitTile } from \"@/lib/pokerstudy/poker\";\n\nexport type CardSize = \"xs\" | \"sm\" | \"md\" | \"lg\";\n\nconst SIZE_CLASS: Record<CardSize, string> = {\n  xs: \"h-8 w-6 text-[0.65rem]\",\n  sm: \"h-11 w-8 text-sm\",\n  md: \"h-[4.5rem] w-[3.25rem] text-lg\",\n  lg: \"h-24 w-[4.25rem] text-xl\",\n};\n\n/** Face styling shared by every card. Radius, ink and suit color follow the registry theme. */\nfunction faceStyle(bg: string): CSSProperties {\n  return {\n    backgroundColor: bg,\n    color: \"var(--ps-card-ink, #fff)\",\n    borderRadius: \"var(--ps-card-radius, 0.25rem)\",\n    fontFamily: \"var(--font-card, inherit)\",\n  };\n}\n\nfunction emptyStyle(): CSSProperties {\n  return {\n    borderRadius: \"var(--ps-card-radius, 0.25rem)\",\n    borderColor: \"var(--ps-slot-border, #3f3f46)\",\n    background: \"var(--ps-slot, rgb(9 9 11 / 0.4))\",\n  };\n}\n\nexport interface PlayingCardProps {\n  /** Rank and suit, such as \"Ah\" or \"Td\". Pass null for an empty slot. */\n  card?: string | null;\n  /** Draw size. Defaults to md. */\n  size?: CardSize;\n  /** Extra classes on the card box. */\n  className?: string;\n}\n\n/**\n * One playing card as a broadcast-style tile: the face is the suit color, with\n * a white rank and a small corner glyph. `card` is rank + suit (\"Ah\", \"Td\").\n * An empty slot (`card == null`) is a dashed outline, so a flop-only board\n * still reserves room for the turn and river.\n */\nexport function PlayingCard({ card, size = \"md\", className = \"\" }: PlayingCardProps) {\n  const box = SIZE_CLASS[size];\n  if (!card) {\n    return <div aria-hidden style={emptyStyle()} className={`${box} ${className} shrink-0 border border-dashed`} />;\n  }\n  const rank = card[0] === \"T\" ? \"10\" : card[0].toUpperCase();\n  const { sym, bg } = suitTile(card[1] ?? \"\");\n  return (\n    <div\n      role=\"img\"\n      aria-label={`${rank}${sym}`}\n      style={faceStyle(bg)}\n      className={`${box} ${className} relative flex shrink-0 items-center justify-center border border-white/25 font-extrabold leading-none`}\n    >\n      <span className=\"absolute left-[7%] top-[4%] text-[0.9em]\">{sym}</span>\n      <span\n        className={`mt-[0.35em] ${rank === \"10\" ? \"text-[1.7em] tracking-tighter\" : \"text-[2.1em]\"} drop-shadow-sm`}\n      >\n        {rank}\n      </span>\n    </div>\n  );\n}\n\nexport interface MiniCardProps {\n  /** Rank and suit, such as \"Ah\" or \"Td\". */\n  card: string;\n  /** Box and font size. Defaults to a row-sized tile. */\n  className?: string;\n}\n\n/**\n * A small inline card for action strips, table rows and combo lists: the same\n * suit-colored face with just the rank, since a corner glyph is unreadable at\n * this size. `className` sets the box and font size.\n */\nexport function MiniCard({ card, className = \"h-8 w-6 text-base\" }: MiniCardProps) {\n  const rank = card[0] === \"T\" ? \"10\" : card[0].toUpperCase();\n  const { sym, bg } = suitTile(card[1] ?? \"\");\n  return (\n    <span\n      role=\"img\"\n      aria-label={`${rank}${sym}`}\n      title={`${rank}${sym}`}\n      style={faceStyle(bg)}\n      className={`${className} inline-flex shrink-0 items-center justify-center border border-white/25 font-extrabold leading-none`}\n    >\n      <span className={rank === \"10\" ? \"text-[0.85em] tracking-tighter\" : undefined}>{rank}</span>\n    </span>\n  );\n}\n\nexport interface HoleCardsProps {\n  /** Hole cards. Null or an empty list renders two empty slots. */\n  cards: string[] | null;\n  /** Draw size before Omaha hands shrink one step to fit. Defaults to md. */\n  size?: CardSize;\n}\n\n/** Hole cards: two for hold'em, four or more for Omaha (shrunk one size to fit). */\nexport function HoleCards({ cards, size = \"md\" }: HoleCardsProps) {\n  const faces = cards && cards.length > 0 ? cards : [null, null];\n  const fit: CardSize = faces.length > 2 ? (size === \"lg\" ? \"md\" : size === \"md\" ? \"sm\" : \"xs\") : size;\n  return (\n    <div className=\"flex items-center gap-1\">\n      {faces.map((c, i) => (\n        <PlayingCard key={c ?? `empty-${i}`} card={c} size={fit} />\n      ))}\n    </div>\n  );\n}\n\nexport interface BoardProps {\n  /** Up to five board cards. Missing streets render as empty slots. */\n  cards: string[];\n  /** Draw size for every slot. Defaults to md. */\n  size?: CardSize;\n}\n\n/** A five-slot board. Missing streets render as empty slots. */\nexport function Board({ cards, size = \"md\" }: BoardProps) {\n  return (\n    <div className=\"flex items-center gap-1\">\n      {Array.from({ length: 5 }, (_, i) => (\n        <PlayingCard key={cards[i] ?? `slot-${i}`} card={cards[i] ?? null} size={size} />\n      ))}\n    </div>\n  );\n}\n",
      "type": "registry:component",
      "target": "components/pokerstudy/playing-card.tsx"
    }
  ],
  "type": "registry:component"
}