@scriptonita/chess-football-ui 0.6.0 → 0.7.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/dist/index.cjs +329 -173
- package/dist/index.cjs.map +1 -1
- package/dist/index.d.cts +103 -3
- package/dist/index.d.ts +103 -3
- package/dist/index.js +320 -174
- package/dist/index.js.map +1 -1
- package/package.json +1 -1
package/dist/index.js
CHANGED
|
@@ -53,26 +53,66 @@ var BLACK_PIECE_STYLE = {
|
|
|
53
53
|
border: "1px solid rgba(0,0,0,0.55)",
|
|
54
54
|
color: "#fafafa"
|
|
55
55
|
};
|
|
56
|
+
var ON_BOARD_CHIP_STYLE = {
|
|
57
|
+
white: {
|
|
58
|
+
...WHITE_PIECE_STYLE,
|
|
59
|
+
boxShadow: [
|
|
60
|
+
"inset 0 1.5px 1px rgba(255,255,255,0.95)",
|
|
61
|
+
"inset 0 -2px 3px rgba(0,0,0,0.18)",
|
|
62
|
+
"0 1px 1px rgba(0,0,0,0.35)"
|
|
63
|
+
].join(", ")
|
|
64
|
+
},
|
|
65
|
+
black: {
|
|
66
|
+
...BLACK_PIECE_STYLE,
|
|
67
|
+
boxShadow: [
|
|
68
|
+
"inset 0 1.5px 1px rgba(255,255,255,0.22)",
|
|
69
|
+
"inset 0 -2px 3px rgba(0,0,0,0.6)",
|
|
70
|
+
"0 1px 1px rgba(0,0,0,0.5)"
|
|
71
|
+
].join(", ")
|
|
72
|
+
}
|
|
73
|
+
};
|
|
56
74
|
var MOVED_STYLE = {
|
|
57
75
|
filter: "saturate(0.15) brightness(0.75)"
|
|
58
76
|
};
|
|
59
|
-
function GamePiece({ piece, isSelected, hasBall, onClick }) {
|
|
60
|
-
const
|
|
61
|
-
const style = piece.hasMovedThisTurn ? { ...baseStyle, ...MOVED_STYLE } : baseStyle;
|
|
77
|
+
function GamePiece({ piece, isSelected, hasBall, onClick, isMoving = false }) {
|
|
78
|
+
const chipStyle = ON_BOARD_CHIP_STYLE[piece.side];
|
|
62
79
|
return /* @__PURE__ */ jsxs(
|
|
63
80
|
"div",
|
|
64
81
|
{
|
|
65
82
|
onClick,
|
|
66
|
-
style,
|
|
83
|
+
style: piece.hasMovedThisTurn ? MOVED_STYLE : void 0,
|
|
67
84
|
className: cn(
|
|
68
|
-
"relative w-[88%] h-[88%]
|
|
85
|
+
"relative w-[88%] h-[88%] rounded-full cursor-pointer z-10 transition-transform duration-200 ease-out motion-reduce:transition-none",
|
|
69
86
|
isSelected && "ring-4 ring-primary scale-110 z-30",
|
|
87
|
+
isMoving && "scale-[1.08] z-40",
|
|
70
88
|
piece.hasMovedThisTurn && "opacity-70"
|
|
71
89
|
),
|
|
72
90
|
children: [
|
|
73
|
-
/* @__PURE__ */ jsx(
|
|
74
|
-
|
|
75
|
-
|
|
91
|
+
/* @__PURE__ */ jsx(
|
|
92
|
+
"div",
|
|
93
|
+
{
|
|
94
|
+
"aria-hidden": "true",
|
|
95
|
+
"data-testid": "piece-shadow",
|
|
96
|
+
"data-lifted": isMoving ? "true" : "false",
|
|
97
|
+
className: cn(
|
|
98
|
+
"absolute inset-0 rounded-full bg-black pointer-events-none transition-all duration-200 ease-out motion-reduce:transition-none",
|
|
99
|
+
isMoving ? "translate-x-[9%] translate-y-[24%] scale-[1.04] opacity-35 blur-[5px]" : "translate-x-[4%] translate-y-[9%] scale-95 opacity-50 blur-[3px]"
|
|
100
|
+
)
|
|
101
|
+
}
|
|
102
|
+
),
|
|
103
|
+
/* @__PURE__ */ jsxs(
|
|
104
|
+
"div",
|
|
105
|
+
{
|
|
106
|
+
"data-testid": "piece-chip",
|
|
107
|
+
style: chipStyle,
|
|
108
|
+
className: "absolute inset-0 rounded-full flex items-center justify-center",
|
|
109
|
+
children: [
|
|
110
|
+
/* @__PURE__ */ jsx(PieceIcon, { type: piece.type, side: piece.side }),
|
|
111
|
+
hasBall && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-full ring-2 ring-orange-400 pointer-events-none" }),
|
|
112
|
+
isSelected && /* @__PURE__ */ jsx("div", { className: "absolute inset-0 rounded-full animate-pulse motion-reduce:animate-none ring-2 ring-yellow-400 ring-offset-2 ring-offset-transparent" })
|
|
113
|
+
]
|
|
114
|
+
}
|
|
115
|
+
)
|
|
76
116
|
]
|
|
77
117
|
}
|
|
78
118
|
);
|
|
@@ -184,10 +224,204 @@ function GameI18nProvider({ t, children }) {
|
|
|
184
224
|
function useGameT() {
|
|
185
225
|
return useContext(GameI18nContext);
|
|
186
226
|
}
|
|
227
|
+
function BallHolderChip({ className }) {
|
|
228
|
+
const t = useGameT();
|
|
229
|
+
const boardState = useGameStore((s) => s.boardState);
|
|
230
|
+
const holder = boardState?.pieces.find((p) => p.id === boardState.ball.holderId) ?? null;
|
|
231
|
+
if (!holder) return null;
|
|
232
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("flex items-center gap-1.5 min-h-[1.75rem]", className), "aria-live": "polite", children: [
|
|
233
|
+
/* @__PURE__ */ jsxs(
|
|
234
|
+
"div",
|
|
235
|
+
{
|
|
236
|
+
style: holder.side === "white" ? WHITE_PIECE_STYLE : BLACK_PIECE_STYLE,
|
|
237
|
+
className: "relative w-7 h-7 flex items-center justify-center rounded-full shrink-0",
|
|
238
|
+
children: [
|
|
239
|
+
/* @__PURE__ */ jsx(PieceIcon, { type: holder.type, side: holder.side }),
|
|
240
|
+
/* @__PURE__ */ jsx("div", { className: "absolute -bottom-1 -right-1 w-3.5 h-3.5 flex items-center justify-center rounded-full bg-bg-secondary border border-border-subtle", children: /* @__PURE__ */ jsx("div", { className: "w-2.5 h-2.5", children: /* @__PURE__ */ jsx(Football, {}) }) })
|
|
241
|
+
]
|
|
242
|
+
}
|
|
243
|
+
),
|
|
244
|
+
/* @__PURE__ */ jsxs("span", { className: "font-inter text-[11px] text-fg-secondary", children: [
|
|
245
|
+
t("ballHolder"),
|
|
246
|
+
": ",
|
|
247
|
+
t(`pieces.${holder.type}`)
|
|
248
|
+
] })
|
|
249
|
+
] });
|
|
250
|
+
}
|
|
251
|
+
function KeyboardShortcutsList({ className }) {
|
|
252
|
+
const t = useGameT();
|
|
253
|
+
return /* @__PURE__ */ jsxs("ul", { className: cn("font-mono text-[10px] text-fg-secondary leading-relaxed", className), children: [
|
|
254
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
255
|
+
/* @__PURE__ */ jsx("kbd", { className: "text-fg-primary", children: "\u2191\u2193\u2190\u2192" }),
|
|
256
|
+
" ",
|
|
257
|
+
t("shortcuts.arrows")
|
|
258
|
+
] }),
|
|
259
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
260
|
+
/* @__PURE__ */ jsx("kbd", { className: "text-fg-primary", children: "Enter" }),
|
|
261
|
+
" ",
|
|
262
|
+
t("shortcuts.select")
|
|
263
|
+
] }),
|
|
264
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
265
|
+
/* @__PURE__ */ jsx("kbd", { className: "text-fg-primary", children: "M" }),
|
|
266
|
+
" ",
|
|
267
|
+
t("shortcuts.move")
|
|
268
|
+
] }),
|
|
269
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
270
|
+
/* @__PURE__ */ jsx("kbd", { className: "text-fg-primary", children: "P" }),
|
|
271
|
+
" ",
|
|
272
|
+
t("shortcuts.pass")
|
|
273
|
+
] }),
|
|
274
|
+
/* @__PURE__ */ jsxs("li", { children: [
|
|
275
|
+
/* @__PURE__ */ jsx("kbd", { className: "text-fg-primary", children: "Esc" }),
|
|
276
|
+
" ",
|
|
277
|
+
t("shortcuts.cancel")
|
|
278
|
+
] })
|
|
279
|
+
] });
|
|
280
|
+
}
|
|
281
|
+
var PITCH_COLS = 9;
|
|
282
|
+
var PITCH_ROWS = 12;
|
|
283
|
+
var isGoalAreaSquare = (x, y) => x >= 2 && x <= 6 && (y >= 0 && y <= 1 || y >= PITCH_ROWS - 2 && y <= PITCH_ROWS - 1);
|
|
284
|
+
var GOAL_AREA_SHADE_CLASS = "shadow-[inset_0_0_0_100vmax_rgba(0,0,0,0.32)]";
|
|
285
|
+
function pitchSquareClass(x, y) {
|
|
286
|
+
const isEven = (x + y) % 2 === 0;
|
|
287
|
+
return cn(
|
|
288
|
+
"relative aspect-square w-full",
|
|
289
|
+
isEven ? "bg-field-green-1" : "bg-field-green-2",
|
|
290
|
+
isGoalAreaSquare(x, y) && GOAL_AREA_SHADE_CLASS
|
|
291
|
+
);
|
|
292
|
+
}
|
|
293
|
+
var GRAIN_TILE_SVG = '<svg xmlns="http://www.w3.org/2000/svg" width="160" height="160"><filter id="g" x="0" y="0" width="100%" height="100%" color-interpolation-filters="sRGB"><feTurbulence type="fractalNoise" baseFrequency="0.9" numOctaves="3" seed="7" stitchTiles="stitch"/><feColorMatrix type="saturate" values="0"/></filter><rect width="100%" height="100%" filter="url(#g)"/></svg>';
|
|
294
|
+
var GRAIN_TILE_URL = `url("data:image/svg+xml;utf8,${encodeURIComponent(GRAIN_TILE_SVG)}")`;
|
|
295
|
+
var VIGNETTE = "radial-gradient(120% 95% at 50% 38%, rgba(255,255,255,0.06) 0%, rgba(255,255,255,0) 42%, rgba(0,0,0,0.26) 100%)";
|
|
296
|
+
function GrassOverlay({ className }) {
|
|
297
|
+
return /* @__PURE__ */ jsxs(
|
|
298
|
+
"div",
|
|
299
|
+
{
|
|
300
|
+
"aria-hidden": "true",
|
|
301
|
+
"data-testid": "grass-overlay",
|
|
302
|
+
className: cn("absolute inset-0 pointer-events-none overflow-hidden", className),
|
|
303
|
+
children: [
|
|
304
|
+
/* @__PURE__ */ jsx(
|
|
305
|
+
"div",
|
|
306
|
+
{
|
|
307
|
+
"data-layer": "grain",
|
|
308
|
+
className: "absolute inset-0 mix-blend-overlay opacity-60",
|
|
309
|
+
style: { backgroundImage: GRAIN_TILE_URL, backgroundSize: "160px 160px" }
|
|
310
|
+
}
|
|
311
|
+
),
|
|
312
|
+
/* @__PURE__ */ jsx("div", { "data-layer": "light", className: "absolute inset-0", style: { background: VIGNETTE } })
|
|
313
|
+
]
|
|
314
|
+
}
|
|
315
|
+
);
|
|
316
|
+
}
|
|
317
|
+
var CHALK = "#f6f5ee";
|
|
318
|
+
function PitchMarkings({ className, children }) {
|
|
319
|
+
const id = useId();
|
|
320
|
+
const filterId = `${id}-chalk`;
|
|
321
|
+
return /* @__PURE__ */ jsxs(
|
|
322
|
+
"svg",
|
|
323
|
+
{
|
|
324
|
+
viewBox: "0 0 9 12",
|
|
325
|
+
preserveAspectRatio: "none",
|
|
326
|
+
className: cn("absolute inset-0 w-full h-full pointer-events-none", className),
|
|
327
|
+
"aria-hidden": "true",
|
|
328
|
+
"data-testid": "pitch-markings",
|
|
329
|
+
children: [
|
|
330
|
+
/* @__PURE__ */ jsx("defs", { children: /* @__PURE__ */ jsxs(
|
|
331
|
+
"filter",
|
|
332
|
+
{
|
|
333
|
+
id: filterId,
|
|
334
|
+
filterUnits: "userSpaceOnUse",
|
|
335
|
+
x: "-0.5",
|
|
336
|
+
y: "-0.5",
|
|
337
|
+
width: "10",
|
|
338
|
+
height: "13",
|
|
339
|
+
colorInterpolationFilters: "sRGB",
|
|
340
|
+
children: [
|
|
341
|
+
/* @__PURE__ */ jsx("feTurbulence", { type: "fractalNoise", baseFrequency: "5.5", numOctaves: "2", seed: "3", result: "edgeNoise" }),
|
|
342
|
+
/* @__PURE__ */ jsx(
|
|
343
|
+
"feDisplacementMap",
|
|
344
|
+
{
|
|
345
|
+
in: "SourceGraphic",
|
|
346
|
+
in2: "edgeNoise",
|
|
347
|
+
scale: "0.045",
|
|
348
|
+
xChannelSelector: "R",
|
|
349
|
+
yChannelSelector: "G",
|
|
350
|
+
result: "rough"
|
|
351
|
+
}
|
|
352
|
+
),
|
|
353
|
+
/* @__PURE__ */ jsx("feTurbulence", { type: "fractalNoise", baseFrequency: "14", numOctaves: "3", seed: "11", result: "dust" }),
|
|
354
|
+
/* @__PURE__ */ jsx(
|
|
355
|
+
"feColorMatrix",
|
|
356
|
+
{
|
|
357
|
+
in: "dust",
|
|
358
|
+
type: "matrix",
|
|
359
|
+
values: "0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0.75 0 0 0 0.55",
|
|
360
|
+
result: "dustMask"
|
|
361
|
+
}
|
|
362
|
+
),
|
|
363
|
+
/* @__PURE__ */ jsx("feComposite", { in: "rough", in2: "dustMask", operator: "arithmetic", k1: "1", k2: "0", k3: "0", k4: "0" })
|
|
364
|
+
]
|
|
365
|
+
}
|
|
366
|
+
) }),
|
|
367
|
+
/* @__PURE__ */ jsx("g", { "data-layer": "halo", fill: "none", stroke: CHALK, strokeOpacity: "0.09", strokeWidth: "0.16", strokeLinecap: "round", children: /* @__PURE__ */ jsx(Markings, {}) }),
|
|
368
|
+
/* @__PURE__ */ jsx(
|
|
369
|
+
"g",
|
|
370
|
+
{
|
|
371
|
+
"data-layer": "chalk",
|
|
372
|
+
filter: `url(#${filterId})`,
|
|
373
|
+
fill: "none",
|
|
374
|
+
stroke: CHALK,
|
|
375
|
+
strokeOpacity: "0.82",
|
|
376
|
+
strokeWidth: "0.07",
|
|
377
|
+
strokeLinecap: "round",
|
|
378
|
+
strokeLinejoin: "round",
|
|
379
|
+
children: /* @__PURE__ */ jsx(Markings, {})
|
|
380
|
+
}
|
|
381
|
+
),
|
|
382
|
+
children
|
|
383
|
+
]
|
|
384
|
+
}
|
|
385
|
+
);
|
|
386
|
+
}
|
|
387
|
+
function Markings() {
|
|
388
|
+
return /* @__PURE__ */ jsxs(Fragment, { children: [
|
|
389
|
+
/* @__PURE__ */ jsx("rect", { x: "0.06", y: "0.06", width: "8.88", height: "11.88" }),
|
|
390
|
+
/* @__PURE__ */ jsx("rect", { x: "2", y: "0.06", width: "5", height: "1.94" }),
|
|
391
|
+
/* @__PURE__ */ jsx("rect", { x: "2", y: "10", width: "5", height: "1.94" }),
|
|
392
|
+
/* @__PURE__ */ jsx("line", { x1: "0.06", y1: "6", x2: "8.94", y2: "6" }),
|
|
393
|
+
/* @__PURE__ */ jsx("circle", { cx: "4.5", cy: "6", r: "1.5" }),
|
|
394
|
+
/* @__PURE__ */ jsx("circle", { cx: "4.5", cy: "6", r: "0.09", fill: CHALK, fillOpacity: "0.9", stroke: "none" }),
|
|
395
|
+
/* @__PURE__ */ jsx("circle", { cx: "4.5", cy: "1.5", r: "0.08", fill: CHALK, fillOpacity: "0.9", stroke: "none" }),
|
|
396
|
+
/* @__PURE__ */ jsx("circle", { cx: "4.5", cy: "10.5", r: "0.08", fill: CHALK, fillOpacity: "0.9", stroke: "none" }),
|
|
397
|
+
/* @__PURE__ */ jsx("path", { d: "M 3.086,2 A 1.5,1.5 0 0 0 5.914,2" }),
|
|
398
|
+
/* @__PURE__ */ jsx("path", { d: "M 3.086,10 A 1.5,1.5 0 0 1 5.914,10" })
|
|
399
|
+
] });
|
|
400
|
+
}
|
|
401
|
+
function PitchSurface({ className, squareClassName, children }) {
|
|
402
|
+
const squares = [];
|
|
403
|
+
for (let y = PITCH_ROWS - 1; y >= 0; y--) {
|
|
404
|
+
for (let x = 0; x < PITCH_COLS; x++) {
|
|
405
|
+
squares.push(
|
|
406
|
+
/* @__PURE__ */ jsx("div", { className: cn(pitchSquareClass(x, y), squareClassName?.(x, y)) }, `${x}-${y}`)
|
|
407
|
+
);
|
|
408
|
+
}
|
|
409
|
+
}
|
|
410
|
+
return /* @__PURE__ */ jsxs("div", { className: cn("relative", className), style: { containerType: "inline-size" }, children: [
|
|
411
|
+
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-9 gap-[1px]", "data-testid": "pitch-squares", children: squares }),
|
|
412
|
+
/* @__PURE__ */ jsx(GrassOverlay, {}),
|
|
413
|
+
/* @__PURE__ */ jsx(PitchMarkings, {}),
|
|
414
|
+
children
|
|
415
|
+
] });
|
|
416
|
+
}
|
|
187
417
|
var COLS = 9;
|
|
188
418
|
var ROWS = 12;
|
|
189
419
|
var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
190
|
-
|
|
420
|
+
var cellX = (x) => `${x * 100}%`;
|
|
421
|
+
var cellY = (y) => `${(ROWS - 1 - y) * 100}%`;
|
|
422
|
+
var PIECE_SLIDE = { type: "spring", stiffness: 190, damping: 28, mass: 1 };
|
|
423
|
+
var BALL_SLIDE = { type: "spring", stiffness: 300, damping: 26, mass: 0.6 };
|
|
424
|
+
function GameBoard({ userSide, showCoordinates = false, keyboardNav = true, toolbar = true }) {
|
|
191
425
|
const t = useGameT();
|
|
192
426
|
const prefersReducedMotion = useReducedMotion();
|
|
193
427
|
const {
|
|
@@ -219,6 +453,14 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true }) {
|
|
|
219
453
|
setInvalidClickAt({ x, y, nonce: invalidClickNonceRef.current });
|
|
220
454
|
};
|
|
221
455
|
const [hoveredPassAt, setHoveredPassAt] = useState(null);
|
|
456
|
+
const [movingIds, setMovingIds] = useState(() => /* @__PURE__ */ new Set());
|
|
457
|
+
const setMoving = (id, moving) => setMovingIds((prev) => {
|
|
458
|
+
if (prev.has(id) === moving) return prev;
|
|
459
|
+
const next = new Set(prev);
|
|
460
|
+
if (moving) next.add(id);
|
|
461
|
+
else next.delete(id);
|
|
462
|
+
return next;
|
|
463
|
+
});
|
|
222
464
|
useEffect(() => {
|
|
223
465
|
if (!invalidClickAt) return;
|
|
224
466
|
const id = setTimeout(() => setInvalidClickAt(null), 100);
|
|
@@ -273,7 +515,6 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true }) {
|
|
|
273
515
|
return [];
|
|
274
516
|
}, [boardState, selectedPieceId]);
|
|
275
517
|
if (!boardState) return null;
|
|
276
|
-
const ballHolderPiece = boardState.pieces.find((p) => p.id === boardState.ball.holderId) ?? null;
|
|
277
518
|
const lastMove = boardState.lastMove;
|
|
278
519
|
const ball = boardState.ball;
|
|
279
520
|
const ballMoved = !!prevBall && (prevBall.pos.x !== ball.pos.x || prevBall.pos.y !== ball.pos.y);
|
|
@@ -416,8 +657,6 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true }) {
|
|
|
416
657
|
const squares = [];
|
|
417
658
|
for (let y = ROWS - 1; y >= 0; y--) {
|
|
418
659
|
for (let x = 0; x < COLS; x++) {
|
|
419
|
-
const isGoalArea = x >= 2 && x <= 6 && (y >= 0 && y <= 1 || y >= 10 && y <= 11);
|
|
420
|
-
const isEven = (x + y) % 2 === 0;
|
|
421
660
|
const isValidMove = validMoves.some((m) => m.x === x && m.y === y);
|
|
422
661
|
const isValidPass = validPasses.some((p) => p.x === x && p.y === y);
|
|
423
662
|
const isAmbiguous = isValidMove && isValidPass;
|
|
@@ -439,9 +678,8 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true }) {
|
|
|
439
678
|
},
|
|
440
679
|
onMouseLeave: () => setHoveredPassAt(null),
|
|
441
680
|
className: cn(
|
|
442
|
-
|
|
443
|
-
|
|
444
|
-
isGoalArea && "bg-[#1a3a18] ring-1 ring-inset ring-emerald-500/30",
|
|
681
|
+
pitchSquareClass(x, y),
|
|
682
|
+
"flex items-center justify-center cursor-pointer",
|
|
445
683
|
!isValidMove && !isValidPass && isLastMove && "bg-last-move-highlight/20",
|
|
446
684
|
// §8: pulsing warning ring on enemy goal area
|
|
447
685
|
isOffsideRisk && isEnemyGoalArea && !isValidMove && !isValidPass && "ring-2 ring-inset ring-warning/60",
|
|
@@ -528,25 +766,8 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true }) {
|
|
|
528
766
|
onKeyDown: handleKeyDown
|
|
529
767
|
} : {},
|
|
530
768
|
children: [
|
|
531
|
-
/* @__PURE__ */ jsxs("div", { className: "hidden md:flex items-center justify-between gap-2 px-2.5 py-1", children: [
|
|
532
|
-
/* @__PURE__ */ jsx(
|
|
533
|
-
/* @__PURE__ */ jsxs(
|
|
534
|
-
"div",
|
|
535
|
-
{
|
|
536
|
-
style: ballHolderPiece.side === "white" ? WHITE_PIECE_STYLE : BLACK_PIECE_STYLE,
|
|
537
|
-
className: "relative w-7 h-7 flex items-center justify-center rounded-full shrink-0",
|
|
538
|
-
children: [
|
|
539
|
-
/* @__PURE__ */ jsx(PieceIcon, { type: ballHolderPiece.type, side: ballHolderPiece.side }),
|
|
540
|
-
/* @__PURE__ */ jsx("div", { className: "absolute -bottom-1 -right-1 w-3.5 h-3.5 flex items-center justify-center rounded-full bg-bg-secondary border border-border-subtle", children: /* @__PURE__ */ jsx("div", { className: "w-2.5 h-2.5", children: /* @__PURE__ */ jsx(Football, {}) }) })
|
|
541
|
-
]
|
|
542
|
-
}
|
|
543
|
-
),
|
|
544
|
-
/* @__PURE__ */ jsxs("span", { className: "font-inter text-[11px] text-fg-secondary", children: [
|
|
545
|
-
t("ballHolder"),
|
|
546
|
-
": ",
|
|
547
|
-
t(`pieces.${ballHolderPiece.type}`)
|
|
548
|
-
] })
|
|
549
|
-
] }) }),
|
|
769
|
+
toolbar && /* @__PURE__ */ jsxs("div", { className: "hidden md:flex items-center justify-between gap-2 px-2.5 py-1", "data-testid": "board-toolbar", children: [
|
|
770
|
+
/* @__PURE__ */ jsx(BallHolderChip, {}),
|
|
550
771
|
keyboardNav && /* @__PURE__ */ jsxs(
|
|
551
772
|
"div",
|
|
552
773
|
{
|
|
@@ -575,33 +796,7 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true }) {
|
|
|
575
796
|
onClick: (e) => e.stopPropagation(),
|
|
576
797
|
children: [
|
|
577
798
|
/* @__PURE__ */ jsx("p", { className: "font-inter text-[11px] font-semibold text-fg-primary mb-1.5", children: t("shortcuts.title") }),
|
|
578
|
-
/* @__PURE__ */
|
|
579
|
-
/* @__PURE__ */ jsxs("li", { children: [
|
|
580
|
-
/* @__PURE__ */ jsx("kbd", { className: "text-fg-primary", children: "\u2191\u2193\u2190\u2192" }),
|
|
581
|
-
" ",
|
|
582
|
-
t("shortcuts.arrows")
|
|
583
|
-
] }),
|
|
584
|
-
/* @__PURE__ */ jsxs("li", { children: [
|
|
585
|
-
/* @__PURE__ */ jsx("kbd", { className: "text-fg-primary", children: "Enter" }),
|
|
586
|
-
" ",
|
|
587
|
-
t("shortcuts.select")
|
|
588
|
-
] }),
|
|
589
|
-
/* @__PURE__ */ jsxs("li", { children: [
|
|
590
|
-
/* @__PURE__ */ jsx("kbd", { className: "text-fg-primary", children: "M" }),
|
|
591
|
-
" ",
|
|
592
|
-
t("shortcuts.move")
|
|
593
|
-
] }),
|
|
594
|
-
/* @__PURE__ */ jsxs("li", { children: [
|
|
595
|
-
/* @__PURE__ */ jsx("kbd", { className: "text-fg-primary", children: "P" }),
|
|
596
|
-
" ",
|
|
597
|
-
t("shortcuts.pass")
|
|
598
|
-
] }),
|
|
599
|
-
/* @__PURE__ */ jsxs("li", { children: [
|
|
600
|
-
/* @__PURE__ */ jsx("kbd", { className: "text-fg-primary", children: "Esc" }),
|
|
601
|
-
" ",
|
|
602
|
-
t("shortcuts.cancel")
|
|
603
|
-
] })
|
|
604
|
-
] })
|
|
799
|
+
/* @__PURE__ */ jsx(KeyboardShortcutsList, {})
|
|
605
800
|
]
|
|
606
801
|
}
|
|
607
802
|
)
|
|
@@ -611,58 +806,51 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true }) {
|
|
|
611
806
|
] }),
|
|
612
807
|
/* @__PURE__ */ jsxs("div", { className: "relative", style: { containerType: "inline-size" }, children: [
|
|
613
808
|
/* @__PURE__ */ jsx("div", { className: "grid grid-cols-9 gap-[1px]", children: renderSquares() }),
|
|
614
|
-
/* @__PURE__ */
|
|
809
|
+
/* @__PURE__ */ jsx(GrassOverlay, {}),
|
|
810
|
+
/* @__PURE__ */ jsx(PitchMarkings, {}),
|
|
811
|
+
/* @__PURE__ */ jsx(
|
|
615
812
|
"svg",
|
|
616
813
|
{
|
|
617
814
|
viewBox: "0 0 9 12",
|
|
618
815
|
preserveAspectRatio: "none",
|
|
619
816
|
className: "absolute inset-0 w-full h-full pointer-events-none",
|
|
620
817
|
"aria-hidden": "true",
|
|
621
|
-
children:
|
|
622
|
-
|
|
623
|
-
|
|
624
|
-
|
|
625
|
-
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
|
|
630
|
-
|
|
631
|
-
|
|
632
|
-
"
|
|
633
|
-
|
|
634
|
-
|
|
635
|
-
x1: passCarrier.pos.x + 0.5,
|
|
636
|
-
y1: ROWS - 1 - passCarrier.pos.y + 0.5,
|
|
637
|
-
x2: hoveredPassAt.x + 0.5,
|
|
638
|
-
y2: ROWS - 1 - hoveredPassAt.y + 0.5,
|
|
639
|
-
stroke: trajectoryIntercepted ? "var(--danger)" : "var(--pass-highlight)",
|
|
640
|
-
strokeWidth: "0.08",
|
|
641
|
-
strokeDasharray: "0.15 0.1",
|
|
642
|
-
strokeLinecap: "round"
|
|
643
|
-
}
|
|
644
|
-
)
|
|
645
|
-
]
|
|
818
|
+
children: hoveredPassAt && passCarrier && /* @__PURE__ */ jsx(
|
|
819
|
+
"line",
|
|
820
|
+
{
|
|
821
|
+
"data-testid": "pass-trajectory-line",
|
|
822
|
+
x1: passCarrier.pos.x + 0.5,
|
|
823
|
+
y1: ROWS - 1 - passCarrier.pos.y + 0.5,
|
|
824
|
+
x2: hoveredPassAt.x + 0.5,
|
|
825
|
+
y2: ROWS - 1 - hoveredPassAt.y + 0.5,
|
|
826
|
+
stroke: trajectoryIntercepted ? "var(--danger)" : "var(--pass-highlight)",
|
|
827
|
+
strokeWidth: "0.08",
|
|
828
|
+
strokeDasharray: "0.15 0.1",
|
|
829
|
+
strokeLinecap: "round"
|
|
830
|
+
}
|
|
831
|
+
)
|
|
646
832
|
}
|
|
647
833
|
),
|
|
648
|
-
/* @__PURE__ */ jsxs("div", { className: "absolute inset-0 pointer-events-none", children: [
|
|
834
|
+
/* @__PURE__ */ jsxs("div", { className: "absolute inset-0 pointer-events-none", "data-testid": "pieces-layer", children: [
|
|
649
835
|
/* @__PURE__ */ jsx(AnimatePresence, { mode: "sync", children: boardState.pieces.map((piece) => {
|
|
650
836
|
const isPickupCarrier = pickupCarrierId === piece.id;
|
|
651
|
-
const pieceTransition = isPickupCarrier ? { duration: PICKUP_DURATION, ease: "linear" } :
|
|
837
|
+
const pieceTransition = isPickupCarrier ? { duration: PICKUP_DURATION, ease: "linear" } : PIECE_SLIDE;
|
|
652
838
|
return /* @__PURE__ */ jsx(
|
|
653
839
|
motion.div,
|
|
654
840
|
{
|
|
655
|
-
|
|
841
|
+
"data-piece-id": piece.id,
|
|
656
842
|
initial: false,
|
|
657
|
-
animate: {
|
|
658
|
-
left: `${piece.pos.x / COLS * 100}%`,
|
|
659
|
-
top: `${(ROWS - 1 - piece.pos.y) / ROWS * 100}%`
|
|
660
|
-
},
|
|
843
|
+
animate: { x: cellX(piece.pos.x), y: cellY(piece.pos.y) },
|
|
661
844
|
transition: pieceTransition,
|
|
845
|
+
onAnimationStart: () => setMoving(piece.id, true),
|
|
846
|
+
onAnimationComplete: () => setMoving(piece.id, false),
|
|
662
847
|
style: {
|
|
663
848
|
position: "absolute",
|
|
849
|
+
left: 0,
|
|
850
|
+
top: 0,
|
|
664
851
|
width: `${100 / COLS}%`,
|
|
665
|
-
height: `${100 / ROWS}
|
|
852
|
+
height: `${100 / ROWS}%`,
|
|
853
|
+
willChange: "transform"
|
|
666
854
|
},
|
|
667
855
|
className: "flex items-center justify-center pointer-events-auto",
|
|
668
856
|
children: /* @__PURE__ */ jsx(
|
|
@@ -671,6 +859,7 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true }) {
|
|
|
671
859
|
piece,
|
|
672
860
|
isSelected: selectedPieceId === piece.id,
|
|
673
861
|
hasBall: boardState.ball.holderId === piece.id,
|
|
862
|
+
isMoving: movingIds.has(piece.id),
|
|
674
863
|
onClick: (e) => handlePieceClick(piece.id, piece.pos.x, piece.pos.y, e)
|
|
675
864
|
}
|
|
676
865
|
)
|
|
@@ -679,23 +868,23 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true }) {
|
|
|
679
868
|
);
|
|
680
869
|
}) }),
|
|
681
870
|
(() => {
|
|
682
|
-
const targetX = ball.pos.x
|
|
683
|
-
const targetY = (
|
|
871
|
+
const targetX = cellX(ball.pos.x);
|
|
872
|
+
const targetY = cellY(ball.pos.y);
|
|
684
873
|
let animate;
|
|
685
874
|
let transition;
|
|
686
875
|
if (isPickupOnMove && prevBall) {
|
|
687
|
-
const prevX = prevBall.pos.x
|
|
688
|
-
const prevY = (
|
|
876
|
+
const prevX = cellX(prevBall.pos.x);
|
|
877
|
+
const prevY = cellY(prevBall.pos.y);
|
|
689
878
|
const pulseStart = Math.max(0.05, pickupProportion - 0.06);
|
|
690
879
|
animate = {
|
|
691
|
-
|
|
692
|
-
|
|
880
|
+
x: [prevX, prevX, prevX, targetX],
|
|
881
|
+
y: [prevY, prevY, prevY, targetY],
|
|
693
882
|
scale: [1, 1, 1.35, 1]
|
|
694
883
|
};
|
|
695
884
|
transition = { duration: PICKUP_DURATION, times: [0, pulseStart, pickupProportion, 1], ease: "linear" };
|
|
696
885
|
} else {
|
|
697
|
-
animate = {
|
|
698
|
-
transition =
|
|
886
|
+
animate = { x: targetX, y: targetY, scale: 1 };
|
|
887
|
+
transition = ball.holderId ? PIECE_SLIDE : BALL_SLIDE;
|
|
699
888
|
}
|
|
700
889
|
return /* @__PURE__ */ jsx(
|
|
701
890
|
motion.div,
|
|
@@ -705,9 +894,12 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true }) {
|
|
|
705
894
|
transition,
|
|
706
895
|
style: {
|
|
707
896
|
position: "absolute",
|
|
897
|
+
left: 0,
|
|
898
|
+
top: 0,
|
|
708
899
|
width: `${100 / COLS}%`,
|
|
709
900
|
height: `${100 / ROWS}%`,
|
|
710
|
-
zIndex: 50
|
|
901
|
+
zIndex: 50,
|
|
902
|
+
willChange: "transform"
|
|
711
903
|
},
|
|
712
904
|
className: "flex items-center justify-center pointer-events-none",
|
|
713
905
|
children: /* @__PURE__ */ jsx("div", { className: "w-[60%] h-[60%]", children: /* @__PURE__ */ jsx(Football, {}) })
|
|
@@ -1268,77 +1460,31 @@ var COLS2 = 9;
|
|
|
1268
1460
|
var ROWS2 = 12;
|
|
1269
1461
|
function StaticGameBoard() {
|
|
1270
1462
|
const boardState = useMemo(() => getInitialBoardState("white"), []);
|
|
1271
|
-
const renderSquares = () => {
|
|
1272
|
-
const squares = [];
|
|
1273
|
-
for (let y = ROWS2 - 1; y >= 0; y--) {
|
|
1274
|
-
for (let x = 0; x < COLS2; x++) {
|
|
1275
|
-
const isGoalArea = x >= 2 && x <= 6 && (y >= 0 && y <= 1 || y >= 10 && y <= 11);
|
|
1276
|
-
const isEven = (x + y) % 2 === 0;
|
|
1277
|
-
squares.push(
|
|
1278
|
-
/* @__PURE__ */ jsx(
|
|
1279
|
-
"div",
|
|
1280
|
-
{
|
|
1281
|
-
className: cn(
|
|
1282
|
-
"relative aspect-square w-full",
|
|
1283
|
-
isEven ? "bg-[#2d5a27]" : "bg-[#356a2d]",
|
|
1284
|
-
isGoalArea && "bg-[#1a3a18] ring-1 ring-inset ring-emerald-500/30"
|
|
1285
|
-
)
|
|
1286
|
-
},
|
|
1287
|
-
`${x}-${y}`
|
|
1288
|
-
)
|
|
1289
|
-
);
|
|
1290
|
-
}
|
|
1291
|
-
}
|
|
1292
|
-
return squares;
|
|
1293
|
-
};
|
|
1294
1463
|
return /* @__PURE__ */ jsxs("div", { className: "relative w-full overflow-hidden rounded-xl shadow-2xl border-4 border-[#1a3317] bg-[#1a3317]", children: [
|
|
1295
|
-
/* @__PURE__ */
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
1304
|
-
children: [
|
|
1305
|
-
/* @__PURE__ */ jsx("rect", { x: "2", y: "0", width: "5", height: "2", fill: "none", stroke: "white", strokeOpacity: "0.3", strokeWidth: "0.05" }),
|
|
1306
|
-
/* @__PURE__ */ jsx("rect", { x: "2", y: "10", width: "5", height: "2", fill: "none", stroke: "white", strokeOpacity: "0.3", strokeWidth: "0.05" }),
|
|
1307
|
-
/* @__PURE__ */ jsx("line", { x1: "0", y1: "6", x2: "9", y2: "6", stroke: "white", strokeOpacity: "0.35", strokeWidth: "0.05" }),
|
|
1308
|
-
/* @__PURE__ */ jsx("circle", { cx: "4.5", cy: "6", r: "1.5", fill: "none", stroke: "white", strokeOpacity: "0.3", strokeWidth: "0.05" }),
|
|
1309
|
-
/* @__PURE__ */ jsx("circle", { cx: "4.5", cy: "6", r: "0.12", fill: "white", fillOpacity: "0.5" }),
|
|
1310
|
-
/* @__PURE__ */ jsx("circle", { cx: "4.5", cy: "1.5", r: "0.1", fill: "white", fillOpacity: "0.5" }),
|
|
1311
|
-
/* @__PURE__ */ jsx("circle", { cx: "4.5", cy: "10.5", r: "0.1", fill: "white", fillOpacity: "0.5" }),
|
|
1312
|
-
/* @__PURE__ */ jsx("path", { d: "M 3.086,2 A 1.5,1.5 0 0 0 5.914,2", fill: "none", stroke: "white", strokeOpacity: "0.3", strokeWidth: "0.05" }),
|
|
1313
|
-
/* @__PURE__ */ jsx("path", { d: "M 3.086,10 A 1.5,1.5 0 0 1 5.914,10", fill: "none", stroke: "white", strokeOpacity: "0.3", strokeWidth: "0.05" })
|
|
1314
|
-
]
|
|
1315
|
-
}
|
|
1316
|
-
),
|
|
1317
|
-
/* @__PURE__ */ jsx("div", { className: "absolute inset-0 pointer-events-none", children: boardState.pieces.map((piece) => /* @__PURE__ */ jsx(
|
|
1318
|
-
"div",
|
|
1319
|
-
{
|
|
1320
|
-
style: {
|
|
1321
|
-
position: "absolute",
|
|
1322
|
-
left: `${piece.pos.x / COLS2 * 100}%`,
|
|
1323
|
-
top: `${(ROWS2 - 1 - piece.pos.y) / ROWS2 * 100}%`,
|
|
1324
|
-
width: `${100 / COLS2}%`,
|
|
1325
|
-
height: `${100 / ROWS2}%`
|
|
1326
|
-
},
|
|
1327
|
-
className: "flex items-center justify-center",
|
|
1328
|
-
children: /* @__PURE__ */ jsx(
|
|
1329
|
-
GamePiece,
|
|
1330
|
-
{
|
|
1331
|
-
piece,
|
|
1332
|
-
isSelected: false,
|
|
1333
|
-
hasBall: boardState.ball.holderId === piece.id,
|
|
1334
|
-
onClick: () => {
|
|
1335
|
-
}
|
|
1336
|
-
}
|
|
1337
|
-
)
|
|
1464
|
+
/* @__PURE__ */ jsx(PitchSurface, { children: /* @__PURE__ */ jsx("div", { className: "absolute inset-0 pointer-events-none", children: boardState.pieces.map((piece) => /* @__PURE__ */ jsx(
|
|
1465
|
+
"div",
|
|
1466
|
+
{
|
|
1467
|
+
style: {
|
|
1468
|
+
position: "absolute",
|
|
1469
|
+
left: `${piece.pos.x / COLS2 * 100}%`,
|
|
1470
|
+
top: `${(ROWS2 - 1 - piece.pos.y) / ROWS2 * 100}%`,
|
|
1471
|
+
width: `${100 / COLS2}%`,
|
|
1472
|
+
height: `${100 / ROWS2}%`
|
|
1338
1473
|
},
|
|
1339
|
-
|
|
1340
|
-
|
|
1341
|
-
|
|
1474
|
+
className: "flex items-center justify-center",
|
|
1475
|
+
children: /* @__PURE__ */ jsx(
|
|
1476
|
+
GamePiece,
|
|
1477
|
+
{
|
|
1478
|
+
piece,
|
|
1479
|
+
isSelected: false,
|
|
1480
|
+
hasBall: boardState.ball.holderId === piece.id,
|
|
1481
|
+
onClick: () => {
|
|
1482
|
+
}
|
|
1483
|
+
}
|
|
1484
|
+
)
|
|
1485
|
+
},
|
|
1486
|
+
piece.id
|
|
1487
|
+
)) }) }),
|
|
1342
1488
|
/* @__PURE__ */ jsx(
|
|
1343
1489
|
"div",
|
|
1344
1490
|
{
|
|
@@ -1544,6 +1690,6 @@ function HistoryChip({ lastMove, onClick, className }) {
|
|
|
1544
1690
|
);
|
|
1545
1691
|
}
|
|
1546
1692
|
|
|
1547
|
-
export { ActionPoints, Avatar, AvatarFallback, AvatarImage, Button, ConfirmDialog, EventToast, Football, GAME_I18N_KEYS, GameBoard, GameControls, GameI18nProvider, GamePiece, MiniScoreboard, MobileHistory, MoveHistory, Scoreboard, SelectedPieceDetail, StaticGameBoard, TurnBanner, cn, useGameT };
|
|
1693
|
+
export { ActionPoints, Avatar, AvatarFallback, AvatarImage, BallHolderChip, Button, ConfirmDialog, EventToast, Football, GAME_I18N_KEYS, GOAL_AREA_SHADE_CLASS, GameBoard, GameControls, GameI18nProvider, GamePiece, GrassOverlay, KeyboardShortcutsList, MiniScoreboard, MobileHistory, MoveHistory, PITCH_COLS, PITCH_ROWS, PitchMarkings, PitchSurface, Scoreboard, SelectedPieceDetail, StaticGameBoard, TurnBanner, cn, isGoalAreaSquare, pitchSquareClass, useGameT };
|
|
1548
1694
|
//# sourceMappingURL=index.js.map
|
|
1549
1695
|
//# sourceMappingURL=index.js.map
|