@scriptonita/chess-football-ui 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +14 -3
- package/dist/{chunk-LTFNSTAQ.cjs → chunk-BSE4DUXM.cjs} +18 -5
- package/dist/chunk-BSE4DUXM.cjs.map +1 -0
- package/dist/{chunk-N24LGLLO.js → chunk-C5HCN7O5.js} +18 -6
- package/dist/chunk-C5HCN7O5.js.map +1 -0
- package/dist/index.cjs +196 -71
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +56 -5
- package/dist/index.d.ts +56 -5
- package/dist/index.js +193 -70
- package/dist/index.js.map +1 -1
- package/dist/store.cjs +7 -3
- package/dist/store.d.cts +9 -1
- package/dist/store.d.ts +9 -1
- package/dist/store.js +1 -1
- package/package.json +1 -1
- package/dist/chunk-LTFNSTAQ.cjs.map +0 -1
- package/dist/chunk-N24LGLLO.js.map +0 -1
package/README.md
CHANGED
|
@@ -52,9 +52,20 @@ expect(missing).toEqual([])
|
|
|
52
52
|
```
|
|
53
53
|
|
|
54
54
|
**2. Design tokens.** Components emit Tailwind classes bound to tokens the app defines. A
|
|
55
|
-
token that is missing fails silently — the rule
|
|
56
|
-
`--last-move-highlight
|
|
57
|
-
|
|
55
|
+
token that is missing fails silently — Tailwind emits the rule, the variable resolves to
|
|
56
|
+
nothing, and the declaration is dropped. That is how `--last-move-highlight` went missing
|
|
57
|
+
in the CrazyGames SPA: the last-move highlight and the bot's turn replay never rendered,
|
|
58
|
+
with no error anywhere. `REQUIRED_TOKENS` is the manifest; assert it the same way:
|
|
59
|
+
|
|
60
|
+
```ts
|
|
61
|
+
import { REQUIRED_TOKENS } from '@scriptonita/chess-football-ui'
|
|
62
|
+
|
|
63
|
+
const css = readFileSync('src/index.css', 'utf8')
|
|
64
|
+
expect(REQUIRED_TOKENS.filter(t => !css.includes(`${t}:`))).toEqual([])
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
`--field-frame` is new in 0.9.0: the board frame was hardcoded `#1a3317` in three places,
|
|
68
|
+
hand-tuned for the webapp's grass and visibly wrong against the CrazyGames pitch.
|
|
58
69
|
|
|
59
70
|
## Peer dependencies
|
|
60
71
|
|
|
@@ -4,19 +4,31 @@ var zustand = require('zustand');
|
|
|
4
4
|
var chessFootballEngine = require('@scriptonita/chess-football-engine');
|
|
5
5
|
|
|
6
6
|
// src/store/use-game-store.ts
|
|
7
|
-
var
|
|
7
|
+
var DEFAULT_MAX_AP = 5;
|
|
8
|
+
var INITIAL = {
|
|
8
9
|
boardState: null,
|
|
9
10
|
selectedPieceId: null,
|
|
10
|
-
interactionMode: null
|
|
11
|
+
interactionMode: null
|
|
12
|
+
};
|
|
13
|
+
var useGameStore = zustand.create((set) => ({
|
|
14
|
+
...INITIAL,
|
|
11
15
|
setBoardState: (state) => set({ boardState: state }),
|
|
12
16
|
setSelectedPieceId: (id) => set({ selectedPieceId: id }),
|
|
13
17
|
setInteractionMode: (mode) => set({ interactionMode: mode }),
|
|
18
|
+
/**
|
|
19
|
+
* The store is a module-global singleton, so a new match opens on the
|
|
20
|
+
* previous match's final board — which ended on a goal, and was therefore
|
|
21
|
+
* re-detected as a fresh goal (the bug behind webapp PR #42). Apps were
|
|
22
|
+
* each reaching for `useGameStore.setState({...})` by hand to work around
|
|
23
|
+
* it; this makes the intent explicit and keeps the shape in one place.
|
|
24
|
+
*/
|
|
25
|
+
reset: () => set({ ...INITIAL }),
|
|
14
26
|
resetTurn: () => set((state) => {
|
|
15
27
|
if (!state.boardState) return state;
|
|
16
28
|
return {
|
|
17
29
|
boardState: {
|
|
18
30
|
...state.boardState,
|
|
19
|
-
actionPoints: state.boardState.maxActionPoints ??
|
|
31
|
+
actionPoints: state.boardState.maxActionPoints ?? DEFAULT_MAX_AP,
|
|
20
32
|
pieces: state.boardState.pieces.map((p) => ({ ...p, hasMovedThisTurn: false }))
|
|
21
33
|
}
|
|
22
34
|
};
|
|
@@ -50,6 +62,7 @@ Object.defineProperty(exports, "getInitialBoardState", {
|
|
|
50
62
|
enumerable: true,
|
|
51
63
|
get: function () { return chessFootballEngine.getInitialBoardState; }
|
|
52
64
|
});
|
|
65
|
+
exports.DEFAULT_MAX_AP = DEFAULT_MAX_AP;
|
|
53
66
|
exports.useGameStore = useGameStore;
|
|
54
|
-
//# sourceMappingURL=chunk-
|
|
55
|
-
//# sourceMappingURL=chunk-
|
|
67
|
+
//# sourceMappingURL=chunk-BSE4DUXM.cjs.map
|
|
68
|
+
//# sourceMappingURL=chunk-BSE4DUXM.cjs.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/store/use-game-store.ts"],"names":["create","applyMove","applyPass","applyEndTurn"],"mappings":"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","file":"chunk-BSE4DUXM.cjs","sourcesContent":["import { create } from 'zustand'\nimport type { BoardState, Position } from '@scriptonita/chess-football-engine'\nimport { applyMove, applyPass, applyEndTurn } from '@scriptonita/chess-football-engine'\n\n// The canonical kickoff position lives in the engine (rules-adjacent). Re-exported\n// here so app imports from `@scriptonita/chess-football-ui/store` keep working.\nexport { getInitialBoardState } from '@scriptonita/chess-football-engine'\n\n/**\n * Fallback when a board predates configurable action points. `maxActionPoints`\n * is optional on `BoardState`, and every consumer was inventing its own `?? 5`\n * — except `TurnBanner`, which had none and rendered \"3/\" on an old match.\n */\nexport const DEFAULT_MAX_AP = 5\n\ninterface GameStore {\n boardState: BoardState | null\n selectedPieceId: string | null\n interactionMode: 'move' | 'pass' | null\n setBoardState: (state: BoardState) => void\n setSelectedPieceId: (id: string | null) => void\n setInteractionMode: (mode: 'move' | 'pass' | null) => void\n resetTurn: () => void\n /** Return the store to its initial state. */\n reset: () => void\n movePiece: (pieceId: string, to: Position) => void\n passBall: (to: Position) => void\n endTurn: () => void\n}\n\nconst INITIAL = {\n boardState: null,\n selectedPieceId: null,\n interactionMode: null,\n} satisfies Pick<GameStore, 'boardState' | 'selectedPieceId' | 'interactionMode'>\n\nexport const useGameStore = create<GameStore>((set) => ({\n ...INITIAL,\n\n setBoardState: (state) => set({ boardState: state }),\n setSelectedPieceId: (id) => set({ selectedPieceId: id }),\n setInteractionMode: (mode) => set({ interactionMode: mode }),\n\n /**\n * The store is a module-global singleton, so a new match opens on the\n * previous match's final board — which ended on a goal, and was therefore\n * re-detected as a fresh goal (the bug behind webapp PR #42). Apps were\n * each reaching for `useGameStore.setState({...})` by hand to work around\n * it; this makes the intent explicit and keeps the shape in one place.\n */\n reset: () => set({ ...INITIAL }),\n\n resetTurn: () => set((state) => {\n if (!state.boardState) return state\n return {\n boardState: {\n ...state.boardState,\n actionPoints: state.boardState.maxActionPoints ?? DEFAULT_MAX_AP,\n pieces: state.boardState.pieces.map(p => ({ ...p, hasMovedThisTurn: false })),\n },\n }\n }),\n\n movePiece: (pieceId, to) => set((state) => {\n if (!state.boardState || state.boardState.actionPoints <= 0) return state\n const piece = state.boardState.pieces.find(p => p.id === pieceId)\n if (!piece || piece.hasMovedThisTurn) return state\n\n const { boardState } = applyMove(state.boardState, pieceId, to)\n return { boardState, selectedPieceId: null, interactionMode: null }\n }),\n\n passBall: (to) => set((state) => {\n if (!state.boardState || state.boardState.actionPoints <= 0 || !state.boardState.ball.holderId) return state\n const holder = state.boardState.pieces.find(p => p.id === state.boardState!.ball.holderId)\n if (!holder) return state\n\n const { boardState, forcedTurnEnd } = applyPass(state.boardState, to)\n const keepSelection = !forcedTurnEnd && boardState.ball.holderId\n return {\n boardState,\n selectedPieceId: keepSelection ? boardState.ball.holderId : null,\n interactionMode: keepSelection ? 'pass' : null,\n }\n }),\n\n endTurn: () => set((state) => {\n if (!state.boardState) return state\n return { boardState: applyEndTurn(state.boardState) }\n }),\n}))\n"]}
|
|
@@ -3,19 +3,31 @@ import { applyEndTurn, applyPass, applyMove } from '@scriptonita/chess-football-
|
|
|
3
3
|
export { getInitialBoardState } from '@scriptonita/chess-football-engine';
|
|
4
4
|
|
|
5
5
|
// src/store/use-game-store.ts
|
|
6
|
-
var
|
|
6
|
+
var DEFAULT_MAX_AP = 5;
|
|
7
|
+
var INITIAL = {
|
|
7
8
|
boardState: null,
|
|
8
9
|
selectedPieceId: null,
|
|
9
|
-
interactionMode: null
|
|
10
|
+
interactionMode: null
|
|
11
|
+
};
|
|
12
|
+
var useGameStore = create((set) => ({
|
|
13
|
+
...INITIAL,
|
|
10
14
|
setBoardState: (state) => set({ boardState: state }),
|
|
11
15
|
setSelectedPieceId: (id) => set({ selectedPieceId: id }),
|
|
12
16
|
setInteractionMode: (mode) => set({ interactionMode: mode }),
|
|
17
|
+
/**
|
|
18
|
+
* The store is a module-global singleton, so a new match opens on the
|
|
19
|
+
* previous match's final board — which ended on a goal, and was therefore
|
|
20
|
+
* re-detected as a fresh goal (the bug behind webapp PR #42). Apps were
|
|
21
|
+
* each reaching for `useGameStore.setState({...})` by hand to work around
|
|
22
|
+
* it; this makes the intent explicit and keeps the shape in one place.
|
|
23
|
+
*/
|
|
24
|
+
reset: () => set({ ...INITIAL }),
|
|
13
25
|
resetTurn: () => set((state) => {
|
|
14
26
|
if (!state.boardState) return state;
|
|
15
27
|
return {
|
|
16
28
|
boardState: {
|
|
17
29
|
...state.boardState,
|
|
18
|
-
actionPoints: state.boardState.maxActionPoints ??
|
|
30
|
+
actionPoints: state.boardState.maxActionPoints ?? DEFAULT_MAX_AP,
|
|
19
31
|
pieces: state.boardState.pieces.map((p) => ({ ...p, hasMovedThisTurn: false }))
|
|
20
32
|
}
|
|
21
33
|
};
|
|
@@ -45,6 +57,6 @@ var useGameStore = create((set) => ({
|
|
|
45
57
|
})
|
|
46
58
|
}));
|
|
47
59
|
|
|
48
|
-
export { useGameStore };
|
|
49
|
-
//# sourceMappingURL=chunk-
|
|
50
|
-
//# sourceMappingURL=chunk-
|
|
60
|
+
export { DEFAULT_MAX_AP, useGameStore };
|
|
61
|
+
//# sourceMappingURL=chunk-C5HCN7O5.js.map
|
|
62
|
+
//# sourceMappingURL=chunk-C5HCN7O5.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"sources":["../src/store/use-game-store.ts"],"names":[],"mappings":"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","file":"chunk-C5HCN7O5.js","sourcesContent":["import { create } from 'zustand'\nimport type { BoardState, Position } from '@scriptonita/chess-football-engine'\nimport { applyMove, applyPass, applyEndTurn } from '@scriptonita/chess-football-engine'\n\n// The canonical kickoff position lives in the engine (rules-adjacent). Re-exported\n// here so app imports from `@scriptonita/chess-football-ui/store` keep working.\nexport { getInitialBoardState } from '@scriptonita/chess-football-engine'\n\n/**\n * Fallback when a board predates configurable action points. `maxActionPoints`\n * is optional on `BoardState`, and every consumer was inventing its own `?? 5`\n * — except `TurnBanner`, which had none and rendered \"3/\" on an old match.\n */\nexport const DEFAULT_MAX_AP = 5\n\ninterface GameStore {\n boardState: BoardState | null\n selectedPieceId: string | null\n interactionMode: 'move' | 'pass' | null\n setBoardState: (state: BoardState) => void\n setSelectedPieceId: (id: string | null) => void\n setInteractionMode: (mode: 'move' | 'pass' | null) => void\n resetTurn: () => void\n /** Return the store to its initial state. */\n reset: () => void\n movePiece: (pieceId: string, to: Position) => void\n passBall: (to: Position) => void\n endTurn: () => void\n}\n\nconst INITIAL = {\n boardState: null,\n selectedPieceId: null,\n interactionMode: null,\n} satisfies Pick<GameStore, 'boardState' | 'selectedPieceId' | 'interactionMode'>\n\nexport const useGameStore = create<GameStore>((set) => ({\n ...INITIAL,\n\n setBoardState: (state) => set({ boardState: state }),\n setSelectedPieceId: (id) => set({ selectedPieceId: id }),\n setInteractionMode: (mode) => set({ interactionMode: mode }),\n\n /**\n * The store is a module-global singleton, so a new match opens on the\n * previous match's final board — which ended on a goal, and was therefore\n * re-detected as a fresh goal (the bug behind webapp PR #42). Apps were\n * each reaching for `useGameStore.setState({...})` by hand to work around\n * it; this makes the intent explicit and keeps the shape in one place.\n */\n reset: () => set({ ...INITIAL }),\n\n resetTurn: () => set((state) => {\n if (!state.boardState) return state\n return {\n boardState: {\n ...state.boardState,\n actionPoints: state.boardState.maxActionPoints ?? DEFAULT_MAX_AP,\n pieces: state.boardState.pieces.map(p => ({ ...p, hasMovedThisTurn: false })),\n },\n }\n }),\n\n movePiece: (pieceId, to) => set((state) => {\n if (!state.boardState || state.boardState.actionPoints <= 0) return state\n const piece = state.boardState.pieces.find(p => p.id === pieceId)\n if (!piece || piece.hasMovedThisTurn) return state\n\n const { boardState } = applyMove(state.boardState, pieceId, to)\n return { boardState, selectedPieceId: null, interactionMode: null }\n }),\n\n passBall: (to) => set((state) => {\n if (!state.boardState || state.boardState.actionPoints <= 0 || !state.boardState.ball.holderId) return state\n const holder = state.boardState.pieces.find(p => p.id === state.boardState!.ball.holderId)\n if (!holder) return state\n\n const { boardState, forcedTurnEnd } = applyPass(state.boardState, to)\n const keepSelection = !forcedTurnEnd && boardState.ball.holderId\n return {\n boardState,\n selectedPieceId: keepSelection ? boardState.ball.holderId : null,\n interactionMode: keepSelection ? 'pass' : null,\n }\n }),\n\n endTurn: () => set((state) => {\n if (!state.boardState) return state\n return { boardState: applyEndTurn(state.boardState) }\n }),\n}))\n"]}
|
package/dist/index.cjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
'use strict';
|
|
2
2
|
|
|
3
|
-
var
|
|
3
|
+
var chunkBSE4DUXM_cjs = require('./chunk-BSE4DUXM.cjs');
|
|
4
|
+
var shallow = require('zustand/react/shallow');
|
|
4
5
|
var chessFootballEngine = require('@scriptonita/chess-football-engine');
|
|
5
6
|
var clsx = require('clsx');
|
|
6
7
|
var tailwindMerge = require('tailwind-merge');
|
|
@@ -104,7 +105,10 @@ function GamePiece({ piece, isSelected, hasBall, onClick, isMoving = false }) {
|
|
|
104
105
|
style: piece.hasMovedThisTurn ? MOVED_STYLE : void 0,
|
|
105
106
|
className: cn(
|
|
106
107
|
"relative w-[88%] h-[88%] rounded-full cursor-pointer z-10 transition-transform duration-200 ease-out motion-reduce:transition-none",
|
|
107
|
-
|
|
108
|
+
// `--primary` was the package's one remaining shadcn token, and
|
|
109
|
+
// CrazyGames had to define it by hand just to make the selection
|
|
110
|
+
// ring visible. It is part of the design system now.
|
|
111
|
+
isSelected && "ring-4 ring-accent-green scale-110 z-30",
|
|
108
112
|
isMoving && "scale-[1.08] z-40",
|
|
109
113
|
piece.hasMovedThisTurn && "opacity-70"
|
|
110
114
|
),
|
|
@@ -265,7 +269,7 @@ function useGameT() {
|
|
|
265
269
|
}
|
|
266
270
|
function BallHolderChip({ className }) {
|
|
267
271
|
const t = useGameT();
|
|
268
|
-
const boardState =
|
|
272
|
+
const boardState = chunkBSE4DUXM_cjs.useGameStore((s) => s.boardState);
|
|
269
273
|
const holder = boardState?.pieces.find((p) => p.id === boardState.ball.holderId) ?? null;
|
|
270
274
|
if (!holder) return null;
|
|
271
275
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex items-center gap-1.5 min-h-[1.75rem]", className), "aria-live": "polite", children: [
|
|
@@ -460,18 +464,29 @@ var cellX = (x) => `${x * 100}%`;
|
|
|
460
464
|
var cellY = (y) => `${(ROWS - 1 - y) * 100}%`;
|
|
461
465
|
var PIECE_SLIDE = { type: "spring", stiffness: 190, damping: 28, mass: 1 };
|
|
462
466
|
var BALL_SLIDE = { type: "spring", stiffness: 300, damping: 26, mass: 0.6 };
|
|
463
|
-
function GameBoard({ userSide, showCoordinates = false, keyboardNav = true, toolbar = true }) {
|
|
467
|
+
function GameBoard({ userSide, showCoordinates = false, keyboardNav = true, toolbar = true, className }) {
|
|
464
468
|
const t = useGameT();
|
|
465
469
|
const boardId = React.useId();
|
|
466
470
|
const prefersReducedMotion = framerMotion.useReducedMotion();
|
|
467
471
|
const {
|
|
468
472
|
boardState,
|
|
469
473
|
selectedPieceId,
|
|
474
|
+
interactionMode,
|
|
470
475
|
setSelectedPieceId,
|
|
471
476
|
setInteractionMode,
|
|
472
477
|
movePiece,
|
|
473
478
|
passBall
|
|
474
|
-
} =
|
|
479
|
+
} = chunkBSE4DUXM_cjs.useGameStore(
|
|
480
|
+
shallow.useShallow((s) => ({
|
|
481
|
+
boardState: s.boardState,
|
|
482
|
+
selectedPieceId: s.selectedPieceId,
|
|
483
|
+
interactionMode: s.interactionMode,
|
|
484
|
+
setSelectedPieceId: s.setSelectedPieceId,
|
|
485
|
+
setInteractionMode: s.setInteractionMode,
|
|
486
|
+
movePiece: s.movePiece,
|
|
487
|
+
passBall: s.passBall
|
|
488
|
+
}))
|
|
489
|
+
);
|
|
475
490
|
const [cursor, setCursor] = React.useState(null);
|
|
476
491
|
const [disambiguateAt, setDisambiguateAt] = React.useState(null);
|
|
477
492
|
const [shortcutsOpen, setShortcutsOpen] = React.useState(false);
|
|
@@ -539,21 +554,21 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true, tool
|
|
|
539
554
|
return () => clearTimeout(id);
|
|
540
555
|
}, [replay]);
|
|
541
556
|
const validMoves = React.useMemo(() => {
|
|
542
|
-
if (!boardState) return [];
|
|
557
|
+
if (!boardState || interactionMode === "pass") return [];
|
|
543
558
|
const sp = boardState.pieces.find((p) => p.id === selectedPieceId);
|
|
544
559
|
if (sp && !sp.hasMovedThisTurn) {
|
|
545
560
|
return chessFootballEngine.getValidMoves(sp, boardState);
|
|
546
561
|
}
|
|
547
562
|
return [];
|
|
548
|
-
}, [boardState, selectedPieceId]);
|
|
563
|
+
}, [boardState, selectedPieceId, interactionMode]);
|
|
549
564
|
const validPasses = React.useMemo(() => {
|
|
550
|
-
if (!boardState) return [];
|
|
565
|
+
if (!boardState || interactionMode === "move") return [];
|
|
551
566
|
const sp = boardState.pieces.find((p) => p.id === selectedPieceId);
|
|
552
567
|
if (sp && boardState.ball.holderId === sp.id) {
|
|
553
568
|
return chessFootballEngine.getValidPasses(sp, boardState);
|
|
554
569
|
}
|
|
555
570
|
return [];
|
|
556
|
-
}, [boardState, selectedPieceId]);
|
|
571
|
+
}, [boardState, selectedPieceId, interactionMode]);
|
|
557
572
|
if (!boardState) return null;
|
|
558
573
|
const lastMove = boardState.lastMove;
|
|
559
574
|
const ball = boardState.ball;
|
|
@@ -667,8 +682,11 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true, tool
|
|
|
667
682
|
activate(c.x, c.y);
|
|
668
683
|
break;
|
|
669
684
|
}
|
|
685
|
+
// Bare M / P only: Cmd+P is Print and Ctrl+M is a browser shortcut,
|
|
686
|
+
// and both used to resolve the popover behind the user's back.
|
|
670
687
|
case "m":
|
|
671
688
|
case "M":
|
|
689
|
+
if (e.ctrlKey || e.metaKey || e.altKey) break;
|
|
672
690
|
if (disambiguateAt && selectedPieceId) {
|
|
673
691
|
movePiece(selectedPieceId, disambiguateAt);
|
|
674
692
|
setDisambiguateAt(null);
|
|
@@ -676,6 +694,7 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true, tool
|
|
|
676
694
|
break;
|
|
677
695
|
case "p":
|
|
678
696
|
case "P":
|
|
697
|
+
if (e.ctrlKey || e.metaKey || e.altKey) break;
|
|
679
698
|
if (disambiguateAt) {
|
|
680
699
|
passBall(disambiguateAt);
|
|
681
700
|
setDisambiguateAt(null);
|
|
@@ -740,22 +759,29 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true, tool
|
|
|
740
759
|
if (isValidPass) setHoveredPassAt({ x, y });
|
|
741
760
|
},
|
|
742
761
|
onMouseLeave: () => setHoveredPassAt(null),
|
|
762
|
+
onTouchStart: () => {
|
|
763
|
+
if (isValidPass) setHoveredPassAt({ x, y });
|
|
764
|
+
},
|
|
743
765
|
className: cn(
|
|
744
766
|
pitchSquareClass(x, y),
|
|
745
767
|
"flex items-center justify-center cursor-pointer",
|
|
746
768
|
!isValidMove && !isValidPass && isLastMove && "bg-last-move-highlight/20",
|
|
747
769
|
// §8: pulsing warning ring on enemy goal area
|
|
748
770
|
isOffsideRisk && isEnemyGoalArea && !isValidMove && !isValidPass && "ring-2 ring-inset ring-warning/60",
|
|
749
|
-
|
|
750
|
-
|
|
751
|
-
|
|
771
|
+
// Move and pass used raw yellow-400/sky-400 here while
|
|
772
|
+
// the popover a few lines below used the
|
|
773
|
+
// move-highlight/pass-highlight tokens — two palettes
|
|
774
|
+
// for the same two concepts. One palette now: tokens.
|
|
775
|
+
isAmbiguous && "ring-[3px] ring-inset ring-move-highlight/90 bg-move-highlight/20",
|
|
776
|
+
!isAmbiguous && isValidMove && "ring-[3px] ring-inset ring-move-highlight/80 bg-move-highlight/15",
|
|
777
|
+
!isAmbiguous && isValidPass && "ring-[3px] ring-inset ring-pass-highlight/80 bg-pass-highlight/15"
|
|
752
778
|
),
|
|
753
779
|
children: [
|
|
754
|
-
!isAmbiguous && isValidMove && !pieceAt && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-3 h-3 rounded-full bg-
|
|
755
|
-
!isAmbiguous && isValidPass && !pieceAt && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-3 h-3 rounded-full
|
|
756
|
-
isAmbiguous && !pieceAt && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex gap-0.5", children: [
|
|
757
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2.5 h-2.5 rounded-full bg-
|
|
758
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-2.5 h-2.5 rounded-full
|
|
780
|
+
!isAmbiguous && isValidMove && !pieceAt && /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": "move-marker", className: "w-3 h-3 rounded-full bg-move-highlight/65" }),
|
|
781
|
+
!isAmbiguous && isValidPass && !pieceAt && /* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": "pass-marker", className: "w-3.5 h-3.5 rounded-full border-2 border-pass-highlight/80" }),
|
|
782
|
+
isAmbiguous && !pieceAt && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-0.5", children: [
|
|
783
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": "move-marker", className: "w-2.5 h-2.5 rounded-full bg-move-highlight/80" }),
|
|
784
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { "data-testid": "pass-marker", className: "w-2.5 h-2.5 rounded-full border-2 border-pass-highlight/90" })
|
|
759
785
|
] }),
|
|
760
786
|
isCursor && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 ring-2 ring-inset ring-white pointer-events-none z-20", "aria-hidden": "true" }),
|
|
761
787
|
isDisambiguateTarget && selectedPieceId && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
@@ -820,10 +846,14 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true, tool
|
|
|
820
846
|
"data-testid": "board-root",
|
|
821
847
|
className: cn(
|
|
822
848
|
// §6: Mobile edge-to-edge — thin 2px margin, no border, no radius
|
|
823
|
-
|
|
849
|
+
// The frame was hardcoded #1a3317 in three files, hand-tuned for
|
|
850
|
+
// the webapp's darker grass and visibly wrong against the
|
|
851
|
+
// CrazyGames pitch. It is a token now (see README).
|
|
852
|
+
"w-full mx-0.5 bg-field-frame overflow-hidden",
|
|
824
853
|
// §5+§6: Desktop — centered, rounded, bordered, height-based max-width
|
|
825
|
-
"md:mx-auto md:rounded-xl md:shadow-2xl md:border-4 md:border-
|
|
826
|
-
"md:max-w-[min(700px,calc((100dvh_-_150px)*0.75))]"
|
|
854
|
+
"md:mx-auto md:rounded-xl md:shadow-2xl md:border-4 md:border-field-frame",
|
|
855
|
+
"md:max-w-[min(700px,calc((100dvh_-_150px)*0.75))]",
|
|
856
|
+
!showCoordinates && className
|
|
827
857
|
),
|
|
828
858
|
children: [
|
|
829
859
|
toolbar && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "hidden md:flex items-center justify-between gap-2 px-2.5 py-1", "data-testid": "board-toolbar", children: [
|
|
@@ -1020,7 +1050,7 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true, tool
|
|
|
1020
1050
|
);
|
|
1021
1051
|
if (!showCoordinates) return board;
|
|
1022
1052
|
const ranksTopToBottom = [...chessFootballEngine.RANK_LABELS].reverse();
|
|
1023
|
-
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-full max-w-[540px] md:max-w-none mx-auto",
|
|
1053
|
+
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("w-full max-w-[540px] md:max-w-none mx-auto", className), children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1024
1054
|
"div",
|
|
1025
1055
|
{
|
|
1026
1056
|
className: "grid gap-1 select-none",
|
|
@@ -1052,8 +1082,20 @@ function Avatar({ className, children }) {
|
|
|
1052
1082
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("relative w-9 h-9 rounded-full overflow-hidden shrink-0", className), children });
|
|
1053
1083
|
}
|
|
1054
1084
|
function AvatarImage({ src, alt, className }) {
|
|
1055
|
-
|
|
1056
|
-
|
|
1085
|
+
const [failed, setFailed] = React.useState(false);
|
|
1086
|
+
React.useEffect(() => {
|
|
1087
|
+
setFailed(false);
|
|
1088
|
+
}, [src]);
|
|
1089
|
+
if (!src || failed) return null;
|
|
1090
|
+
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
1091
|
+
"img",
|
|
1092
|
+
{
|
|
1093
|
+
src,
|
|
1094
|
+
alt: alt ?? "",
|
|
1095
|
+
onError: () => setFailed(true),
|
|
1096
|
+
className: cn("w-full h-full object-cover", className)
|
|
1097
|
+
}
|
|
1098
|
+
);
|
|
1057
1099
|
}
|
|
1058
1100
|
function AvatarFallback({ className, children }) {
|
|
1059
1101
|
return /* @__PURE__ */ jsxRuntime.jsx("div", { className: cn("absolute inset-0 flex items-center justify-center text-xs font-semibold", className), children });
|
|
@@ -1109,6 +1151,51 @@ function ActionPoints({ total, remaining, size = 20, className, kingMustRelease
|
|
|
1109
1151
|
);
|
|
1110
1152
|
}) });
|
|
1111
1153
|
}
|
|
1154
|
+
function PlayerIdentity({
|
|
1155
|
+
name,
|
|
1156
|
+
team,
|
|
1157
|
+
variant = "full",
|
|
1158
|
+
isActive = false,
|
|
1159
|
+
align = "left",
|
|
1160
|
+
adornment,
|
|
1161
|
+
className
|
|
1162
|
+
}) {
|
|
1163
|
+
if (variant === "compact") {
|
|
1164
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1165
|
+
"span",
|
|
1166
|
+
{
|
|
1167
|
+
className: cn(
|
|
1168
|
+
"font-inter text-[11px] font-medium truncate max-w-[160px]",
|
|
1169
|
+
isActive ? "text-fg-secondary" : "text-fg-muted",
|
|
1170
|
+
className
|
|
1171
|
+
),
|
|
1172
|
+
children: [
|
|
1173
|
+
name,
|
|
1174
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-mono uppercase tracking-[0.5px] text-fg-muted", children: [
|
|
1175
|
+
" \xB7 ",
|
|
1176
|
+
team
|
|
1177
|
+
] })
|
|
1178
|
+
]
|
|
1179
|
+
}
|
|
1180
|
+
);
|
|
1181
|
+
}
|
|
1182
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col gap-1 min-w-0", align === "right" && "items-end", className), children: [
|
|
1183
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1184
|
+
"span",
|
|
1185
|
+
{
|
|
1186
|
+
className: cn(
|
|
1187
|
+
"font-inter text-[13px] font-semibold leading-none truncate transition-colors duration-300",
|
|
1188
|
+
isActive ? "text-fg-primary" : "text-fg-muted"
|
|
1189
|
+
),
|
|
1190
|
+
children: name
|
|
1191
|
+
}
|
|
1192
|
+
),
|
|
1193
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex items-center gap-1", align === "right" && "flex-row-reverse"), children: [
|
|
1194
|
+
/* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-[10px] text-fg-muted uppercase tracking-[0.5px]", children: team }),
|
|
1195
|
+
adornment
|
|
1196
|
+
] })
|
|
1197
|
+
] });
|
|
1198
|
+
}
|
|
1112
1199
|
function initials(name) {
|
|
1113
1200
|
return (name?.split(" ").map((w) => w[0]).join("").toUpperCase() || "?").slice(0, 2);
|
|
1114
1201
|
}
|
|
@@ -1117,10 +1204,11 @@ function Scoreboard({
|
|
|
1117
1204
|
opponentUsername,
|
|
1118
1205
|
creatorAvatarUrl,
|
|
1119
1206
|
opponentAvatarUrl,
|
|
1120
|
-
userSide
|
|
1207
|
+
userSide,
|
|
1208
|
+
className
|
|
1121
1209
|
}) {
|
|
1122
1210
|
const t = useGameT();
|
|
1123
|
-
const
|
|
1211
|
+
const boardState = chunkBSE4DUXM_cjs.useGameStore((s) => s.boardState);
|
|
1124
1212
|
if (!boardState?.score) return null;
|
|
1125
1213
|
const { score, turn, actionPoints, kingMustRelease } = boardState;
|
|
1126
1214
|
const myScore = userSide === "white" ? score.white : score.black;
|
|
@@ -1134,8 +1222,8 @@ function Scoreboard({
|
|
|
1134
1222
|
const isMyTurn = turn === userSide;
|
|
1135
1223
|
const myKingMustRelease = isMyTurn && kingMustRelease === turn;
|
|
1136
1224
|
const rivalKingMustRelease = !isMyTurn && kingMustRelease === turn;
|
|
1137
|
-
const maxAP = boardState.maxActionPoints ??
|
|
1138
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full bg-bg-secondary flex items-center justify-between px-5 py-2.5", children: [
|
|
1225
|
+
const maxAP = boardState.maxActionPoints ?? chunkBSE4DUXM_cjs.DEFAULT_MAX_AP;
|
|
1226
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("w-full bg-bg-secondary flex items-center justify-between px-5 py-2.5", className), children: [
|
|
1139
1227
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1140
1228
|
PlayerInfo,
|
|
1141
1229
|
{
|
|
@@ -1180,14 +1268,16 @@ function PlayerInfo({ name, team, avatarUrl, isActive, actionPoints, maxActionPo
|
|
|
1180
1268
|
/* @__PURE__ */ jsxRuntime.jsx(AvatarFallback, { className: "bg-bg-surface-elevated text-fg-secondary text-xs font-semibold", children: initials(name) })
|
|
1181
1269
|
] }),
|
|
1182
1270
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col gap-1", align === "right" && "items-end"), children: [
|
|
1183
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1184
|
-
|
|
1185
|
-
|
|
1186
|
-
|
|
1187
|
-
|
|
1188
|
-
|
|
1189
|
-
|
|
1190
|
-
|
|
1271
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1272
|
+
PlayerIdentity,
|
|
1273
|
+
{
|
|
1274
|
+
name,
|
|
1275
|
+
team,
|
|
1276
|
+
isActive,
|
|
1277
|
+
align,
|
|
1278
|
+
adornment: isActive ? /* @__PURE__ */ jsxRuntime.jsx("span", { className: "w-1.5 h-1.5 rounded-full bg-accent-green animate-pulse motion-reduce:animate-none", "aria-hidden": "true" }) : void 0
|
|
1279
|
+
}
|
|
1280
|
+
),
|
|
1191
1281
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1192
1282
|
ActionPoints,
|
|
1193
1283
|
{
|
|
@@ -1208,7 +1298,7 @@ function MiniScoreboard({
|
|
|
1208
1298
|
className
|
|
1209
1299
|
}) {
|
|
1210
1300
|
const t = useGameT();
|
|
1211
|
-
const
|
|
1301
|
+
const boardState = chunkBSE4DUXM_cjs.useGameStore((s) => s.boardState);
|
|
1212
1302
|
if (!boardState?.score) return null;
|
|
1213
1303
|
const { score, turn } = boardState;
|
|
1214
1304
|
const myScore = userSide === "white" ? score.white : score.black;
|
|
@@ -1242,14 +1332,7 @@ function MiniScoreboard({
|
|
|
1242
1332
|
}
|
|
1243
1333
|
function SideCell({ name, team, score, isActive, align }) {
|
|
1244
1334
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex flex-col min-w-0", align === "right" ? "items-end" : "items-start"), children: [
|
|
1245
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1246
|
-
"font-mono text-[10px] uppercase tracking-[0.5px] truncate max-w-[160px]",
|
|
1247
|
-
isActive ? "text-fg-secondary" : "text-fg-muted"
|
|
1248
|
-
), children: [
|
|
1249
|
-
name,
|
|
1250
|
-
" \xB7 ",
|
|
1251
|
-
team
|
|
1252
|
-
] }),
|
|
1335
|
+
/* @__PURE__ */ jsxRuntime.jsx(PlayerIdentity, { name, team, variant: "compact", isActive }),
|
|
1253
1336
|
/* @__PURE__ */ jsxRuntime.jsx("span", { className: cn(
|
|
1254
1337
|
"font-anton text-[32px] leading-none tabular-nums",
|
|
1255
1338
|
isActive ? "text-fg-primary" : "text-fg-secondary"
|
|
@@ -1393,11 +1476,9 @@ function ConfirmDialog({ open, title, description, confirmLabel, cancelLabel, on
|
|
|
1393
1476
|
}
|
|
1394
1477
|
);
|
|
1395
1478
|
}
|
|
1396
|
-
function GameControls({ isMyTurn }) {
|
|
1397
|
-
const
|
|
1398
|
-
|
|
1399
|
-
endTurn
|
|
1400
|
-
} = chunkLTFNSTAQ_cjs.useGameStore();
|
|
1479
|
+
function GameControls({ isMyTurn, className }) {
|
|
1480
|
+
const boardState = chunkBSE4DUXM_cjs.useGameStore((s) => s.boardState);
|
|
1481
|
+
const endTurn = chunkBSE4DUXM_cjs.useGameStore((s) => s.endTurn);
|
|
1401
1482
|
const t = useGameT();
|
|
1402
1483
|
const [showEndTurnConfirm, setShowEndTurnConfirm] = React.useState(false);
|
|
1403
1484
|
if (!boardState) return null;
|
|
@@ -1409,7 +1490,7 @@ function GameControls({ isMyTurn }) {
|
|
|
1409
1490
|
endTurn();
|
|
1410
1491
|
}
|
|
1411
1492
|
};
|
|
1412
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "w-full bg-bg-secondary flex flex-col gap-3 px-5 pt-3 pb-5", children: [
|
|
1493
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("w-full bg-bg-secondary flex flex-col gap-3 px-5 pt-3 pb-5", className), children: [
|
|
1413
1494
|
isMyTurn && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1414
1495
|
Button,
|
|
1415
1496
|
{
|
|
@@ -1429,7 +1510,15 @@ function GameControls({ isMyTurn }) {
|
|
|
1429
1510
|
]
|
|
1430
1511
|
}
|
|
1431
1512
|
),
|
|
1432
|
-
!isMyTurn && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1513
|
+
!isMyTurn && /* @__PURE__ */ jsxRuntime.jsx(
|
|
1514
|
+
"div",
|
|
1515
|
+
{
|
|
1516
|
+
role: "status",
|
|
1517
|
+
"aria-live": "polite",
|
|
1518
|
+
className: "flex items-center justify-center h-11 rounded-md bg-bg-surface border border-border-subtle",
|
|
1519
|
+
children: /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-inter text-sm text-fg-muted", children: t("waitingRival") })
|
|
1520
|
+
}
|
|
1521
|
+
),
|
|
1433
1522
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1434
1523
|
ConfirmDialog,
|
|
1435
1524
|
{
|
|
@@ -1456,7 +1545,9 @@ var PIECE_ICON2 = {
|
|
|
1456
1545
|
};
|
|
1457
1546
|
function SelectedPieceDetail({ isMyTurn, className }) {
|
|
1458
1547
|
const t = useGameT();
|
|
1459
|
-
const { boardState, selectedPieceId } =
|
|
1548
|
+
const { boardState, selectedPieceId } = chunkBSE4DUXM_cjs.useGameStore(
|
|
1549
|
+
shallow.useShallow((s) => ({ boardState: s.boardState, selectedPieceId: s.selectedPieceId }))
|
|
1550
|
+
);
|
|
1460
1551
|
if (!boardState) return null;
|
|
1461
1552
|
const piece = boardState.pieces.find((p) => p.id === selectedPieceId);
|
|
1462
1553
|
if (!piece) {
|
|
@@ -1490,7 +1581,6 @@ function SelectedPieceDetail({ isMyTurn, className }) {
|
|
|
1490
1581
|
),
|
|
1491
1582
|
role: "status",
|
|
1492
1583
|
"aria-live": "polite",
|
|
1493
|
-
"aria-label": `${longName} ${square}`,
|
|
1494
1584
|
children: [
|
|
1495
1585
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1496
1586
|
"div",
|
|
@@ -1519,7 +1609,7 @@ function SelectedPieceDetail({ isMyTurn, className }) {
|
|
|
1519
1609
|
}
|
|
1520
1610
|
function MoveHistory({ scrollable = true, limit, className }) {
|
|
1521
1611
|
const t = useGameT();
|
|
1522
|
-
const
|
|
1612
|
+
const boardState = chunkBSE4DUXM_cjs.useGameStore((s) => s.boardState);
|
|
1523
1613
|
const scrollRef = React.useRef(null);
|
|
1524
1614
|
const history = boardState?.moveHistory ?? [];
|
|
1525
1615
|
const visible = limit ? history.slice(-limit) : history;
|
|
@@ -1626,9 +1716,9 @@ function HistoryRow({ entry, isLatest }) {
|
|
|
1626
1716
|
}
|
|
1627
1717
|
var COLS2 = 9;
|
|
1628
1718
|
var ROWS2 = 12;
|
|
1629
|
-
function StaticGameBoard() {
|
|
1630
|
-
const boardState = React.useMemo(() =>
|
|
1631
|
-
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full overflow-hidden rounded-xl shadow-2xl border-4 border-
|
|
1719
|
+
function StaticGameBoard({ className } = {}) {
|
|
1720
|
+
const boardState = React.useMemo(() => chunkBSE4DUXM_cjs.getInitialBoardState("white"), []);
|
|
1721
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("relative w-full overflow-hidden rounded-xl shadow-2xl border-4 border-field-frame bg-field-frame", className), children: [
|
|
1632
1722
|
/* @__PURE__ */ jsxRuntime.jsx(PitchSurface, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 pointer-events-none", children: boardState.pieces.map((piece) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1633
1723
|
"div",
|
|
1634
1724
|
{
|
|
@@ -1672,9 +1762,10 @@ function StaticGameBoard() {
|
|
|
1672
1762
|
}
|
|
1673
1763
|
function TurnBanner({ isMyTurn, waitingLabel, className }) {
|
|
1674
1764
|
const t = useGameT();
|
|
1675
|
-
const
|
|
1765
|
+
const boardState = chunkBSE4DUXM_cjs.useGameStore((s) => s.boardState);
|
|
1676
1766
|
if (!boardState) return null;
|
|
1677
|
-
const { actionPoints,
|
|
1767
|
+
const { actionPoints, turnNumber } = boardState;
|
|
1768
|
+
const maxActionPoints = boardState.maxActionPoints ?? chunkBSE4DUXM_cjs.DEFAULT_MAX_AP;
|
|
1678
1769
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
1679
1770
|
"div",
|
|
1680
1771
|
{
|
|
@@ -1686,16 +1777,19 @@ function TurnBanner({ isMyTurn, waitingLabel, className }) {
|
|
|
1686
1777
|
"aria-live": "polite",
|
|
1687
1778
|
"aria-atomic": "true",
|
|
1688
1779
|
children: [
|
|
1689
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1690
|
-
|
|
1691
|
-
|
|
1692
|
-
|
|
1693
|
-
|
|
1694
|
-
|
|
1695
|
-
|
|
1696
|
-
|
|
1697
|
-
|
|
1698
|
-
|
|
1780
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-baseline gap-2 min-w-0", children: [
|
|
1781
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1782
|
+
"span",
|
|
1783
|
+
{
|
|
1784
|
+
className: cn(
|
|
1785
|
+
"font-anton text-sm tracking-[1.5px] uppercase truncate",
|
|
1786
|
+
isMyTurn ? "text-accent-green" : "text-fg-muted"
|
|
1787
|
+
),
|
|
1788
|
+
children: isMyTurn ? t("yourTurn") : waitingLabel ?? t("waitingRival")
|
|
1789
|
+
}
|
|
1790
|
+
),
|
|
1791
|
+
typeof turnNumber === "number" && /* @__PURE__ */ jsxRuntime.jsx("span", { className: "font-mono text-[10px] text-fg-muted uppercase tracking-[0.5px] shrink-0", children: t("history.turn", { n: turnNumber }) })
|
|
1792
|
+
] }),
|
|
1699
1793
|
isMyTurn && /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "flex items-center gap-1", "aria-label": t("actionPointsAriaLabel", { remaining: actionPoints, total: maxActionPoints }), children: [
|
|
1700
1794
|
/* @__PURE__ */ jsxRuntime.jsx(lucideReact.Zap, { size: 12, className: "text-accent-green", "aria-hidden": "true" }),
|
|
1701
1795
|
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-mono text-xs text-fg-secondary tabular-nums", children: [
|
|
@@ -1717,7 +1811,7 @@ var TOAST_CONFIG = {
|
|
|
1717
1811
|
};
|
|
1718
1812
|
function EventToast({ className }) {
|
|
1719
1813
|
const t = useGameT();
|
|
1720
|
-
const
|
|
1814
|
+
const boardState = chunkBSE4DUXM_cjs.useGameStore((s) => s.boardState);
|
|
1721
1815
|
const [toast, setToast] = React.useState(null);
|
|
1722
1816
|
React.useEffect(() => {
|
|
1723
1817
|
const lastMove = boardState?.lastMove;
|
|
@@ -1756,7 +1850,7 @@ function EventToast({ className }) {
|
|
|
1756
1850
|
);
|
|
1757
1851
|
}
|
|
1758
1852
|
function MobileHistory({ className }) {
|
|
1759
|
-
const
|
|
1853
|
+
const boardState = chunkBSE4DUXM_cjs.useGameStore((s) => s.boardState);
|
|
1760
1854
|
const [open, setOpen] = React.useState(false);
|
|
1761
1855
|
const titleId = `${React.useId()}-title`;
|
|
1762
1856
|
const panelRef = useDialogA11y({ open, onClose: () => setOpen(false) });
|
|
@@ -1863,6 +1957,35 @@ function HistoryChip({ lastMove, onClick, className }) {
|
|
|
1863
1957
|
);
|
|
1864
1958
|
}
|
|
1865
1959
|
|
|
1960
|
+
// src/tokens.ts
|
|
1961
|
+
var REQUIRED_TOKENS = [
|
|
1962
|
+
// Surfaces
|
|
1963
|
+
"--bg-primary",
|
|
1964
|
+
"--bg-secondary",
|
|
1965
|
+
"--bg-surface",
|
|
1966
|
+
"--bg-surface-elevated",
|
|
1967
|
+
// Text
|
|
1968
|
+
"--fg-primary",
|
|
1969
|
+
"--fg-secondary",
|
|
1970
|
+
"--fg-muted",
|
|
1971
|
+
// Accent (the primary CTA rests on the dark step — see Button)
|
|
1972
|
+
"--accent-green",
|
|
1973
|
+
"--accent-green-light",
|
|
1974
|
+
"--accent-green-dark",
|
|
1975
|
+
// Borders
|
|
1976
|
+
"--border-subtle",
|
|
1977
|
+
// Semantic
|
|
1978
|
+
"--danger",
|
|
1979
|
+
"--warning",
|
|
1980
|
+
// Board
|
|
1981
|
+
"--field-green-1",
|
|
1982
|
+
"--field-green-2",
|
|
1983
|
+
"--field-frame",
|
|
1984
|
+
"--move-highlight",
|
|
1985
|
+
"--pass-highlight",
|
|
1986
|
+
"--last-move-highlight"
|
|
1987
|
+
];
|
|
1988
|
+
|
|
1866
1989
|
exports.ActionPoints = ActionPoints;
|
|
1867
1990
|
exports.Avatar = Avatar;
|
|
1868
1991
|
exports.AvatarFallback = AvatarFallback;
|
|
@@ -1887,6 +2010,8 @@ exports.PITCH_COLS = PITCH_COLS;
|
|
|
1887
2010
|
exports.PITCH_ROWS = PITCH_ROWS;
|
|
1888
2011
|
exports.PitchMarkings = PitchMarkings;
|
|
1889
2012
|
exports.PitchSurface = PitchSurface;
|
|
2013
|
+
exports.PlayerIdentity = PlayerIdentity;
|
|
2014
|
+
exports.REQUIRED_TOKENS = REQUIRED_TOKENS;
|
|
1890
2015
|
exports.Scoreboard = Scoreboard;
|
|
1891
2016
|
exports.SelectedPieceDetail = SelectedPieceDetail;
|
|
1892
2017
|
exports.StaticGameBoard = StaticGameBoard;
|