@stargazeryan/poker-ui 0.0.0-stage → 0.2.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/LICENSE +21 -0
- package/README.md +128 -2
- package/dist/index.cjs +396 -0
- package/dist/index.cjs.map +1 -0
- package/dist/index.d.cts +130 -0
- package/dist/index.d.ts +130 -0
- package/dist/index.js +357 -0
- package/dist/index.js.map +1 -0
- package/package.json +41 -4
- package/src/styles.css +62 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Ryan
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
package/README.md
CHANGED
|
@@ -1,3 +1,129 @@
|
|
|
1
|
-
#
|
|
1
|
+
# @stargazeryan/poker-ui
|
|
2
2
|
|
|
3
|
-
|
|
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.
|
|
7
|
+
|
|
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`.
|
|
15
|
+
|
|
16
|
+
## Install
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
npm install @stargazeryan/poker-ui
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
Peer requirements: `react` >= 18, `react-dom` >= 18, Tailwind CSS v4.
|
|
23
|
+
|
|
24
|
+
## Setup (Tailwind v4) — important
|
|
25
|
+
|
|
26
|
+
This is a **compiled package**, so your app's Tailwind build cannot see the
|
|
27
|
+
class names used inside it. Add the package as a content source and import its
|
|
28
|
+
theme CSS:
|
|
29
|
+
|
|
30
|
+
```css
|
|
31
|
+
/* app.css */
|
|
32
|
+
@import "tailwindcss";
|
|
33
|
+
@import "@stargazeryan/poker-ui/styles.css";
|
|
34
|
+
|
|
35
|
+
@source "../node_modules/@stargazeryan/poker-ui";
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
> Without the `@source` line, the components render unstyled. Adjust the path to
|
|
39
|
+
> wherever your `node_modules` lives.
|
|
40
|
+
|
|
41
|
+
## Usage
|
|
42
|
+
|
|
43
|
+
```tsx
|
|
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>,
|
|
53
|
+
null, null, null, null,
|
|
54
|
+
];
|
|
55
|
+
|
|
56
|
+
export function Table() {
|
|
57
|
+
return (
|
|
58
|
+
<div style={{ height: 640 }}>
|
|
59
|
+
<CardTable
|
|
60
|
+
seatCount={6}
|
|
61
|
+
seats={seats}
|
|
62
|
+
yourSeatIndex={0}
|
|
63
|
+
center={
|
|
64
|
+
<>
|
|
65
|
+
<CommunityCards cards={["Ah", "Kd", "7c", null, null]} size="lg" />
|
|
66
|
+
<Pot label="Main" amount={240} />
|
|
67
|
+
</>
|
|
68
|
+
}
|
|
69
|
+
/>
|
|
70
|
+
</div>
|
|
71
|
+
);
|
|
72
|
+
}
|
|
73
|
+
```
|
|
74
|
+
|
|
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
|
+
```
|
|
84
|
+
|
|
85
|
+
## Components
|
|
86
|
+
|
|
87
|
+
| Component | Props |
|
|
88
|
+
| --- | --- |
|
|
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` |
|
|
94
|
+
|
|
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
|
+
```
|
|
107
|
+
|
|
108
|
+
## Theming
|
|
109
|
+
|
|
110
|
+
Override the tokens in your own `@theme` block, or pass `className`:
|
|
111
|
+
|
|
112
|
+
```css
|
|
113
|
+
@theme {
|
|
114
|
+
--color-felt: #0b3d2e;
|
|
115
|
+
--color-gold: #ffcc33;
|
|
116
|
+
}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
## Demo
|
|
120
|
+
|
|
121
|
+
```bash
|
|
122
|
+
npm install --include=dev
|
|
123
|
+
npm run demo # Vite dev server
|
|
124
|
+
npm run demo:build # static build
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
## License
|
|
128
|
+
|
|
129
|
+
MIT © 2026 Ryan
|
package/dist/index.cjs
ADDED
|
@@ -0,0 +1,396 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
"use strict";
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __export = (target, all) => {
|
|
8
|
+
for (var name in all)
|
|
9
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
10
|
+
};
|
|
11
|
+
var __copyProps = (to, from, except, desc) => {
|
|
12
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
13
|
+
for (let key of __getOwnPropNames(from))
|
|
14
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
15
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
16
|
+
}
|
|
17
|
+
return to;
|
|
18
|
+
};
|
|
19
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
20
|
+
|
|
21
|
+
// src/index.ts
|
|
22
|
+
var index_exports = {};
|
|
23
|
+
__export(index_exports, {
|
|
24
|
+
BB: () => BB,
|
|
25
|
+
CardBack: () => CardBack,
|
|
26
|
+
CardTable: () => CardTable,
|
|
27
|
+
CommunityCards: () => CommunityCards,
|
|
28
|
+
HoleCards: () => HoleCards,
|
|
29
|
+
PlayingCard: () => PlayingCard,
|
|
30
|
+
PokerTable: () => CardTable,
|
|
31
|
+
Pot: () => Pot,
|
|
32
|
+
Seat: () => Seat,
|
|
33
|
+
cn: () => cn,
|
|
34
|
+
formatBB: () => formatBB,
|
|
35
|
+
getSuitSymbol: () => getSuitSymbol,
|
|
36
|
+
nextSeatIndex: () => nextSeatIndex,
|
|
37
|
+
parseCard: () => parseCard
|
|
38
|
+
});
|
|
39
|
+
module.exports = __toCommonJS(index_exports);
|
|
40
|
+
|
|
41
|
+
// src/cn.ts
|
|
42
|
+
function cn(...parts) {
|
|
43
|
+
return parts.filter(Boolean).join(" ");
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// src/format.ts
|
|
47
|
+
var BB = 20;
|
|
48
|
+
function formatBB(amount) {
|
|
49
|
+
const bb = amount / BB;
|
|
50
|
+
return `${Number.isInteger(bb) ? bb : bb.toFixed(1)} BB`;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
// src/seat.ts
|
|
54
|
+
function nextSeatIndex(current, seatCount, opts = {}) {
|
|
55
|
+
const n = Math.max(1, Math.floor(seatCount));
|
|
56
|
+
const dir = opts.dir ?? 1;
|
|
57
|
+
const skip = opts.skip;
|
|
58
|
+
const start = (Math.floor(current) % n + n) % n;
|
|
59
|
+
let i = start;
|
|
60
|
+
for (let step = 0; step < n - 1; step++) {
|
|
61
|
+
i = (i + dir + n) % n;
|
|
62
|
+
if (!skip || !skip(i)) return i;
|
|
63
|
+
}
|
|
64
|
+
return start;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// src/components/PlayingCard.tsx
|
|
68
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
69
|
+
var SIZE_CLASSES = {
|
|
70
|
+
xs: "w-8 h-11",
|
|
71
|
+
sm: "w-10 h-14",
|
|
72
|
+
md: "w-16 h-22",
|
|
73
|
+
lg: "w-24 h-32"
|
|
74
|
+
};
|
|
75
|
+
var CORNER_RANK = {
|
|
76
|
+
xs: "text-[11px]",
|
|
77
|
+
sm: "text-sm",
|
|
78
|
+
md: "text-lg",
|
|
79
|
+
lg: "text-2xl"
|
|
80
|
+
};
|
|
81
|
+
var CORNER_SUIT = {
|
|
82
|
+
xs: "text-[9px]",
|
|
83
|
+
sm: "text-xs",
|
|
84
|
+
md: "text-sm",
|
|
85
|
+
lg: "text-base"
|
|
86
|
+
};
|
|
87
|
+
var CENTER_SUIT = {
|
|
88
|
+
xs: "text-base",
|
|
89
|
+
sm: "text-xl",
|
|
90
|
+
md: "text-3xl",
|
|
91
|
+
lg: "text-5xl"
|
|
92
|
+
};
|
|
93
|
+
var BACK_EMBLEM_SIZE = {
|
|
94
|
+
xs: 14,
|
|
95
|
+
sm: 18,
|
|
96
|
+
md: 28,
|
|
97
|
+
lg: 42
|
|
98
|
+
};
|
|
99
|
+
function parseCard(card) {
|
|
100
|
+
if (!card || card.length < 2) return { rank: "", suit: "" };
|
|
101
|
+
return { rank: card.slice(0, -1), suit: card.slice(-1) };
|
|
102
|
+
}
|
|
103
|
+
function getSuitSymbol(suit) {
|
|
104
|
+
switch (suit) {
|
|
105
|
+
case "s":
|
|
106
|
+
return "\u2660";
|
|
107
|
+
case "h":
|
|
108
|
+
return "\u2665";
|
|
109
|
+
case "d":
|
|
110
|
+
return "\u2666";
|
|
111
|
+
case "c":
|
|
112
|
+
return "\u2663";
|
|
113
|
+
default:
|
|
114
|
+
return suit;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function getSuitClass(suit) {
|
|
118
|
+
switch (suit) {
|
|
119
|
+
case "s":
|
|
120
|
+
case "c":
|
|
121
|
+
return "text-neutral-900";
|
|
122
|
+
case "h":
|
|
123
|
+
case "d":
|
|
124
|
+
return "text-red-600";
|
|
125
|
+
default:
|
|
126
|
+
return "";
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
function PlayingCard({ card, size = "md", className }) {
|
|
130
|
+
if (!card) return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardBack, { size, className });
|
|
131
|
+
const { rank, suit } = parseCard(card);
|
|
132
|
+
const suitSymbol = getSuitSymbol(suit);
|
|
133
|
+
const suitClass = getSuitClass(suit);
|
|
134
|
+
const showCenter = size === "md" || size === "lg";
|
|
135
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)(
|
|
136
|
+
"div",
|
|
137
|
+
{
|
|
138
|
+
className: cn(
|
|
139
|
+
SIZE_CLASSES[size],
|
|
140
|
+
"relative bg-gradient-to-br from-white via-white to-neutral-100",
|
|
141
|
+
"rounded-md border border-neutral-300/90 overflow-hidden",
|
|
142
|
+
"shadow-[0_2px_6px_rgba(0,0,0,0.45),inset_0_1px_0_rgba(255,255,255,0.9)]",
|
|
143
|
+
className
|
|
144
|
+
),
|
|
145
|
+
style: { aspectRatio: "10/14" },
|
|
146
|
+
children: [
|
|
147
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: cn("absolute top-[3px] left-[5px] flex flex-col items-center leading-none", suitClass), children: [
|
|
148
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: cn(CORNER_RANK[size], "font-bold tracking-tight"), children: rank }),
|
|
149
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: cn(CORNER_SUIT[size], "-mt-[1px]"), children: suitSymbol })
|
|
150
|
+
] }),
|
|
151
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("div", { className: cn("absolute bottom-[3px] right-[5px] flex flex-col items-center leading-none rotate-180", suitClass), children: [
|
|
152
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: cn(CORNER_RANK[size], "font-bold tracking-tight"), children: rank }),
|
|
153
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: cn(CORNER_SUIT[size], "-mt-[1px]"), children: suitSymbol })
|
|
154
|
+
] }),
|
|
155
|
+
showCenter && /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("absolute inset-0 flex items-center justify-center opacity-90", suitClass), children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("span", { className: CENTER_SUIT[size], children: suitSymbol }) })
|
|
156
|
+
]
|
|
157
|
+
}
|
|
158
|
+
);
|
|
159
|
+
}
|
|
160
|
+
function CardBackEmblem({ size }) {
|
|
161
|
+
const s = BACK_EMBLEM_SIZE[size];
|
|
162
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsxs)("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", children: [
|
|
163
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsxs)("g", { fill: "#f0c04a", opacity: "0.92", children: [
|
|
164
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "20", cy: "11", rx: "4", ry: "7" }),
|
|
165
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "20", cy: "29", rx: "4", ry: "7" }),
|
|
166
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "11", cy: "20", rx: "7", ry: "4" }),
|
|
167
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("ellipse", { cx: "29", cy: "20", rx: "7", ry: "4" })
|
|
168
|
+
] }),
|
|
169
|
+
/* @__PURE__ */ (0, import_jsx_runtime.jsx)("circle", { cx: "20", cy: "20", r: "3.2", fill: "#5a0c18" })
|
|
170
|
+
] });
|
|
171
|
+
}
|
|
172
|
+
function CardBack({ size = "md", className }) {
|
|
173
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(
|
|
174
|
+
"div",
|
|
175
|
+
{
|
|
176
|
+
className: cn(
|
|
177
|
+
SIZE_CLASSES[size],
|
|
178
|
+
"relative rounded-md p-[3px] overflow-hidden",
|
|
179
|
+
"bg-gradient-to-br from-[#7a1020] via-[#5a0c18] to-[#3f0810]",
|
|
180
|
+
"border-2 border-[#c9a24b]/70 shadow-[0_2px_6px_rgba(0,0,0,0.45)]",
|
|
181
|
+
className
|
|
182
|
+
),
|
|
183
|
+
style: { aspectRatio: "10/14" },
|
|
184
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)("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)]", children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(CardBackEmblem, { size }) })
|
|
185
|
+
}
|
|
186
|
+
);
|
|
187
|
+
}
|
|
188
|
+
function HoleCards({ cards, size = "lg", className }) {
|
|
189
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("flex gap-2", className), children: cards.map((card, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlayingCard, { card, size }, i)) });
|
|
190
|
+
}
|
|
191
|
+
function CommunityCards({ cards, size = "md", className }) {
|
|
192
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: cn("flex gap-1.5", className), children: cards.map((card, i) => /* @__PURE__ */ (0, import_jsx_runtime.jsx)("div", { className: "animate-card-deal", style: { animationDelay: `${i * 90}ms` }, children: /* @__PURE__ */ (0, import_jsx_runtime.jsx)(PlayingCard, { card, size }) }, i)) });
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
// src/components/Seat.tsx
|
|
196
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
197
|
+
function Seat({
|
|
198
|
+
isActive = false,
|
|
199
|
+
isWinner = false,
|
|
200
|
+
isDimmed = false,
|
|
201
|
+
onClick,
|
|
202
|
+
header,
|
|
203
|
+
badge,
|
|
204
|
+
children,
|
|
205
|
+
footer,
|
|
206
|
+
overlay,
|
|
207
|
+
width = 160,
|
|
208
|
+
height = 176,
|
|
209
|
+
className
|
|
210
|
+
}) {
|
|
211
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className: cn("relative", className), style: { width, height }, children: [
|
|
212
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsxs)(
|
|
213
|
+
"div",
|
|
214
|
+
{
|
|
215
|
+
role: onClick ? "button" : void 0,
|
|
216
|
+
tabIndex: onClick ? 0 : void 0,
|
|
217
|
+
onClick,
|
|
218
|
+
onKeyDown: onClick ? (e) => {
|
|
219
|
+
if (e.key === "Enter" || e.key === " ") onClick();
|
|
220
|
+
} : void 0,
|
|
221
|
+
className: cn(
|
|
222
|
+
"flex h-full w-full flex-col items-center justify-between gap-1 rounded-2xl border bg-neutral-900/80 p-2 text-neutral-100 backdrop-blur transition",
|
|
223
|
+
isActive ? "border-cyan-400 shadow-[0_0_18px_rgba(34,211,238,0.55)]" : "border-neutral-700",
|
|
224
|
+
isWinner && "border-amber-400 shadow-[0_0_18px_rgba(251,191,36,0.6)]",
|
|
225
|
+
isDimmed && "opacity-50",
|
|
226
|
+
onClick && "cursor-pointer hover:border-neutral-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400"
|
|
227
|
+
),
|
|
228
|
+
children: [
|
|
229
|
+
header !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex w-full items-center justify-between gap-1 text-[11px]", children: header }),
|
|
230
|
+
/* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex flex-1 items-center justify-center", children }),
|
|
231
|
+
footer !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "flex w-full items-center justify-between text-[11px] text-neutral-300", children: footer })
|
|
232
|
+
]
|
|
233
|
+
}
|
|
234
|
+
),
|
|
235
|
+
badge !== void 0 && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("div", { className: "absolute -top-1 -right-1", children: badge }),
|
|
236
|
+
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 })
|
|
237
|
+
] });
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
// src/components/Pot.tsx
|
|
241
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
242
|
+
function Pot({ amount, label, format = formatBB, className }) {
|
|
243
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("div", { className: cn("flex items-center gap-2 font-mono text-sm text-amber-400", className), children: [
|
|
244
|
+
label && /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("span", { className: "text-neutral-400", children: label }),
|
|
245
|
+
/* @__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) })
|
|
246
|
+
] });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/components/CardTable.tsx
|
|
250
|
+
var import_react = require("react");
|
|
251
|
+
var import_jsx_runtime4 = require("react/jsx-runtime");
|
|
252
|
+
var DESIGN_W = 1200;
|
|
253
|
+
var DESIGN_H = 900;
|
|
254
|
+
var MARGIN_X = 164;
|
|
255
|
+
var MARGIN_Y = 196;
|
|
256
|
+
var TABLE_RX = DESIGN_W / 2 - MARGIN_X;
|
|
257
|
+
var TABLE_RY = DESIGN_H / 2 - MARGIN_Y;
|
|
258
|
+
var FELT_GAP = 11;
|
|
259
|
+
var BET_SCALE = 0.72;
|
|
260
|
+
var BET_RX = TABLE_RX * BET_SCALE;
|
|
261
|
+
var BET_RY = TABLE_RY * BET_SCALE;
|
|
262
|
+
function getEllipsePoint(cx, cy, rx, ry, angleDeg) {
|
|
263
|
+
const rad = angleDeg * Math.PI / 180;
|
|
264
|
+
return { x: cx + rx * Math.cos(rad), y: cy + ry * Math.sin(rad) };
|
|
265
|
+
}
|
|
266
|
+
function calculateSeatLayouts(seatCount, yourSeatIndex, seatWidth, seatHeight) {
|
|
267
|
+
const cx = DESIGN_W / 2;
|
|
268
|
+
const cy = DESIGN_H / 2;
|
|
269
|
+
const ringX = TABLE_RX - FELT_GAP + seatWidth / 2 + 6;
|
|
270
|
+
const ringY = TABLE_RY - FELT_GAP + seatHeight / 2 + 6;
|
|
271
|
+
const step = 360 / seatCount;
|
|
272
|
+
const angleOf = (i) => -90 + i * step;
|
|
273
|
+
const rotationOffset = yourSeatIndex !== null ? 90 - angleOf(yourSeatIndex) : 0;
|
|
274
|
+
const layouts = [];
|
|
275
|
+
for (let i = 0; i < seatCount; i++) {
|
|
276
|
+
const pt = getEllipsePoint(cx, cy, ringX, ringY, angleOf(i) + rotationOffset);
|
|
277
|
+
layouts.push({
|
|
278
|
+
x: pt.x - seatWidth / 2,
|
|
279
|
+
y: pt.y - seatHeight / 2,
|
|
280
|
+
index: i,
|
|
281
|
+
isYou: i === yourSeatIndex
|
|
282
|
+
});
|
|
283
|
+
}
|
|
284
|
+
return layouts;
|
|
285
|
+
}
|
|
286
|
+
function CardTable({
|
|
287
|
+
seatCount = 6,
|
|
288
|
+
seats,
|
|
289
|
+
renderSeat,
|
|
290
|
+
yourSeatIndex = null,
|
|
291
|
+
center,
|
|
292
|
+
seatWidth = 160,
|
|
293
|
+
seatHeight = 176,
|
|
294
|
+
className
|
|
295
|
+
}) {
|
|
296
|
+
const count = Math.max(2, Math.min(10, Math.floor(seatCount)));
|
|
297
|
+
const outerRef = (0, import_react.useRef)(null);
|
|
298
|
+
const [scale, setScale] = (0, import_react.useState)(1);
|
|
299
|
+
const layouts = (0, import_react.useMemo)(
|
|
300
|
+
() => calculateSeatLayouts(count, yourSeatIndex, seatWidth, seatHeight),
|
|
301
|
+
[count, yourSeatIndex, seatWidth, seatHeight]
|
|
302
|
+
);
|
|
303
|
+
(0, import_react.useLayoutEffect)(() => {
|
|
304
|
+
const el = outerRef.current;
|
|
305
|
+
if (!el) return;
|
|
306
|
+
const compute = () => {
|
|
307
|
+
const w = el.clientWidth;
|
|
308
|
+
const h = el.clientHeight;
|
|
309
|
+
if (!w || !h) return;
|
|
310
|
+
setScale(Math.min(w / DESIGN_W, h / DESIGN_H));
|
|
311
|
+
};
|
|
312
|
+
compute();
|
|
313
|
+
const ro = new ResizeObserver(compute);
|
|
314
|
+
ro.observe(el);
|
|
315
|
+
return () => ro.disconnect();
|
|
316
|
+
}, []);
|
|
317
|
+
const tableFrameInset = { inset: `${MARGIN_Y}px ${MARGIN_X}px` };
|
|
318
|
+
const feltInset = { inset: `${MARGIN_Y + FELT_GAP}px ${MARGIN_X + FELT_GAP}px` };
|
|
319
|
+
const rimInset = { inset: `${MARGIN_Y + 6}px ${MARGIN_X + 6}px` };
|
|
320
|
+
const betLineInset = {
|
|
321
|
+
inset: `${MARGIN_Y + (TABLE_RY - BET_RY)}px ${MARGIN_X + (TABLE_RX - BET_RX)}px`
|
|
322
|
+
};
|
|
323
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
324
|
+
"div",
|
|
325
|
+
{
|
|
326
|
+
ref: outerRef,
|
|
327
|
+
className: cn("relative flex h-full w-full min-h-0 items-center justify-center overflow-hidden", className),
|
|
328
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { style: { width: DESIGN_W * scale, height: DESIGN_H * scale }, children: /* @__PURE__ */ (0, import_jsx_runtime4.jsxs)(
|
|
329
|
+
"div",
|
|
330
|
+
{
|
|
331
|
+
className: "relative",
|
|
332
|
+
style: { width: DESIGN_W, height: DESIGN_H, transform: `scale(${scale})`, transformOrigin: "top left" },
|
|
333
|
+
children: [
|
|
334
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
335
|
+
"div",
|
|
336
|
+
{
|
|
337
|
+
className: "absolute -inset-6 rounded-[50%] bg-[radial-gradient(closest-side,rgba(240,192,74,0.16),transparent_72%)] blur-3xl",
|
|
338
|
+
"aria-hidden": "true"
|
|
339
|
+
}
|
|
340
|
+
),
|
|
341
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
342
|
+
"div",
|
|
343
|
+
{
|
|
344
|
+
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)]",
|
|
345
|
+
style: tableFrameInset,
|
|
346
|
+
"aria-hidden": "true"
|
|
347
|
+
}
|
|
348
|
+
),
|
|
349
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
350
|
+
"div",
|
|
351
|
+
{
|
|
352
|
+
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)]",
|
|
353
|
+
style: feltInset,
|
|
354
|
+
"aria-hidden": "true"
|
|
355
|
+
}
|
|
356
|
+
),
|
|
357
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
|
|
358
|
+
"div",
|
|
359
|
+
{
|
|
360
|
+
className: "absolute rounded-[50%] border-2 border-white/10 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.25)]",
|
|
361
|
+
style: betLineInset,
|
|
362
|
+
"aria-hidden": "true"
|
|
363
|
+
}
|
|
364
|
+
),
|
|
365
|
+
/* @__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" }) }),
|
|
366
|
+
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 }),
|
|
367
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "absolute inset-0 z-20", children: layouts.map(({ x, y, index, isYou }) => {
|
|
368
|
+
const content = seats ? seats[index] ?? null : renderSeat ? renderSeat(index, isYou) : null;
|
|
369
|
+
if (content === null) return null;
|
|
370
|
+
return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "absolute", style: { left: `${x}px`, top: `${y}px` }, children: content }, index);
|
|
371
|
+
}) }),
|
|
372
|
+
/* @__PURE__ */ (0, import_jsx_runtime4.jsx)("div", { className: "absolute rounded-[50%] border border-white/10 pointer-events-none", style: rimInset, "aria-hidden": "true" })
|
|
373
|
+
]
|
|
374
|
+
}
|
|
375
|
+
) })
|
|
376
|
+
}
|
|
377
|
+
);
|
|
378
|
+
}
|
|
379
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
380
|
+
0 && (module.exports = {
|
|
381
|
+
BB,
|
|
382
|
+
CardBack,
|
|
383
|
+
CardTable,
|
|
384
|
+
CommunityCards,
|
|
385
|
+
HoleCards,
|
|
386
|
+
PlayingCard,
|
|
387
|
+
PokerTable,
|
|
388
|
+
Pot,
|
|
389
|
+
Seat,
|
|
390
|
+
cn,
|
|
391
|
+
formatBB,
|
|
392
|
+
getSuitSymbol,
|
|
393
|
+
nextSeatIndex,
|
|
394
|
+
parseCard
|
|
395
|
+
});
|
|
396
|
+
//# sourceMappingURL=index.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/index.ts","../src/cn.ts","../src/format.ts","../src/seat.ts","../src/components/PlayingCard.tsx","../src/components/Seat.tsx","../src/components/Pot.tsx","../src/components/CardTable.tsx"],"sourcesContent":["export { cn } from \"./cn\";\nexport { BB, formatBB } from \"./format\";\nexport { nextSeatIndex, type NextSeatOptions } from \"./seat\";\n\nexport {\n PlayingCard,\n CardBack,\n HoleCards,\n CommunityCards,\n parseCard,\n getSuitSymbol,\n type PlayingCardProps,\n type CardBackProps,\n type HoleCardsProps,\n type CommunityCardsProps,\n type CardSize,\n} from \"./components/PlayingCard\";\n\nexport { Seat, type SeatProps } from \"./components/Seat\";\nexport { Pot, type PotProps } from \"./components/Pot\";\nexport { CardTable, type CardTableProps } from \"./components/CardTable\";\n\n// Back-compat alias (the table is now game-agnostic).\nexport { CardTable as PokerTable, type CardTableProps as PokerTableProps } from \"./components/CardTable\";\n","export function cn(...parts: Array<string | false | null | undefined>): string {\n return parts.filter(Boolean).join(\" \");\n}\n","/** Big Blind value used for stack display. */\nexport const BB = 20;\n\n/** Format a chip amount as a Big-Blind string, e.g. 1234 -> \"61.7 BB\". */\nexport function formatBB(amount: number): string {\n const bb = amount / BB;\n return `${Number.isInteger(bb) ? bb : bb.toFixed(1)} BB`;\n}\n","/**\n * Seat-ring helpers. Pure functions — they contain no game-specific rules.\n */\n\nexport interface NextSeatOptions {\n /** +1 for clockwise, -1 for counter-clockwise. Default +1. */\n dir?: 1 | -1;\n /** Return true to skip a seat (e.g. folded / empty / out). */\n skip?: (index: number) => boolean;\n}\n\n/**\n * Return the index of the next seat on the ring after `current`, skipping any\n * seat matched by `opts.skip`. Wraps around. Returns `current` if every other\n * seat is skipped.\n */\nexport function nextSeatIndex(current: number, seatCount: number, opts: NextSeatOptions = {}): number {\n const n = Math.max(1, Math.floor(seatCount));\n const dir = opts.dir ?? 1;\n const skip = opts.skip;\n const start = ((Math.floor(current) % n) + n) % n;\n\n let i = start;\n for (let step = 0; step < n - 1; step++) {\n i = (i + dir + n) % n;\n if (!skip || !skip(i)) return i;\n }\n return start;\n}\n","import { cn } from \"../cn\";\n\nexport type CardSize = \"xs\" | \"sm\" | \"md\" | \"lg\";\n\nconst SIZE_CLASSES: Record<CardSize, string> = {\n xs: \"w-8 h-11\",\n sm: \"w-10 h-14\",\n md: \"w-16 h-22\",\n lg: \"w-24 h-32\",\n};\n\nconst CORNER_RANK: Record<CardSize, string> = {\n xs: \"text-[11px]\",\n sm: \"text-sm\",\n md: \"text-lg\",\n lg: \"text-2xl\",\n};\nconst CORNER_SUIT: Record<CardSize, string> = {\n xs: \"text-[9px]\",\n sm: \"text-xs\",\n md: \"text-sm\",\n lg: \"text-base\",\n};\nconst CENTER_SUIT: Record<CardSize, string> = {\n xs: \"text-base\",\n sm: \"text-xl\",\n md: \"text-3xl\",\n lg: \"text-5xl\",\n};\nconst BACK_EMBLEM_SIZE: Record<CardSize, number> = {\n xs: 14,\n sm: 18,\n md: 28,\n lg: 42,\n};\n\n/** Parse a compact card code like \"As\", \"10h\", \"kd\" into rank + suit. */\nexport function parseCard(card: string): { rank: string; suit: string } {\n if (!card || card.length < 2) return { rank: \"\", suit: \"\" };\n return { rank: card.slice(0, -1), suit: card.slice(-1) };\n}\n\nexport function getSuitSymbol(suit: string): string {\n switch (suit) {\n case \"s\": return \"♠\";\n case \"h\": return \"♥\";\n case \"d\": return \"♦\";\n case \"c\": return \"♣\";\n default: return suit;\n }\n}\n\nfunction getSuitClass(suit: string): string {\n switch (suit) {\n case \"s\":\n case \"c\":\n return \"text-neutral-900\";\n case \"h\":\n case \"d\":\n return \"text-red-600\";\n default:\n return \"\";\n }\n}\n\nexport interface PlayingCardProps {\n /** Compact card code (\"As\", \"10h\") or null to render a face-down card back. */\n card: string | null;\n size?: CardSize;\n className?: string;\n}\n\n/**\n * A single playing card. Size is driven by the `size` prop (never viewport\n * breakpoints) so cards scale predictably inside any container.\n */\nexport function PlayingCard({ card, size = \"md\", className }: PlayingCardProps) {\n if (!card) return <CardBack size={size} className={className} />;\n\n const { rank, suit } = parseCard(card);\n const suitSymbol = getSuitSymbol(suit);\n const suitClass = getSuitClass(suit);\n const showCenter = size === \"md\" || size === \"lg\";\n\n return (\n <div\n className={cn(\n SIZE_CLASSES[size],\n \"relative bg-gradient-to-br from-white via-white to-neutral-100\",\n \"rounded-md border border-neutral-300/90 overflow-hidden\",\n \"shadow-[0_2px_6px_rgba(0,0,0,0.45),inset_0_1px_0_rgba(255,255,255,0.9)]\",\n className,\n )}\n style={{ aspectRatio: \"10/14\" }}\n >\n <div className={cn(\"absolute top-[3px] left-[5px] flex flex-col items-center leading-none\", suitClass)}>\n <span className={cn(CORNER_RANK[size], \"font-bold tracking-tight\")}>{rank}</span>\n <span className={cn(CORNER_SUIT[size], \"-mt-[1px]\")}>{suitSymbol}</span>\n </div>\n <div className={cn(\"absolute bottom-[3px] right-[5px] flex flex-col items-center leading-none rotate-180\", suitClass)}>\n <span className={cn(CORNER_RANK[size], \"font-bold tracking-tight\")}>{rank}</span>\n <span className={cn(CORNER_SUIT[size], \"-mt-[1px]\")}>{suitSymbol}</span>\n </div>\n {showCenter && (\n <div className={cn(\"absolute inset-0 flex items-center justify-center opacity-90\", suitClass)}>\n <span className={CENTER_SUIT[size]}>{suitSymbol}</span>\n </div>\n )}\n </div>\n );\n}\n\nexport interface CardBackProps {\n size?: CardSize;\n className?: string;\n}\n\nfunction CardBackEmblem({ size }: { size: CardSize }) {\n const s = BACK_EMBLEM_SIZE[size];\n return (\n <svg width={s} height={s} viewBox=\"0 0 40 40\" className=\"drop-shadow-[0_1px_2px_rgba(0,0,0,0.6)]\" aria-hidden=\"true\">\n <g fill=\"#f0c04a\" opacity=\"0.92\">\n <ellipse cx=\"20\" cy=\"11\" rx=\"4\" ry=\"7\" />\n <ellipse cx=\"20\" cy=\"29\" rx=\"4\" ry=\"7\" />\n <ellipse cx=\"11\" cy=\"20\" rx=\"7\" ry=\"4\" />\n <ellipse cx=\"29\" cy=\"20\" rx=\"7\" ry=\"4\" />\n </g>\n <circle cx=\"20\" cy=\"20\" r=\"3.2\" fill=\"#5a0c18\" />\n </svg>\n );\n}\n\n/** The face-down card back (dark red + gold rosette). */\nexport function CardBack({ size = \"md\", className }: CardBackProps) {\n return (\n <div\n className={cn(\n SIZE_CLASSES[size],\n \"relative rounded-md p-[3px] overflow-hidden\",\n \"bg-gradient-to-br from-[#7a1020] via-[#5a0c18] to-[#3f0810]\",\n \"border-2 border-[#c9a24b]/70 shadow-[0_2px_6px_rgba(0,0,0,0.45)]\",\n className,\n )}\n style={{ aspectRatio: \"10/14\" }}\n >\n <div className=\"h-full w-full rounded-[4px] border border-white/20 flex items-center justify-center bg-[repeating-linear-gradient(45deg,rgba(255,255,255,0.09)_0_6px,transparent_6px_12px),repeating-linear-gradient(-45deg,rgba(255,255,255,0.06)_0_6px,transparent_6px_12px)]\">\n <CardBackEmblem size={size} />\n </div>\n </div>\n );\n}\n\nexport interface HoleCardsProps {\n cards: (string | null)[];\n size?: CardSize;\n className?: string;\n}\n\n/** A row of a player's two hole cards. */\nexport function HoleCards({ cards, size = \"lg\", className }: HoleCardsProps) {\n return (\n <div className={cn(\"flex gap-2\", className)}>\n {cards.map((card, i) => (\n <PlayingCard key={i} card={card} size={size} />\n ))}\n </div>\n );\n}\n\nexport interface CommunityCardsProps {\n cards: (string | null)[];\n size?: CardSize;\n className?: string;\n}\n\n/** The five community cards, each dealt with a staggered flip animation. */\nexport function CommunityCards({ cards, size = \"md\", className }: CommunityCardsProps) {\n return (\n <div className={cn(\"flex gap-1.5\", className)}>\n {cards.map((card, i) => (\n <div key={i} className=\"animate-card-deal\" style={{ animationDelay: `${i * 90}ms` }}>\n <PlayingCard card={card} size={size} />\n </div>\n ))}\n </div>\n );\n}\n","import type { ReactNode } from \"react\";\nimport { cn } from \"../cn\";\n\nexport interface SeatProps {\n /** Turn/active highlight (generic). */\n isActive?: boolean;\n /** Winner highlight (generic). */\n isWinner?: boolean;\n /** Dimmed, e.g. folded or out (generic). */\n isDimmed?: boolean;\n /** Optional click handler (e.g. \"sit here\"). */\n onClick?: () => void;\n\n /** Slot: top row — name / badges. */\n header?: ReactNode;\n /** Slot: top-right floating element — e.g. a dealer button. */\n badge?: ReactNode;\n /** Slot: center content — 0..N cards, avatar, anything. */\n children?: ReactNode;\n /** Slot: bottom row — stack / bet / status. */\n footer?: ReactNode;\n /** Slot: overlay — e.g. an \"All-in\" label. */\n overlay?: ReactNode;\n\n width?: number;\n height?: number;\n className?: string;\n}\n\n/**\n * A neutral seat: a frame with generic visual states and named slots.\n * It knows nothing about any particular game — put whatever you want into\n * `header` / `children` / `footer` / `badge` / `overlay`.\n */\nexport function Seat({\n isActive = false,\n isWinner = false,\n isDimmed = false,\n onClick,\n header,\n badge,\n children,\n footer,\n overlay,\n width = 160,\n height = 176,\n className,\n}: SeatProps) {\n return (\n <div className={cn(\"relative\", className)} style={{ width, height }}>\n <div\n role={onClick ? \"button\" : undefined}\n tabIndex={onClick ? 0 : undefined}\n onClick={onClick}\n onKeyDown={\n onClick\n ? (e) => {\n if (e.key === \"Enter\" || e.key === \" \") onClick();\n }\n : undefined\n }\n className={cn(\n \"flex h-full w-full flex-col items-center justify-between gap-1 rounded-2xl border bg-neutral-900/80 p-2 text-neutral-100 backdrop-blur transition\",\n isActive ? \"border-cyan-400 shadow-[0_0_18px_rgba(34,211,238,0.55)]\" : \"border-neutral-700\",\n isWinner && \"border-amber-400 shadow-[0_0_18px_rgba(251,191,36,0.6)]\",\n isDimmed && \"opacity-50\",\n onClick && \"cursor-pointer hover:border-neutral-500 focus:outline-none focus-visible:ring-2 focus-visible:ring-cyan-400\",\n )}\n >\n {header !== undefined && (\n <div className=\"flex w-full items-center justify-between gap-1 text-[11px]\">{header}</div>\n )}\n <div className=\"flex flex-1 items-center justify-center\">{children}</div>\n {footer !== undefined && (\n <div className=\"flex w-full items-center justify-between text-[11px] text-neutral-300\">{footer}</div>\n )}\n </div>\n\n {badge !== undefined && <div className=\"absolute -top-1 -right-1\">{badge}</div>}\n {overlay !== undefined && (\n <div className=\"pointer-events-none absolute inset-x-0 top-1/2 -translate-y-1/2 text-center\">{overlay}</div>\n )}\n </div>\n );\n}\n","import { cn } from \"../cn\";\nimport { formatBB } from \"../format\";\n\nexport interface PotProps {\n amount: number;\n /** Optional label, e.g. \"Main\" / \"Side\". */\n label?: string;\n /** Formatter for the amount; defaults to Big-Blind formatting. */\n format?: (amount: number) => string;\n className?: string;\n}\n\n/** A small, standalone pot pill. Optional — the table does not require it. */\nexport function Pot({ amount, label, format = formatBB, className }: PotProps) {\n return (\n <div className={cn(\"flex items-center gap-2 font-mono text-sm text-amber-400\", className)}>\n {label && <span className=\"text-neutral-400\">{label}</span>}\n <span className=\"whitespace-nowrap rounded-full border border-amber-400/50 bg-neutral-900/80 px-3 py-1 shadow-inner\">\n {format(amount)}\n </span>\n </div>\n );\n}\n","import { useRef, useLayoutEffect, useState, useMemo, type ReactNode } from \"react\";\nimport { cn } from \"../cn\";\n\nexport interface CardTableProps {\n /** Number of seats (2–10). Default 6. */\n seatCount?: number;\n /** Optional per-seat content. Length should equal `seatCount`. */\n seats?: (ReactNode | null)[];\n /** Alternative to `seats`: render a seat by index. */\n renderSeat?: (index: number, isYou: boolean) => ReactNode;\n /** Which seat belongs to the viewer; rotates that seat to the bottom. */\n yourSeatIndex?: number | null;\n /** Optional content in the middle of the table (cards, pot, anything). */\n center?: ReactNode;\n /** Seat frame size used for layout. */\n seatWidth?: number;\n seatHeight?: number;\n className?: string;\n}\n\nconst DESIGN_W = 1200;\nconst DESIGN_H = 900;\nconst MARGIN_X = 164;\nconst MARGIN_Y = 196;\nconst TABLE_RX = DESIGN_W / 2 - MARGIN_X;\nconst TABLE_RY = DESIGN_H / 2 - MARGIN_Y;\nconst FELT_GAP = 11;\nconst BET_SCALE = 0.72;\nconst BET_RX = TABLE_RX * BET_SCALE;\nconst BET_RY = TABLE_RY * BET_SCALE;\n\ninterface SeatLayout {\n x: number;\n y: number;\n index: number;\n isYou: boolean;\n}\n\nfunction getEllipsePoint(cx: number, cy: number, rx: number, ry: number, angleDeg: number) {\n const rad = (angleDeg * Math.PI) / 180;\n return { x: cx + rx * Math.cos(rad), y: cy + ry * Math.sin(rad) };\n}\n\nfunction calculateSeatLayouts(\n seatCount: number,\n yourSeatIndex: number | null,\n seatWidth: number,\n seatHeight: number,\n): SeatLayout[] {\n const cx = DESIGN_W / 2;\n const cy = DESIGN_H / 2;\n const ringX = TABLE_RX - FELT_GAP + seatWidth / 2 + 6;\n const ringY = TABLE_RY - FELT_GAP + seatHeight / 2 + 6;\n\n // Even angular distribution, first seat at the top (-90°).\n const step = 360 / seatCount;\n const angleOf = (i: number) => -90 + i * step;\n\n // Rotate so the viewer's seat sits at the bottom (90°).\n const rotationOffset = yourSeatIndex !== null ? 90 - angleOf(yourSeatIndex) : 0;\n\n const layouts: SeatLayout[] = [];\n for (let i = 0; i < seatCount; i++) {\n const pt = getEllipsePoint(cx, cy, ringX, ringY, angleOf(i) + rotationOffset);\n layouts.push({\n x: pt.x - seatWidth / 2,\n y: pt.y - seatHeight / 2,\n index: i,\n isYou: i === yourSeatIndex,\n });\n }\n return layouts;\n}\n\n/**\n * A responsive, game-agnostic card table. It scales a fixed 1200x900 design\n * onto its container and arranges `seatCount` seats around the felt. Purely\n * presentational: supply seat content and an optional `center` slot.\n */\nexport function CardTable({\n seatCount = 6,\n seats,\n renderSeat,\n yourSeatIndex = null,\n center,\n seatWidth = 160,\n seatHeight = 176,\n className,\n}: CardTableProps) {\n const count = Math.max(2, Math.min(10, Math.floor(seatCount)));\n const outerRef = useRef<HTMLDivElement>(null);\n const [scale, setScale] = useState(1);\n\n const layouts = useMemo(\n () => calculateSeatLayouts(count, yourSeatIndex, seatWidth, seatHeight),\n [count, yourSeatIndex, seatWidth, seatHeight],\n );\n\n useLayoutEffect(() => {\n const el = outerRef.current;\n if (!el) return;\n const compute = () => {\n const w = el.clientWidth;\n const h = el.clientHeight;\n if (!w || !h) return;\n setScale(Math.min(w / DESIGN_W, h / DESIGN_H));\n };\n compute();\n const ro = new ResizeObserver(compute);\n ro.observe(el);\n return () => ro.disconnect();\n }, []);\n\n const tableFrameInset = { inset: `${MARGIN_Y}px ${MARGIN_X}px` };\n const feltInset = { inset: `${MARGIN_Y + FELT_GAP}px ${MARGIN_X + FELT_GAP}px` };\n const rimInset = { inset: `${MARGIN_Y + 6}px ${MARGIN_X + 6}px` };\n const betLineInset = {\n inset: `${MARGIN_Y + (TABLE_RY - BET_RY)}px ${MARGIN_X + (TABLE_RX - BET_RX)}px`,\n };\n\n return (\n <div\n ref={outerRef}\n className={cn(\"relative flex h-full w-full min-h-0 items-center justify-center overflow-hidden\", className)}\n >\n <div style={{ width: DESIGN_W * scale, height: DESIGN_H * scale }}>\n <div\n className=\"relative\"\n style={{ width: DESIGN_W, height: DESIGN_H, transform: `scale(${scale})`, transformOrigin: \"top left\" }}\n >\n <div\n className=\"absolute -inset-6 rounded-[50%] bg-[radial-gradient(closest-side,rgba(240,192,74,0.16),transparent_72%)] blur-3xl\"\n aria-hidden=\"true\"\n />\n\n {/* wooden rail */}\n <div\n className=\"absolute rounded-[50%] bg-gradient-to-b from-[#a5744a] via-[#6b3f22] to-[#38200f] shadow-[0_26px_70px_-12px_rgba(0,0,0,0.85),inset_0_3px_0_rgba(255,255,255,0.20),inset_0_-6px_16px_rgba(0,0,0,0.5)]\"\n style={tableFrameInset}\n aria-hidden=\"true\"\n />\n\n {/* felt */}\n <div\n className=\"absolute rounded-[50%] bg-[radial-gradient(ellipse_at_50%_32%,#23804b,#0f4d2e_55%,#093b22)] shadow-[inset_0_0_90px_rgba(0,0,0,0.55),inset_0_3px_10px_rgba(255,255,255,0.06)]\"\n style={feltInset}\n aria-hidden=\"true\"\n />\n\n {/* betting line */}\n <div\n className=\"absolute rounded-[50%] border-2 border-white/10 shadow-[inset_0_0_0_1px_rgba(0,0,0,0.25)]\"\n style={betLineInset}\n aria-hidden=\"true\"\n />\n\n {/* center suit watermark */}\n <div className=\"absolute flex items-center justify-center pointer-events-none\" style={feltInset} aria-hidden=\"true\">\n <span className=\"text-white/[0.05] text-[110px] font-black tracking-[0.2em] select-none leading-none\">\n ♠♥♦♣\n </span>\n </div>\n\n {/* center slot (optional) */}\n {center !== undefined && (\n <div className=\"pointer-events-none absolute inset-0 flex flex-col items-center justify-center\">\n {center}\n </div>\n )}\n\n {/* seats */}\n <div className=\"absolute inset-0 z-20\">\n {layouts.map(({ x, y, index, isYou }) => {\n const content = seats ? (seats[index] ?? null) : renderSeat ? renderSeat(index, isYou) : null;\n if (content === null) return null;\n return (\n <div key={index} className=\"absolute\" style={{ left: `${x}px`, top: `${y}px` }}>\n {content}\n </div>\n );\n })}\n </div>\n\n {/* outer rim highlight */}\n <div className=\"absolute rounded-[50%] border border-white/10 pointer-events-none\" style={rimInset} aria-hidden=\"true\" />\n </div>\n </div>\n </div>\n );\n}\n"],"mappings":"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","names":["import_jsx_runtime","import_jsx_runtime","import_jsx_runtime"]}
|