@stargazeryan/poker-ui 0.1.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/README.md CHANGED
@@ -1,11 +1,17 @@
1
1
  # @stargazeryan/poker-ui
2
2
 
3
- Themable **React + Tailwind CSS v4** components for poker tables and playing cards.
3
+ Game-agnostic **React + Tailwind CSS v4** card-table UI: tables, seats, and
4
+ playing cards. Seats are neutral frames with **slots**, so you decide what goes
5
+ in them (0 cards, N cards, avatars, anything). The table supports **2–10 seats**
6
+ and an optional center slot.
4
7
 
5
- - 🃏 `PlayingCard` / `CardBack` / `HoleCards` / `CommunityCards`
6
- - 🎴 `PokerTable` — a responsive, scale-to-fit oval table with six seats
7
- - 💺 `Seat` — presentational, fully controlled by a view-model
8
- - 🎨 Theme tokens via Tailwind v4 `@theme`
8
+ - 🎴 `PlayingCard` / `CardBack` / `HoleCards` / `CommunityCards`
9
+ - 🎯 `CardTable` — responsive, scale-to-fit table with `seatCount` seats
10
+ - 💺 `Seat` — a neutral seat frame with slots + generic states
11
+ - 🪙 `Pot` — an optional pot pill (not required by the table)
12
+ - 🔁 `nextSeatIndex()` — pure helper for "next seat on the ring"
13
+
14
+ > Formerly `PokerTable`; kept as a back-compat alias for `CardTable`.
9
15
 
10
16
  ## Install
11
17
 
@@ -13,7 +19,7 @@ Themable **React + Tailwind CSS v4** components for poker tables and playing car
13
19
  npm install @stargazeryan/poker-ui
14
20
  ```
15
21
 
16
- Peer requirements: `react` >= 18, `react-dom` >= 18, and Tailwind CSS v4.
22
+ Peer requirements: `react` >= 18, `react-dom` >= 18, Tailwind CSS v4.
17
23
 
18
24
  ## Setup (Tailwind v4) — important
19
25
 
@@ -35,48 +41,73 @@ theme CSS:
35
41
  ## Usage
36
42
 
37
43
  ```tsx
38
- import { PokerTable, PlayingCard, CardBack, type SeatView } from "@stargazeryan/poker-ui";
39
-
40
- const seats: (SeatView | null)[] = [
41
- { nickname: "You", stack: 2000, holeCards: ["As", "Kh"], isYou: true, isDealer: true },
42
- { nickname: "Bot", stack: 1500, holeCards: [null, null] },
44
+ import { CardTable, Seat, HoleCards, CommunityCards, Pot } from "@stargazeryan/poker-ui";
45
+
46
+ const seats = [
47
+ <Seat key={0} isActive header={<b>You</b>} footer={<span>100 BB</span>}>
48
+ <HoleCards cards={["As", "Kh"]} size="sm" />
49
+ </Seat>,
50
+ <Seat key={1} header={<b>Bot</b>} footer={<span>49 BB</span>}>
51
+ <HoleCards cards={[null, null]} size="sm" />
52
+ </Seat>,
43
53
  null, null, null, null,
44
54
  ];
45
55
 
46
56
  export function Table() {
47
57
  return (
48
58
  <div style={{ height: 640 }}>
49
- <PokerTable
59
+ <CardTable
60
+ seatCount={6}
50
61
  seats={seats}
51
62
  yourSeatIndex={0}
52
- communityCards={["Ah", "Kd", "7c", null, null]}
53
- pot={240}
63
+ center={
64
+ <>
65
+ <CommunityCards cards={["Ah", "Kd", "7c", null, null]} size="lg" />
66
+ <Pot label="Main" amount={240} />
67
+ </>
68
+ }
54
69
  />
55
70
  </div>
56
71
  );
57
72
  }
58
73
  ```
59
74
 
60
- Cards use a compact code: rank + suit, suit as `s | h | d | c` — e.g. `"As"`,
61
- `"10h"`, `"kd"`. Pass `null` for a face-down card back.
75
+ ### 4-seat game with no pot and no community cards (e.g. Big Two)
76
+
77
+ ```tsx
78
+ <CardTable
79
+ seatCount={4}
80
+ yourSeatIndex={2}
81
+ renderSeat={(i) => <Seat header={<b>P{i}</b>}>{/* 0..N cards */}</Seat>}
82
+ />
83
+ ```
62
84
 
63
85
  ## Components
64
86
 
65
- | Component | Purpose |
87
+ | Component | Props |
66
88
  | --- | --- |
67
- | `PlayingCard` | One card (`card`, `size`: `xs \| sm \| md \| lg`) |
68
- | `CardBack` | Face-down card |
69
- | `HoleCards` | A row of hole cards |
70
- | `CommunityCards` | Five community cards with a staggered deal animation |
71
- | `Seat` | Presentational seat (`seat: SeatView \| null`) |
72
- | `PokerTable` | Full table; `seats`, `yourSeatIndex`, `communityCards`, `pot`/`pots`, `seatSize`, `renderSeat` |
89
+ | `CardTable` | `seatCount` (2–10), `seats` or `renderSeat(i, isYou)`, `yourSeatIndex`, `center`, `seatWidth`, `seatHeight` |
90
+ | `Seat` | slots: `header`, `children`, `footer`, `badge`, `overlay`; states: `isActive`, `isWinner`, `isDimmed`; `onClick` |
91
+ | `Pot` | `amount`, `label`, `format` |
92
+ | `PlayingCard` | `card` (`"As"`, `"10h"`, `null` = back), `size`: `xs \| sm \| md \| lg` |
93
+ | `CardBack` / `HoleCards` / `CommunityCards` | `size`, `cards` |
73
94
 
74
- Also exported: `cn`, `formatBB`, `BB`, `parseCard`, `getSuitSymbol`,
75
- and the types `SeatView`, `PokerTableProps`, `PotRow`, `CardSize`.
95
+ Also exported: `nextSeatIndex`, `cn`, `formatBB`, `BB`, `parseCard`,
96
+ `getSuitSymbol`, and the types `SeatProps`, `CardTableProps`, `PotProps`, `CardSize`.
97
+
98
+ ## "Whose turn is next?"
99
+
100
+ Turn order is game logic, not UI. Compute it in your own engine and pass the
101
+ result into `Seat`'s `isActive`. A generic helper is provided:
102
+
103
+ ```ts
104
+ import { nextSeatIndex } from "@stargazeryan/poker-ui";
105
+ const next = nextSeatIndex(current, seatCount, { skip: (i) => players[i].out });
106
+ ```
76
107
 
77
108
  ## Theming
78
109
 
79
- Override the tokens in your own `@theme` block, or pass `className` props:
110
+ Override the tokens in your own `@theme` block, or pass `className`:
80
111
 
81
112
  ```css
82
113
  @theme {
@@ -88,7 +119,7 @@ Override the tokens in your own `@theme` block, or pass `className` props:
88
119
  ## Demo
89
120
 
90
121
  ```bash
91
- npm install
122
+ npm install --include=dev
92
123
  npm run demo # Vite dev server
93
124
  npm run demo:build # static build
94
125
  ```
package/dist/index.cjs CHANGED
@@ -23,21 +23,25 @@ var index_exports = {};
23
23
  __export(index_exports, {
24
24
  BB: () => BB,
25
25
  CardBack: () => CardBack,
26
+ CardTable: () => CardTable,
26
27
  CommunityCards: () => CommunityCards,
27
28
  HoleCards: () => HoleCards,
28
29
  PlayingCard: () => PlayingCard,
29
- PokerTable: () => PokerTable,
30
+ PokerTable: () => CardTable,
31
+ Pot: () => Pot,
30
32
  Seat: () => Seat,
31
33
  cn: () => cn,
32
34
  formatBB: () => formatBB,
33
35
  getSuitSymbol: () => getSuitSymbol,
36
+ nextSeatIndex: () => nextSeatIndex,
34
37
  parseCard: () => parseCard
35
38
  });
36
39
  module.exports = __toCommonJS(index_exports);
37
40
 
38
41
  // src/cn.ts
42
+ var import_tailwind_merge = require("tailwind-merge");
39
43
  function cn(...parts) {
40
- return parts.filter(Boolean).join(" ");
44
+ return (0, import_tailwind_merge.twMerge)(parts.filter(Boolean).join(" "));
41
45
  }
42
46
 
43
47
  // src/format.ts
@@ -47,6 +51,20 @@ function formatBB(amount) {
47
51
  return `${Number.isInteger(bb) ? bb : bb.toFixed(1)} BB`;
48
52
  }
49
53
 
54
+ // src/seat.ts
55
+ function nextSeatIndex(current, seatCount, opts = {}) {
56
+ const n = Math.max(1, Math.floor(seatCount));
57
+ const dir = opts.dir ?? 1;
58
+ const skip = opts.skip;
59
+ const start = (Math.floor(current) % n + n) % n;
60
+ let i = start;
61
+ for (let step = 0; step < n - 1; step++) {
62
+ i = (i + dir + n) % n;
63
+ if (!skip || !skip(i)) return i;
64
+ }
65
+ return start;
66
+ }
67
+
50
68
  // src/components/PlayingCard.tsx
51
69
  var import_jsx_runtime = require("react/jsx-runtime");
52
70
  var SIZE_CLASSES = {
@@ -177,61 +195,75 @@ function CommunityCards({ cards, size = "md", className }) {
177
195
 
178
196
  // src/components/Seat.tsx
179
197
  var import_jsx_runtime2 = require("react/jsx-runtime");
180
- function Seat({ seat, size = "sm", className }) {
181
- if (!seat) {
182
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(
183
- "div",
184
- {
185
- className: cn(
186
- "w-40 h-44 rounded-2xl border-2 border-dashed border-white/10",
187
- "flex items-center justify-center text-white/20 text-xs",
188
- className
189
- ),
190
- children: "Empty seat"
191
- }
192
- );
193
- }
194
- const showCards = !seat.folded && !seat.busted && !!seat.holeCards && seat.holeCards.length > 0;
195
- return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: cn("relative w-40 h-44", className), children: [
196
- seat.actionLabel && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "animate-bubble-pop absolute -top-6 left-1/2 -translate-x-1/2 whitespace-nowrap rounded-full bg-white px-2.5 py-1 text-[11px] font-semibold text-neutral-900 shadow-lg", children: seat.actionLabel }),
198
+ function Seat({
199
+ isActive = false,
200
+ isWinner = false,
201
+ isDimmed = false,
202
+ onClick,
203
+ header,
204
+ children,
205
+ footer,
206
+ badge,
207
+ overlay,
208
+ above,
209
+ below,
210
+ topLeft,
211
+ topRight,
212
+ topCenter,
213
+ width = 160,
214
+ height = 176,
215
+ className,
216
+ frameClassName,
217
+ frameStyle
218
+ }) {
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 }),
197
224
  /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
198
225
  "div",
199
226
  {
227
+ role: onClick ? "button" : void 0,
228
+ tabIndex: onClick ? 0 : void 0,
229
+ onClick,
230
+ onKeyDown: onClick ? (e) => {
231
+ if (e.key === "Enter" || e.key === " ") onClick();
232
+ } : void 0,
233
+ style: { width, height, ...frameStyle },
200
234
  className: cn(
201
- "flex h-full w-full flex-col items-center justify-between rounded-2xl border bg-neutral-900/80 p-2 backdrop-blur",
202
- seat.isTurn ? "border-cyan-400 shadow-[0_0_18px_rgba(34,211,238,0.55)]" : "border-neutral-700",
203
- seat.isWinner && "border-amber-400 shadow-[0_0_18px_rgba(251,191,36,0.6)]",
204
- seat.folded && "opacity-50"
235
+ "relative flex flex-col items-center justify-between gap-1 rounded-2xl border bg-neutral-900/80 p-2 text-neutral-100 transition",
236
+ isActive ? "border-cyan-400 shadow-[0_0_18px_rgba(34,211,238,0.55)]" : "border-neutral-700",
237
+ isWinner && "border-amber-400 shadow-[0_0_18px_rgba(251,191,36,0.6)]",
238
+ isDimmed && "opacity-50",
239
+ onClick && "cursor-pointer hover:border-neutral-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400",
240
+ frameClassName
205
241
  ),
206
242
  children: [
207
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex w-full items-center justify-between text-[11px]", children: [
208
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("span", { className: "truncate font-semibold text-neutral-100", children: [
209
- seat.isWinner ? "\u{1F451} " : "",
210
- seat.nickname,
211
- seat.isYou ? " \u{1F448}" : ""
212
- ] }),
213
- seat.isDealer && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "flex h-4 w-4 items-center justify-center rounded-full bg-white text-[9px] font-bold text-neutral-900", children: "D" })
214
- ] }),
215
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex flex-1 items-center justify-center", children: seat.busted ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "text-xs font-semibold text-red-400", children: "Busted" }) : showCards ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(HoleCards, { cards: seat.holeCards, size }) : /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex gap-2", children: [
216
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CardBack, { size }),
217
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(CardBack, { size })
218
- ] }) }),
219
- /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: "flex w-full items-center justify-between text-[11px] text-neutral-300", children: [
220
- /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-mono", children: formatBB(seat.stack) }),
221
- seat.allIn ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "rounded bg-red-600/80 px-1.5 text-[10px] font-bold text-white", children: "ALL-IN" }) : seat.currentBet && seat.currentBet > 0 ? /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { className: "font-mono text-amber-300", children: formatBB(seat.currentBet) }) : null
222
- ] })
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 })
223
247
  ]
224
248
  }
225
- )
249
+ ),
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 })
226
252
  ] });
227
253
  }
228
254
 
229
- // src/components/PokerTable.tsx
230
- var import_react = require("react");
255
+ // src/components/Pot.tsx
231
256
  var import_jsx_runtime3 = require("react/jsx-runtime");
232
- var SEAT_COUNT = 6;
233
- var SEAT_W = 160;
234
- var SEAT_H = 176;
257
+ function Pot({ amount, label, format = formatBB, className }) {
258
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: cn("flex items-center gap-2 font-mono text-sm text-amber-400", className), children: [
259
+ label && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-neutral-400", children: label }),
260
+ /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "whitespace-nowrap rounded-full border border-amber-400/50 bg-neutral-900/80 px-3 py-1 shadow-inner", children: format(amount) })
261
+ ] });
262
+ }
263
+
264
+ // src/components/CardTable.tsx
265
+ var import_react = require("react");
266
+ var import_jsx_runtime4 = require("react/jsx-runtime");
235
267
  var DESIGN_W = 1200;
236
268
  var DESIGN_H = 900;
237
269
  var MARGIN_X = 164;
@@ -242,47 +274,46 @@ var FELT_GAP = 11;
242
274
  var BET_SCALE = 0.72;
243
275
  var BET_RX = TABLE_RX * BET_SCALE;
244
276
  var BET_RY = TABLE_RY * BET_SCALE;
245
- var SEAT_RING_X = TABLE_RX - FELT_GAP + SEAT_W / 2 + 6;
246
- var SEAT_RING_Y = TABLE_RY - FELT_GAP + SEAT_H / 2 + 6;
247
- var BASE_ANGLES = [-90, -30, 30, 90, 150, 210];
248
277
  function getEllipsePoint(cx, cy, rx, ry, angleDeg) {
249
278
  const rad = angleDeg * Math.PI / 180;
250
279
  return { x: cx + rx * Math.cos(rad), y: cy + ry * Math.sin(rad) };
251
280
  }
252
- function calculateSeatLayouts(seats, yourSeatIndex) {
281
+ function calculateSeatLayouts(seatCount, yourSeatIndex, seatWidth, seatHeight) {
253
282
  const cx = DESIGN_W / 2;
254
283
  const cy = DESIGN_H / 2;
255
- const rotationOffset = yourSeatIndex !== null ? BASE_ANGLES[3] - BASE_ANGLES[yourSeatIndex] : 0;
284
+ const ringX = TABLE_RX - FELT_GAP + seatWidth / 2 + 6;
285
+ const ringY = TABLE_RY - FELT_GAP + seatHeight / 2 + 6;
286
+ const step = 360 / seatCount;
287
+ const angleOf = (i) => -90 + i * step;
288
+ const rotationOffset = yourSeatIndex !== null ? 90 - angleOf(yourSeatIndex) : 0;
256
289
  const layouts = [];
257
- for (let i = 0; i < SEAT_COUNT; i++) {
258
- const pt = getEllipsePoint(cx, cy, SEAT_RING_X, SEAT_RING_Y, BASE_ANGLES[i] + rotationOffset);
290
+ for (let i = 0; i < seatCount; i++) {
291
+ const pt = getEllipsePoint(cx, cy, ringX, ringY, angleOf(i) + rotationOffset);
259
292
  layouts.push({
260
- x: pt.x - SEAT_W / 2,
261
- y: pt.y - SEAT_H / 2,
262
- logicalIndex: i,
263
- isYou: i === yourSeatIndex,
264
- seat: seats[i] ?? null
293
+ x: pt.x - seatWidth / 2,
294
+ y: pt.y - seatHeight / 2,
295
+ index: i,
296
+ isYou: i === yourSeatIndex
265
297
  });
266
298
  }
267
299
  return layouts;
268
300
  }
269
- function PokerTable({
301
+ function CardTable({
302
+ seatCount = 6,
270
303
  seats,
271
- yourSeatIndex = null,
272
- communityCards = [],
273
- pot = 0,
274
- pots = [],
275
- isWaiting = false,
276
- winnerSeats = [],
277
- seatSize = "sm",
278
304
  renderSeat,
305
+ yourSeatIndex = null,
306
+ center,
307
+ seatWidth = 160,
308
+ seatHeight = 176,
279
309
  className
280
310
  }) {
311
+ const count = Math.max(2, Math.min(10, Math.floor(seatCount)));
281
312
  const outerRef = (0, import_react.useRef)(null);
282
313
  const [scale, setScale] = (0, import_react.useState)(1);
283
314
  const layouts = (0, import_react.useMemo)(
284
- () => calculateSeatLayouts(seats, yourSeatIndex),
285
- [seats, yourSeatIndex]
315
+ () => calculateSeatLayouts(count, yourSeatIndex, seatWidth, seatHeight),
316
+ [count, yourSeatIndex, seatWidth, seatHeight]
286
317
  );
287
318
  (0, import_react.useLayoutEffect)(() => {
288
319
  const el = outerRef.current;
@@ -304,26 +335,25 @@ function PokerTable({
304
335
  const betLineInset = {
305
336
  inset: `${MARGIN_Y + (TABLE_RY - BET_RY)}px ${MARGIN_X + (TABLE_RX - BET_RX)}px`
306
337
  };
307
- const potRows = pots.length > 0 ? pots : pot > 0 ? [{ label: "Pot", amount: pot }] : [];
308
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
338
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
309
339
  "div",
310
340
  {
311
341
  ref: outerRef,
312
- className: cn("relative flex h-full w-full items-center justify-center overflow-hidden min-h-0", className),
313
- children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { style: { width: DESIGN_W * scale, height: DESIGN_H * scale }, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)(
342
+ className: cn("relative flex h-full w-full min-h-0 items-center justify-center overflow-hidden", className),
343
+ children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { width: DESIGN_W * scale, height: DESIGN_H * scale }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
314
344
  "div",
315
345
  {
316
346
  className: "relative",
317
347
  style: { width: DESIGN_W, height: DESIGN_H, transform: `scale(${scale})`, transformOrigin: "top left" },
318
348
  children: [
319
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
349
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
320
350
  "div",
321
351
  {
322
352
  className: "absolute -inset-6 rounded-[50%] bg-[radial-gradient(closest-side,rgba(240,192,74,0.16),transparent_72%)] blur-3xl",
323
353
  "aria-hidden": "true"
324
354
  }
325
355
  ),
326
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
356
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
327
357
  "div",
328
358
  {
329
359
  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)]",
@@ -331,7 +361,7 @@ function PokerTable({
331
361
  "aria-hidden": "true"
332
362
  }
333
363
  ),
334
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
364
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
335
365
  "div",
336
366
  {
337
367
  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)]",
@@ -339,7 +369,7 @@ function PokerTable({
339
369
  "aria-hidden": "true"
340
370
  }
341
371
  ),
342
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
372
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
343
373
  "div",
344
374
  {
345
375
  className: "absolute rounded-[50%] border-2 border-white/10 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.25)]",
@@ -347,22 +377,14 @@ function PokerTable({
347
377
  "aria-hidden": "true"
348
378
  }
349
379
  ),
350
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute flex items-center justify-center pointer-events-none", style: feltInset, "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-white/[0.05] text-[110px] font-black tracking-[0.2em] select-none leading-none", children: "\u2660\u2665\u2666\u2663" }) }),
351
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "absolute inset-0 flex flex-col items-center justify-center pointer-events-none", children: [
352
- /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "mb-4 flex flex-col items-center gap-3", children: [
353
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(CommunityCards, { cards: communityCards, size: "lg", className: "filter drop-shadow-lg" }),
354
- potRows.length > 0 && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "flex flex-col items-center gap-1", children: potRows.map((p, i) => /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: "flex items-center gap-2 font-mono text-sm text-amber-400", children: [
355
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-neutral-400", children: p.label }),
356
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "whitespace-nowrap rounded-full border border-amber-400/50 bg-neutral-900/80 px-3 py-1 shadow-inner", children: formatBB(p.amount) })
357
- ] }, i)) })
358
- ] }),
359
- isWaiting && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "rounded-full border border-neutral-700 bg-neutral-900/70 px-3 py-1 text-xs text-neutral-400", children: "Waiting to start" })
360
- ] }),
361
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute inset-0 z-20 pointer-events-none", children: layouts.map(({ x, y, logicalIndex, isYou, seat }) => {
362
- const merged = seat ? { ...seat, isYou, isWinner: winnerSeats.includes(logicalIndex) || seat.isWinner } : null;
363
- return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute", style: { left: `${x}px`, top: `${y}px` }, children: renderSeat ? renderSeat(merged, logicalIndex) : /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Seat, { seat: merged, size: seatSize }) }, logicalIndex);
380
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "absolute flex items-center justify-center pointer-events-none", style: feltInset, "aria-hidden": "true", children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("span", { className: "text-white/[0.05] text-[110px] font-black tracking-[0.2em] select-none leading-none", children: "\u2660\u2665\u2666\u2663" }) }),
381
+ center !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "pointer-events-none absolute inset-0 flex flex-col items-center justify-center", children: center }),
382
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "absolute inset-0 z-20", children: layouts.map(({ x, y, index, isYou }) => {
383
+ const content = seats ? seats[index] ?? null : renderSeat ? renderSeat(index, isYou) : null;
384
+ if (content === null) return null;
385
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "absolute", style: { left: `${x}px`, top: `${y}px` }, children: content }, index);
364
386
  }) }),
365
- /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className: "absolute rounded-[50%] border border-white/10 pointer-events-none", style: rimInset, "aria-hidden": "true" })
387
+ /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "absolute rounded-[50%] border border-white/10 pointer-events-none", style: rimInset, "aria-hidden": "true" })
366
388
  ]
367
389
  }
368
390
  ) })
@@ -373,14 +395,17 @@ function PokerTable({
373
395
  0 && (module.exports = {
374
396
  BB,
375
397
  CardBack,
398
+ CardTable,
376
399
  CommunityCards,
377
400
  HoleCards,
378
401
  PlayingCard,
379
402
  PokerTable,
403
+ Pot,
380
404
  Seat,
381
405
  cn,
382
406
  formatBB,
383
407
  getSuitSymbol,
408
+ nextSeatIndex,
384
409
  parseCard
385
410
  });
386
411
  //# sourceMappingURL=index.cjs.map
@@ -1 +1 @@
1
- {"version":3,"sources":["../src/index.ts","../src/cn.ts","../src/format.ts","../src/components/PlayingCard.tsx","../src/components/Seat.tsx","../src/components/PokerTable.tsx"],"sourcesContent":["export { cn } from \"./cn\";\nexport { BB, formatBB } from \"./format\";\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, type SeatView } from \"./components/Seat\";\n\nexport { PokerTable, type PokerTableProps, type PotRow } from \"./components/PokerTable\";\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","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 { cn } from \"../cn\";\nimport { formatBB } from \"../format\";\nimport { CardBack, HoleCards, type CardSize } from \"./PlayingCard\";\n\n/** Presentational view-model for one seat (no game logic). */\nexport interface SeatView {\n nickname: string;\n stack: number;\n holeCards?: (string | null)[] | null;\n isDealer?: boolean;\n isTurn?: boolean;\n folded?: boolean;\n busted?: boolean;\n allIn?: boolean;\n currentBet?: number;\n isYou?: boolean;\n isWinner?: boolean;\n /** Optional short label shown in a bubble (e.g. \"Raise 500\"). */\n actionLabel?: string | null;\n}\n\nexport interface SeatProps {\n seat: SeatView | null;\n size?: CardSize;\n className?: string;\n}\n\n/**\n * A presentational seat: nickname, stack, hole cards, dealer/turn/winner\n * markers. It is fully controlled — all state arrives through `seat`.\n */\nexport function Seat({ seat, size = \"sm\", className }: SeatProps) {\n if (!seat) {\n return (\n <div\n className={cn(\n \"w-40 h-44 rounded-2xl border-2 border-dashed border-white/10\",\n \"flex items-center justify-center text-white/20 text-xs\",\n className,\n )}\n >\n Empty seat\n </div>\n );\n }\n\n const showCards = !seat.folded && !seat.busted && !!seat.holeCards && seat.holeCards.length > 0;\n\n return (\n <div className={cn(\"relative w-40 h-44\", className)}>\n {seat.actionLabel && (\n <div className=\"animate-bubble-pop absolute -top-6 left-1/2 -translate-x-1/2 whitespace-nowrap rounded-full bg-white px-2.5 py-1 text-[11px] font-semibold text-neutral-900 shadow-lg\">\n {seat.actionLabel}\n </div>\n )}\n\n <div\n className={cn(\n \"flex h-full w-full flex-col items-center justify-between rounded-2xl border bg-neutral-900/80 p-2 backdrop-blur\",\n seat.isTurn ? \"border-cyan-400 shadow-[0_0_18px_rgba(34,211,238,0.55)]\" : \"border-neutral-700\",\n seat.isWinner && \"border-amber-400 shadow-[0_0_18px_rgba(251,191,36,0.6)]\",\n seat.folded && \"opacity-50\",\n )}\n >\n <div className=\"flex w-full items-center justify-between text-[11px]\">\n <span className=\"truncate font-semibold text-neutral-100\">\n {seat.isWinner ? \"👑 \" : \"\"}\n {seat.nickname}\n {seat.isYou ? \" 👈\" : \"\"}\n </span>\n {seat.isDealer && (\n <span className=\"flex h-4 w-4 items-center justify-center rounded-full bg-white text-[9px] font-bold text-neutral-900\">\n D\n </span>\n )}\n </div>\n\n <div className=\"flex flex-1 items-center justify-center\">\n {seat.busted ? (\n <span className=\"text-xs font-semibold text-red-400\">Busted</span>\n ) : showCards ? (\n <HoleCards cards={seat.holeCards as (string | null)[]} size={size} />\n ) : (\n <div className=\"flex gap-2\">\n <CardBack size={size} />\n <CardBack size={size} />\n </div>\n )}\n </div>\n\n <div className=\"flex w-full items-center justify-between text-[11px] text-neutral-300\">\n <span className=\"font-mono\">{formatBB(seat.stack)}</span>\n {seat.allIn ? (\n <span className=\"rounded bg-red-600/80 px-1.5 text-[10px] font-bold text-white\">ALL-IN</span>\n ) : seat.currentBet && seat.currentBet > 0 ? (\n <span className=\"font-mono text-amber-300\">{formatBB(seat.currentBet)}</span>\n ) : null}\n </div>\n </div>\n </div>\n );\n}\n","import { useRef, useLayoutEffect, useState, useMemo } from \"react\";\nimport { cn } from \"../cn\";\nimport { formatBB } from \"../format\";\nimport { CommunityCards, type CardSize } from \"./PlayingCard\";\nimport { Seat, type SeatView } from \"./Seat\";\n\nexport interface PotRow {\n label: string;\n amount: number;\n}\n\nexport interface PokerTableProps {\n /** Six seats (some may be empty). */\n seats: (SeatView | null)[];\n /** Which seat belongs to the viewer; rotates that seat to the bottom. */\n yourSeatIndex?: number | null;\n communityCards?: (string | null)[];\n pot?: number;\n pots?: PotRow[];\n isWaiting?: boolean;\n winnerSeats?: number[];\n seatSize?: CardSize;\n /** Optional override for rendering a seat. */\n renderSeat?: (seat: SeatView | null, index: number) => React.ReactNode;\n className?: string;\n}\n\nconst SEAT_COUNT = 6;\nconst SEAT_W = 160;\nconst SEAT_H = 176;\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;\nconst SEAT_RING_X = TABLE_RX - FELT_GAP + SEAT_W / 2 + 6;\nconst SEAT_RING_Y = TABLE_RY - FELT_GAP + SEAT_H / 2 + 6;\n\nconst BASE_ANGLES = [-90, -30, 30, 90, 150, 210];\n\ninterface SeatLayout {\n x: number;\n y: number;\n logicalIndex: number;\n isYou: boolean;\n seat: SeatView | null;\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 seats: (SeatView | null)[],\n yourSeatIndex: number | null,\n): SeatLayout[] {\n const cx = DESIGN_W / 2;\n const cy = DESIGN_H / 2;\n const rotationOffset =\n yourSeatIndex !== null ? BASE_ANGLES[3] - BASE_ANGLES[yourSeatIndex] : 0;\n\n const layouts: SeatLayout[] = [];\n for (let i = 0; i < SEAT_COUNT; i++) {\n const pt = getEllipsePoint(cx, cy, SEAT_RING_X, SEAT_RING_Y, BASE_ANGLES[i] + rotationOffset);\n layouts.push({\n x: pt.x - SEAT_W / 2,\n y: pt.y - SEAT_H / 2,\n logicalIndex: i,\n isYou: i === yourSeatIndex,\n seat: seats[i] ?? null,\n });\n }\n return layouts;\n}\n\n/**\n * A responsive, themable poker table. It scales a fixed 1200x900 design onto\n * its container and arranges six seats around the felt. Purely presentational:\n * pass seat view-models and card codes in.\n */\nexport function PokerTable({\n seats,\n yourSeatIndex = null,\n communityCards = [],\n pot = 0,\n pots = [],\n isWaiting = false,\n winnerSeats = [],\n seatSize = \"sm\",\n renderSeat,\n className,\n}: PokerTableProps) {\n const outerRef = useRef<HTMLDivElement>(null);\n const [scale, setScale] = useState(1);\n\n const layouts = useMemo(\n () => calculateSeatLayouts(seats, yourSeatIndex),\n [seats, yourSeatIndex],\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 const potRows: PotRow[] = pots.length > 0 ? pots : pot > 0 ? [{ label: \"Pot\", amount: pot }] : [];\n\n return (\n <div\n ref={outerRef}\n className={cn(\"relative flex h-full w-full items-center justify-center overflow-hidden min-h-0\", 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 {/* ambient spotlight */}\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: community cards + pots */}\n <div className=\"absolute inset-0 flex flex-col items-center justify-center pointer-events-none\">\n <div className=\"mb-4 flex flex-col items-center gap-3\">\n <CommunityCards cards={communityCards} size=\"lg\" className=\"filter drop-shadow-lg\" />\n {potRows.length > 0 && (\n <div className=\"flex flex-col items-center gap-1\">\n {potRows.map((p, i) => (\n <div key={i} className=\"flex items-center gap-2 font-mono text-sm text-amber-400\">\n <span className=\"text-neutral-400\">{p.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 {formatBB(p.amount)}\n </span>\n </div>\n ))}\n </div>\n )}\n </div>\n {isWaiting && (\n <div className=\"rounded-full border border-neutral-700 bg-neutral-900/70 px-3 py-1 text-xs text-neutral-400\">\n Waiting to start\n </div>\n )}\n </div>\n\n {/* seats */}\n <div className=\"absolute inset-0 z-20 pointer-events-none\">\n {layouts.map(({ x, y, logicalIndex, isYou, seat }) => {\n const merged: SeatView | null = seat\n ? { ...seat, isYou, isWinner: winnerSeats.includes(logicalIndex) || seat.isWinner }\n : null;\n return (\n <div key={logicalIndex} className=\"absolute\" style={{ left: `${x}px`, top: `${y}px` }}>\n {renderSeat ? renderSeat(merged, logicalIndex) : <Seat seat={merged} size={seatSize} />}\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;;;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;;;ACsEoB;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;;;ACxJM,IAAAA,sBAAA;AAHC,SAAS,KAAK,EAAE,MAAM,OAAO,MAAM,UAAU,GAAc;AAChE,MAAI,CAAC,MAAM;AACT,WACE;AAAA,MAAC;AAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA;AAAA,UACA;AAAA,QACF;AAAA,QACD;AAAA;AAAA,IAED;AAAA,EAEJ;AAEA,QAAM,YAAY,CAAC,KAAK,UAAU,CAAC,KAAK,UAAU,CAAC,CAAC,KAAK,aAAa,KAAK,UAAU,SAAS;AAE9F,SACE,8CAAC,SAAI,WAAW,GAAG,sBAAsB,SAAS,GAC/C;AAAA,SAAK,eACJ,6CAAC,SAAI,WAAU,yKACZ,eAAK,aACR;AAAA,IAGF;AAAA,MAAC;AAAA;AAAA,QACC,WAAW;AAAA,UACT;AAAA,UACA,KAAK,SAAS,4DAA4D;AAAA,UAC1E,KAAK,YAAY;AAAA,UACjB,KAAK,UAAU;AAAA,QACjB;AAAA,QAEA;AAAA,wDAAC,SAAI,WAAU,wDACb;AAAA,0DAAC,UAAK,WAAU,2CACb;AAAA,mBAAK,WAAW,eAAQ;AAAA,cACxB,KAAK;AAAA,cACL,KAAK,QAAQ,eAAQ;AAAA,eACxB;AAAA,YACC,KAAK,YACJ,6CAAC,UAAK,WAAU,wGAAuG,eAEvH;AAAA,aAEJ;AAAA,UAEA,6CAAC,SAAI,WAAU,2CACZ,eAAK,SACJ,6CAAC,UAAK,WAAU,sCAAqC,oBAAM,IACzD,YACF,6CAAC,aAAU,OAAO,KAAK,WAAgC,MAAY,IAEnE,8CAAC,SAAI,WAAU,cACb;AAAA,yDAAC,YAAS,MAAY;AAAA,YACtB,6CAAC,YAAS,MAAY;AAAA,aACxB,GAEJ;AAAA,UAEA,8CAAC,SAAI,WAAU,yEACb;AAAA,yDAAC,UAAK,WAAU,aAAa,mBAAS,KAAK,KAAK,GAAE;AAAA,YACjD,KAAK,QACJ,6CAAC,UAAK,WAAU,iEAAgE,oBAAM,IACpF,KAAK,cAAc,KAAK,aAAa,IACvC,6CAAC,UAAK,WAAU,4BAA4B,mBAAS,KAAK,UAAU,GAAE,IACpE;AAAA,aACN;AAAA;AAAA;AAAA,IACF;AAAA,KACF;AAEJ;;;ACrGA,mBAA2D;AA6IjD,IAAAC,sBAAA;AAlHV,IAAM,aAAa;AACnB,IAAM,SAAS;AACf,IAAM,SAAS;AACf,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;AAC1B,IAAM,cAAc,WAAW,WAAW,SAAS,IAAI;AACvD,IAAM,cAAc,WAAW,WAAW,SAAS,IAAI;AAEvD,IAAM,cAAc,CAAC,KAAK,KAAK,IAAI,IAAI,KAAK,GAAG;AAU/C,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,OACA,eACc;AACd,QAAM,KAAK,WAAW;AACtB,QAAM,KAAK,WAAW;AACtB,QAAM,iBACJ,kBAAkB,OAAO,YAAY,CAAC,IAAI,YAAY,aAAa,IAAI;AAEzE,QAAM,UAAwB,CAAC;AAC/B,WAAS,IAAI,GAAG,IAAI,YAAY,KAAK;AACnC,UAAM,KAAK,gBAAgB,IAAI,IAAI,aAAa,aAAa,YAAY,CAAC,IAAI,cAAc;AAC5F,YAAQ,KAAK;AAAA,MACX,GAAG,GAAG,IAAI,SAAS;AAAA,MACnB,GAAG,GAAG,IAAI,SAAS;AAAA,MACnB,cAAc;AAAA,MACd,OAAO,MAAM;AAAA,MACb,MAAM,MAAM,CAAC,KAAK;AAAA,IACpB,CAAC;AAAA,EACH;AACA,SAAO;AACT;AAOO,SAAS,WAAW;AAAA,EACzB;AAAA,EACA,gBAAgB;AAAA,EAChB,iBAAiB,CAAC;AAAA,EAClB,MAAM;AAAA,EACN,OAAO,CAAC;AAAA,EACR,YAAY;AAAA,EACZ,cAAc,CAAC;AAAA,EACf,WAAW;AAAA,EACX;AAAA,EACA;AACF,GAAoB;AAClB,QAAM,eAAW,qBAAuB,IAAI;AAC5C,QAAM,CAAC,OAAO,QAAQ,QAAI,uBAAS,CAAC;AAEpC,QAAM,cAAU;AAAA,IACd,MAAM,qBAAqB,OAAO,aAAa;AAAA,IAC/C,CAAC,OAAO,aAAa;AAAA,EACvB;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,QAAM,UAAoB,KAAK,SAAS,IAAI,OAAO,MAAM,IAAI,CAAC,EAAE,OAAO,OAAO,QAAQ,IAAI,CAAC,IAAI,CAAC;AAEhG,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,UAGtG;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,YAGA,8CAAC,SAAI,WAAU,kFACb;AAAA,4DAAC,SAAI,WAAU,yCACb;AAAA,6DAAC,kBAAe,OAAO,gBAAgB,MAAK,MAAK,WAAU,yBAAwB;AAAA,gBAClF,QAAQ,SAAS,KAChB,6CAAC,SAAI,WAAU,oCACZ,kBAAQ,IAAI,CAAC,GAAG,MACf,8CAAC,SAAY,WAAU,4DACrB;AAAA,+DAAC,UAAK,WAAU,oBAAoB,YAAE,OAAM;AAAA,kBAC5C,6CAAC,UAAK,WAAU,sGACb,mBAAS,EAAE,MAAM,GACpB;AAAA,qBAJQ,CAKV,CACD,GACH;AAAA,iBAEJ;AAAA,cACC,aACC,6CAAC,SAAI,WAAU,+FAA8F,8BAE7G;AAAA,eAEJ;AAAA,YAGA,6CAAC,SAAI,WAAU,6CACZ,kBAAQ,IAAI,CAAC,EAAE,GAAG,GAAG,cAAc,OAAO,KAAK,MAAM;AACpD,oBAAM,SAA0B,OAC5B,EAAE,GAAG,MAAM,OAAO,UAAU,YAAY,SAAS,YAAY,KAAK,KAAK,SAAS,IAChF;AACJ,qBACE,6CAAC,SAAuB,WAAU,YAAW,OAAO,EAAE,MAAM,GAAG,CAAC,MAAM,KAAK,GAAG,CAAC,KAAK,GACjF,uBAAa,WAAW,QAAQ,YAAY,IAAI,6CAAC,QAAK,MAAM,QAAQ,MAAM,UAAU,KAD7E,YAEV;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"]}
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"]}