@beta-gamer/react 0.1.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.
@@ -0,0 +1,133 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+ import * as socket_io_client from 'socket.io-client';
4
+ import { Socket } from 'socket.io-client';
5
+ import * as _socket_io_component_emitter from '@socket.io/component-emitter';
6
+
7
+ type GameType = 'chess' | 'checkers' | 'connect4' | 'tictactoe' | 'subway-runner';
8
+ type MatchType = 'matchmaking' | 'private' | 'bot';
9
+ type SessionMode = 'live' | 'test' | 'training';
10
+ type SessionStatus = 'pending' | 'active' | 'ended';
11
+ interface SessionPlayer {
12
+ id: string;
13
+ displayName: string;
14
+ }
15
+ interface SessionTheme {
16
+ primaryColor?: string;
17
+ backgroundColor?: string;
18
+ boardLight?: string;
19
+ boardDark?: string;
20
+ fontFamily?: string;
21
+ [key: string]: string | undefined;
22
+ }
23
+ interface SessionTokenPayload {
24
+ sessionId: string;
25
+ game: GameType;
26
+ mode: SessionMode;
27
+ matchType: MatchType;
28
+ tenantId: string;
29
+ players: SessionPlayer[];
30
+ roomCode: string | null;
31
+ theme: SessionTheme;
32
+ exp: number;
33
+ }
34
+ interface GameState {
35
+ status: SessionStatus;
36
+ players: SessionPlayer[];
37
+ currentTurn?: string;
38
+ winner?: string | null;
39
+ reason?: string;
40
+ }
41
+
42
+ interface BetaGamerContextValue {
43
+ token: string;
44
+ session: SessionTokenPayload;
45
+ socket: Socket | null;
46
+ gameState: GameState;
47
+ theme: SessionTheme;
48
+ }
49
+ interface BetaGamerProviderProps {
50
+ token: string;
51
+ serverUrl?: string;
52
+ children: React.ReactNode;
53
+ }
54
+ declare function BetaGamerProvider({ token, serverUrl, children }: BetaGamerProviderProps): react_jsx_runtime.JSX.Element;
55
+ declare function useBetaGamer(): BetaGamerContextValue;
56
+
57
+ /** Returns the current game state (status, players, winner, etc.) */
58
+ declare function useGameState(): GameState;
59
+ /** Returns the decoded session payload (game, mode, matchType, players, theme) */
60
+ declare function useSession(): SessionTokenPayload;
61
+ /** Returns the raw Socket.IO socket — for advanced custom event handling */
62
+ declare function useSocket(): socket_io_client.Socket<_socket_io_component_emitter.DefaultEventsMap, _socket_io_component_emitter.DefaultEventsMap> | null;
63
+ /** Returns the active theme tokens */
64
+ declare function useTheme(): SessionTheme;
65
+
66
+ interface PlayerCardProps {
67
+ /** 'self' = players[0], 'opponent' = players[1] */
68
+ player: 'self' | 'opponent';
69
+ className?: string;
70
+ }
71
+ /** Displays a player's name and active-turn indicator. Style it however you want. */
72
+ declare function PlayerCard({ player, className }: PlayerCardProps): react_jsx_runtime.JSX.Element | null;
73
+
74
+ interface TimerProps {
75
+ /** Which player's clock to show */
76
+ player: 'self' | 'opponent';
77
+ /** Initial seconds on the clock (e.g. 600 for 10 min) */
78
+ initialSeconds?: number;
79
+ className?: string;
80
+ }
81
+ /** A live countdown clock that syncs with the server via socket events. */
82
+ declare function Timer({ player, initialSeconds, className }: TimerProps): react_jsx_runtime.JSX.Element;
83
+
84
+ interface ChessBoardProps {
85
+ className?: string;
86
+ }
87
+ /**
88
+ * The chess board — handles all game rendering and move input internally.
89
+ * Place this anywhere in your layout. Style the container via className.
90
+ */
91
+ declare function ChessBoard({ className }: ChessBoardProps): react_jsx_runtime.JSX.Element | null;
92
+
93
+ interface ChessMoveHistoryProps {
94
+ className?: string;
95
+ }
96
+ /** Scrollable move history in algebraic notation. Syncs via socket. */
97
+ declare function ChessMoveHistory({ className }: ChessMoveHistoryProps): react_jsx_runtime.JSX.Element;
98
+
99
+ interface ChessCapturedPiecesProps {
100
+ player: 'self' | 'opponent';
101
+ className?: string;
102
+ }
103
+ /** Shows pieces captured by the given player. */
104
+ declare function ChessCapturedPieces({ player, className }: ChessCapturedPiecesProps): react_jsx_runtime.JSX.Element;
105
+
106
+ declare function CheckersBoard({ className }: {
107
+ className?: string;
108
+ }): react_jsx_runtime.JSX.Element | null;
109
+
110
+ declare function Connect4Board({ className }: {
111
+ className?: string;
112
+ }): react_jsx_runtime.JSX.Element | null;
113
+ declare function Connect4Score({ className }: {
114
+ className?: string;
115
+ }): react_jsx_runtime.JSX.Element;
116
+
117
+ declare function TictactoeBoard({ className }: {
118
+ className?: string;
119
+ }): react_jsx_runtime.JSX.Element | null;
120
+
121
+ declare function SubwayRunnerScore({ className }: {
122
+ className?: string;
123
+ }): react_jsx_runtime.JSX.Element;
124
+
125
+ declare function SubwayRunnerLives({ className }: {
126
+ className?: string;
127
+ }): react_jsx_runtime.JSX.Element;
128
+
129
+ declare function SubwayRunnerGame({ className }: {
130
+ className?: string;
131
+ }): react_jsx_runtime.JSX.Element | null;
132
+
133
+ export { BetaGamerProvider, CheckersBoard, ChessBoard, ChessCapturedPieces, ChessMoveHistory, Connect4Board, Connect4Score, type GameState, type GameType, type MatchType, PlayerCard, type SessionMode, type SessionPlayer, type SessionStatus, type SessionTheme, type SessionTokenPayload, SubwayRunnerGame, SubwayRunnerLives, SubwayRunnerScore, TictactoeBoard, Timer, useBetaGamer, useGameState, useSession, useSocket, useTheme };
@@ -0,0 +1,133 @@
1
+ import * as react_jsx_runtime from 'react/jsx-runtime';
2
+ import React from 'react';
3
+ import * as socket_io_client from 'socket.io-client';
4
+ import { Socket } from 'socket.io-client';
5
+ import * as _socket_io_component_emitter from '@socket.io/component-emitter';
6
+
7
+ type GameType = 'chess' | 'checkers' | 'connect4' | 'tictactoe' | 'subway-runner';
8
+ type MatchType = 'matchmaking' | 'private' | 'bot';
9
+ type SessionMode = 'live' | 'test' | 'training';
10
+ type SessionStatus = 'pending' | 'active' | 'ended';
11
+ interface SessionPlayer {
12
+ id: string;
13
+ displayName: string;
14
+ }
15
+ interface SessionTheme {
16
+ primaryColor?: string;
17
+ backgroundColor?: string;
18
+ boardLight?: string;
19
+ boardDark?: string;
20
+ fontFamily?: string;
21
+ [key: string]: string | undefined;
22
+ }
23
+ interface SessionTokenPayload {
24
+ sessionId: string;
25
+ game: GameType;
26
+ mode: SessionMode;
27
+ matchType: MatchType;
28
+ tenantId: string;
29
+ players: SessionPlayer[];
30
+ roomCode: string | null;
31
+ theme: SessionTheme;
32
+ exp: number;
33
+ }
34
+ interface GameState {
35
+ status: SessionStatus;
36
+ players: SessionPlayer[];
37
+ currentTurn?: string;
38
+ winner?: string | null;
39
+ reason?: string;
40
+ }
41
+
42
+ interface BetaGamerContextValue {
43
+ token: string;
44
+ session: SessionTokenPayload;
45
+ socket: Socket | null;
46
+ gameState: GameState;
47
+ theme: SessionTheme;
48
+ }
49
+ interface BetaGamerProviderProps {
50
+ token: string;
51
+ serverUrl?: string;
52
+ children: React.ReactNode;
53
+ }
54
+ declare function BetaGamerProvider({ token, serverUrl, children }: BetaGamerProviderProps): react_jsx_runtime.JSX.Element;
55
+ declare function useBetaGamer(): BetaGamerContextValue;
56
+
57
+ /** Returns the current game state (status, players, winner, etc.) */
58
+ declare function useGameState(): GameState;
59
+ /** Returns the decoded session payload (game, mode, matchType, players, theme) */
60
+ declare function useSession(): SessionTokenPayload;
61
+ /** Returns the raw Socket.IO socket — for advanced custom event handling */
62
+ declare function useSocket(): socket_io_client.Socket<_socket_io_component_emitter.DefaultEventsMap, _socket_io_component_emitter.DefaultEventsMap> | null;
63
+ /** Returns the active theme tokens */
64
+ declare function useTheme(): SessionTheme;
65
+
66
+ interface PlayerCardProps {
67
+ /** 'self' = players[0], 'opponent' = players[1] */
68
+ player: 'self' | 'opponent';
69
+ className?: string;
70
+ }
71
+ /** Displays a player's name and active-turn indicator. Style it however you want. */
72
+ declare function PlayerCard({ player, className }: PlayerCardProps): react_jsx_runtime.JSX.Element | null;
73
+
74
+ interface TimerProps {
75
+ /** Which player's clock to show */
76
+ player: 'self' | 'opponent';
77
+ /** Initial seconds on the clock (e.g. 600 for 10 min) */
78
+ initialSeconds?: number;
79
+ className?: string;
80
+ }
81
+ /** A live countdown clock that syncs with the server via socket events. */
82
+ declare function Timer({ player, initialSeconds, className }: TimerProps): react_jsx_runtime.JSX.Element;
83
+
84
+ interface ChessBoardProps {
85
+ className?: string;
86
+ }
87
+ /**
88
+ * The chess board — handles all game rendering and move input internally.
89
+ * Place this anywhere in your layout. Style the container via className.
90
+ */
91
+ declare function ChessBoard({ className }: ChessBoardProps): react_jsx_runtime.JSX.Element | null;
92
+
93
+ interface ChessMoveHistoryProps {
94
+ className?: string;
95
+ }
96
+ /** Scrollable move history in algebraic notation. Syncs via socket. */
97
+ declare function ChessMoveHistory({ className }: ChessMoveHistoryProps): react_jsx_runtime.JSX.Element;
98
+
99
+ interface ChessCapturedPiecesProps {
100
+ player: 'self' | 'opponent';
101
+ className?: string;
102
+ }
103
+ /** Shows pieces captured by the given player. */
104
+ declare function ChessCapturedPieces({ player, className }: ChessCapturedPiecesProps): react_jsx_runtime.JSX.Element;
105
+
106
+ declare function CheckersBoard({ className }: {
107
+ className?: string;
108
+ }): react_jsx_runtime.JSX.Element | null;
109
+
110
+ declare function Connect4Board({ className }: {
111
+ className?: string;
112
+ }): react_jsx_runtime.JSX.Element | null;
113
+ declare function Connect4Score({ className }: {
114
+ className?: string;
115
+ }): react_jsx_runtime.JSX.Element;
116
+
117
+ declare function TictactoeBoard({ className }: {
118
+ className?: string;
119
+ }): react_jsx_runtime.JSX.Element | null;
120
+
121
+ declare function SubwayRunnerScore({ className }: {
122
+ className?: string;
123
+ }): react_jsx_runtime.JSX.Element;
124
+
125
+ declare function SubwayRunnerLives({ className }: {
126
+ className?: string;
127
+ }): react_jsx_runtime.JSX.Element;
128
+
129
+ declare function SubwayRunnerGame({ className }: {
130
+ className?: string;
131
+ }): react_jsx_runtime.JSX.Element | null;
132
+
133
+ export { BetaGamerProvider, CheckersBoard, ChessBoard, ChessCapturedPieces, ChessMoveHistory, Connect4Board, Connect4Score, type GameState, type GameType, type MatchType, PlayerCard, type SessionMode, type SessionPlayer, type SessionStatus, type SessionTheme, type SessionTokenPayload, SubwayRunnerGame, SubwayRunnerLives, SubwayRunnerScore, TictactoeBoard, Timer, useBetaGamer, useGameState, useSession, useSocket, useTheme };
package/dist/index.js ADDED
@@ -0,0 +1,368 @@
1
+ "use strict";
2
+ var __defProp = Object.defineProperty;
3
+ var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
4
+ var __getOwnPropNames = Object.getOwnPropertyNames;
5
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
6
+ var __export = (target, all) => {
7
+ for (var name in all)
8
+ __defProp(target, name, { get: all[name], enumerable: true });
9
+ };
10
+ var __copyProps = (to, from, except, desc) => {
11
+ if (from && typeof from === "object" || typeof from === "function") {
12
+ for (let key of __getOwnPropNames(from))
13
+ if (!__hasOwnProp.call(to, key) && key !== except)
14
+ __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
15
+ }
16
+ return to;
17
+ };
18
+ var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
19
+
20
+ // src/index.ts
21
+ var index_exports = {};
22
+ __export(index_exports, {
23
+ BetaGamerProvider: () => BetaGamerProvider,
24
+ CheckersBoard: () => CheckersBoard,
25
+ ChessBoard: () => ChessBoard,
26
+ ChessCapturedPieces: () => ChessCapturedPieces,
27
+ ChessMoveHistory: () => ChessMoveHistory,
28
+ Connect4Board: () => Connect4Board,
29
+ Connect4Score: () => Connect4Score,
30
+ PlayerCard: () => PlayerCard,
31
+ SubwayRunnerGame: () => SubwayRunnerGame,
32
+ SubwayRunnerLives: () => SubwayRunnerLives,
33
+ SubwayRunnerScore: () => SubwayRunnerScore,
34
+ TictactoeBoard: () => TictactoeBoard,
35
+ Timer: () => Timer,
36
+ useBetaGamer: () => useBetaGamer,
37
+ useGameState: () => useGameState,
38
+ useSession: () => useSession,
39
+ useSocket: () => useSocket,
40
+ useTheme: () => useTheme
41
+ });
42
+ module.exports = __toCommonJS(index_exports);
43
+
44
+ // src/context/BetaGamerProvider.tsx
45
+ var import_react = require("react");
46
+ var import_socket = require("socket.io-client");
47
+ var import_jsx_runtime = require("react/jsx-runtime");
48
+ function decodeToken(token) {
49
+ const payload = token.split(".")[1];
50
+ return JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/")));
51
+ }
52
+ var BetaGamerContext = (0, import_react.createContext)(null);
53
+ function BetaGamerProvider({ token, serverUrl = "https://api.beta-gamer.com", children }) {
54
+ const session = decodeToken(token);
55
+ const socketRef = (0, import_react.useRef)(null);
56
+ const [gameState, setGameState] = (0, import_react.useState)({
57
+ status: "pending",
58
+ players: session.players
59
+ });
60
+ (0, import_react.useEffect)(() => {
61
+ const socket = (0, import_socket.io)(`${serverUrl}/${session.game}`, {
62
+ auth: { token },
63
+ transports: ["websocket"]
64
+ });
65
+ socketRef.current = socket;
66
+ socket.on("game:state", (state) => {
67
+ setGameState((prev) => ({ ...prev, ...state }));
68
+ });
69
+ socket.on("game:ended", ({ winner, reason }) => {
70
+ setGameState((prev) => ({ ...prev, status: "ended", winner, reason }));
71
+ });
72
+ const root = document.documentElement;
73
+ const t = session.theme ?? {};
74
+ if (t.primaryColor) root.style.setProperty("--bg-primary", t.primaryColor);
75
+ if (t.backgroundColor) root.style.setProperty("--bg-surface", t.backgroundColor);
76
+ if (t.boardLight) root.style.setProperty("--board-light", t.boardLight);
77
+ if (t.boardDark) root.style.setProperty("--board-dark", t.boardDark);
78
+ if (t.fontFamily) root.style.setProperty("--font-family", t.fontFamily);
79
+ return () => {
80
+ socket.disconnect();
81
+ };
82
+ }, [token, serverUrl, session.game]);
83
+ return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(BetaGamerContext.Provider, { value: { token, session, socket: socketRef.current, gameState, theme: session.theme ?? {} }, children });
84
+ }
85
+ function useBetaGamer() {
86
+ const ctx = (0, import_react.useContext)(BetaGamerContext);
87
+ if (!ctx) throw new Error("useBetaGamer must be used inside <BetaGamerProvider>");
88
+ return ctx;
89
+ }
90
+
91
+ // src/hooks/index.ts
92
+ function useGameState() {
93
+ return useBetaGamer().gameState;
94
+ }
95
+ function useSession() {
96
+ return useBetaGamer().session;
97
+ }
98
+ function useSocket() {
99
+ return useBetaGamer().socket;
100
+ }
101
+ function useTheme() {
102
+ return useBetaGamer().theme;
103
+ }
104
+
105
+ // src/components/PlayerCard.tsx
106
+ var import_jsx_runtime2 = require("react/jsx-runtime");
107
+ function PlayerCard({ player, className }) {
108
+ const { players } = useSession();
109
+ const { currentTurn } = useGameState();
110
+ const p = player === "self" ? players[0] : players[1];
111
+ if (!p) return null;
112
+ const isActive = currentTurn === p.id;
113
+ return /* @__PURE__ */ (0, import_jsx_runtime2.jsxs)("div", { className, "data-active": isActive, "data-player": player, children: [
114
+ /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { "data-role": "display-name", children: p.displayName }),
115
+ isActive && /* @__PURE__ */ (0, import_jsx_runtime2.jsx)("span", { "data-role": "turn-indicator", "aria-label": "Your turn" })
116
+ ] });
117
+ }
118
+
119
+ // src/components/Timer.tsx
120
+ var import_react2 = require("react");
121
+ var import_jsx_runtime3 = require("react/jsx-runtime");
122
+ function Timer({ player, initialSeconds = 600, className }) {
123
+ const socket = useSocket();
124
+ const { status } = useGameState();
125
+ const [seconds, setSeconds] = (0, import_react2.useState)(initialSeconds);
126
+ (0, import_react2.useEffect)(() => {
127
+ if (!socket) return;
128
+ const handler = (clocks) => {
129
+ if (clocks[player] !== void 0) setSeconds(clocks[player]);
130
+ };
131
+ socket.on("game:clock", handler);
132
+ return () => {
133
+ socket.off("game:clock", handler);
134
+ };
135
+ }, [socket, player]);
136
+ (0, import_react2.useEffect)(() => {
137
+ if (status !== "active") return;
138
+ const id = setInterval(() => setSeconds((s2) => Math.max(0, s2 - 1)), 1e3);
139
+ return () => clearInterval(id);
140
+ }, [status]);
141
+ const m = Math.floor(seconds / 60).toString().padStart(2, "0");
142
+ const s = (seconds % 60).toString().padStart(2, "0");
143
+ return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)("div", { className, "data-player": player, "data-low": seconds < 30, children: /* @__PURE__ */ (0, import_jsx_runtime3.jsxs)("span", { "data-role": "clock", children: [
144
+ m,
145
+ ":",
146
+ s
147
+ ] }) });
148
+ }
149
+
150
+ // src/components/chess/ChessBoard.tsx
151
+ var import_react3 = require("react");
152
+ var import_jsx_runtime4 = require("react/jsx-runtime");
153
+ function ChessBoard({ className }) {
154
+ const { token, session } = useBetaGamer();
155
+ const iframeRef = (0, import_react3.useRef)(null);
156
+ (0, import_react3.useEffect)(() => {
157
+ const iframe = iframeRef.current;
158
+ if (!iframe) return;
159
+ const onLoad = () => iframe.contentWindow?.postMessage({ type: "bg:init", token }, "*");
160
+ iframe.addEventListener("load", onLoad);
161
+ return () => iframe.removeEventListener("load", onLoad);
162
+ }, [token]);
163
+ if (session.game !== "chess") return null;
164
+ return /* @__PURE__ */ (0, import_jsx_runtime4.jsx)(
165
+ "iframe",
166
+ {
167
+ ref: iframeRef,
168
+ src: `${process.env.NEXT_PUBLIC_BETAGAMER_URL ?? "https://api.beta-gamer.com"}/embed/chess`,
169
+ className,
170
+ style: { border: "none", width: "100%", height: "100%" },
171
+ allow: "autoplay",
172
+ title: "Chess"
173
+ }
174
+ );
175
+ }
176
+
177
+ // src/components/chess/ChessMoveHistory.tsx
178
+ var import_react4 = require("react");
179
+ var import_jsx_runtime5 = require("react/jsx-runtime");
180
+ function ChessMoveHistory({ className }) {
181
+ const socket = useSocket();
182
+ const [moves, setMoves] = (0, import_react4.useState)([]);
183
+ (0, import_react4.useEffect)(() => {
184
+ if (!socket) return;
185
+ const handler = ({ san, moveIndex }) => {
186
+ const moveNumber = Math.floor(moveIndex / 2) + 1;
187
+ const isWhite = moveIndex % 2 === 0;
188
+ setMoves((prev) => {
189
+ const next = [...prev];
190
+ if (isWhite) {
191
+ next.push({ moveNumber, white: san });
192
+ } else {
193
+ const last = next[next.length - 1];
194
+ if (last && last.moveNumber === moveNumber) last.black = san;
195
+ }
196
+ return next;
197
+ });
198
+ };
199
+ socket.on("chess:move", handler);
200
+ return () => {
201
+ socket.off("chess:move", handler);
202
+ };
203
+ }, [socket]);
204
+ return /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("div", { className, "data-role": "move-history", children: moves.map(({ moveNumber, white, black }) => /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("div", { "data-role": "move-row", children: [
205
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsxs)("span", { "data-role": "move-number", children: [
206
+ moveNumber,
207
+ "."
208
+ ] }),
209
+ /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "data-role": "move-white", children: white }),
210
+ black && /* @__PURE__ */ (0, import_jsx_runtime5.jsx)("span", { "data-role": "move-black", children: black })
211
+ ] }, moveNumber)) });
212
+ }
213
+
214
+ // src/components/chess/ChessCapturedPieces.tsx
215
+ var import_react5 = require("react");
216
+ var import_jsx_runtime6 = require("react/jsx-runtime");
217
+ var PIECE_SYMBOLS = {
218
+ p: "\u265F",
219
+ n: "\u265E",
220
+ b: "\u265D",
221
+ r: "\u265C",
222
+ q: "\u265B"
223
+ };
224
+ function ChessCapturedPieces({ player, className }) {
225
+ const socket = useSocket();
226
+ const { players } = useGameState();
227
+ const [captured, setCaptured] = (0, import_react5.useState)([]);
228
+ (0, import_react5.useEffect)(() => {
229
+ if (!socket) return;
230
+ const handler = ({ capturedBy, piece }) => {
231
+ const p = player === "self" ? players[0] : players[1];
232
+ if (p && capturedBy === p.id) setCaptured((prev) => [...prev, piece]);
233
+ };
234
+ socket.on("chess:capture", handler);
235
+ return () => {
236
+ socket.off("chess:capture", handler);
237
+ };
238
+ }, [socket, player, players]);
239
+ return /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("div", { className, "data-role": "captured-pieces", "data-player": player, children: captured.map((piece, i) => /* @__PURE__ */ (0, import_jsx_runtime6.jsx)("span", { "data-role": "piece", "data-type": piece, children: PIECE_SYMBOLS[piece.toLowerCase()] ?? piece }, i)) });
240
+ }
241
+
242
+ // src/components/checkers/index.tsx
243
+ var import_react6 = require("react");
244
+ var import_jsx_runtime7 = require("react/jsx-runtime");
245
+ function CheckersBoard({ className }) {
246
+ const { token, session } = useBetaGamer();
247
+ const iframeRef = (0, import_react6.useRef)(null);
248
+ (0, import_react6.useEffect)(() => {
249
+ const iframe = iframeRef.current;
250
+ if (!iframe) return;
251
+ const onLoad = () => iframe.contentWindow?.postMessage({ type: "bg:init", token }, "*");
252
+ iframe.addEventListener("load", onLoad);
253
+ return () => iframe.removeEventListener("load", onLoad);
254
+ }, [token]);
255
+ if (session.game !== "checkers") return null;
256
+ return /* @__PURE__ */ (0, import_jsx_runtime7.jsx)("iframe", { ref: iframeRef, src: `${process.env.NEXT_PUBLIC_BETAGAMER_URL ?? "https://api.beta-gamer.com"}/embed/checkers`, className, style: { border: "none", width: "100%", height: "100%" }, title: "Checkers" });
257
+ }
258
+
259
+ // src/components/connect4/index.tsx
260
+ var import_react7 = require("react");
261
+ var import_jsx_runtime8 = require("react/jsx-runtime");
262
+ function Connect4Board({ className }) {
263
+ const { token, session } = useBetaGamer();
264
+ const iframeRef = (0, import_react7.useRef)(null);
265
+ (0, import_react7.useEffect)(() => {
266
+ const iframe = iframeRef.current;
267
+ if (!iframe) return;
268
+ const onLoad = () => iframe.contentWindow?.postMessage({ type: "bg:init", token }, "*");
269
+ iframe.addEventListener("load", onLoad);
270
+ return () => iframe.removeEventListener("load", onLoad);
271
+ }, [token]);
272
+ if (session.game !== "connect4") return null;
273
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("iframe", { ref: iframeRef, src: `${process.env.NEXT_PUBLIC_BETAGAMER_URL ?? "https://api.beta-gamer.com"}/embed/connect4`, className, style: { border: "none", width: "100%", height: "100%" }, title: "Connect 4" });
274
+ }
275
+ function Connect4Score({ className }) {
276
+ const { gameState } = useBetaGamer();
277
+ const scores = gameState?.scores ?? {};
278
+ return /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("div", { className, "data-role": "score", children: Object.entries(scores).map(([id, score]) => /* @__PURE__ */ (0, import_jsx_runtime8.jsx)("span", { "data-player": id, "data-role": "score-value", children: score }, id)) });
279
+ }
280
+
281
+ // src/components/tictactoe/index.tsx
282
+ var import_react8 = require("react");
283
+ var import_jsx_runtime9 = require("react/jsx-runtime");
284
+ function TictactoeBoard({ className }) {
285
+ const { token, session } = useBetaGamer();
286
+ const iframeRef = (0, import_react8.useRef)(null);
287
+ (0, import_react8.useEffect)(() => {
288
+ const iframe = iframeRef.current;
289
+ if (!iframe) return;
290
+ const onLoad = () => iframe.contentWindow?.postMessage({ type: "bg:init", token }, "*");
291
+ iframe.addEventListener("load", onLoad);
292
+ return () => iframe.removeEventListener("load", onLoad);
293
+ }, [token]);
294
+ if (session.game !== "tictactoe") return null;
295
+ return /* @__PURE__ */ (0, import_jsx_runtime9.jsx)("iframe", { ref: iframeRef, src: `${process.env.NEXT_PUBLIC_BETAGAMER_URL ?? "https://api.beta-gamer.com"}/embed/tictactoe`, className, style: { border: "none", width: "100%", height: "100%" }, title: "Tic-tac-toe" });
296
+ }
297
+
298
+ // src/components/subway-runner/index.tsx
299
+ var import_react11 = require("react");
300
+
301
+ // src/components/subway-runner/SubwayRunnerScore.tsx
302
+ var import_react9 = require("react");
303
+ var import_jsx_runtime10 = require("react/jsx-runtime");
304
+ function SubwayRunnerScore({ className }) {
305
+ const socket = useSocket();
306
+ const [score, setScore] = (0, import_react9.useState)(0);
307
+ (0, import_react9.useEffect)(() => {
308
+ if (!socket) return;
309
+ socket.on("runner:score", (s) => setScore(s));
310
+ return () => {
311
+ socket.off("runner:score");
312
+ };
313
+ }, [socket]);
314
+ return /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("div", { className, "data-role": "score", children: /* @__PURE__ */ (0, import_jsx_runtime10.jsx)("span", { "data-role": "score-value", children: score }) });
315
+ }
316
+
317
+ // src/components/subway-runner/SubwayRunnerLives.tsx
318
+ var import_react10 = require("react");
319
+ var import_jsx_runtime11 = require("react/jsx-runtime");
320
+ function SubwayRunnerLives({ className }) {
321
+ const socket = useSocket();
322
+ const [lives, setLives] = (0, import_react10.useState)(3);
323
+ (0, import_react10.useEffect)(() => {
324
+ if (!socket) return;
325
+ socket.on("runner:lives", (l) => setLives(l));
326
+ return () => {
327
+ socket.off("runner:lives");
328
+ };
329
+ }, [socket]);
330
+ return /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("div", { className, "data-role": "lives", children: Array.from({ length: lives }).map((_, i) => /* @__PURE__ */ (0, import_jsx_runtime11.jsx)("span", { "data-role": "life", "aria-label": "life" }, i)) });
331
+ }
332
+
333
+ // src/components/subway-runner/index.tsx
334
+ var import_jsx_runtime12 = require("react/jsx-runtime");
335
+ function SubwayRunnerGame({ className }) {
336
+ const { token, session } = useBetaGamer();
337
+ const iframeRef = (0, import_react11.useRef)(null);
338
+ (0, import_react11.useEffect)(() => {
339
+ const iframe = iframeRef.current;
340
+ if (!iframe) return;
341
+ const onLoad = () => iframe.contentWindow?.postMessage({ type: "bg:init", token }, "*");
342
+ iframe.addEventListener("load", onLoad);
343
+ return () => iframe.removeEventListener("load", onLoad);
344
+ }, [token]);
345
+ if (session.game !== "subway-runner") return null;
346
+ return /* @__PURE__ */ (0, import_jsx_runtime12.jsx)("iframe", { ref: iframeRef, src: `${process.env.NEXT_PUBLIC_BETAGAMER_URL ?? "https://api.beta-gamer.com"}/embed/subway-runner`, className, style: { border: "none", width: "100%", height: "100%" }, title: "Subway Runner" });
347
+ }
348
+ // Annotate the CommonJS export names for ESM import in node:
349
+ 0 && (module.exports = {
350
+ BetaGamerProvider,
351
+ CheckersBoard,
352
+ ChessBoard,
353
+ ChessCapturedPieces,
354
+ ChessMoveHistory,
355
+ Connect4Board,
356
+ Connect4Score,
357
+ PlayerCard,
358
+ SubwayRunnerGame,
359
+ SubwayRunnerLives,
360
+ SubwayRunnerScore,
361
+ TictactoeBoard,
362
+ Timer,
363
+ useBetaGamer,
364
+ useGameState,
365
+ useSession,
366
+ useSocket,
367
+ useTheme
368
+ });
package/dist/index.mjs ADDED
@@ -0,0 +1,324 @@
1
+ // src/context/BetaGamerProvider.tsx
2
+ import { createContext, useContext, useEffect, useRef, useState } from "react";
3
+ import { io } from "socket.io-client";
4
+ import { jsx } from "react/jsx-runtime";
5
+ function decodeToken(token) {
6
+ const payload = token.split(".")[1];
7
+ return JSON.parse(atob(payload.replace(/-/g, "+").replace(/_/g, "/")));
8
+ }
9
+ var BetaGamerContext = createContext(null);
10
+ function BetaGamerProvider({ token, serverUrl = "https://api.beta-gamer.com", children }) {
11
+ const session = decodeToken(token);
12
+ const socketRef = useRef(null);
13
+ const [gameState, setGameState] = useState({
14
+ status: "pending",
15
+ players: session.players
16
+ });
17
+ useEffect(() => {
18
+ const socket = io(`${serverUrl}/${session.game}`, {
19
+ auth: { token },
20
+ transports: ["websocket"]
21
+ });
22
+ socketRef.current = socket;
23
+ socket.on("game:state", (state) => {
24
+ setGameState((prev) => ({ ...prev, ...state }));
25
+ });
26
+ socket.on("game:ended", ({ winner, reason }) => {
27
+ setGameState((prev) => ({ ...prev, status: "ended", winner, reason }));
28
+ });
29
+ const root = document.documentElement;
30
+ const t = session.theme ?? {};
31
+ if (t.primaryColor) root.style.setProperty("--bg-primary", t.primaryColor);
32
+ if (t.backgroundColor) root.style.setProperty("--bg-surface", t.backgroundColor);
33
+ if (t.boardLight) root.style.setProperty("--board-light", t.boardLight);
34
+ if (t.boardDark) root.style.setProperty("--board-dark", t.boardDark);
35
+ if (t.fontFamily) root.style.setProperty("--font-family", t.fontFamily);
36
+ return () => {
37
+ socket.disconnect();
38
+ };
39
+ }, [token, serverUrl, session.game]);
40
+ return /* @__PURE__ */ jsx(BetaGamerContext.Provider, { value: { token, session, socket: socketRef.current, gameState, theme: session.theme ?? {} }, children });
41
+ }
42
+ function useBetaGamer() {
43
+ const ctx = useContext(BetaGamerContext);
44
+ if (!ctx) throw new Error("useBetaGamer must be used inside <BetaGamerProvider>");
45
+ return ctx;
46
+ }
47
+
48
+ // src/hooks/index.ts
49
+ function useGameState() {
50
+ return useBetaGamer().gameState;
51
+ }
52
+ function useSession() {
53
+ return useBetaGamer().session;
54
+ }
55
+ function useSocket() {
56
+ return useBetaGamer().socket;
57
+ }
58
+ function useTheme() {
59
+ return useBetaGamer().theme;
60
+ }
61
+
62
+ // src/components/PlayerCard.tsx
63
+ import { jsx as jsx2, jsxs } from "react/jsx-runtime";
64
+ function PlayerCard({ player, className }) {
65
+ const { players } = useSession();
66
+ const { currentTurn } = useGameState();
67
+ const p = player === "self" ? players[0] : players[1];
68
+ if (!p) return null;
69
+ const isActive = currentTurn === p.id;
70
+ return /* @__PURE__ */ jsxs("div", { className, "data-active": isActive, "data-player": player, children: [
71
+ /* @__PURE__ */ jsx2("span", { "data-role": "display-name", children: p.displayName }),
72
+ isActive && /* @__PURE__ */ jsx2("span", { "data-role": "turn-indicator", "aria-label": "Your turn" })
73
+ ] });
74
+ }
75
+
76
+ // src/components/Timer.tsx
77
+ import { useEffect as useEffect2, useState as useState2 } from "react";
78
+ import { jsx as jsx3, jsxs as jsxs2 } from "react/jsx-runtime";
79
+ function Timer({ player, initialSeconds = 600, className }) {
80
+ const socket = useSocket();
81
+ const { status } = useGameState();
82
+ const [seconds, setSeconds] = useState2(initialSeconds);
83
+ useEffect2(() => {
84
+ if (!socket) return;
85
+ const handler = (clocks) => {
86
+ if (clocks[player] !== void 0) setSeconds(clocks[player]);
87
+ };
88
+ socket.on("game:clock", handler);
89
+ return () => {
90
+ socket.off("game:clock", handler);
91
+ };
92
+ }, [socket, player]);
93
+ useEffect2(() => {
94
+ if (status !== "active") return;
95
+ const id = setInterval(() => setSeconds((s2) => Math.max(0, s2 - 1)), 1e3);
96
+ return () => clearInterval(id);
97
+ }, [status]);
98
+ const m = Math.floor(seconds / 60).toString().padStart(2, "0");
99
+ const s = (seconds % 60).toString().padStart(2, "0");
100
+ return /* @__PURE__ */ jsx3("div", { className, "data-player": player, "data-low": seconds < 30, children: /* @__PURE__ */ jsxs2("span", { "data-role": "clock", children: [
101
+ m,
102
+ ":",
103
+ s
104
+ ] }) });
105
+ }
106
+
107
+ // src/components/chess/ChessBoard.tsx
108
+ import { useEffect as useEffect3, useRef as useRef2 } from "react";
109
+ import { jsx as jsx4 } from "react/jsx-runtime";
110
+ function ChessBoard({ className }) {
111
+ const { token, session } = useBetaGamer();
112
+ const iframeRef = useRef2(null);
113
+ useEffect3(() => {
114
+ const iframe = iframeRef.current;
115
+ if (!iframe) return;
116
+ const onLoad = () => iframe.contentWindow?.postMessage({ type: "bg:init", token }, "*");
117
+ iframe.addEventListener("load", onLoad);
118
+ return () => iframe.removeEventListener("load", onLoad);
119
+ }, [token]);
120
+ if (session.game !== "chess") return null;
121
+ return /* @__PURE__ */ jsx4(
122
+ "iframe",
123
+ {
124
+ ref: iframeRef,
125
+ src: `${process.env.NEXT_PUBLIC_BETAGAMER_URL ?? "https://api.beta-gamer.com"}/embed/chess`,
126
+ className,
127
+ style: { border: "none", width: "100%", height: "100%" },
128
+ allow: "autoplay",
129
+ title: "Chess"
130
+ }
131
+ );
132
+ }
133
+
134
+ // src/components/chess/ChessMoveHistory.tsx
135
+ import { useEffect as useEffect4, useState as useState3 } from "react";
136
+ import { jsx as jsx5, jsxs as jsxs3 } from "react/jsx-runtime";
137
+ function ChessMoveHistory({ className }) {
138
+ const socket = useSocket();
139
+ const [moves, setMoves] = useState3([]);
140
+ useEffect4(() => {
141
+ if (!socket) return;
142
+ const handler = ({ san, moveIndex }) => {
143
+ const moveNumber = Math.floor(moveIndex / 2) + 1;
144
+ const isWhite = moveIndex % 2 === 0;
145
+ setMoves((prev) => {
146
+ const next = [...prev];
147
+ if (isWhite) {
148
+ next.push({ moveNumber, white: san });
149
+ } else {
150
+ const last = next[next.length - 1];
151
+ if (last && last.moveNumber === moveNumber) last.black = san;
152
+ }
153
+ return next;
154
+ });
155
+ };
156
+ socket.on("chess:move", handler);
157
+ return () => {
158
+ socket.off("chess:move", handler);
159
+ };
160
+ }, [socket]);
161
+ return /* @__PURE__ */ jsx5("div", { className, "data-role": "move-history", children: moves.map(({ moveNumber, white, black }) => /* @__PURE__ */ jsxs3("div", { "data-role": "move-row", children: [
162
+ /* @__PURE__ */ jsxs3("span", { "data-role": "move-number", children: [
163
+ moveNumber,
164
+ "."
165
+ ] }),
166
+ /* @__PURE__ */ jsx5("span", { "data-role": "move-white", children: white }),
167
+ black && /* @__PURE__ */ jsx5("span", { "data-role": "move-black", children: black })
168
+ ] }, moveNumber)) });
169
+ }
170
+
171
+ // src/components/chess/ChessCapturedPieces.tsx
172
+ import { useEffect as useEffect5, useState as useState4 } from "react";
173
+ import { jsx as jsx6 } from "react/jsx-runtime";
174
+ var PIECE_SYMBOLS = {
175
+ p: "\u265F",
176
+ n: "\u265E",
177
+ b: "\u265D",
178
+ r: "\u265C",
179
+ q: "\u265B"
180
+ };
181
+ function ChessCapturedPieces({ player, className }) {
182
+ const socket = useSocket();
183
+ const { players } = useGameState();
184
+ const [captured, setCaptured] = useState4([]);
185
+ useEffect5(() => {
186
+ if (!socket) return;
187
+ const handler = ({ capturedBy, piece }) => {
188
+ const p = player === "self" ? players[0] : players[1];
189
+ if (p && capturedBy === p.id) setCaptured((prev) => [...prev, piece]);
190
+ };
191
+ socket.on("chess:capture", handler);
192
+ return () => {
193
+ socket.off("chess:capture", handler);
194
+ };
195
+ }, [socket, player, players]);
196
+ return /* @__PURE__ */ jsx6("div", { className, "data-role": "captured-pieces", "data-player": player, children: captured.map((piece, i) => /* @__PURE__ */ jsx6("span", { "data-role": "piece", "data-type": piece, children: PIECE_SYMBOLS[piece.toLowerCase()] ?? piece }, i)) });
197
+ }
198
+
199
+ // src/components/checkers/index.tsx
200
+ import { useEffect as useEffect6, useRef as useRef3 } from "react";
201
+ import { jsx as jsx7 } from "react/jsx-runtime";
202
+ function CheckersBoard({ className }) {
203
+ const { token, session } = useBetaGamer();
204
+ const iframeRef = useRef3(null);
205
+ useEffect6(() => {
206
+ const iframe = iframeRef.current;
207
+ if (!iframe) return;
208
+ const onLoad = () => iframe.contentWindow?.postMessage({ type: "bg:init", token }, "*");
209
+ iframe.addEventListener("load", onLoad);
210
+ return () => iframe.removeEventListener("load", onLoad);
211
+ }, [token]);
212
+ if (session.game !== "checkers") return null;
213
+ return /* @__PURE__ */ jsx7("iframe", { ref: iframeRef, src: `${process.env.NEXT_PUBLIC_BETAGAMER_URL ?? "https://api.beta-gamer.com"}/embed/checkers`, className, style: { border: "none", width: "100%", height: "100%" }, title: "Checkers" });
214
+ }
215
+
216
+ // src/components/connect4/index.tsx
217
+ import { useEffect as useEffect7, useRef as useRef4 } from "react";
218
+ import { jsx as jsx8 } from "react/jsx-runtime";
219
+ function Connect4Board({ className }) {
220
+ const { token, session } = useBetaGamer();
221
+ const iframeRef = useRef4(null);
222
+ useEffect7(() => {
223
+ const iframe = iframeRef.current;
224
+ if (!iframe) return;
225
+ const onLoad = () => iframe.contentWindow?.postMessage({ type: "bg:init", token }, "*");
226
+ iframe.addEventListener("load", onLoad);
227
+ return () => iframe.removeEventListener("load", onLoad);
228
+ }, [token]);
229
+ if (session.game !== "connect4") return null;
230
+ return /* @__PURE__ */ jsx8("iframe", { ref: iframeRef, src: `${process.env.NEXT_PUBLIC_BETAGAMER_URL ?? "https://api.beta-gamer.com"}/embed/connect4`, className, style: { border: "none", width: "100%", height: "100%" }, title: "Connect 4" });
231
+ }
232
+ function Connect4Score({ className }) {
233
+ const { gameState } = useBetaGamer();
234
+ const scores = gameState?.scores ?? {};
235
+ return /* @__PURE__ */ jsx8("div", { className, "data-role": "score", children: Object.entries(scores).map(([id, score]) => /* @__PURE__ */ jsx8("span", { "data-player": id, "data-role": "score-value", children: score }, id)) });
236
+ }
237
+
238
+ // src/components/tictactoe/index.tsx
239
+ import { useEffect as useEffect8, useRef as useRef5 } from "react";
240
+ import { jsx as jsx9 } from "react/jsx-runtime";
241
+ function TictactoeBoard({ className }) {
242
+ const { token, session } = useBetaGamer();
243
+ const iframeRef = useRef5(null);
244
+ useEffect8(() => {
245
+ const iframe = iframeRef.current;
246
+ if (!iframe) return;
247
+ const onLoad = () => iframe.contentWindow?.postMessage({ type: "bg:init", token }, "*");
248
+ iframe.addEventListener("load", onLoad);
249
+ return () => iframe.removeEventListener("load", onLoad);
250
+ }, [token]);
251
+ if (session.game !== "tictactoe") return null;
252
+ return /* @__PURE__ */ jsx9("iframe", { ref: iframeRef, src: `${process.env.NEXT_PUBLIC_BETAGAMER_URL ?? "https://api.beta-gamer.com"}/embed/tictactoe`, className, style: { border: "none", width: "100%", height: "100%" }, title: "Tic-tac-toe" });
253
+ }
254
+
255
+ // src/components/subway-runner/index.tsx
256
+ import { useEffect as useEffect11, useRef as useRef6 } from "react";
257
+
258
+ // src/components/subway-runner/SubwayRunnerScore.tsx
259
+ import { useEffect as useEffect9, useState as useState5 } from "react";
260
+ import { jsx as jsx10 } from "react/jsx-runtime";
261
+ function SubwayRunnerScore({ className }) {
262
+ const socket = useSocket();
263
+ const [score, setScore] = useState5(0);
264
+ useEffect9(() => {
265
+ if (!socket) return;
266
+ socket.on("runner:score", (s) => setScore(s));
267
+ return () => {
268
+ socket.off("runner:score");
269
+ };
270
+ }, [socket]);
271
+ return /* @__PURE__ */ jsx10("div", { className, "data-role": "score", children: /* @__PURE__ */ jsx10("span", { "data-role": "score-value", children: score }) });
272
+ }
273
+
274
+ // src/components/subway-runner/SubwayRunnerLives.tsx
275
+ import { useEffect as useEffect10, useState as useState6 } from "react";
276
+ import { jsx as jsx11 } from "react/jsx-runtime";
277
+ function SubwayRunnerLives({ className }) {
278
+ const socket = useSocket();
279
+ const [lives, setLives] = useState6(3);
280
+ useEffect10(() => {
281
+ if (!socket) return;
282
+ socket.on("runner:lives", (l) => setLives(l));
283
+ return () => {
284
+ socket.off("runner:lives");
285
+ };
286
+ }, [socket]);
287
+ return /* @__PURE__ */ jsx11("div", { className, "data-role": "lives", children: Array.from({ length: lives }).map((_, i) => /* @__PURE__ */ jsx11("span", { "data-role": "life", "aria-label": "life" }, i)) });
288
+ }
289
+
290
+ // src/components/subway-runner/index.tsx
291
+ import { jsx as jsx12 } from "react/jsx-runtime";
292
+ function SubwayRunnerGame({ className }) {
293
+ const { token, session } = useBetaGamer();
294
+ const iframeRef = useRef6(null);
295
+ useEffect11(() => {
296
+ const iframe = iframeRef.current;
297
+ if (!iframe) return;
298
+ const onLoad = () => iframe.contentWindow?.postMessage({ type: "bg:init", token }, "*");
299
+ iframe.addEventListener("load", onLoad);
300
+ return () => iframe.removeEventListener("load", onLoad);
301
+ }, [token]);
302
+ if (session.game !== "subway-runner") return null;
303
+ return /* @__PURE__ */ jsx12("iframe", { ref: iframeRef, src: `${process.env.NEXT_PUBLIC_BETAGAMER_URL ?? "https://api.beta-gamer.com"}/embed/subway-runner`, className, style: { border: "none", width: "100%", height: "100%" }, title: "Subway Runner" });
304
+ }
305
+ export {
306
+ BetaGamerProvider,
307
+ CheckersBoard,
308
+ ChessBoard,
309
+ ChessCapturedPieces,
310
+ ChessMoveHistory,
311
+ Connect4Board,
312
+ Connect4Score,
313
+ PlayerCard,
314
+ SubwayRunnerGame,
315
+ SubwayRunnerLives,
316
+ SubwayRunnerScore,
317
+ TictactoeBoard,
318
+ Timer,
319
+ useBetaGamer,
320
+ useGameState,
321
+ useSession,
322
+ useSocket,
323
+ useTheme
324
+ };
package/package.json ADDED
@@ -0,0 +1,36 @@
1
+ {
2
+ "name": "@beta-gamer/react",
3
+ "version": "0.1.0",
4
+ "description": "React SDK for Beta Gamer GaaS — composable game components",
5
+ "main": "./dist/index.js",
6
+ "module": "./dist/index.mjs",
7
+ "types": "./dist/index.d.ts",
8
+ "exports": {
9
+ ".": {
10
+ "import": "./dist/index.mjs",
11
+ "require": "./dist/index.js",
12
+ "types": "./dist/index.d.ts"
13
+ }
14
+ },
15
+ "files": ["dist"],
16
+ "scripts": {
17
+ "build": "tsup src/index.ts --format esm,cjs --dts --external react",
18
+ "dev": "tsup src/index.ts --format esm,cjs --dts --external react --watch"
19
+ },
20
+ "peerDependencies": {
21
+ "react": ">=18"
22
+ },
23
+ "dependencies": {
24
+ "socket.io-client": "^4.8.1"
25
+ },
26
+ "devDependencies": {
27
+ "@types/react": "^19",
28
+ "tsup": "^8.0.0",
29
+ "typescript": "^5"
30
+ },
31
+ "repository": {
32
+ "type": "git",
33
+ "url": "https://github.com/acetennyson/beta-gamer"
34
+ },
35
+ "license": "MIT"
36
+ }