@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/README.md +38 -22
- package/dist/index.cjs +119 -40
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +32 -10
- package/dist/index.d.ts +32 -10
- package/dist/index.js +119 -40
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
- package/src/styles.css +83 -1
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
|
|
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
|
-
/**
|
|
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
|
|
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
|
-
/**
|
|
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
|
|
98
|
-
|
|
99
|
-
"div",
|
|
100
|
-
|
|
101
|
-
className: cn(
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
),
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
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
|
-
|
|
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
|
-
|
|
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
|
-
|
|
199
|
-
|
|
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-
|
|
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 {
|
|
219
|
-
|
|
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 =
|
|
275
|
-
const [scale, setScale] =
|
|
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
|
}
|