@stargazeryan/poker-ui 0.3.0 → 0.4.1

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.d.cts CHANGED
@@ -35,12 +35,14 @@ declare function getSuitSymbol(suit: string): string;
35
35
  interface PlayingCardProps {
36
36
  /** Compact card code ("As", "10h") or null to render a face-down card back. */
37
37
  card: string | null;
38
+ /** xs / sm show a single, larger, centered index; md / lg show corner + center. */
38
39
  size?: CardSize;
39
40
  className?: string;
40
41
  }
41
42
  /**
42
43
  * A single playing card. Size is driven by the `size` prop (never viewport
43
- * breakpoints) so cards scale predictably inside any container.
44
+ * breakpoints) so cards scale predictably inside any container. Small sizes
45
+ * (xs / sm) show only a larger upright index, centered in the card.
44
46
  */
45
47
  declare function PlayingCard({ card, size, className }: PlayingCardProps): react.JSX.Element;
46
48
  interface CardBackProps {
@@ -53,24 +55,34 @@ interface HoleCardsProps {
53
55
  cards: (string | null)[];
54
56
  size?: CardSize;
55
57
  className?: string;
58
+ /** Animate the cards dealing in, one after another. Default true. */
59
+ deal?: boolean;
56
60
  }
57
- /** A row of a player's two hole cards. */
58
- declare function HoleCards({ cards, size, className }: HoleCardsProps): react.JSX.Element;
61
+ /** A row of a player's hole cards. */
62
+ declare function HoleCards({ cards, size, className, deal }: HoleCardsProps): react.JSX.Element;
59
63
  interface CommunityCardsProps {
60
64
  cards: (string | null)[];
61
65
  size?: CardSize;
62
66
  className?: string;
67
+ /** Animate each card with a 3D flip. Default true. */
68
+ animated?: boolean;
69
+ /** Reserve a fixed number of slots so the row width never changes. */
70
+ slots?: number;
63
71
  }
64
- /** The five community cards, each dealt with a staggered flip animation. */
65
- declare function CommunityCards({ cards, size, className }: CommunityCardsProps): react.JSX.Element;
72
+ /** A row of community cards, each flipping in with a staggered delay. */
73
+ declare function CommunityCards({ cards, size, className, animated, slots }: CommunityCardsProps): react.JSX.Element;
66
74
 
67
75
  interface SeatProps {
68
- /** Turn/active highlight (generic). */
76
+ /** Turn/active highlight (generic). Animated by default. */
69
77
  isActive?: boolean;
70
- /** Winner highlight (generic). */
78
+ /** Winner highlight (generic). Animated by default. */
71
79
  isWinner?: boolean;
72
80
  /** Dimmed, e.g. folded or out (generic). */
73
81
  isDimmed?: boolean;
82
+ /** Enable the turn/winner glow animations. Default true. */
83
+ animated?: boolean;
84
+ /** Render a default 👑 in the top-left corner when `isWinner`. Default false. */
85
+ winnerCrown?: boolean;
74
86
  /** Optional click handler (e.g. "sit here"). */
75
87
  onClick?: () => void;
76
88
  /** Slot: top row inside the frame — name / badges. */
@@ -107,7 +119,7 @@ interface SeatProps {
107
119
  * `above` / `below` / `topLeft` / `topRight` / `topCenter` / `overlay` for
108
120
  * floating bits. It knows nothing about any particular game.
109
121
  */
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;
122
+ declare function Seat({ isActive, isWinner, isDimmed, animated, winnerCrown, onClick, header, children, footer, badge, overlay, above, below, topLeft, topRight, topCenter, width, height, className, frameClassName, frameStyle, }: SeatProps): react.JSX.Element;
111
123
 
112
124
  interface PotProps {
113
125
  amount: number;
@@ -115,10 +127,12 @@ interface PotProps {
115
127
  label?: string;
116
128
  /** Formatter for the amount; defaults to Big-Blind formatting. */
117
129
  format?: (amount: number) => string;
130
+ /** Animate the amount counting up when it changes. Default true. */
131
+ animate?: boolean;
118
132
  className?: string;
119
133
  }
120
134
  /** A small, standalone pot pill. Optional — the table does not require it. */
121
- declare function Pot({ amount, label, format, className }: PotProps): react.JSX.Element;
135
+ declare function Pot({ amount, label, format, animate, className }: PotProps): react.JSX.Element;
122
136
 
123
137
  interface CardTableProps {
124
138
  /** Number of seats (2–10). Default 6. */
@@ -131,6 +145,14 @@ interface CardTableProps {
131
145
  yourSeatIndex?: number | null;
132
146
  /** Optional content in the middle of the table (cards, pot, anything). */
133
147
  center?: ReactNode;
148
+ /** Ambient spotlight + felt shimmer. Default true. */
149
+ ambient?: boolean;
150
+ /** Highlight the table edge (e.g. when it is your turn). Default false. */
151
+ yourTurn?: boolean;
152
+ /** Winning seat indices; when non-empty, confetti falls. */
153
+ winnerSeats?: number[];
154
+ /** Show confetti for winners. Default true. */
155
+ confetti?: boolean;
134
156
  /** Seat frame size used for layout. */
135
157
  seatWidth?: number;
136
158
  seatHeight?: number;
@@ -141,6 +163,6 @@ interface CardTableProps {
141
163
  * onto its container and arranges `seatCount` seats around the felt. Purely
142
164
  * presentational: supply seat content and an optional `center` slot.
143
165
  */
144
- declare function CardTable({ seatCount, seats, renderSeat, yourSeatIndex, center, seatWidth, seatHeight, className, }: CardTableProps): react.JSX.Element;
166
+ declare function CardTable({ seatCount, seats, renderSeat, yourSeatIndex, center, ambient, yourTurn, winnerSeats, confetti, seatWidth, seatHeight, className, }: CardTableProps): react.JSX.Element;
145
167
 
146
168
  export { BB, CardBack, type CardBackProps, type CardSize, CardTable, type CardTableProps, CommunityCards, type CommunityCardsProps, HoleCards, type HoleCardsProps, type NextSeatOptions, PlayingCard, type PlayingCardProps, CardTable as PokerTable, type CardTableProps as PokerTableProps, Pot, type PotProps, Seat, type SeatProps, cn, formatBB, getSuitSymbol, nextSeatIndex, parseCard };
package/dist/index.d.ts CHANGED
@@ -35,12 +35,14 @@ declare function getSuitSymbol(suit: string): string;
35
35
  interface PlayingCardProps {
36
36
  /** Compact card code ("As", "10h") or null to render a face-down card back. */
37
37
  card: string | null;
38
+ /** xs / sm show a single, larger, centered index; md / lg show corner + center. */
38
39
  size?: CardSize;
39
40
  className?: string;
40
41
  }
41
42
  /**
42
43
  * A single playing card. Size is driven by the `size` prop (never viewport
43
- * breakpoints) so cards scale predictably inside any container.
44
+ * breakpoints) so cards scale predictably inside any container. Small sizes
45
+ * (xs / sm) show only a larger upright index, centered in the card.
44
46
  */
45
47
  declare function PlayingCard({ card, size, className }: PlayingCardProps): react.JSX.Element;
46
48
  interface CardBackProps {
@@ -53,24 +55,34 @@ interface HoleCardsProps {
53
55
  cards: (string | null)[];
54
56
  size?: CardSize;
55
57
  className?: string;
58
+ /** Animate the cards dealing in, one after another. Default true. */
59
+ deal?: boolean;
56
60
  }
57
- /** A row of a player's two hole cards. */
58
- declare function HoleCards({ cards, size, className }: HoleCardsProps): react.JSX.Element;
61
+ /** A row of a player's hole cards. */
62
+ declare function HoleCards({ cards, size, className, deal }: HoleCardsProps): react.JSX.Element;
59
63
  interface CommunityCardsProps {
60
64
  cards: (string | null)[];
61
65
  size?: CardSize;
62
66
  className?: string;
67
+ /** Animate each card with a 3D flip. Default true. */
68
+ animated?: boolean;
69
+ /** Reserve a fixed number of slots so the row width never changes. */
70
+ slots?: number;
63
71
  }
64
- /** The five community cards, each dealt with a staggered flip animation. */
65
- declare function CommunityCards({ cards, size, className }: CommunityCardsProps): react.JSX.Element;
72
+ /** A row of community cards, each flipping in with a staggered delay. */
73
+ declare function CommunityCards({ cards, size, className, animated, slots }: CommunityCardsProps): react.JSX.Element;
66
74
 
67
75
  interface SeatProps {
68
- /** Turn/active highlight (generic). */
76
+ /** Turn/active highlight (generic). Animated by default. */
69
77
  isActive?: boolean;
70
- /** Winner highlight (generic). */
78
+ /** Winner highlight (generic). Animated by default. */
71
79
  isWinner?: boolean;
72
80
  /** Dimmed, e.g. folded or out (generic). */
73
81
  isDimmed?: boolean;
82
+ /** Enable the turn/winner glow animations. Default true. */
83
+ animated?: boolean;
84
+ /** Render a default 👑 in the top-left corner when `isWinner`. Default false. */
85
+ winnerCrown?: boolean;
74
86
  /** Optional click handler (e.g. "sit here"). */
75
87
  onClick?: () => void;
76
88
  /** Slot: top row inside the frame — name / badges. */
@@ -107,7 +119,7 @@ interface SeatProps {
107
119
  * `above` / `below` / `topLeft` / `topRight` / `topCenter` / `overlay` for
108
120
  * floating bits. It knows nothing about any particular game.
109
121
  */
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;
122
+ declare function Seat({ isActive, isWinner, isDimmed, animated, winnerCrown, onClick, header, children, footer, badge, overlay, above, below, topLeft, topRight, topCenter, width, height, className, frameClassName, frameStyle, }: SeatProps): react.JSX.Element;
111
123
 
112
124
  interface PotProps {
113
125
  amount: number;
@@ -115,10 +127,12 @@ interface PotProps {
115
127
  label?: string;
116
128
  /** Formatter for the amount; defaults to Big-Blind formatting. */
117
129
  format?: (amount: number) => string;
130
+ /** Animate the amount counting up when it changes. Default true. */
131
+ animate?: boolean;
118
132
  className?: string;
119
133
  }
120
134
  /** A small, standalone pot pill. Optional — the table does not require it. */
121
- declare function Pot({ amount, label, format, className }: PotProps): react.JSX.Element;
135
+ declare function Pot({ amount, label, format, animate, className }: PotProps): react.JSX.Element;
122
136
 
123
137
  interface CardTableProps {
124
138
  /** Number of seats (2–10). Default 6. */
@@ -131,6 +145,14 @@ interface CardTableProps {
131
145
  yourSeatIndex?: number | null;
132
146
  /** Optional content in the middle of the table (cards, pot, anything). */
133
147
  center?: ReactNode;
148
+ /** Ambient spotlight + felt shimmer. Default true. */
149
+ ambient?: boolean;
150
+ /** Highlight the table edge (e.g. when it is your turn). Default false. */
151
+ yourTurn?: boolean;
152
+ /** Winning seat indices; when non-empty, confetti falls. */
153
+ winnerSeats?: number[];
154
+ /** Show confetti for winners. Default true. */
155
+ confetti?: boolean;
134
156
  /** Seat frame size used for layout. */
135
157
  seatWidth?: number;
136
158
  seatHeight?: number;
@@ -141,6 +163,6 @@ interface CardTableProps {
141
163
  * onto its container and arranges `seatCount` seats around the felt. Purely
142
164
  * presentational: supply seat content and an optional `center` slot.
143
165
  */
144
- declare function CardTable({ seatCount, seats, renderSeat, yourSeatIndex, center, seatWidth, seatHeight, className, }: CardTableProps): react.JSX.Element;
166
+ declare function CardTable({ seatCount, seats, renderSeat, yourSeatIndex, center, ambient, yourTurn, winnerSeats, confetti, seatWidth, seatHeight, className, }: CardTableProps): react.JSX.Element;
145
167
 
146
168
  export { BB, CardBack, type CardBackProps, type CardSize, CardTable, type CardTableProps, CommunityCards, type CommunityCardsProps, HoleCards, type HoleCardsProps, type NextSeatOptions, PlayingCard, type PlayingCardProps, CardTable as PokerTable, type CardTableProps as PokerTableProps, Pot, type PotProps, Seat, type SeatProps, cn, formatBB, getSuitSymbol, nextSeatIndex, parseCard };
package/dist/index.js CHANGED
@@ -53,6 +53,14 @@ var CENTER_SUIT = {
53
53
  md: "text-3xl",
54
54
  lg: "text-5xl"
55
55
  };
56
+ var SMALL_RANK = {
57
+ xs: "text-base",
58
+ sm: "text-xl"
59
+ };
60
+ var SMALL_SUIT = {
61
+ xs: "text-sm",
62
+ sm: "text-base"
63
+ };
56
64
  var BACK_EMBLEM_SIZE = {
57
65
  xs: 14,
58
66
  sm: 18,
@@ -89,36 +97,30 @@ function getSuitClass(suit) {
89
97
  return "";
90
98
  }
91
99
  }
100
+ var CARD_FACE = "relative bg-gradient-to-br from-white via-white to-neutral-100 rounded-md border border-neutral-300/90 overflow-hidden shadow-[0_2px_6px_rgba(0,0,0,0.45),inset_0_1px_0_rgba(255,255,255,0.9)]";
92
101
  function PlayingCard({ card, size = "md", className }) {
93
102
  if (!card) return /* @__PURE__ */ jsx(CardBack, { size, className });
94
103
  const { rank, suit } = parseCard(card);
95
104
  const suitSymbol = getSuitSymbol(suit);
96
105
  const suitClass = getSuitClass(suit);
97
- const showCenter = size === "md" || size === "lg";
98
- return /* @__PURE__ */ jsxs(
99
- "div",
100
- {
101
- className: cn(
102
- SIZE_CLASSES[size],
103
- "relative bg-gradient-to-br from-white via-white to-neutral-100",
104
- "rounded-md border border-neutral-300/90 overflow-hidden",
105
- "shadow-[0_2px_6px_rgba(0,0,0,0.45),inset_0_1px_0_rgba(255,255,255,0.9)]",
106
- className
107
- ),
108
- style: { aspectRatio: "10/14" },
109
- children: [
110
- /* @__PURE__ */ jsxs("div", { className: cn("absolute top-[3px] left-[5px] flex flex-col items-center leading-none", suitClass), children: [
111
- /* @__PURE__ */ jsx("span", { className: cn(CORNER_RANK[size], "font-bold tracking-tight"), children: rank }),
112
- /* @__PURE__ */ jsx("span", { className: cn(CORNER_SUIT[size], "-mt-[1px]"), children: suitSymbol })
113
- ] }),
114
- /* @__PURE__ */ jsxs("div", { className: cn("absolute bottom-[3px] right-[5px] flex flex-col items-center leading-none rotate-180", suitClass), children: [
115
- /* @__PURE__ */ jsx("span", { className: cn(CORNER_RANK[size], "font-bold tracking-tight"), children: rank }),
116
- /* @__PURE__ */ jsx("span", { className: cn(CORNER_SUIT[size], "-mt-[1px]"), children: suitSymbol })
117
- ] }),
118
- showCenter && /* @__PURE__ */ jsx("div", { className: cn("absolute inset-0 flex items-center justify-center opacity-90", suitClass), children: /* @__PURE__ */ jsx("span", { className: CENTER_SUIT[size], children: suitSymbol }) })
119
- ]
120
- }
121
- );
106
+ const large = size === "md" || size === "lg";
107
+ if (!large) {
108
+ return /* @__PURE__ */ jsx("div", { className: cn(SIZE_CLASSES[size], CARD_FACE, className), style: { aspectRatio: "10/14" }, children: /* @__PURE__ */ jsxs("div", { className: cn("absolute inset-0 flex flex-col items-center justify-center leading-none", suitClass), children: [
109
+ /* @__PURE__ */ jsx("span", { className: cn(SMALL_RANK[size], "font-bold tracking-tight"), children: rank }),
110
+ /* @__PURE__ */ jsx("span", { className: cn(SMALL_SUIT[size], "mt-[1px]"), children: suitSymbol })
111
+ ] }) });
112
+ }
113
+ return /* @__PURE__ */ jsxs("div", { className: cn(SIZE_CLASSES[size], CARD_FACE, className), style: { aspectRatio: "10/14" }, children: [
114
+ /* @__PURE__ */ jsxs("div", { className: cn("absolute top-[3px] left-[5px] flex flex-col items-center leading-none", suitClass), children: [
115
+ /* @__PURE__ */ jsx("span", { className: cn(CORNER_RANK[size], "font-bold tracking-tight"), children: rank }),
116
+ /* @__PURE__ */ jsx("span", { className: cn(CORNER_SUIT[size], "-mt-[1px]"), children: suitSymbol })
117
+ ] }),
118
+ /* @__PURE__ */ jsxs("div", { className: cn("absolute bottom-[3px] right-[5px] flex flex-col items-center leading-none rotate-180", suitClass), children: [
119
+ /* @__PURE__ */ jsx("span", { className: cn(CORNER_RANK[size], "font-bold tracking-tight"), children: rank }),
120
+ /* @__PURE__ */ jsx("span", { className: cn(CORNER_SUIT[size], "-mt-[1px]"), children: suitSymbol })
121
+ ] }),
122
+ /* @__PURE__ */ jsx("div", { className: cn("absolute inset-0 flex items-center justify-center opacity-90", suitClass), children: /* @__PURE__ */ jsx("span", { className: CENTER_SUIT[size], children: suitSymbol }) })
123
+ ] });
122
124
  }
123
125
  function CardBackEmblem({ size }) {
124
126
  const s = BACK_EMBLEM_SIZE[size];
@@ -148,11 +150,20 @@ function CardBack({ size = "md", className }) {
148
150
  }
149
151
  );
150
152
  }
151
- function HoleCards({ cards, size = "lg", className }) {
152
- return /* @__PURE__ */ jsx("div", { className: cn("flex gap-2", className), children: cards.map((card, i) => /* @__PURE__ */ jsx(PlayingCard, { card, size }, i)) });
153
+ function HoleCards({ cards, size = "lg", className, deal = true }) {
154
+ return /* @__PURE__ */ jsx("div", { className: cn("flex gap-2", className), children: cards.map((card, i) => /* @__PURE__ */ jsx("div", { className: cn(deal && "animate-deal-in"), style: deal ? { animationDelay: `${i * 100}ms` } : void 0, children: /* @__PURE__ */ jsx(PlayingCard, { card, size }) }, i)) });
153
155
  }
154
- function CommunityCards({ cards, size = "md", className }) {
155
- return /* @__PURE__ */ jsx("div", { className: cn("flex gap-1.5", className), children: cards.map((card, i) => /* @__PURE__ */ jsx("div", { className: "animate-card-deal", style: { animationDelay: `${i * 90}ms` }, children: /* @__PURE__ */ jsx(PlayingCard, { card, size }) }, i)) });
156
+ function CommunityCards({ cards, size = "md", className, animated = true, slots }) {
157
+ const count = slots ?? cards.length;
158
+ const list = Array.from({ length: count }, (_, i) => cards[i] ?? null);
159
+ return /* @__PURE__ */ jsx("div", { className: cn("flex gap-1.5", animated && "[perspective:800px]", className), children: list.map((card, i) => /* @__PURE__ */ jsx("div", { className: SIZE_CLASSES[size], style: { aspectRatio: "10/14" }, children: card ? /* @__PURE__ */ jsx(
160
+ "div",
161
+ {
162
+ className: cn("h-full w-full", animated && "animate-card-flip"),
163
+ style: animated ? { animationDelay: `${i * 70}ms` } : void 0,
164
+ children: /* @__PURE__ */ jsx(PlayingCard, { card, size })
165
+ }
166
+ ) : null }, i)) });
156
167
  }
157
168
 
158
169
  // src/components/Seat.tsx
@@ -161,6 +172,8 @@ function Seat({
161
172
  isActive = false,
162
173
  isWinner = false,
163
174
  isDimmed = false,
175
+ animated = true,
176
+ winnerCrown = false,
164
177
  onClick,
165
178
  header,
166
179
  children,
@@ -178,9 +191,12 @@ function Seat({
178
191
  frameClassName,
179
192
  frameStyle
180
193
  }) {
194
+ const activeClass = animated ? "border-amber-400 animate-turn-glow" : "border-amber-400 shadow-[0_0_18px_rgba(243,156,18,0.5)]";
195
+ const winnerClass = animated ? "border-amber-400 animate-winner-glow" : "border-amber-400 shadow-[0_0_0_3px_rgba(243,156,18,0.9)]";
196
+ const effTopLeft = topLeft !== void 0 ? topLeft : isWinner && winnerCrown ? /* @__PURE__ */ jsx2("span", { className: "text-3xl drop-shadow-[0_2px_4px_rgba(0,0,0,0.6)] animate-bounce", children: "\u{1F451}" }) : void 0;
181
197
  return /* @__PURE__ */ jsxs2("div", { className: cn("relative flex flex-col items-center", className), style: { width }, children: [
182
198
  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 }),
199
+ effTopLeft !== void 0 && /* @__PURE__ */ jsx2("div", { className: "absolute -top-3 -left-2 z-40", children: effTopLeft }),
184
200
  topRight !== void 0 && /* @__PURE__ */ jsx2("div", { className: "absolute -top-2 -right-2 z-40", children: topRight }),
185
201
  above !== void 0 && /* @__PURE__ */ jsx2("div", { className: "pointer-events-none absolute -top-12 left-1/2 z-30 -translate-x-1/2", children: above }),
186
202
  /* @__PURE__ */ jsxs2(
@@ -195,10 +211,11 @@ function Seat({
195
211
  style: { width, height, ...frameStyle },
196
212
  className: cn(
197
213
  "relative flex flex-col items-center justify-between gap-1 rounded-2xl border bg-neutral-900/80 p-2 text-neutral-100 transition",
198
- isActive ? "border-cyan-400 shadow-[0_0_18px_rgba(34,211,238,0.55)]" : "border-neutral-700",
199
- isWinner && "border-amber-400 shadow-[0_0_18px_rgba(251,191,36,0.6)]",
214
+ "border-neutral-700",
215
+ isActive && activeClass,
216
+ isWinner && winnerClass,
200
217
  isDimmed && "opacity-50",
201
- onClick && "cursor-pointer hover:border-neutral-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400",
218
+ onClick && "cursor-pointer hover:border-neutral-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-amber-400",
202
219
  frameClassName
203
220
  ),
204
221
  children: [
@@ -215,17 +232,40 @@ function Seat({
215
232
  }
216
233
 
217
234
  // src/components/Pot.tsx
218
- import { jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
219
- function Pot({ amount, label, format = formatBB, className }) {
235
+ import { useEffect, useRef, useState } from "react";
236
+ import { Fragment, jsx as jsx3, jsxs as jsxs3 } from "react/jsx-runtime";
237
+ function CountUp({ value, format }) {
238
+ const [shown, setShown] = useState(value);
239
+ const fromRef = useRef(value);
240
+ useEffect(() => {
241
+ const from = fromRef.current;
242
+ const to = value;
243
+ if (from === to) return;
244
+ let raf = 0;
245
+ const start = performance.now();
246
+ const dur = 500;
247
+ const tick = (now) => {
248
+ const t = Math.min(1, (now - start) / dur);
249
+ const eased = 1 - Math.pow(1 - t, 3);
250
+ setShown(Math.round(from + (to - from) * eased));
251
+ if (t < 1) raf = requestAnimationFrame(tick);
252
+ else fromRef.current = to;
253
+ };
254
+ raf = requestAnimationFrame(tick);
255
+ return () => cancelAnimationFrame(raf);
256
+ }, [value]);
257
+ return /* @__PURE__ */ jsx3(Fragment, { children: format(shown) });
258
+ }
259
+ function Pot({ amount, label, format = formatBB, animate = true, className }) {
220
260
  return /* @__PURE__ */ jsxs3("div", { className: cn("flex items-center gap-2 font-mono text-sm text-amber-400", className), children: [
221
261
  label && /* @__PURE__ */ jsx3("span", { className: "text-neutral-400", children: label }),
222
- /* @__PURE__ */ jsx3("span", { className: "whitespace-nowrap rounded-full border border-amber-400/50 bg-neutral-900/80 px-3 py-1 shadow-inner", children: format(amount) })
262
+ /* @__PURE__ */ jsx3("span", { className: "whitespace-nowrap rounded-full border border-amber-400/50 bg-neutral-900/80 px-3 py-1 shadow-inner", children: animate ? /* @__PURE__ */ jsx3(CountUp, { value: amount, format }) : format(amount) })
223
263
  ] });
224
264
  }
225
265
 
226
266
  // src/components/CardTable.tsx
227
- import { useRef, useLayoutEffect, useState, useMemo } from "react";
228
- import { jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
267
+ import { useRef as useRef2, useLayoutEffect, useState as useState2, useMemo } from "react";
268
+ import { Fragment as Fragment2, jsx as jsx4, jsxs as jsxs4 } from "react/jsx-runtime";
229
269
  var DESIGN_W = 1200;
230
270
  var DESIGN_H = 900;
231
271
  var MARGIN_X = 164;
@@ -236,6 +276,7 @@ var FELT_GAP = 11;
236
276
  var BET_SCALE = 0.72;
237
277
  var BET_RX = TABLE_RX * BET_SCALE;
238
278
  var BET_RY = TABLE_RY * BET_SCALE;
279
+ var CONFETTI_COLORS = ["#f39c12", "#e74c3c", "#2ecc71", "#3498db", "#f1c40f", "#9b59b6"];
239
280
  function getEllipsePoint(cx, cy, rx, ry, angleDeg) {
240
281
  const rad = angleDeg * Math.PI / 180;
241
282
  return { x: cx + rx * Math.cos(rad), y: cy + ry * Math.sin(rad) };
@@ -266,13 +307,17 @@ function CardTable({
266
307
  renderSeat,
267
308
  yourSeatIndex = null,
268
309
  center,
310
+ ambient = true,
311
+ yourTurn = false,
312
+ winnerSeats = [],
313
+ confetti = true,
269
314
  seatWidth = 160,
270
315
  seatHeight = 176,
271
316
  className
272
317
  }) {
273
318
  const count = Math.max(2, Math.min(10, Math.floor(seatCount)));
274
- const outerRef = useRef(null);
275
- const [scale, setScale] = useState(1);
319
+ const outerRef = useRef2(null);
320
+ const [scale, setScale] = useState2(1);
276
321
  const layouts = useMemo(
277
322
  () => calculateSeatLayouts(count, yourSeatIndex, seatWidth, seatHeight),
278
323
  [count, yourSeatIndex, seatWidth, seatHeight]
@@ -294,6 +339,7 @@ function CardTable({
294
339
  const tableFrameInset = { inset: `${MARGIN_Y}px ${MARGIN_X}px` };
295
340
  const feltInset = { inset: `${MARGIN_Y + FELT_GAP}px ${MARGIN_X + FELT_GAP}px` };
296
341
  const rimInset = { inset: `${MARGIN_Y + 6}px ${MARGIN_X + 6}px` };
342
+ const ringInset = { inset: `${MARGIN_Y + 2}px ${MARGIN_X + 2}px` };
297
343
  const betLineInset = {
298
344
  inset: `${MARGIN_Y + (TABLE_RY - BET_RY)}px ${MARGIN_X + (TABLE_RX - BET_RX)}px`
299
345
  };
@@ -331,6 +377,17 @@ function CardTable({
331
377
  "aria-hidden": "true"
332
378
  }
333
379
  ),
380
+ ambient && /* @__PURE__ */ jsxs4(Fragment2, { children: [
381
+ /* @__PURE__ */ jsx4(
382
+ "div",
383
+ {
384
+ className: "absolute animate-spotlight rounded-[50%] bg-[radial-gradient(closest-side,rgba(255,214,120,0.14),transparent_70%)] blur-2xl",
385
+ style: feltInset,
386
+ "aria-hidden": "true"
387
+ }
388
+ ),
389
+ /* @__PURE__ */ jsx4("div", { className: "absolute overflow-hidden rounded-[50%]", style: feltInset, "aria-hidden": "true", children: /* @__PURE__ */ jsx4("div", { className: "absolute inset-0 animate-felt-shimmer bg-[linear-gradient(115deg,transparent_42%,rgba(255,255,255,0.06)_50%,transparent_58%)] bg-[length:220%_100%]" }) })
390
+ ] }),
334
391
  /* @__PURE__ */ jsx4(
335
392
  "div",
336
393
  {
@@ -340,12 +397,34 @@ function CardTable({
340
397
  }
341
398
  ),
342
399
  /* @__PURE__ */ jsx4("div", { className: "absolute flex items-center justify-center pointer-events-none", style: feltInset, "aria-hidden": "true", children: /* @__PURE__ */ jsx4("span", { className: "text-white/[0.05] text-[110px] font-black tracking-[0.2em] select-none leading-none", children: "\u2660\u2665\u2666\u2663" }) }),
400
+ yourTurn && /* @__PURE__ */ jsx4(
401
+ "div",
402
+ {
403
+ className: "pointer-events-none absolute z-10 animate-turn-glow rounded-[50%]",
404
+ style: ringInset,
405
+ "aria-hidden": "true"
406
+ }
407
+ ),
343
408
  center !== void 0 && /* @__PURE__ */ jsx4("div", { className: "pointer-events-none absolute inset-0 flex flex-col items-center justify-center", children: center }),
344
409
  /* @__PURE__ */ jsx4("div", { className: "absolute inset-0 z-20", children: layouts.map(({ x, y, index, isYou }) => {
345
410
  const content = seats ? seats[index] ?? null : renderSeat ? renderSeat(index, isYou) : null;
346
411
  if (content === null) return null;
347
412
  return /* @__PURE__ */ jsx4("div", { className: "absolute", style: { left: `${x}px`, top: `${y}px` }, children: content }, index);
348
413
  }) }),
414
+ confetti && winnerSeats.length > 0 && /* @__PURE__ */ jsx4("div", { className: "pointer-events-none absolute inset-0 z-30 overflow-hidden", "aria-hidden": "true", children: Array.from({ length: 16 }).map((_, i) => /* @__PURE__ */ jsx4(
415
+ "span",
416
+ {
417
+ className: "absolute top-[34%] animate-confetti rounded-[2px]",
418
+ style: {
419
+ left: `${6 + i * 5.6}%`,
420
+ width: 6,
421
+ height: 11,
422
+ backgroundColor: CONFETTI_COLORS[i % CONFETTI_COLORS.length],
423
+ animationDelay: `${i % 8 * 90}ms`
424
+ }
425
+ },
426
+ i
427
+ )) }),
349
428
  /* @__PURE__ */ jsx4("div", { className: "absolute rounded-[50%] border border-white/10 pointer-events-none", style: rimInset, "aria-hidden": "true" })
350
429
  ]
351
430
  }