@stargazeryan/poker-ui 0.2.0 → 0.3.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/index.cjs +26 -11
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +26 -10
- package/dist/index.d.ts +26 -10
- package/dist/index.js +26 -11
- package/dist/index.js.map +1 -1
- package/package.json +34 -7
package/dist/index.cjs
CHANGED
|
@@ -39,8 +39,9 @@ __export(index_exports, {
|
|
|
39
39
|
module.exports = __toCommonJS(index_exports);
|
|
40
40
|
|
|
41
41
|
// src/cn.ts
|
|
42
|
+
var import_tailwind_merge = require("tailwind-merge");
|
|
42
43
|
function cn(...parts) {
|
|
43
|
-
return parts.filter(Boolean).join(" ");
|
|
44
|
+
return (0, import_tailwind_merge.twMerge)(parts.filter(Boolean).join(" "));
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
// src/format.ts
|
|
@@ -200,15 +201,26 @@ function Seat({
|
|
|
200
201
|
isDimmed = false,
|
|
201
202
|
onClick,
|
|
202
203
|
header,
|
|
203
|
-
badge,
|
|
204
204
|
children,
|
|
205
205
|
footer,
|
|
206
|
+
badge,
|
|
206
207
|
overlay,
|
|
208
|
+
above,
|
|
209
|
+
below,
|
|
210
|
+
topLeft,
|
|
211
|
+
topRight,
|
|
212
|
+
topCenter,
|
|
207
213
|
width = 160,
|
|
208
214
|
height = 176,
|
|
209
|
-
className
|
|
215
|
+
className,
|
|
216
|
+
frameClassName,
|
|
217
|
+
frameStyle
|
|
210
218
|
}) {
|
|
211
|
-
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: cn("relative", className), style: { width
|
|
219
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: cn("relative flex flex-col items-center", className), style: { width }, children: [
|
|
220
|
+
topCenter !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute -top-2 left-1/2 z-20 flex -translate-x-1/2 gap-1", children: topCenter }),
|
|
221
|
+
topLeft !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute -top-3 -left-2 z-40", children: topLeft }),
|
|
222
|
+
topRight !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute -top-2 -right-2 z-40", children: topRight }),
|
|
223
|
+
above !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "pointer-events-none absolute -top-12 left-1/2 z-30 -translate-x-1/2", children: above }),
|
|
212
224
|
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
213
225
|
"div",
|
|
214
226
|
{
|
|
@@ -218,22 +230,25 @@ function Seat({
|
|
|
218
230
|
onKeyDown: onClick ? (e) => {
|
|
219
231
|
if (e.key === "Enter" || e.key === " ") onClick();
|
|
220
232
|
} : void 0,
|
|
233
|
+
style: { width, height, ...frameStyle },
|
|
221
234
|
className: cn(
|
|
222
|
-
"flex
|
|
235
|
+
"relative flex flex-col items-center justify-between gap-1 rounded-2xl border bg-neutral-900/80 p-2 text-neutral-100 transition",
|
|
223
236
|
isActive ? "border-cyan-400 shadow-[0_0_18px_rgba(34,211,238,0.55)]" : "border-neutral-700",
|
|
224
237
|
isWinner && "border-amber-400 shadow-[0_0_18px_rgba(251,191,36,0.6)]",
|
|
225
238
|
isDimmed && "opacity-50",
|
|
226
|
-
onClick && "cursor-pointer hover:border-neutral-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400"
|
|
239
|
+
onClick && "cursor-pointer hover:border-neutral-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400",
|
|
240
|
+
frameClassName
|
|
227
241
|
),
|
|
228
242
|
children: [
|
|
229
|
-
header !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex w-full items-center justify-between gap-1 text-[11px]", children: header }),
|
|
230
|
-
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex flex-1 items-center justify-center", children }),
|
|
231
|
-
footer !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex w-full items-center justify-between text-[11px] text-neutral-300", children: footer })
|
|
243
|
+
header !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex w-full shrink-0 items-center justify-between gap-1 text-[11px]", children: header }),
|
|
244
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex min-h-0 flex-1 items-center justify-center", children }),
|
|
245
|
+
footer !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex w-full shrink-0 items-center justify-between text-[11px] text-neutral-300", children: footer }),
|
|
246
|
+
overlay !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "pointer-events-none absolute inset-x-0 top-1/2 -translate-y-1/2 text-center", children: overlay })
|
|
232
247
|
]
|
|
233
248
|
}
|
|
234
249
|
),
|
|
235
|
-
badge !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute
|
|
236
|
-
|
|
250
|
+
badge !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute top-0 right-0 z-40", children: badge }),
|
|
251
|
+
below !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "mt-1 flex flex-col items-center gap-1", children: below })
|
|
237
252
|
] });
|
|
238
253
|
}
|
|
239
254
|
|
package/dist/index.cjs.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/index.ts","../src/cn.ts","../src/format.ts","../src/seat.ts","../src/components/PlayingCard.tsx","../src/components/Seat.tsx","../src/components/Pot.tsx","../src/components/CardTable.tsx"],"sourcesContent":["export { cn } from \"./cn\";\nexport { BB, formatBB } from \"./format\";\nexport { nextSeatIndex, type NextSeatOptions } from \"./seat\";\n\nexport {\n PlayingCard,\n CardBack,\n HoleCards,\n CommunityCards,\n parseCard,\n getSuitSymbol,\n type PlayingCardProps,\n type CardBackProps,\n type HoleCardsProps,\n type CommunityCardsProps,\n type CardSize,\n} from \"./components/PlayingCard\";\n\nexport { Seat, type SeatProps } from \"./components/Seat\";\nexport { Pot, type PotProps } from \"./components/Pot\";\nexport { CardTable, type CardTableProps } from \"./components/CardTable\";\n\n// Back-compat alias (the table is now game-agnostic).\nexport { CardTable as PokerTable, type CardTableProps as PokerTableProps } from \"./components/CardTable\";\n","export function cn(...parts: Array<string | false | null | undefined>): string {\n return parts.filter(Boolean).join(\" \");\n}\n","/** Big Blind value used for stack display. */\nexport const BB = 20;\n\n/** Format a chip amount as a Big-Blind string, e.g. 1234 -> \"61.7 BB\". */\nexport function formatBB(amount: number): string {\n const bb = amount / BB;\n return `${Number.isInteger(bb) ? bb : bb.toFixed(1)} BB`;\n}\n","/**\n * Seat-ring helpers. Pure functions — they contain no game-specific rules.\n */\n\nexport interface NextSeatOptions {\n /** +1 for clockwise, -1 for counter-clockwise. Default +1. */\n dir?: 1 | -1;\n /** Return true to skip a seat (e.g. folded / empty / out). */\n skip?: (index: number) => boolean;\n}\n\n/**\n * Return the index of the next seat on the ring after `current`, skipping any\n * seat matched by `opts.skip`. Wraps around. Returns `current` if every other\n * seat is skipped.\n */\nexport function nextSeatIndex(current: number, seatCount: number, opts: NextSeatOptions = {}): number {\n const n = Math.max(1, Math.floor(seatCount));\n const dir = opts.dir ?? 1;\n const skip = opts.skip;\n const start = ((Math.floor(current) % n) + n) % n;\n\n let i = start;\n for (let step = 0; step < n - 1; step++) {\n i = (i + dir + n) % n;\n if (!skip || !skip(i)) return i;\n }\n return start;\n}\n","import { cn } from \"../cn\";\n\nexport type CardSize = \"xs\" | \"sm\" | \"md\" | \"lg\";\n\nconst SIZE_CLASSES: Record<CardSize, string> = {\n xs: \"w-8 h-11\",\n sm: \"w-10 h-14\",\n md: \"w-16 h-22\",\n lg: \"w-24 h-32\",\n};\n\nconst CORNER_RANK: Record<CardSize, string> = {\n xs: \"text-[11px]\",\n sm: \"text-sm\",\n md: \"text-lg\",\n lg: \"text-2xl\",\n};\nconst CORNER_SUIT: Record<CardSize, string> = {\n xs: \"text-[9px]\",\n sm: \"text-xs\",\n md: \"text-sm\",\n lg: \"text-base\",\n};\nconst CENTER_SUIT: Record<CardSize, string> = {\n xs: \"text-base\",\n sm: \"text-xl\",\n md: \"text-3xl\",\n lg: \"text-5xl\",\n};\nconst BACK_EMBLEM_SIZE: Record<CardSize, number> = {\n xs: 14,\n sm: 18,\n md: 28,\n lg: 42,\n};\n\n/** Parse a compact card code like \"As\", \"10h\", \"kd\" into rank + suit. */\nexport function parseCard(card: string): { rank: string; suit: string } {\n if (!card || card.length < 2) return { rank: \"\", suit: \"\" };\n return { rank: card.slice(0, -1), suit: card.slice(-1) };\n}\n\nexport function getSuitSymbol(suit: string): string {\n switch (suit) {\n case \"s\": return \"♠\";\n case \"h\": return \"♥\";\n case \"d\": return \"♦\";\n case \"c\": return \"♣\";\n default: return suit;\n }\n}\n\nfunction getSuitClass(suit: string): string {\n switch (suit) {\n case \"s\":\n case \"c\":\n return \"text-neutral-900\";\n case \"h\":\n case \"d\":\n return \"text-red-600\";\n default:\n return \"\";\n }\n}\n\nexport interface PlayingCardProps {\n /** Compact card code (\"As\", \"10h\") or null to render a face-down card back. */\n card: string | null;\n size?: CardSize;\n className?: string;\n}\n\n/**\n * A single playing card. Size is driven by the `size` prop (never viewport\n * breakpoints) so cards scale predictably inside any container.\n */\nexport function PlayingCard({ card, size = \"md\", className }: PlayingCardProps) {\n if (!card) return <CardBack size={size} className={className} />;\n\n const { rank, suit } = parseCard(card);\n const suitSymbol = getSuitSymbol(suit);\n const suitClass = getSuitClass(suit);\n const showCenter = size === \"md\" || size === \"lg\";\n\n return (\n <div\n className={cn(\n SIZE_CLASSES[size],\n \"relative bg-gradient-to-br from-white via-white to-neutral-100\",\n \"rounded-md border border-neutral-300/90 overflow-hidden\",\n \"shadow-[0_2px_6px_rgba(0,0,0,0.45),inset_0_1px_0_rgba(255,255,255,0.9)]\",\n className,\n )}\n style={{ aspectRatio: \"10/14\" }}\n >\n <div className={cn(\"absolute top-[3px] left-[5px] flex flex-col items-center leading-none\", suitClass)}>\n <span className={cn(CORNER_RANK[size], \"font-bold tracking-tight\")}>{rank}</span>\n <span className={cn(CORNER_SUIT[size], \"-mt-[1px]\")}>{suitSymbol}</span>\n </div>\n <div className={cn(\"absolute bottom-[3px] right-[5px] flex flex-col items-center leading-none rotate-180\", suitClass)}>\n <span className={cn(CORNER_RANK[size], \"font-bold tracking-tight\")}>{rank}</span>\n <span className={cn(CORNER_SUIT[size], \"-mt-[1px]\")}>{suitSymbol}</span>\n </div>\n {showCenter && (\n <div className={cn(\"absolute inset-0 flex items-center justify-center opacity-90\", suitClass)}>\n <span className={CENTER_SUIT[size]}>{suitSymbol}</span>\n </div>\n )}\n </div>\n );\n}\n\nexport interface CardBackProps {\n size?: CardSize;\n className?: string;\n}\n\nfunction CardBackEmblem({ size }: { size: CardSize }) {\n const s = BACK_EMBLEM_SIZE[size];\n return (\n <svg width={s} height={s} viewBox=\"0 0 40 40\" className=\"drop-shadow-[0_1px_2px_rgba(0,0,0,0.6)]\" aria-hidden=\"true\">\n <g fill=\"#f0c04a\" opacity=\"0.92\">\n <ellipse cx=\"20\" cy=\"11\" rx=\"4\" ry=\"7\" />\n <ellipse cx=\"20\" cy=\"29\" rx=\"4\" ry=\"7\" />\n <ellipse cx=\"11\" cy=\"20\" rx=\"7\" ry=\"4\" />\n <ellipse cx=\"29\" cy=\"20\" rx=\"7\" ry=\"4\" />\n </g>\n <circle cx=\"20\" cy=\"20\" r=\"3.2\" fill=\"#5a0c18\" />\n </svg>\n );\n}\n\n/** The face-down card back (dark red + gold rosette). */\nexport function CardBack({ size = \"md\", className }: CardBackProps) {\n return (\n <div\n className={cn(\n SIZE_CLASSES[size],\n \"relative rounded-md p-[3px] overflow-hidden\",\n \"bg-gradient-to-br from-[#7a1020] via-[#5a0c18] to-[#3f0810]\",\n \"border-2 border-[#c9a24b]/70 shadow-[0_2px_6px_rgba(0,0,0,0.45)]\",\n className,\n )}\n style={{ aspectRatio: \"10/14\" }}\n >\n <div className=\"h-full w-full rounded-[4px] border border-white/20 flex items-center justify-center bg-[repeating-linear-gradient(45deg,rgba(255,255,255,0.09)_0_6px,transparent_6px_12px),repeating-linear-gradient(-45deg,rgba(255,255,255,0.06)_0_6px,transparent_6px_12px)]\">\n <CardBackEmblem size={size} />\n </div>\n </div>\n );\n}\n\nexport interface HoleCardsProps {\n cards: (string | null)[];\n size?: CardSize;\n className?: string;\n}\n\n/** A row of a player's two hole cards. */\nexport function HoleCards({ cards, size = \"lg\", className }: HoleCardsProps) {\n return (\n <div className={cn(\"flex gap-2\", className)}>\n {cards.map((card, i) => (\n <PlayingCard key={i} card={card} size={size} />\n ))}\n </div>\n );\n}\n\nexport interface CommunityCardsProps {\n cards: (string | null)[];\n size?: CardSize;\n className?: string;\n}\n\n/** The five community cards, each dealt with a staggered flip animation. */\nexport function CommunityCards({ cards, size = \"md\", className }: CommunityCardsProps) {\n return (\n <div className={cn(\"flex gap-1.5\", className)}>\n {cards.map((card, i) => (\n <div key={i} className=\"animate-card-deal\" style={{ animationDelay: `${i * 90}ms` }}>\n <PlayingCard card={card} size={size} />\n </div>\n ))}\n </div>\n );\n}\n","import type { ReactNode } from \"react\";\nimport { cn } from \"../cn\";\n\nexport interface SeatProps {\n /** Turn/active highlight (generic). */\n isActive?: boolean;\n /** Winner highlight (generic). */\n isWinner?: boolean;\n /** Dimmed, e.g. folded or out (generic). */\n isDimmed?: boolean;\n /** Optional click handler (e.g. \"sit here\"). */\n onClick?: () => void;\n\n /** Slot: top row — name / badges. */\n header?: ReactNode;\n /** Slot: top-right floating element — e.g. a dealer button. */\n badge?: ReactNode;\n /** Slot: center content — 0..N cards, avatar, anything. */\n children?: ReactNode;\n /** Slot: bottom row — stack / bet / status. */\n footer?: ReactNode;\n /** Slot: overlay — e.g. an \"All-in\" label. */\n overlay?: ReactNode;\n\n width?: number;\n height?: number;\n className?: string;\n}\n\n/**\n * A neutral seat: a frame with generic visual states and named slots.\n * It knows nothing about any particular game — put whatever you want into\n * `header` / `children` / `footer` / `badge` / `overlay`.\n */\nexport function Seat({\n isActive = false,\n isWinner = false,\n isDimmed = false,\n onClick,\n header,\n badge,\n children,\n footer,\n overlay,\n width = 160,\n height = 176,\n className,\n}: SeatProps) {\n return (\n <div className={cn(\"relative\", className)} style={{ width, height }}>\n <div\n role={onClick ? \"button\" : undefined}\n tabIndex={onClick ? 0 : undefined}\n onClick={onClick}\n onKeyDown={\n onClick\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") onClick();\n }\n : undefined\n }\n className={cn(\n \"flex h-full w-full flex-col items-center justify-between gap-1 rounded-2xl border bg-neutral-900/80 p-2 text-neutral-100 backdrop-blur transition\",\n isActive ? \"border-cyan-400 shadow-[0_0_18px_rgba(34,211,238,0.55)]\" : \"border-neutral-700\",\n isWinner && \"border-amber-400 shadow-[0_0_18px_rgba(251,191,36,0.6)]\",\n isDimmed && \"opacity-50\",\n onClick && \"cursor-pointer hover:border-neutral-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400\",\n )}\n >\n {header !== undefined && (\n <div className=\"flex w-full items-center justify-between gap-1 text-[11px]\">{header}</div>\n )}\n <div className=\"flex flex-1 items-center justify-center\">{children}</div>\n {footer !== undefined && (\n <div className=\"flex w-full items-center justify-between text-[11px] text-neutral-300\">{footer}</div>\n )}\n </div>\n\n {badge !== undefined && <div className=\"absolute -top-1 -right-1\">{badge}</div>}\n {overlay !== undefined && (\n <div className=\"pointer-events-none absolute inset-x-0 top-1/2 -translate-y-1/2 text-center\">{overlay}</div>\n )}\n </div>\n );\n}\n","import { cn } from \"../cn\";\nimport { formatBB } from \"../format\";\n\nexport interface PotProps {\n amount: number;\n /** Optional label, e.g. \"Main\" / \"Side\". */\n label?: string;\n /** Formatter for the amount; defaults to Big-Blind formatting. */\n format?: (amount: number) => string;\n className?: string;\n}\n\n/** A small, standalone pot pill. Optional — the table does not require it. */\nexport function Pot({ amount, label, format = formatBB, className }: PotProps) {\n return (\n <div className={cn(\"flex items-center gap-2 font-mono text-sm text-amber-400\", className)}>\n {label && <span className=\"text-neutral-400\">{label}</span>}\n <span className=\"whitespace-nowrap rounded-full border border-amber-400/50 bg-neutral-900/80 px-3 py-1 shadow-inner\">\n {format(amount)}\n </span>\n </div>\n );\n}\n","import { useRef, useLayoutEffect, useState, useMemo, type ReactNode } from \"react\";\nimport { cn } from \"../cn\";\n\nexport interface CardTableProps {\n /** Number of seats (2–10). Default 6. */\n seatCount?: number;\n /** Optional per-seat content. Length should equal `seatCount`. */\n seats?: (ReactNode | null)[];\n /** Alternative to `seats`: render a seat by index. */\n renderSeat?: (index: number, isYou: boolean) => ReactNode;\n /** Which seat belongs to the viewer; rotates that seat to the bottom. */\n yourSeatIndex?: number | null;\n /** Optional content in the middle of the table (cards, pot, anything). */\n center?: ReactNode;\n /** Seat frame size used for layout. */\n seatWidth?: number;\n seatHeight?: number;\n className?: string;\n}\n\nconst DESIGN_W = 1200;\nconst DESIGN_H = 900;\nconst MARGIN_X = 164;\nconst MARGIN_Y = 196;\nconst TABLE_RX = DESIGN_W / 2 - MARGIN_X;\nconst TABLE_RY = DESIGN_H / 2 - MARGIN_Y;\nconst FELT_GAP = 11;\nconst BET_SCALE = 0.72;\nconst BET_RX = TABLE_RX * BET_SCALE;\nconst BET_RY = TABLE_RY * BET_SCALE;\n\ninterface SeatLayout {\n x: number;\n y: number;\n index: number;\n isYou: boolean;\n}\n\nfunction getEllipsePoint(cx: number, cy: number, rx: number, ry: number, angleDeg: number) {\n const rad = (angleDeg * Math.PI) / 180;\n return { x: cx + rx * Math.cos(rad), y: cy + ry * Math.sin(rad) };\n}\n\nfunction calculateSeatLayouts(\n seatCount: number,\n yourSeatIndex: number | null,\n seatWidth: number,\n seatHeight: number,\n): SeatLayout[] {\n const cx = DESIGN_W / 2;\n const cy = DESIGN_H / 2;\n const ringX = TABLE_RX - FELT_GAP + seatWidth / 2 + 6;\n const ringY = TABLE_RY - FELT_GAP + seatHeight / 2 + 6;\n\n // Even angular distribution, first seat at the top (-90°).\n const step = 360 / seatCount;\n const angleOf = (i: number) => -90 + i * step;\n\n // Rotate so the viewer's seat sits at the bottom (90°).\n const rotationOffset = yourSeatIndex !== null ? 90 - angleOf(yourSeatIndex) : 0;\n\n const layouts: SeatLayout[] = [];\n for (let i = 0; i < seatCount; i++) {\n const pt = getEllipsePoint(cx, cy, ringX, ringY, angleOf(i) + rotationOffset);\n layouts.push({\n x: pt.x - seatWidth / 2,\n y: pt.y - seatHeight / 2,\n index: i,\n isYou: i === yourSeatIndex,\n });\n }\n return layouts;\n}\n\n/**\n * A responsive, game-agnostic card table. It scales a fixed 1200x900 design\n * onto its container and arranges `seatCount` seats around the felt. Purely\n * presentational: supply seat content and an optional `center` slot.\n */\nexport function CardTable({\n seatCount = 6,\n seats,\n renderSeat,\n yourSeatIndex = null,\n center,\n seatWidth = 160,\n seatHeight = 176,\n className,\n}: CardTableProps) {\n const count = Math.max(2, Math.min(10, Math.floor(seatCount)));\n const outerRef = useRef<HTMLDivElement>(null);\n const [scale, setScale] = useState(1);\n\n const layouts = useMemo(\n () => calculateSeatLayouts(count, yourSeatIndex, seatWidth, seatHeight),\n [count, yourSeatIndex, seatWidth, seatHeight],\n );\n\n useLayoutEffect(() => {\n const el = outerRef.current;\n if (!el) return;\n const compute = () => {\n const w = el.clientWidth;\n const h = el.clientHeight;\n if (!w || !h) return;\n setScale(Math.min(w / DESIGN_W, h / DESIGN_H));\n };\n compute();\n const ro = new ResizeObserver(compute);\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n const tableFrameInset = { inset: `${MARGIN_Y}px ${MARGIN_X}px` };\n const feltInset = { inset: `${MARGIN_Y + FELT_GAP}px ${MARGIN_X + FELT_GAP}px` };\n const rimInset = { inset: `${MARGIN_Y + 6}px ${MARGIN_X + 6}px` };\n const betLineInset = {\n inset: `${MARGIN_Y + (TABLE_RY - BET_RY)}px ${MARGIN_X + (TABLE_RX - BET_RX)}px`,\n };\n\n return (\n <div\n ref={outerRef}\n className={cn(\"relative flex h-full w-full min-h-0 items-center justify-center overflow-hidden\", className)}\n >\n <div style={{ width: DESIGN_W * scale, height: DESIGN_H * scale }}>\n <div\n className=\"relative\"\n style={{ width: DESIGN_W, height: DESIGN_H, transform: `scale(${scale})`, transformOrigin: \"top left\" }}\n >\n <div\n className=\"absolute -inset-6 rounded-[50%] bg-[radial-gradient(closest-side,rgba(240,192,74,0.16),transparent_72%)] blur-3xl\"\n aria-hidden=\"true\"\n />\n\n {/* wooden rail */}\n <div\n className=\"absolute rounded-[50%] bg-gradient-to-b from-[#a5744a] via-[#6b3f22] to-[#38200f] shadow-[0_26px_70px_-12px_rgba(0,0,0,0.85),inset_0_3px_0_rgba(255,255,255,0.20),inset_0_-6px_16px_rgba(0,0,0,0.5)]\"\n style={tableFrameInset}\n aria-hidden=\"true\"\n />\n\n {/* felt */}\n <div\n className=\"absolute rounded-[50%] bg-[radial-gradient(ellipse_at_50%_32%,#23804b,#0f4d2e_55%,#093b22)] shadow-[inset_0_0_90px_rgba(0,0,0,0.55),inset_0_3px_10px_rgba(255,255,255,0.06)]\"\n style={feltInset}\n aria-hidden=\"true\"\n />\n\n {/* betting line */}\n <div\n className=\"absolute rounded-[50%] border-2 border-white/10 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.25)]\"\n style={betLineInset}\n aria-hidden=\"true\"\n />\n\n {/* center suit watermark */}\n <div className=\"absolute flex items-center justify-center pointer-events-none\" style={feltInset} aria-hidden=\"true\">\n <span className=\"text-white/[0.05] text-[110px] font-black tracking-[0.2em] select-none leading-none\">\n ♠♥♦♣\n </span>\n </div>\n\n {/* center slot (optional) */}\n {center !== undefined && (\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center\">\n {center}\n </div>\n )}\n\n {/* seats */}\n <div className=\"absolute inset-0 z-20\">\n {layouts.map(({ x, y, index, isYou }) => {\n const content = seats ? (seats[index] ?? null) : renderSeat ? renderSeat(index, isYou) : null;\n if (content === null) return null;\n return (\n <div key={index} className=\"absolute\" style={{ left: `${x}px`, top: `${y}px` }}>\n {content}\n </div>\n );\n })}\n </div>\n\n {/* outer rim highlight */}\n <div className=\"absolute rounded-[50%] border border-white/10 pointer-events-none\" style={rimInset} aria-hidden=\"true\" />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;;;ACAO,SAAS,MAAM,OAAyD;AAC7E,SAAO,MAAM,OAAO,OAAO,EAAE,KAAK,GAAG;AACvC;;;ACDO,IAAM,KAAK;AAGX,SAAS,SAAS,QAAwB;AAC/C,QAAM,KAAK,SAAS;AACpB,SAAO,GAAG,OAAO,UAAU,EAAE,IAAI,KAAK,GAAG,QAAQ,CAAC,CAAC;AACrD;;;ACSO,SAAS,cAAc,SAAiB,WAAmB,OAAwB,CAAC,GAAW;AACpG,QAAM,IAAI,KAAK,IAAI,GAAG,KAAK,MAAM,SAAS,CAAC;AAC3C,QAAM,MAAM,KAAK,OAAO;AACxB,QAAM,OAAO,KAAK;AAClB,QAAM,SAAU,KAAK,MAAM,OAAO,IAAI,IAAK,KAAK;AAEhD,MAAI,IAAI;AACR,WAAS,OAAO,GAAG,OAAO,IAAI,GAAG,QAAQ;AACvC,SAAK,IAAI,MAAM,KAAK;AACpB,QAAI,CAAC,QAAQ,CAAC,KAAK,CAAC,EAAG,QAAO;AAAA,EAChC;AACA,SAAO;AACT;;;ACiDoB;AAzEpB,IAAM,eAAyC;AAAA,EAC7C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAEA,IAAM,cAAwC;AAAA,EAC5C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AACA,IAAM,cAAwC;AAAA,EAC5C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AACA,IAAM,cAAwC;AAAA,EAC5C,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AACA,IAAM,mBAA6C;AAAA,EACjD,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AAAA,EACJ,IAAI;AACN;AAGO,SAAS,UAAU,MAA8C;AACtE,MAAI,CAAC,QAAQ,KAAK,SAAS,EAAG,QAAO,EAAE,MAAM,IAAI,MAAM,GAAG;AAC1D,SAAO,EAAE,MAAM,KAAK,MAAM,GAAG,EAAE,GAAG,MAAM,KAAK,MAAM,EAAE,EAAE;AACzD;AAEO,SAAS,cAAc,MAAsB;AAClD,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAK,aAAO;AAAA,IACjB,KAAK;AAAK,aAAO;AAAA,IACjB,KAAK;AAAK,aAAO;AAAA,IACjB,KAAK;AAAK,aAAO;AAAA,IACjB;AAAS,aAAO;AAAA,EAClB;AACF;AAEA,SAAS,aAAa,MAAsB;AAC1C,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT,KAAK;AAAA,IACL,KAAK;AACH,aAAO;AAAA,IACT;AACE,aAAO;AAAA,EACX;AACF;AAaO,SAAS,YAAY,EAAE,MAAM,OAAO,MAAM,UAAU,GAAqB;AAC9E,MAAI,CAAC,KAAM,QAAO,4CAAC,YAAS,MAAY,WAAsB;AAE9D,QAAM,EAAE,MAAM,KAAK,IAAI,UAAU,IAAI;AACrC,QAAM,aAAa,cAAc,IAAI;AACrC,QAAM,YAAY,aAAa,IAAI;AACnC,QAAM,aAAa,SAAS,QAAQ,SAAS;AAE7C,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT,aAAa,IAAI;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA,OAAO,EAAE,aAAa,QAAQ;AAAA,MAE9B;AAAA,qDAAC,SAAI,WAAW,GAAG,yEAAyE,SAAS,GACnG;AAAA,sDAAC,UAAK,WAAW,GAAG,YAAY,IAAI,GAAG,0BAA0B,GAAI,gBAAK;AAAA,UAC1E,4CAAC,UAAK,WAAW,GAAG,YAAY,IAAI,GAAG,WAAW,GAAI,sBAAW;AAAA,WACnE;AAAA,QACA,6CAAC,SAAI,WAAW,GAAG,wFAAwF,SAAS,GAClH;AAAA,sDAAC,UAAK,WAAW,GAAG,YAAY,IAAI,GAAG,0BAA0B,GAAI,gBAAK;AAAA,UAC1E,4CAAC,UAAK,WAAW,GAAG,YAAY,IAAI,GAAG,WAAW,GAAI,sBAAW;AAAA,WACnE;AAAA,QACC,cACC,4CAAC,SAAI,WAAW,GAAG,gEAAgE,SAAS,GAC1F,sDAAC,UAAK,WAAW,YAAY,IAAI,GAAI,sBAAW,GAClD;AAAA;AAAA;AAAA,EAEJ;AAEJ;AAOA,SAAS,eAAe,EAAE,KAAK,GAAuB;AACpD,QAAM,IAAI,iBAAiB,IAAI;AAC/B,SACE,6CAAC,SAAI,OAAO,GAAG,QAAQ,GAAG,SAAQ,aAAY,WAAU,2CAA0C,eAAY,QAC5G;AAAA,iDAAC,OAAE,MAAK,WAAU,SAAQ,QACxB;AAAA,kDAAC,aAAQ,IAAG,MAAK,IAAG,MAAK,IAAG,KAAI,IAAG,KAAI;AAAA,MACvC,4CAAC,aAAQ,IAAG,MAAK,IAAG,MAAK,IAAG,KAAI,IAAG,KAAI;AAAA,MACvC,4CAAC,aAAQ,IAAG,MAAK,IAAG,MAAK,IAAG,KAAI,IAAG,KAAI;AAAA,MACvC,4CAAC,aAAQ,IAAG,MAAK,IAAG,MAAK,IAAG,KAAI,IAAG,KAAI;AAAA,OACzC;AAAA,IACA,4CAAC,YAAO,IAAG,MAAK,IAAG,MAAK,GAAE,OAAM,MAAK,WAAU;AAAA,KACjD;AAEJ;AAGO,SAAS,SAAS,EAAE,OAAO,MAAM,UAAU,GAAkB;AAClE,SACE;AAAA,IAAC;AAAA;AAAA,MACC,WAAW;AAAA,QACT,aAAa,IAAI;AAAA,QACjB;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,MACF;AAAA,MACA,OAAO,EAAE,aAAa,QAAQ;AAAA,MAE9B,sDAAC,SAAI,WAAU,mQACb,sDAAC,kBAAe,MAAY,GAC9B;AAAA;AAAA,EACF;AAEJ;AASO,SAAS,UAAU,EAAE,OAAO,OAAO,MAAM,UAAU,GAAmB;AAC3E,SACE,4CAAC,SAAI,WAAW,GAAG,cAAc,SAAS,GACvC,gBAAM,IAAI,CAAC,MAAM,MAChB,4CAAC,eAAoB,MAAY,QAAf,CAA2B,CAC9C,GACH;AAEJ;AASO,SAAS,eAAe,EAAE,OAAO,OAAO,MAAM,UAAU,GAAwB;AACrF,SACE,4CAAC,SAAI,WAAW,GAAG,gBAAgB,SAAS,GACzC,gBAAM,IAAI,CAAC,MAAM,MAChB,4CAAC,SAAY,WAAU,qBAAoB,OAAO,EAAE,gBAAgB,GAAG,IAAI,EAAE,KAAK,GAChF,sDAAC,eAAY,MAAY,MAAY,KAD7B,CAEV,CACD,GACH;AAEJ;;;ACxIM,IAAAA,sBAAA;AAhBC,SAAS,KAAK;AAAA,EACnB,WAAW;AAAA,EACX,WAAW;AAAA,EACX,WAAW;AAAA,EACX;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA,QAAQ;AAAA,EACR,SAAS;AAAA,EACT;AACF,GAAc;AACZ,SACE,8CAAC,SAAI,WAAW,GAAG,YAAY,SAAS,GAAG,OAAO,EAAE,OAAO,OAAO,GAChE;AAAA;AAAA,MAAC;AAAA;AAAA,QACC,MAAM,UAAU,WAAW;AAAA,QAC3B,UAAU,UAAU,IAAI;AAAA,QACxB;AAAA,QACA,WACE,UACI,CAAC,MAAM;AACL,cAAI,EAAE,QAAQ,WAAW,EAAE,QAAQ,IAAK,SAAQ;AAAA,QAClD,IACA;AAAA,QAEN,WAAW;AAAA,UACT;AAAA,UACA,WAAW,4DAA4D;AAAA,UACvE,YAAY;AAAA,UACZ,YAAY;AAAA,UACZ,WAAW;AAAA,QACb;AAAA,QAEC;AAAA,qBAAW,UACV,6CAAC,SAAI,WAAU,8DAA8D,kBAAO;AAAA,UAEtF,6CAAC,SAAI,WAAU,2CAA2C,UAAS;AAAA,UAClE,WAAW,UACV,6CAAC,SAAI,WAAU,yEAAyE,kBAAO;AAAA;AAAA;AAAA,IAEnG;AAAA,IAEC,UAAU,UAAa,6CAAC,SAAI,WAAU,4BAA4B,iBAAM;AAAA,IACxE,YAAY,UACX,6CAAC,SAAI,WAAU,+EAA+E,mBAAQ;AAAA,KAE1G;AAEJ;;;ACrEI,IAAAC,sBAAA;AAFG,SAAS,IAAI,EAAE,QAAQ,OAAO,SAAS,UAAU,UAAU,GAAa;AAC7E,SACE,8CAAC,SAAI,WAAW,GAAG,4DAA4D,SAAS,GACrF;AAAA,aAAS,6CAAC,UAAK,WAAU,oBAAoB,iBAAM;AAAA,IACpD,6CAAC,UAAK,WAAU,sGACb,iBAAO,MAAM,GAChB;AAAA,KACF;AAEJ;;;ACtBA,mBAA2E;AA8HnE,IAAAC,sBAAA;AA1GR,IAAM,WAAW;AACjB,IAAM,WAAW;AACjB,IAAM,WAAW;AACjB,IAAM,WAAW;AACjB,IAAM,WAAW,WAAW,IAAI;AAChC,IAAM,WAAW,WAAW,IAAI;AAChC,IAAM,WAAW;AACjB,IAAM,YAAY;AAClB,IAAM,SAAS,WAAW;AAC1B,IAAM,SAAS,WAAW;AAS1B,SAAS,gBAAgB,IAAY,IAAY,IAAY,IAAY,UAAkB;AACzF,QAAM,MAAO,WAAW,KAAK,KAAM;AACnC,SAAO,EAAE,GAAG,KAAK,KAAK,KAAK,IAAI,GAAG,GAAG,GAAG,KAAK,KAAK,KAAK,IAAI,GAAG,EAAE;AAClE;AAEA,SAAS,qBACP,WACA,eACA,WACA,YACc;AACd,QAAM,KAAK,WAAW;AACtB,QAAM,KAAK,WAAW;AACtB,QAAM,QAAQ,WAAW,WAAW,YAAY,IAAI;AACpD,QAAM,QAAQ,WAAW,WAAW,aAAa,IAAI;AAGrD,QAAM,OAAO,MAAM;AACnB,QAAM,UAAU,CAAC,MAAc,MAAM,IAAI;AAGzC,QAAM,iBAAiB,kBAAkB,OAAO,KAAK,QAAQ,aAAa,IAAI;AAE9E,QAAM,UAAwB,CAAC;AAC/B,WAAS,IAAI,GAAG,IAAI,WAAW,KAAK;AAClC,UAAM,KAAK,gBAAgB,IAAI,IAAI,OAAO,OAAO,QAAQ,CAAC,IAAI,cAAc;AAC5E,YAAQ,KAAK;AAAA,MACX,GAAG,GAAG,IAAI,YAAY;AAAA,MACtB,GAAG,GAAG,IAAI,aAAa;AAAA,MACvB,OAAO;AAAA,MACP,OAAO,MAAM;AAAA,IACf,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAOO,SAAS,UAAU;AAAA,EACxB,YAAY;AAAA,EACZ;AAAA,EACA;AAAA,EACA,gBAAgB;AAAA,EAChB;AAAA,EACA,YAAY;AAAA,EACZ,aAAa;AAAA,EACb;AACF,GAAmB;AACjB,QAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,MAAM,SAAS,CAAC,CAAC;AAC7D,QAAM,eAAW,qBAAuB,IAAI;AAC5C,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAS,CAAC;AAEpC,QAAM,cAAU;AAAA,IACd,MAAM,qBAAqB,OAAO,eAAe,WAAW,UAAU;AAAA,IACtE,CAAC,OAAO,eAAe,WAAW,UAAU;AAAA,EAC9C;AAEA,oCAAgB,MAAM;AACpB,UAAM,KAAK,SAAS;AACpB,QAAI,CAAC,GAAI;AACT,UAAM,UAAU,MAAM;AACpB,YAAM,IAAI,GAAG;AACb,YAAM,IAAI,GAAG;AACb,UAAI,CAAC,KAAK,CAAC,EAAG;AACd,eAAS,KAAK,IAAI,IAAI,UAAU,IAAI,QAAQ,CAAC;AAAA,IAC/C;AACA,YAAQ;AACR,UAAM,KAAK,IAAI,eAAe,OAAO;AACrC,OAAG,QAAQ,EAAE;AACb,WAAO,MAAM,GAAG,WAAW;AAAA,EAC7B,GAAG,CAAC,CAAC;AAEL,QAAM,kBAAkB,EAAE,OAAO,GAAG,QAAQ,MAAM,QAAQ,KAAK;AAC/D,QAAM,YAAY,EAAE,OAAO,GAAG,WAAW,QAAQ,MAAM,WAAW,QAAQ,KAAK;AAC/E,QAAM,WAAW,EAAE,OAAO,GAAG,WAAW,CAAC,MAAM,WAAW,CAAC,KAAK;AAChE,QAAM,eAAe;AAAA,IACnB,OAAO,GAAG,YAAY,WAAW,OAAO,MAAM,YAAY,WAAW,OAAO;AAAA,EAC9E;AAEA,SACE;AAAA,IAAC;AAAA;AAAA,MACC,KAAK;AAAA,MACL,WAAW,GAAG,mFAAmF,SAAS;AAAA,MAE1G,uDAAC,SAAI,OAAO,EAAE,OAAO,WAAW,OAAO,QAAQ,WAAW,MAAM,GAC9D;AAAA,QAAC;AAAA;AAAA,UACC,WAAU;AAAA,UACV,OAAO,EAAE,OAAO,UAAU,QAAQ,UAAU,WAAW,SAAS,KAAK,KAAK,iBAAiB,WAAW;AAAA,UAEtG;AAAA;AAAA,cAAC;AAAA;AAAA,gBACC,WAAU;AAAA,gBACV,eAAY;AAAA;AAAA,YACd;AAAA,YAGA;AAAA,cAAC;AAAA;AAAA,gBACC,WAAU;AAAA,gBACV,OAAO;AAAA,gBACP,eAAY;AAAA;AAAA,YACd;AAAA,YAGA;AAAA,cAAC;AAAA;AAAA,gBACC,WAAU;AAAA,gBACV,OAAO;AAAA,gBACP,eAAY;AAAA;AAAA,YACd;AAAA,YAGA;AAAA,cAAC;AAAA;AAAA,gBACC,WAAU;AAAA,gBACV,OAAO;AAAA,gBACP,eAAY;AAAA;AAAA,YACd;AAAA,YAGA,6CAAC,SAAI,WAAU,iEAAgE,OAAO,WAAW,eAAY,QAC3G,uDAAC,UAAK,WAAU,uFAAsF,sCAEtG,GACF;AAAA,YAGC,WAAW,UACV,6CAAC,SAAI,WAAU,kFACZ,kBACH;AAAA,YAIF,6CAAC,SAAI,WAAU,yBACZ,kBAAQ,IAAI,CAAC,EAAE,GAAG,GAAG,OAAO,MAAM,MAAM;AACvC,oBAAM,UAAU,QAAS,MAAM,KAAK,KAAK,OAAQ,aAAa,WAAW,OAAO,KAAK,IAAI;AACzF,kBAAI,YAAY,KAAM,QAAO;AAC7B,qBACE,6CAAC,SAAgB,WAAU,YAAW,OAAO,EAAE,MAAM,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,KAAK,GAC1E,qBADO,KAEV;AAAA,YAEJ,CAAC,GACH;AAAA,YAGA,6CAAC,SAAI,WAAU,qEAAoE,OAAO,UAAU,eAAY,QAAO;AAAA;AAAA;AAAA,MACzH,GACF;AAAA;AAAA,EACF;AAEJ;","names":["import_jsx_runtime","import_jsx_runtime","import_jsx_runtime"]}
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/cn.ts","../src/format.ts","../src/seat.ts","../src/components/PlayingCard.tsx","../src/components/Seat.tsx","../src/components/Pot.tsx","../src/components/CardTable.tsx"],"sourcesContent":["export { cn } from \"./cn\";\nexport { BB, formatBB } from \"./format\";\nexport { nextSeatIndex, type NextSeatOptions } from \"./seat\";\n\nexport {\n PlayingCard,\n CardBack,\n HoleCards,\n CommunityCards,\n parseCard,\n getSuitSymbol,\n type PlayingCardProps,\n type CardBackProps,\n type HoleCardsProps,\n type CommunityCardsProps,\n type CardSize,\n} from \"./components/PlayingCard\";\n\nexport { Seat, type SeatProps } from \"./components/Seat\";\nexport { Pot, type PotProps } from \"./components/Pot\";\nexport { CardTable, type CardTableProps } from \"./components/CardTable\";\n\n// Back-compat alias (the table is now game-agnostic).\nexport { CardTable as PokerTable, type CardTableProps as PokerTableProps } from \"./components/CardTable\";\n","import { twMerge } from \"tailwind-merge\";\n\n/** Join class names and resolve conflicting Tailwind utilities (last wins). */\nexport function cn(...parts: Array<string | false | null | undefined>): string {\n return twMerge(parts.filter(Boolean).join(\" \"));\n}\n","/** Big Blind value used for stack display. */\nexport const BB = 20;\n\n/** Format a chip amount as a Big-Blind string, e.g. 1234 -> \"61.7 BB\". */\nexport function formatBB(amount: number): string {\n const bb = amount / BB;\n return `${Number.isInteger(bb) ? bb : bb.toFixed(1)} BB`;\n}\n","/**\n * Seat-ring helpers. Pure functions — they contain no game-specific rules.\n */\n\nexport interface NextSeatOptions {\n /** +1 for clockwise, -1 for counter-clockwise. Default +1. */\n dir?: 1 | -1;\n /** Return true to skip a seat (e.g. folded / empty / out). */\n skip?: (index: number) => boolean;\n}\n\n/**\n * Return the index of the next seat on the ring after `current`, skipping any\n * seat matched by `opts.skip`. Wraps around. Returns `current` if every other\n * seat is skipped.\n */\nexport function nextSeatIndex(current: number, seatCount: number, opts: NextSeatOptions = {}): number {\n const n = Math.max(1, Math.floor(seatCount));\n const dir = opts.dir ?? 1;\n const skip = opts.skip;\n const start = ((Math.floor(current) % n) + n) % n;\n\n let i = start;\n for (let step = 0; step < n - 1; step++) {\n i = (i + dir + n) % n;\n if (!skip || !skip(i)) return i;\n }\n return start;\n}\n","import { cn } from \"../cn\";\n\nexport type CardSize = \"xs\" | \"sm\" | \"md\" | \"lg\";\n\nconst SIZE_CLASSES: Record<CardSize, string> = {\n xs: \"w-8 h-11\",\n sm: \"w-10 h-14\",\n md: \"w-16 h-22\",\n lg: \"w-24 h-32\",\n};\n\nconst CORNER_RANK: Record<CardSize, string> = {\n xs: \"text-[11px]\",\n sm: \"text-sm\",\n md: \"text-lg\",\n lg: \"text-2xl\",\n};\nconst CORNER_SUIT: Record<CardSize, string> = {\n xs: \"text-[9px]\",\n sm: \"text-xs\",\n md: \"text-sm\",\n lg: \"text-base\",\n};\nconst CENTER_SUIT: Record<CardSize, string> = {\n xs: \"text-base\",\n sm: \"text-xl\",\n md: \"text-3xl\",\n lg: \"text-5xl\",\n};\nconst BACK_EMBLEM_SIZE: Record<CardSize, number> = {\n xs: 14,\n sm: 18,\n md: 28,\n lg: 42,\n};\n\n/** Parse a compact card code like \"As\", \"10h\", \"kd\" into rank + suit. */\nexport function parseCard(card: string): { rank: string; suit: string } {\n if (!card || card.length < 2) return { rank: \"\", suit: \"\" };\n return { rank: card.slice(0, -1), suit: card.slice(-1) };\n}\n\nexport function getSuitSymbol(suit: string): string {\n switch (suit) {\n case \"s\": return \"♠\";\n case \"h\": return \"♥\";\n case \"d\": return \"♦\";\n case \"c\": return \"♣\";\n default: return suit;\n }\n}\n\nfunction getSuitClass(suit: string): string {\n switch (suit) {\n case \"s\":\n case \"c\":\n return \"text-neutral-900\";\n case \"h\":\n case \"d\":\n return \"text-red-600\";\n default:\n return \"\";\n }\n}\n\nexport interface PlayingCardProps {\n /** Compact card code (\"As\", \"10h\") or null to render a face-down card back. */\n card: string | null;\n size?: CardSize;\n className?: string;\n}\n\n/**\n * A single playing card. Size is driven by the `size` prop (never viewport\n * breakpoints) so cards scale predictably inside any container.\n */\nexport function PlayingCard({ card, size = \"md\", className }: PlayingCardProps) {\n if (!card) return <CardBack size={size} className={className} />;\n\n const { rank, suit } = parseCard(card);\n const suitSymbol = getSuitSymbol(suit);\n const suitClass = getSuitClass(suit);\n const showCenter = size === \"md\" || size === \"lg\";\n\n return (\n <div\n className={cn(\n SIZE_CLASSES[size],\n \"relative bg-gradient-to-br from-white via-white to-neutral-100\",\n \"rounded-md border border-neutral-300/90 overflow-hidden\",\n \"shadow-[0_2px_6px_rgba(0,0,0,0.45),inset_0_1px_0_rgba(255,255,255,0.9)]\",\n className,\n )}\n style={{ aspectRatio: \"10/14\" }}\n >\n <div className={cn(\"absolute top-[3px] left-[5px] flex flex-col items-center leading-none\", suitClass)}>\n <span className={cn(CORNER_RANK[size], \"font-bold tracking-tight\")}>{rank}</span>\n <span className={cn(CORNER_SUIT[size], \"-mt-[1px]\")}>{suitSymbol}</span>\n </div>\n <div className={cn(\"absolute bottom-[3px] right-[5px] flex flex-col items-center leading-none rotate-180\", suitClass)}>\n <span className={cn(CORNER_RANK[size], \"font-bold tracking-tight\")}>{rank}</span>\n <span className={cn(CORNER_SUIT[size], \"-mt-[1px]\")}>{suitSymbol}</span>\n </div>\n {showCenter && (\n <div className={cn(\"absolute inset-0 flex items-center justify-center opacity-90\", suitClass)}>\n <span className={CENTER_SUIT[size]}>{suitSymbol}</span>\n </div>\n )}\n </div>\n );\n}\n\nexport interface CardBackProps {\n size?: CardSize;\n className?: string;\n}\n\nfunction CardBackEmblem({ size }: { size: CardSize }) {\n const s = BACK_EMBLEM_SIZE[size];\n return (\n <svg width={s} height={s} viewBox=\"0 0 40 40\" className=\"drop-shadow-[0_1px_2px_rgba(0,0,0,0.6)]\" aria-hidden=\"true\">\n <g fill=\"#f0c04a\" opacity=\"0.92\">\n <ellipse cx=\"20\" cy=\"11\" rx=\"4\" ry=\"7\" />\n <ellipse cx=\"20\" cy=\"29\" rx=\"4\" ry=\"7\" />\n <ellipse cx=\"11\" cy=\"20\" rx=\"7\" ry=\"4\" />\n <ellipse cx=\"29\" cy=\"20\" rx=\"7\" ry=\"4\" />\n </g>\n <circle cx=\"20\" cy=\"20\" r=\"3.2\" fill=\"#5a0c18\" />\n </svg>\n );\n}\n\n/** The face-down card back (dark red + gold rosette). */\nexport function CardBack({ size = \"md\", className }: CardBackProps) {\n return (\n <div\n className={cn(\n SIZE_CLASSES[size],\n \"relative rounded-md p-[3px] overflow-hidden\",\n \"bg-gradient-to-br from-[#7a1020] via-[#5a0c18] to-[#3f0810]\",\n \"border-2 border-[#c9a24b]/70 shadow-[0_2px_6px_rgba(0,0,0,0.45)]\",\n className,\n )}\n style={{ aspectRatio: \"10/14\" }}\n >\n <div className=\"h-full w-full rounded-[4px] border border-white/20 flex items-center justify-center bg-[repeating-linear-gradient(45deg,rgba(255,255,255,0.09)_0_6px,transparent_6px_12px),repeating-linear-gradient(-45deg,rgba(255,255,255,0.06)_0_6px,transparent_6px_12px)]\">\n <CardBackEmblem size={size} />\n </div>\n </div>\n );\n}\n\nexport interface HoleCardsProps {\n cards: (string | null)[];\n size?: CardSize;\n className?: string;\n}\n\n/** A row of a player's two hole cards. */\nexport function HoleCards({ cards, size = \"lg\", className }: HoleCardsProps) {\n return (\n <div className={cn(\"flex gap-2\", className)}>\n {cards.map((card, i) => (\n <PlayingCard key={i} card={card} size={size} />\n ))}\n </div>\n );\n}\n\nexport interface CommunityCardsProps {\n cards: (string | null)[];\n size?: CardSize;\n className?: string;\n}\n\n/** The five community cards, each dealt with a staggered flip animation. */\nexport function CommunityCards({ cards, size = \"md\", className }: CommunityCardsProps) {\n return (\n <div className={cn(\"flex gap-1.5\", className)}>\n {cards.map((card, i) => (\n <div key={i} className=\"animate-card-deal\" style={{ animationDelay: `${i * 90}ms` }}>\n <PlayingCard card={card} size={size} />\n </div>\n ))}\n </div>\n );\n}\n","import type { CSSProperties, ReactNode } from \"react\";\nimport { cn } from \"../cn\";\n\nexport interface SeatProps {\n /** Turn/active highlight (generic). */\n isActive?: boolean;\n /** Winner highlight (generic). */\n isWinner?: boolean;\n /** Dimmed, e.g. folded or out (generic). */\n isDimmed?: boolean;\n /** Optional click handler (e.g. \"sit here\"). */\n onClick?: () => void;\n\n /** Slot: top row inside the frame — name / badges. */\n header?: ReactNode;\n /** Slot: center content — 0..N cards, avatar, anything. */\n children?: ReactNode;\n /** Slot: bottom row inside the frame — stack / status. */\n footer?: ReactNode;\n /** Slot: floating element at the frame's top-right (legacy alias of topRight). */\n badge?: ReactNode;\n /** Slot: overlay centered over the frame. */\n overlay?: ReactNode;\n\n /** Slot: floating content above the frame, centered (e.g. an action bubble). */\n above?: ReactNode;\n /** Slot: content below the frame, centered (e.g. countdown / bet pill). */\n below?: ReactNode;\n /** Slot: floating content at the frame's top-left corner (e.g. a crown). */\n topLeft?: ReactNode;\n /** Slot: floating content at the frame's top-right corner (e.g. a kick button). */\n topRight?: ReactNode;\n /** Slot: floating content centered above the top edge (e.g. D/SB/BB badges). */\n topCenter?: ReactNode;\n\n width?: number;\n height?: number;\n className?: string;\n /** Extra classes for the inner frame, to override visuals. */\n frameClassName?: string;\n /** Inline styles for the inner frame. */\n frameStyle?: CSSProperties;\n}\n\n/**\n * A neutral seat: a frame with generic visual states and named slots. Put\n * whatever you want into `header` / `children` / `footer`, and use\n * `above` / `below` / `topLeft` / `topRight` / `topCenter` / `overlay` for\n * floating bits. It knows nothing about any particular game.\n */\nexport function Seat({\n isActive = false,\n isWinner = false,\n isDimmed = false,\n onClick,\n header,\n children,\n footer,\n badge,\n overlay,\n above,\n below,\n topLeft,\n topRight,\n topCenter,\n width = 160,\n height = 176,\n className,\n frameClassName,\n frameStyle,\n}: SeatProps) {\n return (\n <div className={cn(\"relative flex flex-col items-center\", className)} style={{ width }}>\n {topCenter !== undefined && (\n <div className=\"absolute -top-2 left-1/2 z-20 flex -translate-x-1/2 gap-1\">{topCenter}</div>\n )}\n {topLeft !== undefined && <div className=\"absolute -top-3 -left-2 z-40\">{topLeft}</div>}\n {topRight !== undefined && <div className=\"absolute -top-2 -right-2 z-40\">{topRight}</div>}\n {above !== undefined && (\n <div className=\"pointer-events-none absolute -top-12 left-1/2 z-30 -translate-x-1/2\">{above}</div>\n )}\n\n <div\n role={onClick ? \"button\" : undefined}\n tabIndex={onClick ? 0 : undefined}\n onClick={onClick}\n onKeyDown={\n onClick\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") onClick();\n }\n : undefined\n }\n style={{ width, height, ...frameStyle }}\n className={cn(\n \"relative flex flex-col items-center justify-between gap-1 rounded-2xl border bg-neutral-900/80 p-2 text-neutral-100 transition\",\n isActive ? \"border-cyan-400 shadow-[0_0_18px_rgba(34,211,238,0.55)]\" : \"border-neutral-700\",\n isWinner && \"border-amber-400 shadow-[0_0_18px_rgba(251,191,36,0.6)]\",\n isDimmed && \"opacity-50\",\n onClick && \"cursor-pointer hover:border-neutral-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400\",\n frameClassName,\n )}\n >\n {header !== undefined && (\n <div className=\"flex w-full shrink-0 items-center justify-between gap-1 text-[11px]\">{header}</div>\n )}\n <div className=\"flex min-h-0 flex-1 items-center justify-center\">{children}</div>\n {footer !== undefined && (\n <div className=\"flex w-full shrink-0 items-center justify-between text-[11px] text-neutral-300\">{footer}</div>\n )}\n\n {overlay !== undefined && (\n <div className=\"pointer-events-none absolute inset-x-0 top-1/2 -translate-y-1/2 text-center\">{overlay}</div>\n )}\n </div>\n\n {badge !== undefined && <div className=\"absolute top-0 right-0 z-40\">{badge}</div>}\n {below !== undefined && <div className=\"mt-1 flex flex-col items-center gap-1\">{below}</div>}\n </div>\n );\n}\n","import { cn } from \"../cn\";\nimport { formatBB } from \"../format\";\n\nexport interface PotProps {\n amount: number;\n /** Optional label, e.g. \"Main\" / \"Side\". */\n label?: string;\n /** Formatter for the amount; defaults to Big-Blind formatting. */\n format?: (amount: number) => string;\n className?: string;\n}\n\n/** A small, standalone pot pill. Optional — the table does not require it. */\nexport function Pot({ amount, label, format = formatBB, className }: PotProps) {\n return (\n <div className={cn(\"flex items-center gap-2 font-mono text-sm text-amber-400\", className)}>\n {label && <span className=\"text-neutral-400\">{label}</span>}\n <span className=\"whitespace-nowrap rounded-full border border-amber-400/50 bg-neutral-900/80 px-3 py-1 shadow-inner\">\n {format(amount)}\n </span>\n </div>\n );\n}\n","import { useRef, useLayoutEffect, useState, useMemo, type ReactNode } from \"react\";\nimport { cn } from \"../cn\";\n\nexport interface CardTableProps {\n /** Number of seats (2–10). Default 6. */\n seatCount?: number;\n /** Optional per-seat content. Length should equal `seatCount`. */\n seats?: (ReactNode | null)[];\n /** Alternative to `seats`: render a seat by index. */\n renderSeat?: (index: number, isYou: boolean) => ReactNode;\n /** Which seat belongs to the viewer; rotates that seat to the bottom. */\n yourSeatIndex?: number | null;\n /** Optional content in the middle of the table (cards, pot, anything). */\n center?: ReactNode;\n /** Seat frame size used for layout. */\n seatWidth?: number;\n seatHeight?: number;\n className?: string;\n}\n\nconst DESIGN_W = 1200;\nconst DESIGN_H = 900;\nconst MARGIN_X = 164;\nconst MARGIN_Y = 196;\nconst TABLE_RX = DESIGN_W / 2 - MARGIN_X;\nconst TABLE_RY = DESIGN_H / 2 - MARGIN_Y;\nconst FELT_GAP = 11;\nconst BET_SCALE = 0.72;\nconst BET_RX = TABLE_RX * BET_SCALE;\nconst BET_RY = TABLE_RY * BET_SCALE;\n\ninterface SeatLayout {\n x: number;\n y: number;\n index: number;\n isYou: boolean;\n}\n\nfunction getEllipsePoint(cx: number, cy: number, rx: number, ry: number, angleDeg: number) {\n const rad = (angleDeg * Math.PI) / 180;\n return { x: cx + rx * Math.cos(rad), y: cy + ry * Math.sin(rad) };\n}\n\nfunction calculateSeatLayouts(\n seatCount: number,\n yourSeatIndex: number | null,\n seatWidth: number,\n seatHeight: number,\n): SeatLayout[] {\n const cx = DESIGN_W / 2;\n const cy = DESIGN_H / 2;\n const ringX = TABLE_RX - FELT_GAP + seatWidth / 2 + 6;\n const ringY = TABLE_RY - FELT_GAP + seatHeight / 2 + 6;\n\n // Even angular distribution, first seat at the top (-90°).\n const step = 360 / seatCount;\n const angleOf = (i: number) => -90 + i * step;\n\n // Rotate so the viewer's seat sits at the bottom (90°).\n const rotationOffset = yourSeatIndex !== null ? 90 - angleOf(yourSeatIndex) : 0;\n\n const layouts: SeatLayout[] = [];\n for (let i = 0; i < seatCount; i++) {\n const pt = getEllipsePoint(cx, cy, ringX, ringY, angleOf(i) + rotationOffset);\n layouts.push({\n x: pt.x - seatWidth / 2,\n y: pt.y - seatHeight / 2,\n index: i,\n isYou: i === yourSeatIndex,\n });\n }\n return layouts;\n}\n\n/**\n * A responsive, game-agnostic card table. It scales a fixed 1200x900 design\n * onto its container and arranges `seatCount` seats around the felt. Purely\n * presentational: supply seat content and an optional `center` slot.\n */\nexport function CardTable({\n seatCount = 6,\n seats,\n renderSeat,\n yourSeatIndex = null,\n center,\n seatWidth = 160,\n seatHeight = 176,\n className,\n}: CardTableProps) {\n const count = Math.max(2, Math.min(10, Math.floor(seatCount)));\n const outerRef = useRef<HTMLDivElement>(null);\n const [scale, setScale] = useState(1);\n\n const layouts = useMemo(\n () => calculateSeatLayouts(count, yourSeatIndex, seatWidth, seatHeight),\n [count, yourSeatIndex, seatWidth, seatHeight],\n );\n\n useLayoutEffect(() => {\n const el = outerRef.current;\n if (!el) return;\n const compute = () => {\n const w = el.clientWidth;\n const h = el.clientHeight;\n if (!w || !h) return;\n setScale(Math.min(w / DESIGN_W, h / DESIGN_H));\n };\n compute();\n const ro = new ResizeObserver(compute);\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n const tableFrameInset = { inset: `${MARGIN_Y}px ${MARGIN_X}px` };\n const feltInset = { inset: `${MARGIN_Y + FELT_GAP}px ${MARGIN_X + FELT_GAP}px` };\n const rimInset = { inset: `${MARGIN_Y + 6}px ${MARGIN_X + 6}px` };\n const betLineInset = {\n inset: `${MARGIN_Y + (TABLE_RY - BET_RY)}px ${MARGIN_X + (TABLE_RX - BET_RX)}px`,\n };\n\n return (\n <div\n ref={outerRef}\n className={cn(\"relative flex h-full w-full min-h-0 items-center justify-center overflow-hidden\", className)}\n >\n <div style={{ width: DESIGN_W * scale, height: DESIGN_H * scale }}>\n <div\n className=\"relative\"\n style={{ width: DESIGN_W, height: DESIGN_H, transform: `scale(${scale})`, transformOrigin: \"top left\" }}\n >\n <div\n className=\"absolute -inset-6 rounded-[50%] bg-[radial-gradient(closest-side,rgba(240,192,74,0.16),transparent_72%)] blur-3xl\"\n aria-hidden=\"true\"\n />\n\n {/* wooden rail */}\n <div\n className=\"absolute rounded-[50%] bg-gradient-to-b from-[#a5744a] via-[#6b3f22] to-[#38200f] shadow-[0_26px_70px_-12px_rgba(0,0,0,0.85),inset_0_3px_0_rgba(255,255,255,0.20),inset_0_-6px_16px_rgba(0,0,0,0.5)]\"\n style={tableFrameInset}\n aria-hidden=\"true\"\n />\n\n {/* felt */}\n <div\n className=\"absolute rounded-[50%] bg-[radial-gradient(ellipse_at_50%_32%,#23804b,#0f4d2e_55%,#093b22)] shadow-[inset_0_0_90px_rgba(0,0,0,0.55),inset_0_3px_10px_rgba(255,255,255,0.06)]\"\n style={feltInset}\n aria-hidden=\"true\"\n />\n\n {/* betting line */}\n <div\n className=\"absolute rounded-[50%] border-2 border-white/10 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.25)]\"\n style={betLineInset}\n aria-hidden=\"true\"\n />\n\n {/* center suit watermark */}\n <div className=\"absolute flex items-center justify-center pointer-events-none\" style={feltInset} aria-hidden=\"true\">\n <span className=\"text-white/[0.05] text-[110px] font-black tracking-[0.2em] select-none leading-none\">\n ♠♥♦♣\n </span>\n </div>\n\n {/* center slot (optional) */}\n {center !== undefined && (\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center\">\n {center}\n </div>\n )}\n\n {/* seats */}\n <div className=\"absolute inset-0 z-20\">\n {layouts.map(({ x, y, index, isYou }) => {\n const content = seats ? (seats[index] ?? null) : renderSeat ? renderSeat(index, isYou) : null;\n if (content === null) return null;\n return (\n <div key={index} className=\"absolute\" style={{ left: `${x}px`, top: `${y}px` }}>\n {content}\n </div>\n );\n })}\n </div>\n\n {/* outer rim highlight */}\n <div className=\"absolute rounded-[50%] border border-white/10 pointer-events-none\" style={rimInset} aria-hidden=\"true\" />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["import_jsx_runtime","import_jsx_runtime","import_jsx_runtime"]}
|
package/dist/index.d.cts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
2
|
+
import { ReactNode, CSSProperties } from 'react';
|
|
3
3
|
|
|
4
|
+
/** Join class names and resolve conflicting Tailwind utilities (last wins). */
|
|
4
5
|
declare function cn(...parts: Array<string | false | null | undefined>): string;
|
|
5
6
|
|
|
6
7
|
/** Big Blind value used for stack display. */
|
|
@@ -72,26 +73,41 @@ interface SeatProps {
|
|
|
72
73
|
isDimmed?: boolean;
|
|
73
74
|
/** Optional click handler (e.g. "sit here"). */
|
|
74
75
|
onClick?: () => void;
|
|
75
|
-
/** Slot: top row — name / badges. */
|
|
76
|
+
/** Slot: top row inside the frame — name / badges. */
|
|
76
77
|
header?: ReactNode;
|
|
77
|
-
/** Slot: top-right floating element — e.g. a dealer button. */
|
|
78
|
-
badge?: ReactNode;
|
|
79
78
|
/** Slot: center content — 0..N cards, avatar, anything. */
|
|
80
79
|
children?: ReactNode;
|
|
81
|
-
/** Slot: bottom row — stack /
|
|
80
|
+
/** Slot: bottom row inside the frame — stack / status. */
|
|
82
81
|
footer?: ReactNode;
|
|
83
|
-
/** Slot:
|
|
82
|
+
/** Slot: floating element at the frame's top-right (legacy alias of topRight). */
|
|
83
|
+
badge?: ReactNode;
|
|
84
|
+
/** Slot: overlay centered over the frame. */
|
|
84
85
|
overlay?: ReactNode;
|
|
86
|
+
/** Slot: floating content above the frame, centered (e.g. an action bubble). */
|
|
87
|
+
above?: ReactNode;
|
|
88
|
+
/** Slot: content below the frame, centered (e.g. countdown / bet pill). */
|
|
89
|
+
below?: ReactNode;
|
|
90
|
+
/** Slot: floating content at the frame's top-left corner (e.g. a crown). */
|
|
91
|
+
topLeft?: ReactNode;
|
|
92
|
+
/** Slot: floating content at the frame's top-right corner (e.g. a kick button). */
|
|
93
|
+
topRight?: ReactNode;
|
|
94
|
+
/** Slot: floating content centered above the top edge (e.g. D/SB/BB badges). */
|
|
95
|
+
topCenter?: ReactNode;
|
|
85
96
|
width?: number;
|
|
86
97
|
height?: number;
|
|
87
98
|
className?: string;
|
|
99
|
+
/** Extra classes for the inner frame, to override visuals. */
|
|
100
|
+
frameClassName?: string;
|
|
101
|
+
/** Inline styles for the inner frame. */
|
|
102
|
+
frameStyle?: CSSProperties;
|
|
88
103
|
}
|
|
89
104
|
/**
|
|
90
|
-
* A neutral seat: a frame with generic visual states and named slots.
|
|
91
|
-
*
|
|
92
|
-
* `
|
|
105
|
+
* A neutral seat: a frame with generic visual states and named slots. Put
|
|
106
|
+
* whatever you want into `header` / `children` / `footer`, and use
|
|
107
|
+
* `above` / `below` / `topLeft` / `topRight` / `topCenter` / `overlay` for
|
|
108
|
+
* floating bits. It knows nothing about any particular game.
|
|
93
109
|
*/
|
|
94
|
-
declare function Seat({ isActive, isWinner, isDimmed, onClick, header,
|
|
110
|
+
declare function Seat({ isActive, isWinner, isDimmed, onClick, header, children, footer, badge, overlay, above, below, topLeft, topRight, topCenter, width, height, className, frameClassName, frameStyle, }: SeatProps): react.JSX.Element;
|
|
95
111
|
|
|
96
112
|
interface PotProps {
|
|
97
113
|
amount: number;
|
package/dist/index.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import * as react from 'react';
|
|
2
|
-
import { ReactNode } from 'react';
|
|
2
|
+
import { ReactNode, CSSProperties } from 'react';
|
|
3
3
|
|
|
4
|
+
/** Join class names and resolve conflicting Tailwind utilities (last wins). */
|
|
4
5
|
declare function cn(...parts: Array<string | false | null | undefined>): string;
|
|
5
6
|
|
|
6
7
|
/** Big Blind value used for stack display. */
|
|
@@ -72,26 +73,41 @@ interface SeatProps {
|
|
|
72
73
|
isDimmed?: boolean;
|
|
73
74
|
/** Optional click handler (e.g. "sit here"). */
|
|
74
75
|
onClick?: () => void;
|
|
75
|
-
/** Slot: top row — name / badges. */
|
|
76
|
+
/** Slot: top row inside the frame — name / badges. */
|
|
76
77
|
header?: ReactNode;
|
|
77
|
-
/** Slot: top-right floating element — e.g. a dealer button. */
|
|
78
|
-
badge?: ReactNode;
|
|
79
78
|
/** Slot: center content — 0..N cards, avatar, anything. */
|
|
80
79
|
children?: ReactNode;
|
|
81
|
-
/** Slot: bottom row — stack /
|
|
80
|
+
/** Slot: bottom row inside the frame — stack / status. */
|
|
82
81
|
footer?: ReactNode;
|
|
83
|
-
/** Slot:
|
|
82
|
+
/** Slot: floating element at the frame's top-right (legacy alias of topRight). */
|
|
83
|
+
badge?: ReactNode;
|
|
84
|
+
/** Slot: overlay centered over the frame. */
|
|
84
85
|
overlay?: ReactNode;
|
|
86
|
+
/** Slot: floating content above the frame, centered (e.g. an action bubble). */
|
|
87
|
+
above?: ReactNode;
|
|
88
|
+
/** Slot: content below the frame, centered (e.g. countdown / bet pill). */
|
|
89
|
+
below?: ReactNode;
|
|
90
|
+
/** Slot: floating content at the frame's top-left corner (e.g. a crown). */
|
|
91
|
+
topLeft?: ReactNode;
|
|
92
|
+
/** Slot: floating content at the frame's top-right corner (e.g. a kick button). */
|
|
93
|
+
topRight?: ReactNode;
|
|
94
|
+
/** Slot: floating content centered above the top edge (e.g. D/SB/BB badges). */
|
|
95
|
+
topCenter?: ReactNode;
|
|
85
96
|
width?: number;
|
|
86
97
|
height?: number;
|
|
87
98
|
className?: string;
|
|
99
|
+
/** Extra classes for the inner frame, to override visuals. */
|
|
100
|
+
frameClassName?: string;
|
|
101
|
+
/** Inline styles for the inner frame. */
|
|
102
|
+
frameStyle?: CSSProperties;
|
|
88
103
|
}
|
|
89
104
|
/**
|
|
90
|
-
* A neutral seat: a frame with generic visual states and named slots.
|
|
91
|
-
*
|
|
92
|
-
* `
|
|
105
|
+
* A neutral seat: a frame with generic visual states and named slots. Put
|
|
106
|
+
* whatever you want into `header` / `children` / `footer`, and use
|
|
107
|
+
* `above` / `below` / `topLeft` / `topRight` / `topCenter` / `overlay` for
|
|
108
|
+
* floating bits. It knows nothing about any particular game.
|
|
93
109
|
*/
|
|
94
|
-
declare function Seat({ isActive, isWinner, isDimmed, onClick, header,
|
|
110
|
+
declare function Seat({ isActive, isWinner, isDimmed, onClick, header, children, footer, badge, overlay, above, below, topLeft, topRight, topCenter, width, height, className, frameClassName, frameStyle, }: SeatProps): react.JSX.Element;
|
|
95
111
|
|
|
96
112
|
interface PotProps {
|
|
97
113
|
amount: number;
|
package/dist/index.js
CHANGED
|
@@ -1,8 +1,9 @@
|
|
|
1
1
|
"use client";
|
|
2
2
|
|
|
3
3
|
// src/cn.ts
|
|
4
|
+
import { twMerge } from "tailwind-merge";
|
|
4
5
|
function cn(...parts) {
|
|
5
|
-
return parts.filter(Boolean).join(" ");
|
|
6
|
+
return twMerge(parts.filter(Boolean).join(" "));
|
|
6
7
|
}
|
|
7
8
|
|
|
8
9
|
// src/format.ts
|
|
@@ -162,15 +163,26 @@ function Seat({
|
|
|
162
163
|
isDimmed = false,
|
|
163
164
|
onClick,
|
|
164
165
|
header,
|
|
165
|
-
badge,
|
|
166
166
|
children,
|
|
167
167
|
footer,
|
|
168
|
+
badge,
|
|
168
169
|
overlay,
|
|
170
|
+
above,
|
|
171
|
+
below,
|
|
172
|
+
topLeft,
|
|
173
|
+
topRight,
|
|
174
|
+
topCenter,
|
|
169
175
|
width = 160,
|
|
170
176
|
height = 176,
|
|
171
|
-
className
|
|
177
|
+
className,
|
|
178
|
+
frameClassName,
|
|
179
|
+
frameStyle
|
|
172
180
|
}) {
|
|
173
|
-
return /* @__PURE__ */ jsxs2("div", { className: cn("relative", className), style: { width
|
|
181
|
+
return /* @__PURE__ */ jsxs2("div", { className: cn("relative flex flex-col items-center", className), style: { width }, children: [
|
|
182
|
+
topCenter !== void 0 && /* @__PURE__ */ jsx2("div", { className: "absolute -top-2 left-1/2 z-20 flex -translate-x-1/2 gap-1", children: topCenter }),
|
|
183
|
+
topLeft !== void 0 && /* @__PURE__ */ jsx2("div", { className: "absolute -top-3 -left-2 z-40", children: topLeft }),
|
|
184
|
+
topRight !== void 0 && /* @__PURE__ */ jsx2("div", { className: "absolute -top-2 -right-2 z-40", children: topRight }),
|
|
185
|
+
above !== void 0 && /* @__PURE__ */ jsx2("div", { className: "pointer-events-none absolute -top-12 left-1/2 z-30 -translate-x-1/2", children: above }),
|
|
174
186
|
/* @__PURE__ */ jsxs2(
|
|
175
187
|
"div",
|
|
176
188
|
{
|
|
@@ -180,22 +192,25 @@ function Seat({
|
|
|
180
192
|
onKeyDown: onClick ? (e) => {
|
|
181
193
|
if (e.key === "Enter" || e.key === " ") onClick();
|
|
182
194
|
} : void 0,
|
|
195
|
+
style: { width, height, ...frameStyle },
|
|
183
196
|
className: cn(
|
|
184
|
-
"flex
|
|
197
|
+
"relative flex flex-col items-center justify-between gap-1 rounded-2xl border bg-neutral-900/80 p-2 text-neutral-100 transition",
|
|
185
198
|
isActive ? "border-cyan-400 shadow-[0_0_18px_rgba(34,211,238,0.55)]" : "border-neutral-700",
|
|
186
199
|
isWinner && "border-amber-400 shadow-[0_0_18px_rgba(251,191,36,0.6)]",
|
|
187
200
|
isDimmed && "opacity-50",
|
|
188
|
-
onClick && "cursor-pointer hover:border-neutral-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400"
|
|
201
|
+
onClick && "cursor-pointer hover:border-neutral-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400",
|
|
202
|
+
frameClassName
|
|
189
203
|
),
|
|
190
204
|
children: [
|
|
191
|
-
header !== void 0 && /* @__PURE__ */ jsx2("div", { className: "flex w-full items-center justify-between gap-1 text-[11px]", children: header }),
|
|
192
|
-
/* @__PURE__ */ jsx2("div", { className: "flex flex-1 items-center justify-center", children }),
|
|
193
|
-
footer !== void 0 && /* @__PURE__ */ jsx2("div", { className: "flex w-full items-center justify-between text-[11px] text-neutral-300", children: footer })
|
|
205
|
+
header !== void 0 && /* @__PURE__ */ jsx2("div", { className: "flex w-full shrink-0 items-center justify-between gap-1 text-[11px]", children: header }),
|
|
206
|
+
/* @__PURE__ */ jsx2("div", { className: "flex min-h-0 flex-1 items-center justify-center", children }),
|
|
207
|
+
footer !== void 0 && /* @__PURE__ */ jsx2("div", { className: "flex w-full shrink-0 items-center justify-between text-[11px] text-neutral-300", children: footer }),
|
|
208
|
+
overlay !== void 0 && /* @__PURE__ */ jsx2("div", { className: "pointer-events-none absolute inset-x-0 top-1/2 -translate-y-1/2 text-center", children: overlay })
|
|
194
209
|
]
|
|
195
210
|
}
|
|
196
211
|
),
|
|
197
|
-
badge !== void 0 && /* @__PURE__ */ jsx2("div", { className: "absolute
|
|
198
|
-
|
|
212
|
+
badge !== void 0 && /* @__PURE__ */ jsx2("div", { className: "absolute top-0 right-0 z-40", children: badge }),
|
|
213
|
+
below !== void 0 && /* @__PURE__ */ jsx2("div", { className: "mt-1 flex flex-col items-center gap-1", children: below })
|
|
199
214
|
] });
|
|
200
215
|
}
|
|
201
216
|
|
package/dist/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/cn.ts","../src/format.ts","../src/seat.ts","../src/components/PlayingCard.tsx","../src/components/Seat.tsx","../src/components/Pot.tsx","../src/components/CardTable.tsx"],"sourcesContent":["export function cn(...parts: Array<string | false | null | undefined>): string {\n return parts.filter(Boolean).join(\" \");\n}\n","/** Big Blind value used for stack display. */\nexport const BB = 20;\n\n/** Format a chip amount as a Big-Blind string, e.g. 1234 -> \"61.7 BB\". */\nexport function formatBB(amount: number): string {\n const bb = amount / BB;\n return `${Number.isInteger(bb) ? bb : bb.toFixed(1)} BB`;\n}\n","/**\n * Seat-ring helpers. Pure functions — they contain no game-specific rules.\n */\n\nexport interface NextSeatOptions {\n /** +1 for clockwise, -1 for counter-clockwise. Default +1. */\n dir?: 1 | -1;\n /** Return true to skip a seat (e.g. folded / empty / out). */\n skip?: (index: number) => boolean;\n}\n\n/**\n * Return the index of the next seat on the ring after `current`, skipping any\n * seat matched by `opts.skip`. Wraps around. Returns `current` if every other\n * seat is skipped.\n */\nexport function nextSeatIndex(current: number, seatCount: number, opts: NextSeatOptions = {}): number {\n const n = Math.max(1, Math.floor(seatCount));\n const dir = opts.dir ?? 1;\n const skip = opts.skip;\n const start = ((Math.floor(current) % n) + n) % n;\n\n let i = start;\n for (let step = 0; step < n - 1; step++) {\n i = (i + dir + n) % n;\n if (!skip || !skip(i)) return i;\n }\n return start;\n}\n","import { cn } from \"../cn\";\n\nexport type CardSize = \"xs\" | \"sm\" | \"md\" | \"lg\";\n\nconst SIZE_CLASSES: Record<CardSize, string> = {\n xs: \"w-8 h-11\",\n sm: \"w-10 h-14\",\n md: \"w-16 h-22\",\n lg: \"w-24 h-32\",\n};\n\nconst CORNER_RANK: Record<CardSize, string> = {\n xs: \"text-[11px]\",\n sm: \"text-sm\",\n md: \"text-lg\",\n lg: \"text-2xl\",\n};\nconst CORNER_SUIT: Record<CardSize, string> = {\n xs: \"text-[9px]\",\n sm: \"text-xs\",\n md: \"text-sm\",\n lg: \"text-base\",\n};\nconst CENTER_SUIT: Record<CardSize, string> = {\n xs: \"text-base\",\n sm: \"text-xl\",\n md: \"text-3xl\",\n lg: \"text-5xl\",\n};\nconst BACK_EMBLEM_SIZE: Record<CardSize, number> = {\n xs: 14,\n sm: 18,\n md: 28,\n lg: 42,\n};\n\n/** Parse a compact card code like \"As\", \"10h\", \"kd\" into rank + suit. */\nexport function parseCard(card: string): { rank: string; suit: string } {\n if (!card || card.length < 2) return { rank: \"\", suit: \"\" };\n return { rank: card.slice(0, -1), suit: card.slice(-1) };\n}\n\nexport function getSuitSymbol(suit: string): string {\n switch (suit) {\n case \"s\": return \"♠\";\n case \"h\": return \"♥\";\n case \"d\": return \"♦\";\n case \"c\": return \"♣\";\n default: return suit;\n }\n}\n\nfunction getSuitClass(suit: string): string {\n switch (suit) {\n case \"s\":\n case \"c\":\n return \"text-neutral-900\";\n case \"h\":\n case \"d\":\n return \"text-red-600\";\n default:\n return \"\";\n }\n}\n\nexport interface PlayingCardProps {\n /** Compact card code (\"As\", \"10h\") or null to render a face-down card back. */\n card: string | null;\n size?: CardSize;\n className?: string;\n}\n\n/**\n * A single playing card. Size is driven by the `size` prop (never viewport\n * breakpoints) so cards scale predictably inside any container.\n */\nexport function PlayingCard({ card, size = \"md\", className }: PlayingCardProps) {\n if (!card) return <CardBack size={size} className={className} />;\n\n const { rank, suit } = parseCard(card);\n const suitSymbol = getSuitSymbol(suit);\n const suitClass = getSuitClass(suit);\n const showCenter = size === \"md\" || size === \"lg\";\n\n return (\n <div\n className={cn(\n SIZE_CLASSES[size],\n \"relative bg-gradient-to-br from-white via-white to-neutral-100\",\n \"rounded-md border border-neutral-300/90 overflow-hidden\",\n \"shadow-[0_2px_6px_rgba(0,0,0,0.45),inset_0_1px_0_rgba(255,255,255,0.9)]\",\n className,\n )}\n style={{ aspectRatio: \"10/14\" }}\n >\n <div className={cn(\"absolute top-[3px] left-[5px] flex flex-col items-center leading-none\", suitClass)}>\n <span className={cn(CORNER_RANK[size], \"font-bold tracking-tight\")}>{rank}</span>\n <span className={cn(CORNER_SUIT[size], \"-mt-[1px]\")}>{suitSymbol}</span>\n </div>\n <div className={cn(\"absolute bottom-[3px] right-[5px] flex flex-col items-center leading-none rotate-180\", suitClass)}>\n <span className={cn(CORNER_RANK[size], \"font-bold tracking-tight\")}>{rank}</span>\n <span className={cn(CORNER_SUIT[size], \"-mt-[1px]\")}>{suitSymbol}</span>\n </div>\n {showCenter && (\n <div className={cn(\"absolute inset-0 flex items-center justify-center opacity-90\", suitClass)}>\n <span className={CENTER_SUIT[size]}>{suitSymbol}</span>\n </div>\n )}\n </div>\n );\n}\n\nexport interface CardBackProps {\n size?: CardSize;\n className?: string;\n}\n\nfunction CardBackEmblem({ size }: { size: CardSize }) {\n const s = BACK_EMBLEM_SIZE[size];\n return (\n <svg width={s} height={s} viewBox=\"0 0 40 40\" className=\"drop-shadow-[0_1px_2px_rgba(0,0,0,0.6)]\" aria-hidden=\"true\">\n <g fill=\"#f0c04a\" opacity=\"0.92\">\n <ellipse cx=\"20\" cy=\"11\" rx=\"4\" ry=\"7\" />\n <ellipse cx=\"20\" cy=\"29\" rx=\"4\" ry=\"7\" />\n <ellipse cx=\"11\" cy=\"20\" rx=\"7\" ry=\"4\" />\n <ellipse cx=\"29\" cy=\"20\" rx=\"7\" ry=\"4\" />\n </g>\n <circle cx=\"20\" cy=\"20\" r=\"3.2\" fill=\"#5a0c18\" />\n </svg>\n );\n}\n\n/** The face-down card back (dark red + gold rosette). */\nexport function CardBack({ size = \"md\", className }: CardBackProps) {\n return (\n <div\n className={cn(\n SIZE_CLASSES[size],\n \"relative rounded-md p-[3px] overflow-hidden\",\n \"bg-gradient-to-br from-[#7a1020] via-[#5a0c18] to-[#3f0810]\",\n \"border-2 border-[#c9a24b]/70 shadow-[0_2px_6px_rgba(0,0,0,0.45)]\",\n className,\n )}\n style={{ aspectRatio: \"10/14\" }}\n >\n <div className=\"h-full w-full rounded-[4px] border border-white/20 flex items-center justify-center bg-[repeating-linear-gradient(45deg,rgba(255,255,255,0.09)_0_6px,transparent_6px_12px),repeating-linear-gradient(-45deg,rgba(255,255,255,0.06)_0_6px,transparent_6px_12px)]\">\n <CardBackEmblem size={size} />\n </div>\n </div>\n );\n}\n\nexport interface HoleCardsProps {\n cards: (string | null)[];\n size?: CardSize;\n className?: string;\n}\n\n/** A row of a player's two hole cards. */\nexport function HoleCards({ cards, size = \"lg\", className }: HoleCardsProps) {\n return (\n <div className={cn(\"flex gap-2\", className)}>\n {cards.map((card, i) => (\n <PlayingCard key={i} card={card} size={size} />\n ))}\n </div>\n );\n}\n\nexport interface CommunityCardsProps {\n cards: (string | null)[];\n size?: CardSize;\n className?: string;\n}\n\n/** The five community cards, each dealt with a staggered flip animation. */\nexport function CommunityCards({ cards, size = \"md\", className }: CommunityCardsProps) {\n return (\n <div className={cn(\"flex gap-1.5\", className)}>\n {cards.map((card, i) => (\n <div key={i} className=\"animate-card-deal\" style={{ animationDelay: `${i * 90}ms` }}>\n <PlayingCard card={card} size={size} />\n </div>\n ))}\n </div>\n );\n}\n","import type { ReactNode } from \"react\";\nimport { cn } from \"../cn\";\n\nexport interface SeatProps {\n /** Turn/active highlight (generic). */\n isActive?: boolean;\n /** Winner highlight (generic). */\n isWinner?: boolean;\n /** Dimmed, e.g. folded or out (generic). */\n isDimmed?: boolean;\n /** Optional click handler (e.g. \"sit here\"). */\n onClick?: () => void;\n\n /** Slot: top row — name / badges. */\n header?: ReactNode;\n /** Slot: top-right floating element — e.g. a dealer button. */\n badge?: ReactNode;\n /** Slot: center content — 0..N cards, avatar, anything. */\n children?: ReactNode;\n /** Slot: bottom row — stack / bet / status. */\n footer?: ReactNode;\n /** Slot: overlay — e.g. an \"All-in\" label. */\n overlay?: ReactNode;\n\n width?: number;\n height?: number;\n className?: string;\n}\n\n/**\n * A neutral seat: a frame with generic visual states and named slots.\n * It knows nothing about any particular game — put whatever you want into\n * `header` / `children` / `footer` / `badge` / `overlay`.\n */\nexport function Seat({\n isActive = false,\n isWinner = false,\n isDimmed = false,\n onClick,\n header,\n badge,\n children,\n footer,\n overlay,\n width = 160,\n height = 176,\n className,\n}: SeatProps) {\n return (\n <div className={cn(\"relative\", className)} style={{ width, height }}>\n <div\n role={onClick ? \"button\" : undefined}\n tabIndex={onClick ? 0 : undefined}\n onClick={onClick}\n onKeyDown={\n onClick\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") onClick();\n }\n : undefined\n }\n className={cn(\n \"flex h-full w-full flex-col items-center justify-between gap-1 rounded-2xl border bg-neutral-900/80 p-2 text-neutral-100 backdrop-blur transition\",\n isActive ? \"border-cyan-400 shadow-[0_0_18px_rgba(34,211,238,0.55)]\" : \"border-neutral-700\",\n isWinner && \"border-amber-400 shadow-[0_0_18px_rgba(251,191,36,0.6)]\",\n isDimmed && \"opacity-50\",\n onClick && \"cursor-pointer hover:border-neutral-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400\",\n )}\n >\n {header !== undefined && (\n <div className=\"flex w-full items-center justify-between gap-1 text-[11px]\">{header}</div>\n )}\n <div className=\"flex flex-1 items-center justify-center\">{children}</div>\n {footer !== undefined && (\n <div className=\"flex w-full items-center justify-between text-[11px] text-neutral-300\">{footer}</div>\n )}\n </div>\n\n {badge !== undefined && <div className=\"absolute -top-1 -right-1\">{badge}</div>}\n {overlay !== undefined && (\n <div className=\"pointer-events-none absolute inset-x-0 top-1/2 -translate-y-1/2 text-center\">{overlay}</div>\n )}\n </div>\n );\n}\n","import { cn } from \"../cn\";\nimport { formatBB } from \"../format\";\n\nexport interface PotProps {\n amount: number;\n /** Optional label, e.g. \"Main\" / \"Side\". */\n label?: string;\n /** Formatter for the amount; defaults to Big-Blind formatting. */\n format?: (amount: number) => string;\n className?: string;\n}\n\n/** A small, standalone pot pill. Optional — the table does not require it. */\nexport function Pot({ amount, label, format = formatBB, className }: PotProps) {\n return (\n <div className={cn(\"flex items-center gap-2 font-mono text-sm text-amber-400\", className)}>\n {label && <span className=\"text-neutral-400\">{label}</span>}\n <span className=\"whitespace-nowrap rounded-full border border-amber-400/50 bg-neutral-900/80 px-3 py-1 shadow-inner\">\n {format(amount)}\n </span>\n </div>\n );\n}\n","import { useRef, useLayoutEffect, useState, useMemo, type ReactNode } from \"react\";\nimport { cn } from \"../cn\";\n\nexport interface CardTableProps {\n /** Number of seats (2–10). Default 6. */\n seatCount?: number;\n /** Optional per-seat content. Length should equal `seatCount`. */\n seats?: (ReactNode | null)[];\n /** Alternative to `seats`: render a seat by index. */\n renderSeat?: (index: number, isYou: boolean) => ReactNode;\n /** Which seat belongs to the viewer; rotates that seat to the bottom. */\n yourSeatIndex?: number | null;\n /** Optional content in the middle of the table (cards, pot, anything). */\n center?: ReactNode;\n /** Seat frame size used for layout. */\n seatWidth?: number;\n seatHeight?: number;\n className?: string;\n}\n\nconst DESIGN_W = 1200;\nconst DESIGN_H = 900;\nconst MARGIN_X = 164;\nconst MARGIN_Y = 196;\nconst TABLE_RX = DESIGN_W / 2 - MARGIN_X;\nconst TABLE_RY = DESIGN_H / 2 - MARGIN_Y;\nconst FELT_GAP = 11;\nconst BET_SCALE = 0.72;\nconst BET_RX = TABLE_RX * BET_SCALE;\nconst BET_RY = TABLE_RY * BET_SCALE;\n\ninterface SeatLayout {\n x: number;\n y: number;\n index: number;\n isYou: boolean;\n}\n\nfunction getEllipsePoint(cx: number, cy: number, rx: number, ry: number, angleDeg: number) {\n const rad = (angleDeg * Math.PI) / 180;\n return { x: cx + rx * Math.cos(rad), y: cy + ry * Math.sin(rad) };\n}\n\nfunction calculateSeatLayouts(\n seatCount: number,\n yourSeatIndex: number | null,\n seatWidth: number,\n seatHeight: number,\n): SeatLayout[] {\n const cx = DESIGN_W / 2;\n const cy = DESIGN_H / 2;\n const ringX = TABLE_RX - FELT_GAP + seatWidth / 2 + 6;\n const ringY = TABLE_RY - FELT_GAP + seatHeight / 2 + 6;\n\n // Even angular distribution, first seat at the top (-90°).\n const step = 360 / seatCount;\n const angleOf = (i: number) => -90 + i * step;\n\n // Rotate so the viewer's seat sits at the bottom (90°).\n const rotationOffset = yourSeatIndex !== null ? 90 - angleOf(yourSeatIndex) : 0;\n\n const layouts: SeatLayout[] = [];\n for (let i = 0; i < seatCount; i++) {\n const pt = getEllipsePoint(cx, cy, ringX, ringY, angleOf(i) + rotationOffset);\n layouts.push({\n x: pt.x - seatWidth / 2,\n y: pt.y - seatHeight / 2,\n index: i,\n isYou: i === yourSeatIndex,\n });\n }\n return layouts;\n}\n\n/**\n * A responsive, game-agnostic card table. It scales a fixed 1200x900 design\n * onto its container and arranges `seatCount` seats around the felt. Purely\n * presentational: supply seat content and an optional `center` slot.\n */\nexport function CardTable({\n seatCount = 6,\n seats,\n renderSeat,\n yourSeatIndex = null,\n center,\n seatWidth = 160,\n seatHeight = 176,\n className,\n}: CardTableProps) {\n const count = Math.max(2, Math.min(10, Math.floor(seatCount)));\n const outerRef = useRef<HTMLDivElement>(null);\n const [scale, setScale] = useState(1);\n\n const layouts = useMemo(\n () => calculateSeatLayouts(count, yourSeatIndex, seatWidth, seatHeight),\n [count, yourSeatIndex, seatWidth, seatHeight],\n );\n\n useLayoutEffect(() => {\n const el = outerRef.current;\n if (!el) return;\n const compute = () => {\n const w = el.clientWidth;\n const h = el.clientHeight;\n if (!w || !h) return;\n setScale(Math.min(w / DESIGN_W, h / DESIGN_H));\n };\n compute();\n const ro = new ResizeObserver(compute);\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n const tableFrameInset = { inset: `${MARGIN_Y}px ${MARGIN_X}px` };\n const feltInset = { inset: `${MARGIN_Y + FELT_GAP}px ${MARGIN_X + FELT_GAP}px` };\n const rimInset = { inset: `${MARGIN_Y + 6}px ${MARGIN_X + 6}px` };\n const betLineInset = {\n inset: `${MARGIN_Y + (TABLE_RY - BET_RY)}px ${MARGIN_X + (TABLE_RX - BET_RX)}px`,\n };\n\n return (\n <div\n ref={outerRef}\n className={cn(\"relative flex h-full w-full min-h-0 items-center justify-center overflow-hidden\", className)}\n >\n <div style={{ width: DESIGN_W * scale, height: DESIGN_H * scale }}>\n <div\n className=\"relative\"\n style={{ width: DESIGN_W, height: DESIGN_H, transform: `scale(${scale})`, transformOrigin: \"top left\" }}\n >\n <div\n className=\"absolute -inset-6 rounded-[50%] bg-[radial-gradient(closest-side,rgba(240,192,74,0.16),transparent_72%)] blur-3xl\"\n aria-hidden=\"true\"\n />\n\n {/* wooden rail */}\n <div\n className=\"absolute rounded-[50%] bg-gradient-to-b from-[#a5744a] via-[#6b3f22] to-[#38200f] shadow-[0_26px_70px_-12px_rgba(0,0,0,0.85),inset_0_3px_0_rgba(255,255,255,0.20),inset_0_-6px_16px_rgba(0,0,0,0.5)]\"\n style={tableFrameInset}\n aria-hidden=\"true\"\n />\n\n {/* felt */}\n <div\n className=\"absolute rounded-[50%] bg-[radial-gradient(ellipse_at_50%_32%,#23804b,#0f4d2e_55%,#093b22)] shadow-[inset_0_0_90px_rgba(0,0,0,0.55),inset_0_3px_10px_rgba(255,255,255,0.06)]\"\n style={feltInset}\n aria-hidden=\"true\"\n />\n\n {/* betting line */}\n <div\n className=\"absolute rounded-[50%] border-2 border-white/10 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.25)]\"\n style={betLineInset}\n aria-hidden=\"true\"\n />\n\n {/* center suit watermark */}\n <div className=\"absolute flex items-center justify-center pointer-events-none\" style={feltInset} aria-hidden=\"true\">\n <span className=\"text-white/[0.05] text-[110px] font-black tracking-[0.2em] select-none leading-none\">\n ♠♥♦♣\n </span>\n </div>\n\n {/* center slot (optional) */}\n {center !== undefined && (\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center\">\n {center}\n </div>\n )}\n\n {/* seats */}\n <div className=\"absolute inset-0 z-20\">\n {layouts.map(({ x, y, index, isYou }) => {\n const content = seats ? (seats[index] ?? null) : renderSeat ? renderSeat(index, isYou) : null;\n if (content === null) return null;\n return (\n <div key={index} className=\"absolute\" style={{ left: `${x}px`, top: `${y}px` }}>\n {content}\n </div>\n );\n })}\n </div>\n\n {/* outer rim highlight */}\n <div className=\"absolute rounded-[50%] border border-white/10 pointer-events-none\" style={rimInset} aria-hidden=\"true\" />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["jsx","jsxs","jsx","jsxs","jsx","jsxs"]}
|
|
1
|
+
{"version":3,"sources":["../src/cn.ts","../src/format.ts","../src/seat.ts","../src/components/PlayingCard.tsx","../src/components/Seat.tsx","../src/components/Pot.tsx","../src/components/CardTable.tsx"],"sourcesContent":["import { twMerge } from \"tailwind-merge\";\n\n/** Join class names and resolve conflicting Tailwind utilities (last wins). */\nexport function cn(...parts: Array<string | false | null | undefined>): string {\n return twMerge(parts.filter(Boolean).join(\" \"));\n}\n","/** Big Blind value used for stack display. */\nexport const BB = 20;\n\n/** Format a chip amount as a Big-Blind string, e.g. 1234 -> \"61.7 BB\". */\nexport function formatBB(amount: number): string {\n const bb = amount / BB;\n return `${Number.isInteger(bb) ? bb : bb.toFixed(1)} BB`;\n}\n","/**\n * Seat-ring helpers. Pure functions — they contain no game-specific rules.\n */\n\nexport interface NextSeatOptions {\n /** +1 for clockwise, -1 for counter-clockwise. Default +1. */\n dir?: 1 | -1;\n /** Return true to skip a seat (e.g. folded / empty / out). */\n skip?: (index: number) => boolean;\n}\n\n/**\n * Return the index of the next seat on the ring after `current`, skipping any\n * seat matched by `opts.skip`. Wraps around. Returns `current` if every other\n * seat is skipped.\n */\nexport function nextSeatIndex(current: number, seatCount: number, opts: NextSeatOptions = {}): number {\n const n = Math.max(1, Math.floor(seatCount));\n const dir = opts.dir ?? 1;\n const skip = opts.skip;\n const start = ((Math.floor(current) % n) + n) % n;\n\n let i = start;\n for (let step = 0; step < n - 1; step++) {\n i = (i + dir + n) % n;\n if (!skip || !skip(i)) return i;\n }\n return start;\n}\n","import { cn } from \"../cn\";\n\nexport type CardSize = \"xs\" | \"sm\" | \"md\" | \"lg\";\n\nconst SIZE_CLASSES: Record<CardSize, string> = {\n xs: \"w-8 h-11\",\n sm: \"w-10 h-14\",\n md: \"w-16 h-22\",\n lg: \"w-24 h-32\",\n};\n\nconst CORNER_RANK: Record<CardSize, string> = {\n xs: \"text-[11px]\",\n sm: \"text-sm\",\n md: \"text-lg\",\n lg: \"text-2xl\",\n};\nconst CORNER_SUIT: Record<CardSize, string> = {\n xs: \"text-[9px]\",\n sm: \"text-xs\",\n md: \"text-sm\",\n lg: \"text-base\",\n};\nconst CENTER_SUIT: Record<CardSize, string> = {\n xs: \"text-base\",\n sm: \"text-xl\",\n md: \"text-3xl\",\n lg: \"text-5xl\",\n};\nconst BACK_EMBLEM_SIZE: Record<CardSize, number> = {\n xs: 14,\n sm: 18,\n md: 28,\n lg: 42,\n};\n\n/** Parse a compact card code like \"As\", \"10h\", \"kd\" into rank + suit. */\nexport function parseCard(card: string): { rank: string; suit: string } {\n if (!card || card.length < 2) return { rank: \"\", suit: \"\" };\n return { rank: card.slice(0, -1), suit: card.slice(-1) };\n}\n\nexport function getSuitSymbol(suit: string): string {\n switch (suit) {\n case \"s\": return \"♠\";\n case \"h\": return \"♥\";\n case \"d\": return \"♦\";\n case \"c\": return \"♣\";\n default: return suit;\n }\n}\n\nfunction getSuitClass(suit: string): string {\n switch (suit) {\n case \"s\":\n case \"c\":\n return \"text-neutral-900\";\n case \"h\":\n case \"d\":\n return \"text-red-600\";\n default:\n return \"\";\n }\n}\n\nexport interface PlayingCardProps {\n /** Compact card code (\"As\", \"10h\") or null to render a face-down card back. */\n card: string | null;\n size?: CardSize;\n className?: string;\n}\n\n/**\n * A single playing card. Size is driven by the `size` prop (never viewport\n * breakpoints) so cards scale predictably inside any container.\n */\nexport function PlayingCard({ card, size = \"md\", className }: PlayingCardProps) {\n if (!card) return <CardBack size={size} className={className} />;\n\n const { rank, suit } = parseCard(card);\n const suitSymbol = getSuitSymbol(suit);\n const suitClass = getSuitClass(suit);\n const showCenter = size === \"md\" || size === \"lg\";\n\n return (\n <div\n className={cn(\n SIZE_CLASSES[size],\n \"relative bg-gradient-to-br from-white via-white to-neutral-100\",\n \"rounded-md border border-neutral-300/90 overflow-hidden\",\n \"shadow-[0_2px_6px_rgba(0,0,0,0.45),inset_0_1px_0_rgba(255,255,255,0.9)]\",\n className,\n )}\n style={{ aspectRatio: \"10/14\" }}\n >\n <div className={cn(\"absolute top-[3px] left-[5px] flex flex-col items-center leading-none\", suitClass)}>\n <span className={cn(CORNER_RANK[size], \"font-bold tracking-tight\")}>{rank}</span>\n <span className={cn(CORNER_SUIT[size], \"-mt-[1px]\")}>{suitSymbol}</span>\n </div>\n <div className={cn(\"absolute bottom-[3px] right-[5px] flex flex-col items-center leading-none rotate-180\", suitClass)}>\n <span className={cn(CORNER_RANK[size], \"font-bold tracking-tight\")}>{rank}</span>\n <span className={cn(CORNER_SUIT[size], \"-mt-[1px]\")}>{suitSymbol}</span>\n </div>\n {showCenter && (\n <div className={cn(\"absolute inset-0 flex items-center justify-center opacity-90\", suitClass)}>\n <span className={CENTER_SUIT[size]}>{suitSymbol}</span>\n </div>\n )}\n </div>\n );\n}\n\nexport interface CardBackProps {\n size?: CardSize;\n className?: string;\n}\n\nfunction CardBackEmblem({ size }: { size: CardSize }) {\n const s = BACK_EMBLEM_SIZE[size];\n return (\n <svg width={s} height={s} viewBox=\"0 0 40 40\" className=\"drop-shadow-[0_1px_2px_rgba(0,0,0,0.6)]\" aria-hidden=\"true\">\n <g fill=\"#f0c04a\" opacity=\"0.92\">\n <ellipse cx=\"20\" cy=\"11\" rx=\"4\" ry=\"7\" />\n <ellipse cx=\"20\" cy=\"29\" rx=\"4\" ry=\"7\" />\n <ellipse cx=\"11\" cy=\"20\" rx=\"7\" ry=\"4\" />\n <ellipse cx=\"29\" cy=\"20\" rx=\"7\" ry=\"4\" />\n </g>\n <circle cx=\"20\" cy=\"20\" r=\"3.2\" fill=\"#5a0c18\" />\n </svg>\n );\n}\n\n/** The face-down card back (dark red + gold rosette). */\nexport function CardBack({ size = \"md\", className }: CardBackProps) {\n return (\n <div\n className={cn(\n SIZE_CLASSES[size],\n \"relative rounded-md p-[3px] overflow-hidden\",\n \"bg-gradient-to-br from-[#7a1020] via-[#5a0c18] to-[#3f0810]\",\n \"border-2 border-[#c9a24b]/70 shadow-[0_2px_6px_rgba(0,0,0,0.45)]\",\n className,\n )}\n style={{ aspectRatio: \"10/14\" }}\n >\n <div className=\"h-full w-full rounded-[4px] border border-white/20 flex items-center justify-center bg-[repeating-linear-gradient(45deg,rgba(255,255,255,0.09)_0_6px,transparent_6px_12px),repeating-linear-gradient(-45deg,rgba(255,255,255,0.06)_0_6px,transparent_6px_12px)]\">\n <CardBackEmblem size={size} />\n </div>\n </div>\n );\n}\n\nexport interface HoleCardsProps {\n cards: (string | null)[];\n size?: CardSize;\n className?: string;\n}\n\n/** A row of a player's two hole cards. */\nexport function HoleCards({ cards, size = \"lg\", className }: HoleCardsProps) {\n return (\n <div className={cn(\"flex gap-2\", className)}>\n {cards.map((card, i) => (\n <PlayingCard key={i} card={card} size={size} />\n ))}\n </div>\n );\n}\n\nexport interface CommunityCardsProps {\n cards: (string | null)[];\n size?: CardSize;\n className?: string;\n}\n\n/** The five community cards, each dealt with a staggered flip animation. */\nexport function CommunityCards({ cards, size = \"md\", className }: CommunityCardsProps) {\n return (\n <div className={cn(\"flex gap-1.5\", className)}>\n {cards.map((card, i) => (\n <div key={i} className=\"animate-card-deal\" style={{ animationDelay: `${i * 90}ms` }}>\n <PlayingCard card={card} size={size} />\n </div>\n ))}\n </div>\n );\n}\n","import type { CSSProperties, ReactNode } from \"react\";\nimport { cn } from \"../cn\";\n\nexport interface SeatProps {\n /** Turn/active highlight (generic). */\n isActive?: boolean;\n /** Winner highlight (generic). */\n isWinner?: boolean;\n /** Dimmed, e.g. folded or out (generic). */\n isDimmed?: boolean;\n /** Optional click handler (e.g. \"sit here\"). */\n onClick?: () => void;\n\n /** Slot: top row inside the frame — name / badges. */\n header?: ReactNode;\n /** Slot: center content — 0..N cards, avatar, anything. */\n children?: ReactNode;\n /** Slot: bottom row inside the frame — stack / status. */\n footer?: ReactNode;\n /** Slot: floating element at the frame's top-right (legacy alias of topRight). */\n badge?: ReactNode;\n /** Slot: overlay centered over the frame. */\n overlay?: ReactNode;\n\n /** Slot: floating content above the frame, centered (e.g. an action bubble). */\n above?: ReactNode;\n /** Slot: content below the frame, centered (e.g. countdown / bet pill). */\n below?: ReactNode;\n /** Slot: floating content at the frame's top-left corner (e.g. a crown). */\n topLeft?: ReactNode;\n /** Slot: floating content at the frame's top-right corner (e.g. a kick button). */\n topRight?: ReactNode;\n /** Slot: floating content centered above the top edge (e.g. D/SB/BB badges). */\n topCenter?: ReactNode;\n\n width?: number;\n height?: number;\n className?: string;\n /** Extra classes for the inner frame, to override visuals. */\n frameClassName?: string;\n /** Inline styles for the inner frame. */\n frameStyle?: CSSProperties;\n}\n\n/**\n * A neutral seat: a frame with generic visual states and named slots. Put\n * whatever you want into `header` / `children` / `footer`, and use\n * `above` / `below` / `topLeft` / `topRight` / `topCenter` / `overlay` for\n * floating bits. It knows nothing about any particular game.\n */\nexport function Seat({\n isActive = false,\n isWinner = false,\n isDimmed = false,\n onClick,\n header,\n children,\n footer,\n badge,\n overlay,\n above,\n below,\n topLeft,\n topRight,\n topCenter,\n width = 160,\n height = 176,\n className,\n frameClassName,\n frameStyle,\n}: SeatProps) {\n return (\n <div className={cn(\"relative flex flex-col items-center\", className)} style={{ width }}>\n {topCenter !== undefined && (\n <div className=\"absolute -top-2 left-1/2 z-20 flex -translate-x-1/2 gap-1\">{topCenter}</div>\n )}\n {topLeft !== undefined && <div className=\"absolute -top-3 -left-2 z-40\">{topLeft}</div>}\n {topRight !== undefined && <div className=\"absolute -top-2 -right-2 z-40\">{topRight}</div>}\n {above !== undefined && (\n <div className=\"pointer-events-none absolute -top-12 left-1/2 z-30 -translate-x-1/2\">{above}</div>\n )}\n\n <div\n role={onClick ? \"button\" : undefined}\n tabIndex={onClick ? 0 : undefined}\n onClick={onClick}\n onKeyDown={\n onClick\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") onClick();\n }\n : undefined\n }\n style={{ width, height, ...frameStyle }}\n className={cn(\n \"relative flex flex-col items-center justify-between gap-1 rounded-2xl border bg-neutral-900/80 p-2 text-neutral-100 transition\",\n isActive ? \"border-cyan-400 shadow-[0_0_18px_rgba(34,211,238,0.55)]\" : \"border-neutral-700\",\n isWinner && \"border-amber-400 shadow-[0_0_18px_rgba(251,191,36,0.6)]\",\n isDimmed && \"opacity-50\",\n onClick && \"cursor-pointer hover:border-neutral-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400\",\n frameClassName,\n )}\n >\n {header !== undefined && (\n <div className=\"flex w-full shrink-0 items-center justify-between gap-1 text-[11px]\">{header}</div>\n )}\n <div className=\"flex min-h-0 flex-1 items-center justify-center\">{children}</div>\n {footer !== undefined && (\n <div className=\"flex w-full shrink-0 items-center justify-between text-[11px] text-neutral-300\">{footer}</div>\n )}\n\n {overlay !== undefined && (\n <div className=\"pointer-events-none absolute inset-x-0 top-1/2 -translate-y-1/2 text-center\">{overlay}</div>\n )}\n </div>\n\n {badge !== undefined && <div className=\"absolute top-0 right-0 z-40\">{badge}</div>}\n {below !== undefined && <div className=\"mt-1 flex flex-col items-center gap-1\">{below}</div>}\n </div>\n );\n}\n","import { cn } from \"../cn\";\nimport { formatBB } from \"../format\";\n\nexport interface PotProps {\n amount: number;\n /** Optional label, e.g. \"Main\" / \"Side\". */\n label?: string;\n /** Formatter for the amount; defaults to Big-Blind formatting. */\n format?: (amount: number) => string;\n className?: string;\n}\n\n/** A small, standalone pot pill. Optional — the table does not require it. */\nexport function Pot({ amount, label, format = formatBB, className }: PotProps) {\n return (\n <div className={cn(\"flex items-center gap-2 font-mono text-sm text-amber-400\", className)}>\n {label && <span className=\"text-neutral-400\">{label}</span>}\n <span className=\"whitespace-nowrap rounded-full border border-amber-400/50 bg-neutral-900/80 px-3 py-1 shadow-inner\">\n {format(amount)}\n </span>\n </div>\n );\n}\n","import { useRef, useLayoutEffect, useState, useMemo, type ReactNode } from \"react\";\nimport { cn } from \"../cn\";\n\nexport interface CardTableProps {\n /** Number of seats (2–10). Default 6. */\n seatCount?: number;\n /** Optional per-seat content. Length should equal `seatCount`. */\n seats?: (ReactNode | null)[];\n /** Alternative to `seats`: render a seat by index. */\n renderSeat?: (index: number, isYou: boolean) => ReactNode;\n /** Which seat belongs to the viewer; rotates that seat to the bottom. */\n yourSeatIndex?: number | null;\n /** Optional content in the middle of the table (cards, pot, anything). */\n center?: ReactNode;\n /** Seat frame size used for layout. */\n seatWidth?: number;\n seatHeight?: number;\n className?: string;\n}\n\nconst DESIGN_W = 1200;\nconst DESIGN_H = 900;\nconst MARGIN_X = 164;\nconst MARGIN_Y = 196;\nconst TABLE_RX = DESIGN_W / 2 - MARGIN_X;\nconst TABLE_RY = DESIGN_H / 2 - MARGIN_Y;\nconst FELT_GAP = 11;\nconst BET_SCALE = 0.72;\nconst BET_RX = TABLE_RX * BET_SCALE;\nconst BET_RY = TABLE_RY * BET_SCALE;\n\ninterface SeatLayout {\n x: number;\n y: number;\n index: number;\n isYou: boolean;\n}\n\nfunction getEllipsePoint(cx: number, cy: number, rx: number, ry: number, angleDeg: number) {\n const rad = (angleDeg * Math.PI) / 180;\n return { x: cx + rx * Math.cos(rad), y: cy + ry * Math.sin(rad) };\n}\n\nfunction calculateSeatLayouts(\n seatCount: number,\n yourSeatIndex: number | null,\n seatWidth: number,\n seatHeight: number,\n): SeatLayout[] {\n const cx = DESIGN_W / 2;\n const cy = DESIGN_H / 2;\n const ringX = TABLE_RX - FELT_GAP + seatWidth / 2 + 6;\n const ringY = TABLE_RY - FELT_GAP + seatHeight / 2 + 6;\n\n // Even angular distribution, first seat at the top (-90°).\n const step = 360 / seatCount;\n const angleOf = (i: number) => -90 + i * step;\n\n // Rotate so the viewer's seat sits at the bottom (90°).\n const rotationOffset = yourSeatIndex !== null ? 90 - angleOf(yourSeatIndex) : 0;\n\n const layouts: SeatLayout[] = [];\n for (let i = 0; i < seatCount; i++) {\n const pt = getEllipsePoint(cx, cy, ringX, ringY, angleOf(i) + rotationOffset);\n layouts.push({\n x: pt.x - seatWidth / 2,\n y: pt.y - seatHeight / 2,\n index: i,\n isYou: i === yourSeatIndex,\n });\n }\n return layouts;\n}\n\n/**\n * A responsive, game-agnostic card table. It scales a fixed 1200x900 design\n * onto its container and arranges `seatCount` seats around the felt. Purely\n * presentational: supply seat content and an optional `center` slot.\n */\nexport function CardTable({\n seatCount = 6,\n seats,\n renderSeat,\n yourSeatIndex = null,\n center,\n seatWidth = 160,\n seatHeight = 176,\n className,\n}: CardTableProps) {\n const count = Math.max(2, Math.min(10, Math.floor(seatCount)));\n const outerRef = useRef<HTMLDivElement>(null);\n const [scale, setScale] = useState(1);\n\n const layouts = useMemo(\n () => calculateSeatLayouts(count, yourSeatIndex, seatWidth, seatHeight),\n [count, yourSeatIndex, seatWidth, seatHeight],\n );\n\n useLayoutEffect(() => {\n const el = outerRef.current;\n if (!el) return;\n const compute = () => {\n const w = el.clientWidth;\n const h = el.clientHeight;\n if (!w || !h) return;\n setScale(Math.min(w / DESIGN_W, h / DESIGN_H));\n };\n compute();\n const ro = new ResizeObserver(compute);\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n const tableFrameInset = { inset: `${MARGIN_Y}px ${MARGIN_X}px` };\n const feltInset = { inset: `${MARGIN_Y + FELT_GAP}px ${MARGIN_X + FELT_GAP}px` };\n const rimInset = { inset: `${MARGIN_Y + 6}px ${MARGIN_X + 6}px` };\n const betLineInset = {\n inset: `${MARGIN_Y + (TABLE_RY - BET_RY)}px ${MARGIN_X + (TABLE_RX - BET_RX)}px`,\n };\n\n return (\n <div\n ref={outerRef}\n className={cn(\"relative flex h-full w-full min-h-0 items-center justify-center overflow-hidden\", className)}\n >\n <div style={{ width: DESIGN_W * scale, height: DESIGN_H * scale }}>\n <div\n className=\"relative\"\n style={{ width: DESIGN_W, height: DESIGN_H, transform: `scale(${scale})`, transformOrigin: \"top left\" }}\n >\n <div\n className=\"absolute -inset-6 rounded-[50%] bg-[radial-gradient(closest-side,rgba(240,192,74,0.16),transparent_72%)] blur-3xl\"\n aria-hidden=\"true\"\n />\n\n {/* wooden rail */}\n <div\n className=\"absolute rounded-[50%] bg-gradient-to-b from-[#a5744a] via-[#6b3f22] to-[#38200f] shadow-[0_26px_70px_-12px_rgba(0,0,0,0.85),inset_0_3px_0_rgba(255,255,255,0.20),inset_0_-6px_16px_rgba(0,0,0,0.5)]\"\n style={tableFrameInset}\n aria-hidden=\"true\"\n />\n\n {/* felt */}\n <div\n className=\"absolute rounded-[50%] bg-[radial-gradient(ellipse_at_50%_32%,#23804b,#0f4d2e_55%,#093b22)] shadow-[inset_0_0_90px_rgba(0,0,0,0.55),inset_0_3px_10px_rgba(255,255,255,0.06)]\"\n style={feltInset}\n aria-hidden=\"true\"\n />\n\n {/* betting line */}\n <div\n className=\"absolute rounded-[50%] border-2 border-white/10 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.25)]\"\n style={betLineInset}\n aria-hidden=\"true\"\n />\n\n {/* center suit watermark */}\n <div className=\"absolute flex items-center justify-center pointer-events-none\" style={feltInset} aria-hidden=\"true\">\n <span className=\"text-white/[0.05] text-[110px] font-black tracking-[0.2em] select-none leading-none\">\n ♠♥♦♣\n </span>\n </div>\n\n {/* center slot (optional) */}\n {center !== undefined && (\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center\">\n {center}\n </div>\n )}\n\n {/* seats */}\n <div className=\"absolute inset-0 z-20\">\n {layouts.map(({ x, y, index, isYou }) => {\n const content = seats ? (seats[index] ?? null) : renderSeat ? renderSeat(index, isYou) : null;\n if (content === null) return null;\n return (\n <div key={index} className=\"absolute\" style={{ left: `${x}px`, top: `${y}px` }}>\n {content}\n </div>\n );\n })}\n </div>\n\n {/* outer rim highlight */}\n <div className=\"absolute rounded-[50%] border border-white/10 pointer-events-none\" style={rimInset} aria-hidden=\"true\" />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["jsx","jsxs","jsx","jsxs","jsx","jsxs"]}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@stargazeryan/poker-ui",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.3.0",
|
|
4
4
|
"description": "Themable React + Tailwind CSS v4 components for poker tables and playing cards.",
|
|
5
5
|
"license": "MIT",
|
|
6
6
|
"author": "Ryan",
|
|
@@ -10,24 +10,48 @@
|
|
|
10
10
|
"url": "git+https://github.com/stargazeryan/poker-ui.git"
|
|
11
11
|
},
|
|
12
12
|
"type": "module",
|
|
13
|
-
"sideEffects": [
|
|
14
|
-
|
|
13
|
+
"sideEffects": [
|
|
14
|
+
"**/*.css"
|
|
15
|
+
],
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"src/styles.css",
|
|
19
|
+
"README.md",
|
|
20
|
+
"LICENSE"
|
|
21
|
+
],
|
|
15
22
|
"main": "./dist/index.cjs",
|
|
16
23
|
"module": "./dist/index.js",
|
|
17
24
|
"types": "./dist/index.d.ts",
|
|
18
25
|
"exports": {
|
|
19
|
-
".": {
|
|
26
|
+
".": {
|
|
27
|
+
"types": "./dist/index.d.ts",
|
|
28
|
+
"import": "./dist/index.js",
|
|
29
|
+
"require": "./dist/index.cjs"
|
|
30
|
+
},
|
|
20
31
|
"./styles.css": "./src/styles.css"
|
|
21
32
|
},
|
|
22
|
-
"publishConfig": {
|
|
33
|
+
"publishConfig": {
|
|
34
|
+
"access": "public"
|
|
35
|
+
},
|
|
23
36
|
"scripts": {
|
|
24
37
|
"build": "tsup",
|
|
25
38
|
"typecheck": "tsc --noEmit",
|
|
26
39
|
"demo": "vite --config demo/vite.config.ts",
|
|
27
40
|
"demo:build": "vite build --config demo/vite.config.ts"
|
|
28
41
|
},
|
|
29
|
-
"keywords": [
|
|
30
|
-
|
|
42
|
+
"keywords": [
|
|
43
|
+
"poker",
|
|
44
|
+
"playing-cards",
|
|
45
|
+
"tailwindcss",
|
|
46
|
+
"react",
|
|
47
|
+
"component",
|
|
48
|
+
"card",
|
|
49
|
+
"table"
|
|
50
|
+
],
|
|
51
|
+
"peerDependencies": {
|
|
52
|
+
"react": ">=18",
|
|
53
|
+
"react-dom": ">=18"
|
|
54
|
+
},
|
|
31
55
|
"devDependencies": {
|
|
32
56
|
"@tailwindcss/vite": "^4.3.3",
|
|
33
57
|
"@types/react": "^19.2.0",
|
|
@@ -39,5 +63,8 @@
|
|
|
39
63
|
"tsup": "^8.3.0",
|
|
40
64
|
"typescript": "^5.6.0",
|
|
41
65
|
"vite": "^6.0.0"
|
|
66
|
+
},
|
|
67
|
+
"dependencies": {
|
|
68
|
+
"tailwind-merge": "^3.7.0"
|
|
42
69
|
}
|
|
43
70
|
}
|