"use client"; import { cn } from "@/lib/cn"; import { Card, SUIT_IS_RED, SUIT_SYMBOL, rankLabel } from "@/lib/hokm/types"; import type { CardBackPattern } from "@/lib/online/types"; import { cardBackMotif, cardBackVisual } from "@/lib/cardBack"; const SIZES = { sm: { w: 44, h: 62, rank: "text-[11px]", center: "text-2xl", radius: 7 }, md: { w: 62, h: 87, rank: "text-sm", center: "text-3xl", radius: 9 }, lg: { w: 78, h: 110, rank: "text-base", center: "text-4xl", radius: 11 }, xl: { w: 92, h: 130, rank: "text-lg", center: "text-5xl", radius: 13 }, } as const; export type CardSize = keyof typeof SIZES; interface CardBack { c1: string; c2: string; accent: string; pattern?: CardBackPattern; motif?: string; } interface CardFront { bg1: string; bg2: string; border: string; } interface Props { card?: Card; faceDown?: boolean; size?: CardSize; className?: string; dimmed?: boolean; back?: CardBack; front?: CardFront; } /* Pip positions (x,y as fractions of the card) for number cards 2–10. Bottom-half pips (y>0.5) render rotated 180°, like a real deck — so each rank looks distinct. */ const PL = 0.3, PC = 0.5, PR = 0.7; const PIP_LAYOUT: Record = { 2: [[PC, 0.24], [PC, 0.76]], 3: [[PC, 0.22], [PC, 0.5], [PC, 0.78]], 4: [[PL, 0.26], [PR, 0.26], [PL, 0.74], [PR, 0.74]], 5: [[PL, 0.26], [PR, 0.26], [PC, 0.5], [PL, 0.74], [PR, 0.74]], 6: [[PL, 0.24], [PR, 0.24], [PL, 0.5], [PR, 0.5], [PL, 0.76], [PR, 0.76]], 7: [[PL, 0.23], [PR, 0.23], [PC, 0.365], [PL, 0.5], [PR, 0.5], [PL, 0.77], [PR, 0.77]], 8: [[PL, 0.23], [PR, 0.23], [PC, 0.365], [PL, 0.5], [PR, 0.5], [PC, 0.635], [PL, 0.77], [PR, 0.77]], 9: [[PL, 0.22], [PR, 0.22], [PL, 0.41], [PR, 0.41], [PC, 0.5], [PL, 0.59], [PR, 0.59], [PL, 0.78], [PR, 0.78]], 10: [[PL, 0.22], [PR, 0.22], [PC, 0.32], [PL, 0.41], [PR, 0.41], [PL, 0.59], [PR, 0.59], [PC, 0.68], [PL, 0.78], [PR, 0.78]], }; function Pips({ rank, symbol, color, w }: { rank: number; symbol: string; color: string; w: number }) { const layout = PIP_LAYOUT[rank]; if (!layout) return null; const size = (rank >= 9 ? 0.165 : rank >= 7 ? 0.19 : 0.22) * w; return (
{layout.map(([x, y], i) => ( 0.5 ? " rotate(180deg)" : ""}`, fontSize: size, lineHeight: 1, color, }} > {symbol} ))}
); } export function PlayingCard({ card, faceDown, size = "md", className, dimmed, back, front, }: Props) { const s = SIZES[size]; /* ── Face-down ─────────────────────────────────────────────────── */ if (faceDown || !card) { const visual = back ? cardBackVisual(back.c1, back.c2, back.accent, back.pattern) : null; const styled = back && visual ? { width: s.w, height: s.h, borderRadius: s.radius, background: visual.background, backgroundSize: visual.backgroundSize, border: `1.5px solid ${back.accent}88`, boxShadow: "0 6px 18px rgba(0,0,0,0.5), inset 0 1px 0 rgba(255,255,255,0.12)", } : { width: s.w, height: s.h }; const motif = back ? cardBackMotif(back.pattern, back.motif) : "✦"; return (
{motif && ( {motif} )}
); } /* ── Face-up ────────────────────────────────────────────────────── */ const red = SUIT_IS_RED[card.suit]; const symbol = SUIT_SYMBOL[card.suit]; const label = rankLabel(card.rank); const rank = card.rank; // UNO-style: suit-aware background const cardBg = front ? `linear-gradient(160deg,${front.bg1},${front.bg2})` : red ? "linear-gradient(160deg,#fff8f7,#fdecea)" : "linear-gradient(160deg,#fefefe,#f4f2ec)"; const borderColor = front?.border ?? (red ? "rgba(200,70,70,0.22)" : "rgba(50,50,80,0.15)"); // Bold suit colours (UNO-style vivid) const inkColor = red ? "#c0202a" : "#1c1c38"; const pipColor = red ? "#e03540" : "#2a2a50"; return (
{/* Top-left corner */}
{label}
{symbol}
{/* Center: pip layout for 2–10, big rank for J/Q/K, single big pip for Ace */} {rank >= 2 && rank <= 10 ? ( ) : rank === 14 ? (
{symbol}
) : (
{label} {symbol}
)} {/* Bottom-right corner (rotated 180°) */}
{label}
{symbol}
{/* Subtle inner rim for red suits — UNO-style */} {red && (
)}
); }