@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.cjs
CHANGED
|
@@ -74,26 +74,66 @@ var BLACK_PIECE_STYLE = {
|
|
|
74
74
|
border: "1px solid rgba(0,0,0,0.55)",
|
|
75
75
|
color: "#fafafa"
|
|
76
76
|
};
|
|
77
|
+
var ON_BOARD_CHIP_STYLE = {
|
|
78
|
+
white: {
|
|
79
|
+
...WHITE_PIECE_STYLE,
|
|
80
|
+
boxShadow: [
|
|
81
|
+
"inset 0 1.5px 1px rgba(255,255,255,0.95)",
|
|
82
|
+
"inset 0 -2px 3px rgba(0,0,0,0.18)",
|
|
83
|
+
"0 1px 1px rgba(0,0,0,0.35)"
|
|
84
|
+
].join(", ")
|
|
85
|
+
},
|
|
86
|
+
black: {
|
|
87
|
+
...BLACK_PIECE_STYLE,
|
|
88
|
+
boxShadow: [
|
|
89
|
+
"inset 0 1.5px 1px rgba(255,255,255,0.22)",
|
|
90
|
+
"inset 0 -2px 3px rgba(0,0,0,0.6)",
|
|
91
|
+
"0 1px 1px rgba(0,0,0,0.5)"
|
|
92
|
+
].join(", ")
|
|
93
|
+
}
|
|
94
|
+
};
|
|
77
95
|
var MOVED_STYLE = {
|
|
78
96
|
filter: "saturate(0.15) brightness(0.75)"
|
|
79
97
|
};
|
|
80
|
-
function GamePiece({ piece, isSelected, hasBall, onClick }) {
|
|
81
|
-
const
|
|
82
|
-
const style = piece.hasMovedThisTurn ? { ...baseStyle, ...MOVED_STYLE } : baseStyle;
|
|
98
|
+
function GamePiece({ piece, isSelected, hasBall, onClick, isMoving = false }) {
|
|
99
|
+
const chipStyle = ON_BOARD_CHIP_STYLE[piece.side];
|
|
83
100
|
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
84
101
|
"div",
|
|
85
102
|
{
|
|
86
103
|
onClick,
|
|
87
|
-
style,
|
|
104
|
+
style: piece.hasMovedThisTurn ? MOVED_STYLE : void 0,
|
|
88
105
|
className: cn(
|
|
89
|
-
"relative w-[88%] h-[88%]
|
|
106
|
+
"relative w-[88%] h-[88%] rounded-full cursor-pointer z-10 transition-transform duration-200 ease-out motion-reduce:transition-none",
|
|
90
107
|
isSelected && "ring-4 ring-primary scale-110 z-30",
|
|
108
|
+
isMoving && "scale-[1.08] z-40",
|
|
91
109
|
piece.hasMovedThisTurn && "opacity-70"
|
|
92
110
|
),
|
|
93
111
|
children: [
|
|
94
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
95
|
-
|
|
96
|
-
|
|
112
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
113
|
+
"div",
|
|
114
|
+
{
|
|
115
|
+
"aria-hidden": "true",
|
|
116
|
+
"data-testid": "piece-shadow",
|
|
117
|
+
"data-lifted": isMoving ? "true" : "false",
|
|
118
|
+
className: cn(
|
|
119
|
+
"absolute inset-0 rounded-full bg-black pointer-events-none transition-all duration-200 ease-out motion-reduce:transition-none",
|
|
120
|
+
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]"
|
|
121
|
+
)
|
|
122
|
+
}
|
|
123
|
+
),
|
|
124
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
125
|
+
"div",
|
|
126
|
+
{
|
|
127
|
+
"data-testid": "piece-chip",
|
|
128
|
+
style: chipStyle,
|
|
129
|
+
className: "absolute inset-0 rounded-full flex items-center justify-center",
|
|
130
|
+
children: [
|
|
131
|
+
/* @__PURE__ */ jsxRuntime.jsx(PieceIcon, { type: piece.type, side: piece.side }),
|
|
132
|
+
hasBall && /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 rounded-full ring-2 ring-orange-400 pointer-events-none" }),
|
|
133
|
+
isSelected && /* @__PURE__ */ jsxRuntime.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" })
|
|
134
|
+
]
|
|
135
|
+
}
|
|
136
|
+
)
|
|
97
137
|
]
|
|
98
138
|
}
|
|
99
139
|
);
|
|
@@ -205,10 +245,204 @@ function GameI18nProvider({ t, children }) {
|
|
|
205
245
|
function useGameT() {
|
|
206
246
|
return React.useContext(GameI18nContext);
|
|
207
247
|
}
|
|
248
|
+
function BallHolderChip({ className }) {
|
|
249
|
+
const t = useGameT();
|
|
250
|
+
const boardState = chunkLTFNSTAQ_cjs.useGameStore((s) => s.boardState);
|
|
251
|
+
const holder = boardState?.pieces.find((p) => p.id === boardState.ball.holderId) ?? null;
|
|
252
|
+
if (!holder) return null;
|
|
253
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("flex items-center gap-1.5 min-h-[1.75rem]", className), "aria-live": "polite", children: [
|
|
254
|
+
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
255
|
+
"div",
|
|
256
|
+
{
|
|
257
|
+
style: holder.side === "white" ? WHITE_PIECE_STYLE : BLACK_PIECE_STYLE,
|
|
258
|
+
className: "relative w-7 h-7 flex items-center justify-center rounded-full shrink-0",
|
|
259
|
+
children: [
|
|
260
|
+
/* @__PURE__ */ jsxRuntime.jsx(PieceIcon, { type: holder.type, side: holder.side }),
|
|
261
|
+
/* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx("div", { className: "w-2.5 h-2.5", children: /* @__PURE__ */ jsxRuntime.jsx(Football, {}) }) })
|
|
262
|
+
]
|
|
263
|
+
}
|
|
264
|
+
),
|
|
265
|
+
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-inter text-[11px] text-fg-secondary", children: [
|
|
266
|
+
t("ballHolder"),
|
|
267
|
+
": ",
|
|
268
|
+
t(`pieces.${holder.type}`)
|
|
269
|
+
] })
|
|
270
|
+
] });
|
|
271
|
+
}
|
|
272
|
+
function KeyboardShortcutsList({ className }) {
|
|
273
|
+
const t = useGameT();
|
|
274
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("ul", { className: cn("font-mono text-[10px] text-fg-secondary leading-relaxed", className), children: [
|
|
275
|
+
/* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
|
|
276
|
+
/* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "text-fg-primary", children: "\u2191\u2193\u2190\u2192" }),
|
|
277
|
+
" ",
|
|
278
|
+
t("shortcuts.arrows")
|
|
279
|
+
] }),
|
|
280
|
+
/* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
|
|
281
|
+
/* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "text-fg-primary", children: "Enter" }),
|
|
282
|
+
" ",
|
|
283
|
+
t("shortcuts.select")
|
|
284
|
+
] }),
|
|
285
|
+
/* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
|
|
286
|
+
/* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "text-fg-primary", children: "M" }),
|
|
287
|
+
" ",
|
|
288
|
+
t("shortcuts.move")
|
|
289
|
+
] }),
|
|
290
|
+
/* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
|
|
291
|
+
/* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "text-fg-primary", children: "P" }),
|
|
292
|
+
" ",
|
|
293
|
+
t("shortcuts.pass")
|
|
294
|
+
] }),
|
|
295
|
+
/* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
|
|
296
|
+
/* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "text-fg-primary", children: "Esc" }),
|
|
297
|
+
" ",
|
|
298
|
+
t("shortcuts.cancel")
|
|
299
|
+
] })
|
|
300
|
+
] });
|
|
301
|
+
}
|
|
302
|
+
var PITCH_COLS = 9;
|
|
303
|
+
var PITCH_ROWS = 12;
|
|
304
|
+
var isGoalAreaSquare = (x, y) => x >= 2 && x <= 6 && (y >= 0 && y <= 1 || y >= PITCH_ROWS - 2 && y <= PITCH_ROWS - 1);
|
|
305
|
+
var GOAL_AREA_SHADE_CLASS = "shadow-[inset_0_0_0_100vmax_rgba(0,0,0,0.32)]";
|
|
306
|
+
function pitchSquareClass(x, y) {
|
|
307
|
+
const isEven = (x + y) % 2 === 0;
|
|
308
|
+
return cn(
|
|
309
|
+
"relative aspect-square w-full",
|
|
310
|
+
isEven ? "bg-field-green-1" : "bg-field-green-2",
|
|
311
|
+
isGoalAreaSquare(x, y) && GOAL_AREA_SHADE_CLASS
|
|
312
|
+
);
|
|
313
|
+
}
|
|
314
|
+
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>';
|
|
315
|
+
var GRAIN_TILE_URL = `url("data:image/svg+xml;utf8,${encodeURIComponent(GRAIN_TILE_SVG)}")`;
|
|
316
|
+
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%)";
|
|
317
|
+
function GrassOverlay({ className }) {
|
|
318
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
319
|
+
"div",
|
|
320
|
+
{
|
|
321
|
+
"aria-hidden": "true",
|
|
322
|
+
"data-testid": "grass-overlay",
|
|
323
|
+
className: cn("absolute inset-0 pointer-events-none overflow-hidden", className),
|
|
324
|
+
children: [
|
|
325
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
326
|
+
"div",
|
|
327
|
+
{
|
|
328
|
+
"data-layer": "grain",
|
|
329
|
+
className: "absolute inset-0 mix-blend-overlay opacity-60",
|
|
330
|
+
style: { backgroundImage: GRAIN_TILE_URL, backgroundSize: "160px 160px" }
|
|
331
|
+
}
|
|
332
|
+
),
|
|
333
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { "data-layer": "light", className: "absolute inset-0", style: { background: VIGNETTE } })
|
|
334
|
+
]
|
|
335
|
+
}
|
|
336
|
+
);
|
|
337
|
+
}
|
|
338
|
+
var CHALK = "#f6f5ee";
|
|
339
|
+
function PitchMarkings({ className, children }) {
|
|
340
|
+
const id = React.useId();
|
|
341
|
+
const filterId = `${id}-chalk`;
|
|
342
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(
|
|
343
|
+
"svg",
|
|
344
|
+
{
|
|
345
|
+
viewBox: "0 0 9 12",
|
|
346
|
+
preserveAspectRatio: "none",
|
|
347
|
+
className: cn("absolute inset-0 w-full h-full pointer-events-none", className),
|
|
348
|
+
"aria-hidden": "true",
|
|
349
|
+
"data-testid": "pitch-markings",
|
|
350
|
+
children: [
|
|
351
|
+
/* @__PURE__ */ jsxRuntime.jsx("defs", { children: /* @__PURE__ */ jsxRuntime.jsxs(
|
|
352
|
+
"filter",
|
|
353
|
+
{
|
|
354
|
+
id: filterId,
|
|
355
|
+
filterUnits: "userSpaceOnUse",
|
|
356
|
+
x: "-0.5",
|
|
357
|
+
y: "-0.5",
|
|
358
|
+
width: "10",
|
|
359
|
+
height: "13",
|
|
360
|
+
colorInterpolationFilters: "sRGB",
|
|
361
|
+
children: [
|
|
362
|
+
/* @__PURE__ */ jsxRuntime.jsx("feTurbulence", { type: "fractalNoise", baseFrequency: "5.5", numOctaves: "2", seed: "3", result: "edgeNoise" }),
|
|
363
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
364
|
+
"feDisplacementMap",
|
|
365
|
+
{
|
|
366
|
+
in: "SourceGraphic",
|
|
367
|
+
in2: "edgeNoise",
|
|
368
|
+
scale: "0.045",
|
|
369
|
+
xChannelSelector: "R",
|
|
370
|
+
yChannelSelector: "G",
|
|
371
|
+
result: "rough"
|
|
372
|
+
}
|
|
373
|
+
),
|
|
374
|
+
/* @__PURE__ */ jsxRuntime.jsx("feTurbulence", { type: "fractalNoise", baseFrequency: "14", numOctaves: "3", seed: "11", result: "dust" }),
|
|
375
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
376
|
+
"feColorMatrix",
|
|
377
|
+
{
|
|
378
|
+
in: "dust",
|
|
379
|
+
type: "matrix",
|
|
380
|
+
values: "0 0 0 0 1 0 0 0 0 1 0 0 0 0 1 0.75 0 0 0 0.55",
|
|
381
|
+
result: "dustMask"
|
|
382
|
+
}
|
|
383
|
+
),
|
|
384
|
+
/* @__PURE__ */ jsxRuntime.jsx("feComposite", { in: "rough", in2: "dustMask", operator: "arithmetic", k1: "1", k2: "0", k3: "0", k4: "0" })
|
|
385
|
+
]
|
|
386
|
+
}
|
|
387
|
+
) }),
|
|
388
|
+
/* @__PURE__ */ jsxRuntime.jsx("g", { "data-layer": "halo", fill: "none", stroke: CHALK, strokeOpacity: "0.09", strokeWidth: "0.16", strokeLinecap: "round", children: /* @__PURE__ */ jsxRuntime.jsx(Markings, {}) }),
|
|
389
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
390
|
+
"g",
|
|
391
|
+
{
|
|
392
|
+
"data-layer": "chalk",
|
|
393
|
+
filter: `url(#${filterId})`,
|
|
394
|
+
fill: "none",
|
|
395
|
+
stroke: CHALK,
|
|
396
|
+
strokeOpacity: "0.82",
|
|
397
|
+
strokeWidth: "0.07",
|
|
398
|
+
strokeLinecap: "round",
|
|
399
|
+
strokeLinejoin: "round",
|
|
400
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(Markings, {})
|
|
401
|
+
}
|
|
402
|
+
),
|
|
403
|
+
children
|
|
404
|
+
]
|
|
405
|
+
}
|
|
406
|
+
);
|
|
407
|
+
}
|
|
408
|
+
function Markings() {
|
|
409
|
+
return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
|
|
410
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "0.06", y: "0.06", width: "8.88", height: "11.88" }),
|
|
411
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "2", y: "0.06", width: "5", height: "1.94" }),
|
|
412
|
+
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "2", y: "10", width: "5", height: "1.94" }),
|
|
413
|
+
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "0.06", y1: "6", x2: "8.94", y2: "6" }),
|
|
414
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "4.5", cy: "6", r: "1.5" }),
|
|
415
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "4.5", cy: "6", r: "0.09", fill: CHALK, fillOpacity: "0.9", stroke: "none" }),
|
|
416
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "4.5", cy: "1.5", r: "0.08", fill: CHALK, fillOpacity: "0.9", stroke: "none" }),
|
|
417
|
+
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "4.5", cy: "10.5", r: "0.08", fill: CHALK, fillOpacity: "0.9", stroke: "none" }),
|
|
418
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M 3.086,2 A 1.5,1.5 0 0 0 5.914,2" }),
|
|
419
|
+
/* @__PURE__ */ jsxRuntime.jsx("path", { d: "M 3.086,10 A 1.5,1.5 0 0 1 5.914,10" })
|
|
420
|
+
] });
|
|
421
|
+
}
|
|
422
|
+
function PitchSurface({ className, squareClassName, children }) {
|
|
423
|
+
const squares = [];
|
|
424
|
+
for (let y = PITCH_ROWS - 1; y >= 0; y--) {
|
|
425
|
+
for (let x = 0; x < PITCH_COLS; x++) {
|
|
426
|
+
squares.push(
|
|
427
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: cn(pitchSquareClass(x, y), squareClassName?.(x, y)) }, `${x}-${y}`)
|
|
428
|
+
);
|
|
429
|
+
}
|
|
430
|
+
}
|
|
431
|
+
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: cn("relative", className), style: { containerType: "inline-size" }, children: [
|
|
432
|
+
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-9 gap-[1px]", "data-testid": "pitch-squares", children: squares }),
|
|
433
|
+
/* @__PURE__ */ jsxRuntime.jsx(GrassOverlay, {}),
|
|
434
|
+
/* @__PURE__ */ jsxRuntime.jsx(PitchMarkings, {}),
|
|
435
|
+
children
|
|
436
|
+
] });
|
|
437
|
+
}
|
|
208
438
|
var COLS = 9;
|
|
209
439
|
var ROWS = 12;
|
|
210
440
|
var clamp = (v, lo, hi) => Math.max(lo, Math.min(hi, v));
|
|
211
|
-
|
|
441
|
+
var cellX = (x) => `${x * 100}%`;
|
|
442
|
+
var cellY = (y) => `${(ROWS - 1 - y) * 100}%`;
|
|
443
|
+
var PIECE_SLIDE = { type: "spring", stiffness: 190, damping: 28, mass: 1 };
|
|
444
|
+
var BALL_SLIDE = { type: "spring", stiffness: 300, damping: 26, mass: 0.6 };
|
|
445
|
+
function GameBoard({ userSide, showCoordinates = false, keyboardNav = true, toolbar = true }) {
|
|
212
446
|
const t = useGameT();
|
|
213
447
|
const prefersReducedMotion = framerMotion.useReducedMotion();
|
|
214
448
|
const {
|
|
@@ -240,6 +474,14 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true }) {
|
|
|
240
474
|
setInvalidClickAt({ x, y, nonce: invalidClickNonceRef.current });
|
|
241
475
|
};
|
|
242
476
|
const [hoveredPassAt, setHoveredPassAt] = React.useState(null);
|
|
477
|
+
const [movingIds, setMovingIds] = React.useState(() => /* @__PURE__ */ new Set());
|
|
478
|
+
const setMoving = (id, moving) => setMovingIds((prev) => {
|
|
479
|
+
if (prev.has(id) === moving) return prev;
|
|
480
|
+
const next = new Set(prev);
|
|
481
|
+
if (moving) next.add(id);
|
|
482
|
+
else next.delete(id);
|
|
483
|
+
return next;
|
|
484
|
+
});
|
|
243
485
|
React.useEffect(() => {
|
|
244
486
|
if (!invalidClickAt) return;
|
|
245
487
|
const id = setTimeout(() => setInvalidClickAt(null), 100);
|
|
@@ -294,7 +536,6 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true }) {
|
|
|
294
536
|
return [];
|
|
295
537
|
}, [boardState, selectedPieceId]);
|
|
296
538
|
if (!boardState) return null;
|
|
297
|
-
const ballHolderPiece = boardState.pieces.find((p) => p.id === boardState.ball.holderId) ?? null;
|
|
298
539
|
const lastMove = boardState.lastMove;
|
|
299
540
|
const ball = boardState.ball;
|
|
300
541
|
const ballMoved = !!prevBall && (prevBall.pos.x !== ball.pos.x || prevBall.pos.y !== ball.pos.y);
|
|
@@ -437,8 +678,6 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true }) {
|
|
|
437
678
|
const squares = [];
|
|
438
679
|
for (let y = ROWS - 1; y >= 0; y--) {
|
|
439
680
|
for (let x = 0; x < COLS; x++) {
|
|
440
|
-
const isGoalArea = x >= 2 && x <= 6 && (y >= 0 && y <= 1 || y >= 10 && y <= 11);
|
|
441
|
-
const isEven = (x + y) % 2 === 0;
|
|
442
681
|
const isValidMove = validMoves.some((m) => m.x === x && m.y === y);
|
|
443
682
|
const isValidPass = validPasses.some((p) => p.x === x && p.y === y);
|
|
444
683
|
const isAmbiguous = isValidMove && isValidPass;
|
|
@@ -460,9 +699,8 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true }) {
|
|
|
460
699
|
},
|
|
461
700
|
onMouseLeave: () => setHoveredPassAt(null),
|
|
462
701
|
className: cn(
|
|
463
|
-
|
|
464
|
-
|
|
465
|
-
isGoalArea && "bg-[#1a3a18] ring-1 ring-inset ring-emerald-500/30",
|
|
702
|
+
pitchSquareClass(x, y),
|
|
703
|
+
"flex items-center justify-center cursor-pointer",
|
|
466
704
|
!isValidMove && !isValidPass && isLastMove && "bg-last-move-highlight/20",
|
|
467
705
|
// §8: pulsing warning ring on enemy goal area
|
|
468
706
|
isOffsideRisk && isEnemyGoalArea && !isValidMove && !isValidPass && "ring-2 ring-inset ring-warning/60",
|
|
@@ -549,25 +787,8 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true }) {
|
|
|
549
787
|
onKeyDown: handleKeyDown
|
|
550
788
|
} : {},
|
|
551
789
|
children: [
|
|
552
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "hidden md:flex items-center justify-between gap-2 px-2.5 py-1", children: [
|
|
553
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
554
|
-
/* @__PURE__ */ jsxRuntime.jsxs(
|
|
555
|
-
"div",
|
|
556
|
-
{
|
|
557
|
-
style: ballHolderPiece.side === "white" ? WHITE_PIECE_STYLE : BLACK_PIECE_STYLE,
|
|
558
|
-
className: "relative w-7 h-7 flex items-center justify-center rounded-full shrink-0",
|
|
559
|
-
children: [
|
|
560
|
-
/* @__PURE__ */ jsxRuntime.jsx(PieceIcon, { type: ballHolderPiece.type, side: ballHolderPiece.side }),
|
|
561
|
-
/* @__PURE__ */ jsxRuntime.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__ */ jsxRuntime.jsx("div", { className: "w-2.5 h-2.5", children: /* @__PURE__ */ jsxRuntime.jsx(Football, {}) }) })
|
|
562
|
-
]
|
|
563
|
-
}
|
|
564
|
-
),
|
|
565
|
-
/* @__PURE__ */ jsxRuntime.jsxs("span", { className: "font-inter text-[11px] text-fg-secondary", children: [
|
|
566
|
-
t("ballHolder"),
|
|
567
|
-
": ",
|
|
568
|
-
t(`pieces.${ballHolderPiece.type}`)
|
|
569
|
-
] })
|
|
570
|
-
] }) }),
|
|
790
|
+
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: [
|
|
791
|
+
/* @__PURE__ */ jsxRuntime.jsx(BallHolderChip, {}),
|
|
571
792
|
keyboardNav && /* @__PURE__ */ jsxRuntime.jsxs(
|
|
572
793
|
"div",
|
|
573
794
|
{
|
|
@@ -596,33 +817,7 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true }) {
|
|
|
596
817
|
onClick: (e) => e.stopPropagation(),
|
|
597
818
|
children: [
|
|
598
819
|
/* @__PURE__ */ jsxRuntime.jsx("p", { className: "font-inter text-[11px] font-semibold text-fg-primary mb-1.5", children: t("shortcuts.title") }),
|
|
599
|
-
/* @__PURE__ */ jsxRuntime.
|
|
600
|
-
/* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
|
|
601
|
-
/* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "text-fg-primary", children: "\u2191\u2193\u2190\u2192" }),
|
|
602
|
-
" ",
|
|
603
|
-
t("shortcuts.arrows")
|
|
604
|
-
] }),
|
|
605
|
-
/* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
|
|
606
|
-
/* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "text-fg-primary", children: "Enter" }),
|
|
607
|
-
" ",
|
|
608
|
-
t("shortcuts.select")
|
|
609
|
-
] }),
|
|
610
|
-
/* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
|
|
611
|
-
/* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "text-fg-primary", children: "M" }),
|
|
612
|
-
" ",
|
|
613
|
-
t("shortcuts.move")
|
|
614
|
-
] }),
|
|
615
|
-
/* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
|
|
616
|
-
/* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "text-fg-primary", children: "P" }),
|
|
617
|
-
" ",
|
|
618
|
-
t("shortcuts.pass")
|
|
619
|
-
] }),
|
|
620
|
-
/* @__PURE__ */ jsxRuntime.jsxs("li", { children: [
|
|
621
|
-
/* @__PURE__ */ jsxRuntime.jsx("kbd", { className: "text-fg-primary", children: "Esc" }),
|
|
622
|
-
" ",
|
|
623
|
-
t("shortcuts.cancel")
|
|
624
|
-
] })
|
|
625
|
-
] })
|
|
820
|
+
/* @__PURE__ */ jsxRuntime.jsx(KeyboardShortcutsList, {})
|
|
626
821
|
]
|
|
627
822
|
}
|
|
628
823
|
)
|
|
@@ -632,58 +827,51 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true }) {
|
|
|
632
827
|
] }),
|
|
633
828
|
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative", style: { containerType: "inline-size" }, children: [
|
|
634
829
|
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "grid grid-cols-9 gap-[1px]", children: renderSquares() }),
|
|
635
|
-
/* @__PURE__ */ jsxRuntime.
|
|
830
|
+
/* @__PURE__ */ jsxRuntime.jsx(GrassOverlay, {}),
|
|
831
|
+
/* @__PURE__ */ jsxRuntime.jsx(PitchMarkings, {}),
|
|
832
|
+
/* @__PURE__ */ jsxRuntime.jsx(
|
|
636
833
|
"svg",
|
|
637
834
|
{
|
|
638
835
|
viewBox: "0 0 9 12",
|
|
639
836
|
preserveAspectRatio: "none",
|
|
640
837
|
className: "absolute inset-0 w-full h-full pointer-events-none",
|
|
641
838
|
"aria-hidden": "true",
|
|
642
|
-
children:
|
|
643
|
-
|
|
644
|
-
|
|
645
|
-
|
|
646
|
-
|
|
647
|
-
|
|
648
|
-
|
|
649
|
-
|
|
650
|
-
|
|
651
|
-
|
|
652
|
-
|
|
653
|
-
"
|
|
654
|
-
|
|
655
|
-
|
|
656
|
-
x1: passCarrier.pos.x + 0.5,
|
|
657
|
-
y1: ROWS - 1 - passCarrier.pos.y + 0.5,
|
|
658
|
-
x2: hoveredPassAt.x + 0.5,
|
|
659
|
-
y2: ROWS - 1 - hoveredPassAt.y + 0.5,
|
|
660
|
-
stroke: trajectoryIntercepted ? "var(--danger)" : "var(--pass-highlight)",
|
|
661
|
-
strokeWidth: "0.08",
|
|
662
|
-
strokeDasharray: "0.15 0.1",
|
|
663
|
-
strokeLinecap: "round"
|
|
664
|
-
}
|
|
665
|
-
)
|
|
666
|
-
]
|
|
839
|
+
children: hoveredPassAt && passCarrier && /* @__PURE__ */ jsxRuntime.jsx(
|
|
840
|
+
"line",
|
|
841
|
+
{
|
|
842
|
+
"data-testid": "pass-trajectory-line",
|
|
843
|
+
x1: passCarrier.pos.x + 0.5,
|
|
844
|
+
y1: ROWS - 1 - passCarrier.pos.y + 0.5,
|
|
845
|
+
x2: hoveredPassAt.x + 0.5,
|
|
846
|
+
y2: ROWS - 1 - hoveredPassAt.y + 0.5,
|
|
847
|
+
stroke: trajectoryIntercepted ? "var(--danger)" : "var(--pass-highlight)",
|
|
848
|
+
strokeWidth: "0.08",
|
|
849
|
+
strokeDasharray: "0.15 0.1",
|
|
850
|
+
strokeLinecap: "round"
|
|
851
|
+
}
|
|
852
|
+
)
|
|
667
853
|
}
|
|
668
854
|
),
|
|
669
|
-
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute inset-0 pointer-events-none", children: [
|
|
855
|
+
/* @__PURE__ */ jsxRuntime.jsxs("div", { className: "absolute inset-0 pointer-events-none", "data-testid": "pieces-layer", children: [
|
|
670
856
|
/* @__PURE__ */ jsxRuntime.jsx(framerMotion.AnimatePresence, { mode: "sync", children: boardState.pieces.map((piece) => {
|
|
671
857
|
const isPickupCarrier = pickupCarrierId === piece.id;
|
|
672
|
-
const pieceTransition = isPickupCarrier ? { duration: PICKUP_DURATION, ease: "linear" } :
|
|
858
|
+
const pieceTransition = isPickupCarrier ? { duration: PICKUP_DURATION, ease: "linear" } : PIECE_SLIDE;
|
|
673
859
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
674
860
|
framerMotion.motion.div,
|
|
675
861
|
{
|
|
676
|
-
|
|
862
|
+
"data-piece-id": piece.id,
|
|
677
863
|
initial: false,
|
|
678
|
-
animate: {
|
|
679
|
-
left: `${piece.pos.x / COLS * 100}%`,
|
|
680
|
-
top: `${(ROWS - 1 - piece.pos.y) / ROWS * 100}%`
|
|
681
|
-
},
|
|
864
|
+
animate: { x: cellX(piece.pos.x), y: cellY(piece.pos.y) },
|
|
682
865
|
transition: pieceTransition,
|
|
866
|
+
onAnimationStart: () => setMoving(piece.id, true),
|
|
867
|
+
onAnimationComplete: () => setMoving(piece.id, false),
|
|
683
868
|
style: {
|
|
684
869
|
position: "absolute",
|
|
870
|
+
left: 0,
|
|
871
|
+
top: 0,
|
|
685
872
|
width: `${100 / COLS}%`,
|
|
686
|
-
height: `${100 / ROWS}
|
|
873
|
+
height: `${100 / ROWS}%`,
|
|
874
|
+
willChange: "transform"
|
|
687
875
|
},
|
|
688
876
|
className: "flex items-center justify-center pointer-events-auto",
|
|
689
877
|
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
@@ -692,6 +880,7 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true }) {
|
|
|
692
880
|
piece,
|
|
693
881
|
isSelected: selectedPieceId === piece.id,
|
|
694
882
|
hasBall: boardState.ball.holderId === piece.id,
|
|
883
|
+
isMoving: movingIds.has(piece.id),
|
|
695
884
|
onClick: (e) => handlePieceClick(piece.id, piece.pos.x, piece.pos.y, e)
|
|
696
885
|
}
|
|
697
886
|
)
|
|
@@ -700,23 +889,23 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true }) {
|
|
|
700
889
|
);
|
|
701
890
|
}) }),
|
|
702
891
|
(() => {
|
|
703
|
-
const targetX = ball.pos.x
|
|
704
|
-
const targetY = (
|
|
892
|
+
const targetX = cellX(ball.pos.x);
|
|
893
|
+
const targetY = cellY(ball.pos.y);
|
|
705
894
|
let animate;
|
|
706
895
|
let transition;
|
|
707
896
|
if (isPickupOnMove && prevBall) {
|
|
708
|
-
const prevX = prevBall.pos.x
|
|
709
|
-
const prevY = (
|
|
897
|
+
const prevX = cellX(prevBall.pos.x);
|
|
898
|
+
const prevY = cellY(prevBall.pos.y);
|
|
710
899
|
const pulseStart = Math.max(0.05, pickupProportion - 0.06);
|
|
711
900
|
animate = {
|
|
712
|
-
|
|
713
|
-
|
|
901
|
+
x: [prevX, prevX, prevX, targetX],
|
|
902
|
+
y: [prevY, prevY, prevY, targetY],
|
|
714
903
|
scale: [1, 1, 1.35, 1]
|
|
715
904
|
};
|
|
716
905
|
transition = { duration: PICKUP_DURATION, times: [0, pulseStart, pickupProportion, 1], ease: "linear" };
|
|
717
906
|
} else {
|
|
718
|
-
animate = {
|
|
719
|
-
transition =
|
|
907
|
+
animate = { x: targetX, y: targetY, scale: 1 };
|
|
908
|
+
transition = ball.holderId ? PIECE_SLIDE : BALL_SLIDE;
|
|
720
909
|
}
|
|
721
910
|
return /* @__PURE__ */ jsxRuntime.jsx(
|
|
722
911
|
framerMotion.motion.div,
|
|
@@ -726,9 +915,12 @@ function GameBoard({ userSide, showCoordinates = false, keyboardNav = true }) {
|
|
|
726
915
|
transition,
|
|
727
916
|
style: {
|
|
728
917
|
position: "absolute",
|
|
918
|
+
left: 0,
|
|
919
|
+
top: 0,
|
|
729
920
|
width: `${100 / COLS}%`,
|
|
730
921
|
height: `${100 / ROWS}%`,
|
|
731
|
-
zIndex: 50
|
|
922
|
+
zIndex: 50,
|
|
923
|
+
willChange: "transform"
|
|
732
924
|
},
|
|
733
925
|
className: "flex items-center justify-center pointer-events-none",
|
|
734
926
|
children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "w-[60%] h-[60%]", children: /* @__PURE__ */ jsxRuntime.jsx(Football, {}) })
|
|
@@ -1289,77 +1481,31 @@ var COLS2 = 9;
|
|
|
1289
1481
|
var ROWS2 = 12;
|
|
1290
1482
|
function StaticGameBoard() {
|
|
1291
1483
|
const boardState = React.useMemo(() => chunkLTFNSTAQ_cjs.getInitialBoardState("white"), []);
|
|
1292
|
-
const renderSquares = () => {
|
|
1293
|
-
const squares = [];
|
|
1294
|
-
for (let y = ROWS2 - 1; y >= 0; y--) {
|
|
1295
|
-
for (let x = 0; x < COLS2; x++) {
|
|
1296
|
-
const isGoalArea = x >= 2 && x <= 6 && (y >= 0 && y <= 1 || y >= 10 && y <= 11);
|
|
1297
|
-
const isEven = (x + y) % 2 === 0;
|
|
1298
|
-
squares.push(
|
|
1299
|
-
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1300
|
-
"div",
|
|
1301
|
-
{
|
|
1302
|
-
className: cn(
|
|
1303
|
-
"relative aspect-square w-full",
|
|
1304
|
-
isEven ? "bg-[#2d5a27]" : "bg-[#356a2d]",
|
|
1305
|
-
isGoalArea && "bg-[#1a3a18] ring-1 ring-inset ring-emerald-500/30"
|
|
1306
|
-
)
|
|
1307
|
-
},
|
|
1308
|
-
`${x}-${y}`
|
|
1309
|
-
)
|
|
1310
|
-
);
|
|
1311
|
-
}
|
|
1312
|
-
}
|
|
1313
|
-
return squares;
|
|
1314
|
-
};
|
|
1315
1484
|
return /* @__PURE__ */ jsxRuntime.jsxs("div", { className: "relative w-full overflow-hidden rounded-xl shadow-2xl border-4 border-[#1a3317] bg-[#1a3317]", children: [
|
|
1316
|
-
/* @__PURE__ */ jsxRuntime.
|
|
1317
|
-
|
|
1318
|
-
|
|
1319
|
-
|
|
1320
|
-
|
|
1321
|
-
|
|
1322
|
-
|
|
1323
|
-
|
|
1324
|
-
|
|
1325
|
-
children: [
|
|
1326
|
-
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "2", y: "0", width: "5", height: "2", fill: "none", stroke: "white", strokeOpacity: "0.3", strokeWidth: "0.05" }),
|
|
1327
|
-
/* @__PURE__ */ jsxRuntime.jsx("rect", { x: "2", y: "10", width: "5", height: "2", fill: "none", stroke: "white", strokeOpacity: "0.3", strokeWidth: "0.05" }),
|
|
1328
|
-
/* @__PURE__ */ jsxRuntime.jsx("line", { x1: "0", y1: "6", x2: "9", y2: "6", stroke: "white", strokeOpacity: "0.35", strokeWidth: "0.05" }),
|
|
1329
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "4.5", cy: "6", r: "1.5", fill: "none", stroke: "white", strokeOpacity: "0.3", strokeWidth: "0.05" }),
|
|
1330
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "4.5", cy: "6", r: "0.12", fill: "white", fillOpacity: "0.5" }),
|
|
1331
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "4.5", cy: "1.5", r: "0.1", fill: "white", fillOpacity: "0.5" }),
|
|
1332
|
-
/* @__PURE__ */ jsxRuntime.jsx("circle", { cx: "4.5", cy: "10.5", r: "0.1", fill: "white", fillOpacity: "0.5" }),
|
|
1333
|
-
/* @__PURE__ */ jsxRuntime.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" }),
|
|
1334
|
-
/* @__PURE__ */ jsxRuntime.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" })
|
|
1335
|
-
]
|
|
1336
|
-
}
|
|
1337
|
-
),
|
|
1338
|
-
/* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 pointer-events-none", children: boardState.pieces.map((piece) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1339
|
-
"div",
|
|
1340
|
-
{
|
|
1341
|
-
style: {
|
|
1342
|
-
position: "absolute",
|
|
1343
|
-
left: `${piece.pos.x / COLS2 * 100}%`,
|
|
1344
|
-
top: `${(ROWS2 - 1 - piece.pos.y) / ROWS2 * 100}%`,
|
|
1345
|
-
width: `${100 / COLS2}%`,
|
|
1346
|
-
height: `${100 / ROWS2}%`
|
|
1347
|
-
},
|
|
1348
|
-
className: "flex items-center justify-center",
|
|
1349
|
-
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1350
|
-
GamePiece,
|
|
1351
|
-
{
|
|
1352
|
-
piece,
|
|
1353
|
-
isSelected: false,
|
|
1354
|
-
hasBall: boardState.ball.holderId === piece.id,
|
|
1355
|
-
onClick: () => {
|
|
1356
|
-
}
|
|
1357
|
-
}
|
|
1358
|
-
)
|
|
1485
|
+
/* @__PURE__ */ jsxRuntime.jsx(PitchSurface, { children: /* @__PURE__ */ jsxRuntime.jsx("div", { className: "absolute inset-0 pointer-events-none", children: boardState.pieces.map((piece) => /* @__PURE__ */ jsxRuntime.jsx(
|
|
1486
|
+
"div",
|
|
1487
|
+
{
|
|
1488
|
+
style: {
|
|
1489
|
+
position: "absolute",
|
|
1490
|
+
left: `${piece.pos.x / COLS2 * 100}%`,
|
|
1491
|
+
top: `${(ROWS2 - 1 - piece.pos.y) / ROWS2 * 100}%`,
|
|
1492
|
+
width: `${100 / COLS2}%`,
|
|
1493
|
+
height: `${100 / ROWS2}%`
|
|
1359
1494
|
},
|
|
1360
|
-
|
|
1361
|
-
|
|
1362
|
-
|
|
1495
|
+
className: "flex items-center justify-center",
|
|
1496
|
+
children: /* @__PURE__ */ jsxRuntime.jsx(
|
|
1497
|
+
GamePiece,
|
|
1498
|
+
{
|
|
1499
|
+
piece,
|
|
1500
|
+
isSelected: false,
|
|
1501
|
+
hasBall: boardState.ball.holderId === piece.id,
|
|
1502
|
+
onClick: () => {
|
|
1503
|
+
}
|
|
1504
|
+
}
|
|
1505
|
+
)
|
|
1506
|
+
},
|
|
1507
|
+
piece.id
|
|
1508
|
+
)) }) }),
|
|
1363
1509
|
/* @__PURE__ */ jsxRuntime.jsx(
|
|
1364
1510
|
"div",
|
|
1365
1511
|
{
|
|
@@ -1569,23 +1715,33 @@ exports.ActionPoints = ActionPoints;
|
|
|
1569
1715
|
exports.Avatar = Avatar;
|
|
1570
1716
|
exports.AvatarFallback = AvatarFallback;
|
|
1571
1717
|
exports.AvatarImage = AvatarImage;
|
|
1718
|
+
exports.BallHolderChip = BallHolderChip;
|
|
1572
1719
|
exports.Button = Button;
|
|
1573
1720
|
exports.ConfirmDialog = ConfirmDialog;
|
|
1574
1721
|
exports.EventToast = EventToast;
|
|
1575
1722
|
exports.Football = Football;
|
|
1576
1723
|
exports.GAME_I18N_KEYS = GAME_I18N_KEYS;
|
|
1724
|
+
exports.GOAL_AREA_SHADE_CLASS = GOAL_AREA_SHADE_CLASS;
|
|
1577
1725
|
exports.GameBoard = GameBoard;
|
|
1578
1726
|
exports.GameControls = GameControls;
|
|
1579
1727
|
exports.GameI18nProvider = GameI18nProvider;
|
|
1580
1728
|
exports.GamePiece = GamePiece;
|
|
1729
|
+
exports.GrassOverlay = GrassOverlay;
|
|
1730
|
+
exports.KeyboardShortcutsList = KeyboardShortcutsList;
|
|
1581
1731
|
exports.MiniScoreboard = MiniScoreboard;
|
|
1582
1732
|
exports.MobileHistory = MobileHistory;
|
|
1583
1733
|
exports.MoveHistory = MoveHistory;
|
|
1734
|
+
exports.PITCH_COLS = PITCH_COLS;
|
|
1735
|
+
exports.PITCH_ROWS = PITCH_ROWS;
|
|
1736
|
+
exports.PitchMarkings = PitchMarkings;
|
|
1737
|
+
exports.PitchSurface = PitchSurface;
|
|
1584
1738
|
exports.Scoreboard = Scoreboard;
|
|
1585
1739
|
exports.SelectedPieceDetail = SelectedPieceDetail;
|
|
1586
1740
|
exports.StaticGameBoard = StaticGameBoard;
|
|
1587
1741
|
exports.TurnBanner = TurnBanner;
|
|
1588
1742
|
exports.cn = cn;
|
|
1743
|
+
exports.isGoalAreaSquare = isGoalAreaSquare;
|
|
1744
|
+
exports.pitchSquareClass = pitchSquareClass;
|
|
1589
1745
|
exports.useGameT = useGameT;
|
|
1590
1746
|
//# sourceMappingURL=index.cjs.map
|
|
1591
1747
|
//# sourceMappingURL=index.cjs.map
|