gigaboard 1.2.3

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.
Files changed (156) hide show
  1. package/LICENSE +21 -0
  2. package/README.md +27 -0
  3. package/dist/board-model-BvJOtwQ3.d.cts +675 -0
  4. package/dist/board-model-BvJOtwQ3.d.ts +675 -0
  5. package/dist/canvas.cjs +8 -0
  6. package/dist/canvas.cjs.map +1 -0
  7. package/dist/canvas.d.cts +14 -0
  8. package/dist/canvas.d.ts +14 -0
  9. package/dist/canvas.js +6 -0
  10. package/dist/canvas.js.map +1 -0
  11. package/dist/chunk-3HNIF4ZE.js +3 -0
  12. package/dist/chunk-3HNIF4ZE.js.map +1 -0
  13. package/dist/chunk-44YKPXXT.js +63 -0
  14. package/dist/chunk-44YKPXXT.js.map +1 -0
  15. package/dist/chunk-5WAOEWKS.js +16 -0
  16. package/dist/chunk-5WAOEWKS.js.map +1 -0
  17. package/dist/chunk-AYVQCHSG.cjs +10 -0
  18. package/dist/chunk-AYVQCHSG.cjs.map +1 -0
  19. package/dist/chunk-B26QLI2K.cjs +18 -0
  20. package/dist/chunk-B26QLI2K.cjs.map +1 -0
  21. package/dist/chunk-B2TBDDJN.cjs +10 -0
  22. package/dist/chunk-B2TBDDJN.cjs.map +1 -0
  23. package/dist/chunk-BFN5JI3T.js +8 -0
  24. package/dist/chunk-BFN5JI3T.js.map +1 -0
  25. package/dist/chunk-BWNBPN3P.cjs +10 -0
  26. package/dist/chunk-BWNBPN3P.cjs.map +1 -0
  27. package/dist/chunk-CXDGJMYL.js +105 -0
  28. package/dist/chunk-CXDGJMYL.js.map +1 -0
  29. package/dist/chunk-EOSX5UX3.js +8 -0
  30. package/dist/chunk-EOSX5UX3.js.map +1 -0
  31. package/dist/chunk-FNRS6YJI.cjs +67 -0
  32. package/dist/chunk-FNRS6YJI.cjs.map +1 -0
  33. package/dist/chunk-G6ZFOJZR.cjs +18 -0
  34. package/dist/chunk-G6ZFOJZR.cjs.map +1 -0
  35. package/dist/chunk-HSMILJKQ.cjs +18 -0
  36. package/dist/chunk-HSMILJKQ.cjs.map +1 -0
  37. package/dist/chunk-I4BQGFP5.cjs +113 -0
  38. package/dist/chunk-I4BQGFP5.cjs.map +1 -0
  39. package/dist/chunk-J6GGNP5S.js +1104 -0
  40. package/dist/chunk-J6GGNP5S.js.map +1 -0
  41. package/dist/chunk-JQSBAWIZ.cjs +4 -0
  42. package/dist/chunk-JQSBAWIZ.cjs.map +1 -0
  43. package/dist/chunk-KW56FEBY.js +8 -0
  44. package/dist/chunk-KW56FEBY.js.map +1 -0
  45. package/dist/chunk-N7ED2MZS.cjs +10 -0
  46. package/dist/chunk-N7ED2MZS.cjs.map +1 -0
  47. package/dist/chunk-NAA5O4BT.js +16 -0
  48. package/dist/chunk-NAA5O4BT.js.map +1 -0
  49. package/dist/chunk-OQLY5GJF.js +16 -0
  50. package/dist/chunk-OQLY5GJF.js.map +1 -0
  51. package/dist/chunk-P72B3YTG.js +16 -0
  52. package/dist/chunk-P72B3YTG.js.map +1 -0
  53. package/dist/chunk-U6WAD322.js +8 -0
  54. package/dist/chunk-U6WAD322.js.map +1 -0
  55. package/dist/chunk-UUTEFNFO.cjs +1148 -0
  56. package/dist/chunk-UUTEFNFO.cjs.map +1 -0
  57. package/dist/chunk-VYSVIIZA.cjs +18 -0
  58. package/dist/chunk-VYSVIIZA.cjs.map +1 -0
  59. package/dist/core.cjs +180 -0
  60. package/dist/core.cjs.map +1 -0
  61. package/dist/core.d.cts +164 -0
  62. package/dist/core.d.ts +164 -0
  63. package/dist/core.js +3 -0
  64. package/dist/core.js.map +1 -0
  65. package/dist/create-image-piece-set-BcmRKGql.d.cts +100 -0
  66. package/dist/create-image-piece-set-BcmRKGql.d.ts +100 -0
  67. package/dist/gigachess-3BDD--yT.d.cts +22 -0
  68. package/dist/gigachess-DolQlR82.d.ts +22 -0
  69. package/dist/index.cjs +2972 -0
  70. package/dist/index.cjs.map +1 -0
  71. package/dist/index.d.cts +435 -0
  72. package/dist/index.d.ts +435 -0
  73. package/dist/index.js +2898 -0
  74. package/dist/index.js.map +1 -0
  75. package/dist/pieces/alpha.cjs +13 -0
  76. package/dist/pieces/alpha.cjs.map +1 -0
  77. package/dist/pieces/alpha.d.cts +17 -0
  78. package/dist/pieces/alpha.d.ts +17 -0
  79. package/dist/pieces/alpha.js +4 -0
  80. package/dist/pieces/alpha.js.map +1 -0
  81. package/dist/pieces/cburnett.cjs +13 -0
  82. package/dist/pieces/cburnett.cjs.map +1 -0
  83. package/dist/pieces/cburnett.d.cts +19 -0
  84. package/dist/pieces/cburnett.d.ts +19 -0
  85. package/dist/pieces/cburnett.js +4 -0
  86. package/dist/pieces/cburnett.js.map +1 -0
  87. package/dist/pieces/chesscom.cjs +14 -0
  88. package/dist/pieces/chesscom.cjs.map +1 -0
  89. package/dist/pieces/chesscom.d.cts +3 -0
  90. package/dist/pieces/chesscom.d.ts +3 -0
  91. package/dist/pieces/chesscom.js +5 -0
  92. package/dist/pieces/chesscom.js.map +1 -0
  93. package/dist/pieces/merida.cjs +13 -0
  94. package/dist/pieces/merida.cjs.map +1 -0
  95. package/dist/pieces/merida.d.cts +17 -0
  96. package/dist/pieces/merida.d.ts +17 -0
  97. package/dist/pieces/merida.js +4 -0
  98. package/dist/pieces/merida.js.map +1 -0
  99. package/dist/pieces/neo.cjs +13 -0
  100. package/dist/pieces/neo.cjs.map +1 -0
  101. package/dist/pieces/neo.d.cts +18 -0
  102. package/dist/pieces/neo.d.ts +18 -0
  103. package/dist/pieces/neo.js +4 -0
  104. package/dist/pieces/neo.js.map +1 -0
  105. package/dist/pieces.cjs +55 -0
  106. package/dist/pieces.cjs.map +1 -0
  107. package/dist/pieces.d.cts +21 -0
  108. package/dist/pieces.d.ts +21 -0
  109. package/dist/pieces.js +24 -0
  110. package/dist/pieces.js.map +1 -0
  111. package/dist/server.cjs +207 -0
  112. package/dist/server.cjs.map +1 -0
  113. package/dist/server.d.cts +60 -0
  114. package/dist/server.d.ts +60 -0
  115. package/dist/server.js +205 -0
  116. package/dist/server.js.map +1 -0
  117. package/dist/sounds/capture.mp3 +0 -0
  118. package/dist/sounds/castle.mp3 +0 -0
  119. package/dist/sounds/game-end.mp3 +0 -0
  120. package/dist/sounds/move-check.mp3 +0 -0
  121. package/dist/sounds/move-opponent.mp3 +0 -0
  122. package/dist/sounds/move-self.mp3 +0 -0
  123. package/dist/sounds/promote.mp3 +0 -0
  124. package/dist/themes/blue.cjs +12 -0
  125. package/dist/themes/blue.cjs.map +1 -0
  126. package/dist/themes/blue.d.cts +102 -0
  127. package/dist/themes/blue.d.ts +102 -0
  128. package/dist/themes/blue.js +3 -0
  129. package/dist/themes/blue.js.map +1 -0
  130. package/dist/themes/brown.cjs +12 -0
  131. package/dist/themes/brown.cjs.map +1 -0
  132. package/dist/themes/brown.d.cts +1 -0
  133. package/dist/themes/brown.d.ts +1 -0
  134. package/dist/themes/brown.js +3 -0
  135. package/dist/themes/brown.js.map +1 -0
  136. package/dist/themes/green.cjs +12 -0
  137. package/dist/themes/green.cjs.map +1 -0
  138. package/dist/themes/green.d.cts +1 -0
  139. package/dist/themes/green.d.ts +1 -0
  140. package/dist/themes/green.js +3 -0
  141. package/dist/themes/green.js.map +1 -0
  142. package/dist/themes/wood.cjs +12 -0
  143. package/dist/themes/wood.cjs.map +1 -0
  144. package/dist/themes/wood.d.cts +1 -0
  145. package/dist/themes/wood.d.ts +1 -0
  146. package/dist/themes/wood.js +3 -0
  147. package/dist/themes/wood.js.map +1 -0
  148. package/dist/themes.cjs +36 -0
  149. package/dist/themes.cjs.map +1 -0
  150. package/dist/themes.d.cts +1 -0
  151. package/dist/themes.d.ts +1 -0
  152. package/dist/themes.js +21 -0
  153. package/dist/themes.js.map +1 -0
  154. package/dist/types-Bg2xiUHE.d.cts +501 -0
  155. package/dist/types-D5eBJGzj.d.ts +501 -0
  156. package/package.json +210 -0
package/dist/index.cjs ADDED
@@ -0,0 +1,2972 @@
1
+ 'use strict';
2
+
3
+ var chunkUUTEFNFO_cjs = require('./chunk-UUTEFNFO.cjs');
4
+ var chunkI4BQGFP5_cjs = require('./chunk-I4BQGFP5.cjs');
5
+ require('./chunk-G6ZFOJZR.cjs');
6
+ require('./chunk-AYVQCHSG.cjs');
7
+ require('./chunk-FNRS6YJI.cjs');
8
+ var react = require('react');
9
+ var jsxRuntime = require('react/jsx-runtime');
10
+
11
+ var _documentCurrentScript = typeof document !== 'undefined' ? document.currentScript : null;
12
+ function useBoardSnapshot(model) {
13
+ return react.useSyncExternalStore(model.subscribe, model.getSnapshot, model.getSnapshot);
14
+ }
15
+ function useBoardSlice(model, selector) {
16
+ const getSnapshot = react.useCallback(() => selector(model.getSnapshot()), [model, selector]);
17
+ return react.useSyncExternalStore(model.subscribe, getSnapshot, getSnapshot);
18
+ }
19
+ function useSquareCell(model, index) {
20
+ const subscribe = react.useCallback(
21
+ (listener) => model ? model.subscribeSquare(index, listener) : () => {
22
+ },
23
+ [model, index]
24
+ );
25
+ const getSnapshot = react.useCallback(() => {
26
+ if (!model) return 0;
27
+ const snap = model.getSnapshot();
28
+ return snap.board[index] ?? 0;
29
+ }, [model, index]);
30
+ return react.useSyncExternalStore(subscribe, getSnapshot, getSnapshot);
31
+ }
32
+ function squareCentrePct(index, orientation) {
33
+ const file = index & 7;
34
+ const rank = index >> 3;
35
+ const col = orientation === "white" ? file : 7 - file;
36
+ const row = orientation === "white" ? 7 - rank : rank;
37
+ return { cx: col * 12.5 + 6.25, cy: row * 12.5 + 6.25 };
38
+ }
39
+ function isKnightMove(fromIndex, toIndex) {
40
+ const fileDelta = Math.abs((toIndex & 7) - (fromIndex & 7));
41
+ const rankDelta = Math.abs((toIndex >> 3) - (fromIndex >> 3));
42
+ return fileDelta === 1 && rankDelta === 2 || fileDelta === 2 && rankDelta === 1;
43
+ }
44
+ function unitVector(from, to) {
45
+ const dx = to.x - from.x;
46
+ const dy = to.y - from.y;
47
+ const len = Math.hypot(dx, dy);
48
+ if (len === 0) return null;
49
+ return { x: dx / len, y: dy / len };
50
+ }
51
+ function arrowPath(fromIndex, toIndex, start, end, startOffset, tipOffset, headLength) {
52
+ if (!isKnightMove(fromIndex, toIndex)) {
53
+ const unit = unitVector(start, end);
54
+ if (unit === null) return null;
55
+ const tip2 = { x: end.x - unit.x * tipOffset, y: end.y - unit.y * tipOffset };
56
+ return {
57
+ start: { x: start.x + unit.x * startOffset, y: start.y + unit.y * startOffset },
58
+ corner: null,
59
+ shaftEnd: { x: tip2.x - unit.x * headLength, y: tip2.y - unit.y * headLength },
60
+ tip: tip2,
61
+ finalUnit: unit
62
+ };
63
+ }
64
+ const dx = end.x - start.x;
65
+ const dy = end.y - start.y;
66
+ const corner = Math.abs(dx) > Math.abs(dy) ? { x: end.x, y: start.y } : { x: start.x, y: end.y };
67
+ const firstUnit = unitVector(start, corner);
68
+ const finalUnit = unitVector(corner, end);
69
+ if (firstUnit === null || finalUnit === null) return null;
70
+ const tip = { x: end.x - finalUnit.x * tipOffset, y: end.y - finalUnit.y * tipOffset };
71
+ return {
72
+ start: { x: start.x + firstUnit.x * startOffset, y: start.y + firstUnit.y * startOffset },
73
+ corner,
74
+ shaftEnd: { x: tip.x - finalUnit.x * headLength, y: tip.y - finalUnit.y * headLength },
75
+ tip,
76
+ finalUnit
77
+ };
78
+ }
79
+ function resolveColor(arrow, palette) {
80
+ if (arrow.brush !== void 0 && palette !== void 0) {
81
+ const brushed = palette[arrow.brush];
82
+ if (brushed !== void 0) return brushed;
83
+ }
84
+ return arrow.color;
85
+ }
86
+ function drawArrow(ctx, fromIndex, toIndex, color, sqSize, orientation, preview) {
87
+ const a = squareCentrePct(fromIndex, orientation);
88
+ const b = squareCentrePct(toIndex, orientation);
89
+ const boardSize = sqSize * 8;
90
+ const start = { x: a.cx / 100 * boardSize, y: a.cy / 100 * boardSize };
91
+ const end = { x: b.cx / 100 * boardSize, y: b.cy / 100 * boardSize };
92
+ if (fromIndex === toIndex) {
93
+ ctx.strokeStyle = color;
94
+ ctx.lineWidth = sqSize * 0.08;
95
+ ctx.globalAlpha = preview ? 0.65 : 1;
96
+ ctx.beginPath();
97
+ ctx.arc(start.x, start.y, sqSize * 0.44, 0, Math.PI * 2);
98
+ ctx.stroke();
99
+ ctx.globalAlpha = 1;
100
+ return;
101
+ }
102
+ const startOffset = sqSize * 0.28;
103
+ const tipOffset = sqSize * 0.08;
104
+ const headLength = sqSize * 0.36;
105
+ const headHalfWidth = sqSize * 0.28;
106
+ const shaftWidth = sqSize * 0.18;
107
+ const path = arrowPath(fromIndex, toIndex, start, end, startOffset, tipOffset, headLength);
108
+ if (path === null) return;
109
+ ctx.globalAlpha = preview ? 0.65 : 1;
110
+ ctx.strokeStyle = color;
111
+ ctx.lineWidth = shaftWidth;
112
+ ctx.lineCap = "butt";
113
+ ctx.lineJoin = "round";
114
+ ctx.beginPath();
115
+ ctx.moveTo(path.start.x, path.start.y);
116
+ if (path.corner !== null) ctx.lineTo(path.corner.x, path.corner.y);
117
+ ctx.lineTo(path.shaftEnd.x, path.shaftEnd.y);
118
+ ctx.stroke();
119
+ const perpX = -path.finalUnit.y;
120
+ const perpY = path.finalUnit.x;
121
+ ctx.fillStyle = color;
122
+ ctx.beginPath();
123
+ ctx.moveTo(path.tip.x, path.tip.y);
124
+ ctx.lineTo(path.shaftEnd.x + perpX * headHalfWidth, path.shaftEnd.y + perpY * headHalfWidth);
125
+ ctx.lineTo(path.shaftEnd.x - perpX * headHalfWidth, path.shaftEnd.y - perpY * headHalfWidth);
126
+ ctx.closePath();
127
+ ctx.fill();
128
+ ctx.globalAlpha = 1;
129
+ }
130
+ function redraw(canvas, arrows, preview, orientation, palette) {
131
+ const ctx = canvas.getContext("2d");
132
+ if (ctx === null) return;
133
+ const dpr = typeof window !== "undefined" ? window.devicePixelRatio || 1 : 1;
134
+ const rect = canvas.getBoundingClientRect();
135
+ const cssWidth = rect.width;
136
+ const cssHeight = rect.height;
137
+ if (cssWidth <= 0 || cssHeight <= 0) return;
138
+ const backingWidth = Math.round(cssWidth * dpr);
139
+ const backingHeight = Math.round(cssHeight * dpr);
140
+ if (canvas.width !== backingWidth) canvas.width = backingWidth;
141
+ if (canvas.height !== backingHeight) canvas.height = backingHeight;
142
+ ctx.setTransform(dpr, 0, 0, dpr, 0, 0);
143
+ ctx.clearRect(0, 0, cssWidth, cssHeight);
144
+ const sqSize = cssWidth / 8;
145
+ for (const arrow of arrows) {
146
+ drawArrow(ctx, arrow.from, arrow.to, resolveColor(arrow, palette), sqSize, orientation, false);
147
+ }
148
+ if (preview !== null) {
149
+ drawArrow(ctx, preview.from, preview.to, preview.color, sqSize, orientation, true);
150
+ }
151
+ }
152
+ function anchorPct(from, to, orientation, center) {
153
+ const a = squareCentrePct(from, orientation);
154
+ const b = squareCentrePct(to, orientation);
155
+ if (center === "orig") return a;
156
+ if (center === "dest") return b;
157
+ return { cx: (a.cx + b.cx) / 2, cy: (a.cy + b.cy) / 2 };
158
+ }
159
+ var ArrowsLayer = react.memo(
160
+ react.forwardRef(function ArrowsLayer2({ model, orientation, palette, below = false }, handle) {
161
+ const canvasRef = react.useRef(null);
162
+ const previewRef = react.useRef(null);
163
+ const allArrows = useBoardSlice(model, (s) => s.arrows);
164
+ const arrows = react.useMemo(
165
+ () => allArrows.filter((a) => a.below === true === below),
166
+ [allArrows, below]
167
+ );
168
+ const hasDecorations = react.useMemo(
169
+ () => arrows.some((a) => a.label !== void 0 || a.customSvg !== void 0),
170
+ [arrows]
171
+ );
172
+ react.useImperativeHandle(
173
+ handle,
174
+ () => ({
175
+ setPreview(preview) {
176
+ if (below) return;
177
+ previewRef.current = preview;
178
+ const canvas = canvasRef.current;
179
+ if (canvas !== null) redraw(canvas, arrows, preview, orientation, palette);
180
+ }
181
+ }),
182
+ [arrows, orientation, palette, below]
183
+ );
184
+ react.useLayoutEffect(() => {
185
+ const canvas = canvasRef.current;
186
+ if (canvas === null) return;
187
+ redraw(canvas, arrows, previewRef.current, orientation, palette);
188
+ }, [arrows, orientation, palette]);
189
+ react.useEffect(() => {
190
+ const canvas = canvasRef.current;
191
+ if (canvas === null) return;
192
+ const parent = canvas.parentElement;
193
+ if (parent === null || typeof ResizeObserver === "undefined") return;
194
+ const observer = new ResizeObserver(() => {
195
+ redraw(canvas, arrows, previewRef.current, orientation, palette);
196
+ });
197
+ observer.observe(parent);
198
+ return () => observer.disconnect();
199
+ }, [arrows, orientation, palette]);
200
+ const layerZIndex = below ? void 0 : 15;
201
+ return /* @__PURE__ */ jsxRuntime.jsxs(jsxRuntime.Fragment, { children: [
202
+ /* @__PURE__ */ jsxRuntime.jsx(
203
+ "canvas",
204
+ {
205
+ ref: canvasRef,
206
+ "data-layer": below ? "arrows-below" : "arrows",
207
+ style: {
208
+ position: "absolute",
209
+ inset: 0,
210
+ width: "100%",
211
+ height: "100%",
212
+ pointerEvents: "none",
213
+ ...layerZIndex !== void 0 ? { zIndex: layerZIndex } : {}
214
+ }
215
+ }
216
+ ),
217
+ hasDecorations ? /* @__PURE__ */ jsxRuntime.jsx(
218
+ "svg",
219
+ {
220
+ "data-layer": below ? "arrows-below-decor" : "arrows-decor",
221
+ viewBox: "0 0 100 100",
222
+ preserveAspectRatio: "none",
223
+ "aria-hidden": "true",
224
+ style: {
225
+ position: "absolute",
226
+ inset: 0,
227
+ width: "100%",
228
+ height: "100%",
229
+ pointerEvents: "none",
230
+ // Decorations share the z-plane of the canvas they annotate.
231
+ ...layerZIndex !== void 0 ? { zIndex: layerZIndex } : {}
232
+ },
233
+ children: arrows.map((arrow) => {
234
+ const key = `${arrow.from}-${arrow.to}-${arrow.color}-${arrow.brush ?? ""}`;
235
+ const color = resolveColor(arrow, palette);
236
+ const nodes = [];
237
+ if (arrow.label !== void 0) {
238
+ const { cx, cy } = anchorPct(arrow.from, arrow.to, orientation, "label");
239
+ nodes.push(
240
+ /* @__PURE__ */ jsxRuntime.jsx(
241
+ "text",
242
+ {
243
+ x: cx,
244
+ y: cy,
245
+ fill: arrow.label.fill ?? color,
246
+ fontSize: "4",
247
+ fontWeight: "700",
248
+ textAnchor: "middle",
249
+ dominantBaseline: "central",
250
+ style: { userSelect: "none" },
251
+ children: arrow.label.text
252
+ },
253
+ `${key}-label`
254
+ )
255
+ );
256
+ }
257
+ if (arrow.customSvg !== void 0) {
258
+ const { cx, cy } = anchorPct(
259
+ arrow.from,
260
+ arrow.to,
261
+ orientation,
262
+ arrow.customSvg.center ?? "dest"
263
+ );
264
+ nodes.push(
265
+ /* @__PURE__ */ jsxRuntime.jsx(
266
+ "g",
267
+ {
268
+ transform: `translate(${cx}, ${cy})`,
269
+ dangerouslySetInnerHTML: { __html: arrow.customSvg.html }
270
+ },
271
+ `${key}-svg`
272
+ )
273
+ );
274
+ }
275
+ return nodes.length === 0 ? null : /* @__PURE__ */ jsxRuntime.jsx("g", { children: nodes }, key);
276
+ })
277
+ }
278
+ ) : null
279
+ ] });
280
+ })
281
+ );
282
+ var FOCUS_STYLE_ID = "gb-focus-ring-styles";
283
+ function injectFocusRingStyles() {
284
+ if (typeof document === "undefined") return;
285
+ if (document.getElementById(FOCUS_STYLE_ID) !== null) return;
286
+ const style = document.createElement("style");
287
+ style.id = FOCUS_STYLE_ID;
288
+ style.textContent = `
289
+ [data-gb-square]:focus-visible {
290
+ box-shadow: inset 0 0 0 3px rgba(255, 206, 76, 0.95);
291
+ }
292
+ `;
293
+ document.head.appendChild(style);
294
+ }
295
+ function algebraicOf(index) {
296
+ const file = index & 7;
297
+ const rank = index >> 3;
298
+ return `${String.fromCharCode(97 + file)}${rank + 1}`;
299
+ }
300
+ function SquareImpl({
301
+ index,
302
+ onClick,
303
+ renderSquare,
304
+ isFocused,
305
+ setSquareRef,
306
+ model,
307
+ getSquareAriaLabel,
308
+ onFocus
309
+ }) {
310
+ const file = index & 7;
311
+ const rank = index >> 3;
312
+ const isLight = (file + rank) % 2 === 1;
313
+ const cell = useSquareCell(model, index);
314
+ const label = getSquareAriaLabel ? getSquareAriaLabel(index, cell) : algebraicOf(index);
315
+ const internalRef = react.useRef(null);
316
+ const lastReportedFocusRef = react.useRef(false);
317
+ const setRef = react.useCallback(
318
+ (el) => {
319
+ internalRef.current = el;
320
+ setSquareRef?.(index, el);
321
+ },
322
+ [index, setSquareRef]
323
+ );
324
+ react.useEffect(() => {
325
+ injectFocusRingStyles();
326
+ }, []);
327
+ react.useEffect(() => {
328
+ if (!isFocused) {
329
+ lastReportedFocusRef.current = false;
330
+ return;
331
+ }
332
+ const el = internalRef.current;
333
+ if (el === null) return;
334
+ const grid = el.closest('[role="grid"]');
335
+ if (grid === null) return;
336
+ if (!grid.contains(document.activeElement)) return;
337
+ if (document.activeElement !== el) el.focus();
338
+ if (!lastReportedFocusRef.current) {
339
+ lastReportedFocusRef.current = true;
340
+ onFocus?.(index, cell);
341
+ }
342
+ }, [isFocused, index, cell, onFocus]);
343
+ const handleFocus = react.useCallback(() => {
344
+ lastReportedFocusRef.current = true;
345
+ onFocus?.(index, cell);
346
+ }, [index, cell, onFocus]);
347
+ const handleClick = () => onClick(index);
348
+ return /* @__PURE__ */ jsxRuntime.jsx(
349
+ "div",
350
+ {
351
+ ref: setRef,
352
+ role: "gridcell",
353
+ "aria-label": label,
354
+ "aria-rowindex": rank + 1,
355
+ "aria-colindex": file + 1,
356
+ tabIndex: isFocused ? 0 : -1,
357
+ "data-square": algebraicOf(index),
358
+ "data-gb-square": "",
359
+ "data-light": isLight ? "true" : "false",
360
+ onClick: handleClick,
361
+ onFocus: handleFocus,
362
+ style: {
363
+ position: "relative",
364
+ // `backgroundColor` (not the `background` shorthand) so the
365
+ // CSS `background-image` set by `useSelectionController` can
366
+ // layer on top without clobbering this base colour.
367
+ backgroundColor: `var(${isLight ? chunkI4BQGFP5_cjs.CSS_VARS.SQ_LIGHT : chunkI4BQGFP5_cjs.CSS_VARS.SQ_DARK})`,
368
+ // Cursor is managed by `useCursorController` via injected CSS so
369
+ // it can flip between `pointer` / `grab` / `grabbing` without a
370
+ // React re-render per hover.
371
+ userSelect: "none",
372
+ // Suppress the browser's default focus ring; the keyboard focus
373
+ // indicator ships via `:focus-visible` (see `injectFocusRingStyles`).
374
+ outline: "none",
375
+ // Squares carry the container-query context so pieces inside can
376
+ // size relative to this cell (85cqh glyph sizing).
377
+ containerType: "size"
378
+ },
379
+ children: renderSquare !== void 0 ? renderSquare({ index, isLight, label }) : null
380
+ }
381
+ );
382
+ }
383
+ var Square = react.memo(SquareImpl);
384
+ Square.displayName = "Square";
385
+ function buildSquareOrder(orientation) {
386
+ const squares = [];
387
+ if (orientation === "white") {
388
+ for (let rank = 7; rank >= 0; rank--) {
389
+ for (let file = 0; file < 8; file++) {
390
+ squares.push(rank * 8 + file);
391
+ }
392
+ }
393
+ } else {
394
+ for (let rank = 0; rank < 8; rank++) {
395
+ for (let file = 7; file >= 0; file--) {
396
+ squares.push(rank * 8 + file);
397
+ }
398
+ }
399
+ }
400
+ return squares;
401
+ }
402
+ var BoardGrid = react.memo(function BoardGrid2({
403
+ orientation,
404
+ onSquareClick,
405
+ renderSquare,
406
+ ariaLabel,
407
+ focusedSquare,
408
+ setSquareRef,
409
+ readOnly,
410
+ model,
411
+ getSquareAriaLabel,
412
+ onSquareFocus
413
+ }) {
414
+ const squares = react.useMemo(() => buildSquareOrder(orientation), [orientation]);
415
+ return /* @__PURE__ */ jsxRuntime.jsx(
416
+ "div",
417
+ {
418
+ role: "grid",
419
+ "aria-label": ariaLabel ?? "Chess board",
420
+ "aria-rowcount": 8,
421
+ "aria-colcount": 8,
422
+ ...readOnly === true ? { "aria-readonly": true } : {},
423
+ style: {
424
+ position: "absolute",
425
+ inset: 0,
426
+ display: "grid",
427
+ gridTemplateColumns: "repeat(8, 1fr)",
428
+ gridTemplateRows: "repeat(8, 1fr)"
429
+ },
430
+ children: squares.map((index) => /* @__PURE__ */ jsxRuntime.jsx(
431
+ Square,
432
+ {
433
+ index,
434
+ onClick: onSquareClick,
435
+ isFocused: focusedSquare === index,
436
+ model,
437
+ getSquareAriaLabel,
438
+ onFocus: onSquareFocus,
439
+ ...renderSquare !== void 0 ? { renderSquare } : {},
440
+ ...setSquareRef !== void 0 ? { setSquareRef } : {}
441
+ },
442
+ index
443
+ ))
444
+ }
445
+ );
446
+ });
447
+ var WHITE_KING = 6;
448
+ var BLACK_KING = 12;
449
+ function findKing(board, turn) {
450
+ const code = turn === 0 ? WHITE_KING : BLACK_KING;
451
+ for (let i = 0; i < 64; i++) {
452
+ if (board[i] === code) return i;
453
+ }
454
+ return null;
455
+ }
456
+ function positionOf(index, orientation) {
457
+ const file = index & 7;
458
+ const rank = index >> 3;
459
+ const col = orientation === "white" ? file : 7 - file;
460
+ const row = orientation === "white" ? 7 - rank : rank;
461
+ return { x: col * 12.5, y: row * 12.5 };
462
+ }
463
+ function CheckLayer({ model, orientation }) {
464
+ const inCheck = useBoardSlice(model, (s) => s.inCheck);
465
+ if (!inCheck) return null;
466
+ return /* @__PURE__ */ jsxRuntime.jsx(CheckLayerInner, { model, orientation });
467
+ }
468
+ function CheckLayerInner({ model, orientation }) {
469
+ const turn = useBoardSlice(model, (s) => s.turn);
470
+ const board = useBoardSlice(model, (s) => s.board);
471
+ const kingSquare = findKing(board, turn);
472
+ if (kingSquare === null) return null;
473
+ const { x, y } = positionOf(kingSquare, orientation);
474
+ return /* @__PURE__ */ jsxRuntime.jsx(
475
+ "div",
476
+ {
477
+ "data-layer": "check",
478
+ "aria-hidden": "true",
479
+ style: {
480
+ position: "absolute",
481
+ left: `${x}%`,
482
+ top: `${y}%`,
483
+ width: "12.5%",
484
+ height: "12.5%",
485
+ background: `var(${chunkI4BQGFP5_cjs.CSS_VARS.CHECK})`,
486
+ pointerEvents: "none",
487
+ zIndex: 2
488
+ }
489
+ }
490
+ );
491
+ }
492
+ var FILE_LETTERS = ["a", "b", "c", "d", "e", "f", "g", "h"];
493
+ function Coordinates({ orientation, ranksPosition = "left" }) {
494
+ const ranksOnRight = ranksPosition === "right";
495
+ return /* @__PURE__ */ jsxRuntime.jsxs(
496
+ "div",
497
+ {
498
+ "aria-hidden": "true",
499
+ style: {
500
+ position: "absolute",
501
+ inset: 0,
502
+ pointerEvents: "none",
503
+ fontSize: "min(1.6vmin, 11px)",
504
+ fontWeight: 600,
505
+ fontFamily: "system-ui, -apple-system, sans-serif"
506
+ },
507
+ children: [
508
+ FILE_LETTERS.map((letter, idx) => {
509
+ const col = orientation === "white" ? idx : 7 - idx;
510
+ const bottomRank = orientation === "white" ? 0 : 7;
511
+ const isLightSquare = (col + bottomRank) % 2 === 1;
512
+ return /* @__PURE__ */ jsxRuntime.jsx(
513
+ "span",
514
+ {
515
+ style: {
516
+ position: "absolute",
517
+ left: `${col * 12.5 + 10}%`,
518
+ bottom: "1%",
519
+ color: `var(${isLightSquare ? chunkI4BQGFP5_cjs.CSS_VARS.COORDINATE_LIGHT : chunkI4BQGFP5_cjs.CSS_VARS.COORDINATE_DARK})`
520
+ },
521
+ children: letter
522
+ },
523
+ `file-${letter}`
524
+ );
525
+ }),
526
+ FILE_LETTERS.map((_, idx) => {
527
+ const rank = idx;
528
+ const row = orientation === "white" ? 7 - rank : rank;
529
+ const edgeFile = ranksOnRight ? orientation === "white" ? 7 : 0 : orientation === "white" ? 0 : 7;
530
+ const isLightSquare = (edgeFile + rank) % 2 === 1;
531
+ const sideStyle = ranksOnRight ? { right: "1%" } : { left: "1%" };
532
+ return /* @__PURE__ */ jsxRuntime.jsx(
533
+ "span",
534
+ {
535
+ style: {
536
+ position: "absolute",
537
+ top: `${row * 12.5 + 1}%`,
538
+ ...sideStyle,
539
+ color: `var(${isLightSquare ? chunkI4BQGFP5_cjs.CSS_VARS.COORDINATE_LIGHT : chunkI4BQGFP5_cjs.CSS_VARS.COORDINATE_DARK})`
540
+ },
541
+ children: rank + 1
542
+ },
543
+ `rank-${rank}`
544
+ );
545
+ })
546
+ ]
547
+ }
548
+ );
549
+ }
550
+ var WRAPPER_STYLE = Object.freeze({
551
+ position: "absolute",
552
+ top: 0,
553
+ left: 0,
554
+ width: "12.5%",
555
+ height: "12.5%",
556
+ pointerEvents: "none",
557
+ willChange: "transform",
558
+ zIndex: 20,
559
+ containerType: "size",
560
+ opacity: 0,
561
+ transform: "translate3d(0, 0, 0)"
562
+ });
563
+ var SLOT_STYLE = Object.freeze({
564
+ position: "absolute",
565
+ inset: 0,
566
+ opacity: 0,
567
+ containerType: "size"
568
+ });
569
+ var DragLayer = react.forwardRef(function DragLayer2({ pieces }, ref) {
570
+ const wrapperRef = react.useRef(null);
571
+ const slotRefs = react.useRef(new Array(13).fill(null));
572
+ const slots = react.useMemo(() => {
573
+ const nodes = [];
574
+ for (let cell = 1; cell <= 12; cell++) {
575
+ const child = pieces({ cell, square: 0 });
576
+ nodes.push(
577
+ /* @__PURE__ */ jsxRuntime.jsx(
578
+ "div",
579
+ {
580
+ ref: (el) => {
581
+ slotRefs.current[cell] = el;
582
+ },
583
+ "data-drag-cell": cell,
584
+ style: SLOT_STYLE,
585
+ children: child
586
+ },
587
+ cell
588
+ )
589
+ );
590
+ }
591
+ return nodes;
592
+ }, [pieces]);
593
+ react.useImperativeHandle(
594
+ ref,
595
+ () => ({
596
+ show(cell) {
597
+ const wrapper = wrapperRef.current;
598
+ if (wrapper === null) return;
599
+ wrapper.style.opacity = "1";
600
+ wrapper.dataset["active"] = "true";
601
+ for (let c = 1; c <= 12; c++) {
602
+ const el = slotRefs.current[c];
603
+ if (el !== null && el !== void 0) {
604
+ el.style.opacity = c === cell ? "1" : "0";
605
+ }
606
+ }
607
+ },
608
+ hide() {
609
+ const wrapper = wrapperRef.current;
610
+ if (wrapper === null) return;
611
+ wrapper.style.opacity = "0";
612
+ wrapper.dataset["active"] = "false";
613
+ },
614
+ setTransform(x, y) {
615
+ const wrapper = wrapperRef.current;
616
+ if (wrapper === null) return;
617
+ wrapper.style.transform = `translate3d(${x}px, ${y}px, 0)`;
618
+ },
619
+ element() {
620
+ return wrapperRef.current;
621
+ }
622
+ }),
623
+ []
624
+ );
625
+ return /* @__PURE__ */ jsxRuntime.jsx(
626
+ "div",
627
+ {
628
+ ref: wrapperRef,
629
+ "data-layer": "drag",
630
+ "data-active": "false",
631
+ "aria-hidden": "true",
632
+ style: WRAPPER_STYLE,
633
+ children: slots
634
+ }
635
+ );
636
+ });
637
+ function positionOf2(index, orientation) {
638
+ const file = index & 7;
639
+ const rank = index >> 3;
640
+ const col = orientation === "white" ? file : 7 - file;
641
+ const row = orientation === "white" ? 7 - rank : rank;
642
+ return { x: col * 12.5, y: row * 12.5 };
643
+ }
644
+ function prefersReducedMotion() {
645
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
646
+ return false;
647
+ }
648
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
649
+ }
650
+ var FLASH_DURATION_MS = 280;
651
+ function IllegalFlashLayer({ at, orientation }) {
652
+ const elRef = react.useRef(null);
653
+ react.useLayoutEffect(() => {
654
+ if (at === null) return;
655
+ const el = elRef.current;
656
+ if (el === null || typeof el.animate !== "function") return;
657
+ const duration = prefersReducedMotion() ? 0 : FLASH_DURATION_MS;
658
+ el.animate(
659
+ [
660
+ { opacity: 0, offset: 0 },
661
+ { opacity: 1, offset: 0.15 },
662
+ { opacity: 0, offset: 1 }
663
+ ],
664
+ { duration, easing: "ease-out", fill: "forwards" }
665
+ );
666
+ }, [at]);
667
+ if (at === null) return null;
668
+ const { x, y } = positionOf2(at, orientation);
669
+ return /* @__PURE__ */ jsxRuntime.jsx(
670
+ "div",
671
+ {
672
+ ref: elRef,
673
+ "data-layer": "illegal-flash",
674
+ "aria-hidden": "true",
675
+ style: {
676
+ position: "absolute",
677
+ left: `${x}%`,
678
+ top: `${y}%`,
679
+ width: "12.5%",
680
+ height: "12.5%",
681
+ background: chunkI4BQGFP5_cjs.DEFAULT_ILLEGAL_FLASH_TINT,
682
+ opacity: 0,
683
+ pointerEvents: "none",
684
+ zIndex: 5
685
+ }
686
+ }
687
+ );
688
+ }
689
+ function algebraicOf2(index) {
690
+ const file = index & 7;
691
+ const rank = index >> 3;
692
+ return `${String.fromCharCode(97 + file)}${rank + 1}`;
693
+ }
694
+ function pieceName(cell) {
695
+ if (cell === 0) return "piece";
696
+ const color = cell > 6 ? "black" : "white";
697
+ const type = (cell - 1) % 6;
698
+ switch (type) {
699
+ case 0:
700
+ return `${color} pawn`;
701
+ case 1:
702
+ return `${color} knight`;
703
+ case 2:
704
+ return `${color} bishop`;
705
+ case 3:
706
+ return `${color} rook`;
707
+ case 4:
708
+ return `${color} queen`;
709
+ case 5:
710
+ return `${color} king`;
711
+ default:
712
+ return `${color} piece`;
713
+ }
714
+ }
715
+ function announcementFor(lastMove, board, wasCapture) {
716
+ const { from, to, kind, promotion } = chunkUUTEFNFO_cjs.decodePackedMove(lastMove);
717
+ if (kind === "castle") {
718
+ const isKingside = (to & 7) > (from & 7);
719
+ return isKingside ? "Castles kingside." : "Castles queenside.";
720
+ }
721
+ const landed = board[to] ?? 0;
722
+ const name = pieceName(landed);
723
+ const fromSq = algebraicOf2(from);
724
+ const toSq = algebraicOf2(to);
725
+ if (kind === "promotion" && promotion !== null) {
726
+ const promoted = promotion === 1 ? "knight" : promotion === 2 ? "bishop" : promotion === 3 ? "rook" : "queen";
727
+ const action = wasCapture ? "captures and promotes" : "promotes";
728
+ return `Pawn ${fromSq} ${action} to ${promoted} on ${toSq}.`;
729
+ }
730
+ if (kind === "en-passant") {
731
+ return `${capitalise(name)} ${fromSq} captures en passant on ${toSq}.`;
732
+ }
733
+ if (wasCapture) {
734
+ return `${capitalise(name)} ${fromSq} captures on ${toSq}.`;
735
+ }
736
+ return `${capitalise(name)} ${fromSq} to ${toSq}.`;
737
+ }
738
+ function capitalise(s) {
739
+ return s.length === 0 ? s : s.charAt(0).toUpperCase() + s.slice(1);
740
+ }
741
+ var srOnlyStyle = {
742
+ position: "absolute",
743
+ width: "1px",
744
+ height: "1px",
745
+ padding: 0,
746
+ margin: "-1px",
747
+ overflow: "hidden",
748
+ clip: "rect(0, 0, 0, 0)",
749
+ whiteSpace: "nowrap",
750
+ border: 0
751
+ };
752
+ function LiveRegion({ model }) {
753
+ const lastMove = useBoardSlice(model, (s) => s.lastMove);
754
+ const message = react.useMemo(() => {
755
+ if (lastMove === null) return "";
756
+ const wasCapture = model.lastAnimations.some(
757
+ (d) => d.kind === "capture" || d.kind === "en-passant"
758
+ );
759
+ return announcementFor(lastMove, model.getSnapshot().board, wasCapture);
760
+ }, [lastMove, model]);
761
+ return /* @__PURE__ */ jsxRuntime.jsx(
762
+ "div",
763
+ {
764
+ role: "status",
765
+ "aria-live": "polite",
766
+ "aria-atomic": "true",
767
+ "data-layer": "live-region",
768
+ style: srOnlyStyle,
769
+ children: message
770
+ }
771
+ );
772
+ }
773
+ function gridCoord(index, orientation) {
774
+ const file = index & 7;
775
+ const rank = index >> 3;
776
+ const col = orientation === "white" ? file + 1 : 8 - file;
777
+ const row = orientation === "white" ? 8 - rank : rank + 1;
778
+ return { col, row };
779
+ }
780
+ function algebraicOf3(index) {
781
+ const file = index & 7;
782
+ const rank = index >> 3;
783
+ return `${String.fromCharCode(97 + file)}${rank + 1}`;
784
+ }
785
+ var SLOT_BASE_STYLE = Object.freeze({
786
+ display: "flex",
787
+ alignItems: "center",
788
+ justifyContent: "center",
789
+ pointerEvents: "none",
790
+ containerType: "size"
791
+ });
792
+ function PieceSlotImpl({ index, model, orientation, pieces }) {
793
+ const cell = useSquareCell(model, index);
794
+ if (cell === 0) return null;
795
+ const { col, row } = gridCoord(index, orientation);
796
+ return /* @__PURE__ */ jsxRuntime.jsx(
797
+ "div",
798
+ {
799
+ "data-piece-square": algebraicOf3(index),
800
+ "data-piece-cell": cell,
801
+ style: { ...SLOT_BASE_STYLE, gridColumnStart: col, gridRowStart: row },
802
+ children: pieces({ cell, square: index })
803
+ }
804
+ );
805
+ }
806
+ var PieceSlot = react.memo(PieceSlotImpl);
807
+ PieceSlot.displayName = "PieceSlot";
808
+ var CONTAINER_STYLE = Object.freeze({
809
+ position: "absolute",
810
+ inset: 0,
811
+ display: "grid",
812
+ gridTemplateColumns: "repeat(8, 1fr)",
813
+ gridTemplateRows: "repeat(8, 1fr)",
814
+ pointerEvents: "none"
815
+ });
816
+ var PieceLayer = react.memo(function PieceLayer2({
817
+ model,
818
+ orientation,
819
+ pieces
820
+ }) {
821
+ const slots = [];
822
+ for (let i = 0; i < 64; i++) {
823
+ slots.push(
824
+ /* @__PURE__ */ jsxRuntime.jsx(
825
+ PieceSlot,
826
+ {
827
+ index: i,
828
+ model,
829
+ orientation,
830
+ pieces
831
+ },
832
+ i
833
+ )
834
+ );
835
+ }
836
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: CONTAINER_STYLE, children: slots });
837
+ });
838
+ function positionOf3(index, orientation) {
839
+ const file = index & 7;
840
+ const rank = index >> 3;
841
+ const col = orientation === "white" ? file : 7 - file;
842
+ const row = orientation === "white" ? 7 - rank : rank;
843
+ return { x: col * 12.5, y: row * 12.5 };
844
+ }
845
+ function PremoveLayer({ model, orientation, pieces }) {
846
+ const premoves = useBoardSlice(model, (s) => s.premoves);
847
+ if (premoves.length === 0) return null;
848
+ const head = premoves[0];
849
+ if (head === void 0) return null;
850
+ return /* @__PURE__ */ jsxRuntime.jsx(
851
+ PremoveLayerInner,
852
+ {
853
+ model,
854
+ orientation,
855
+ pieces,
856
+ from: head.from,
857
+ to: head.to
858
+ }
859
+ );
860
+ }
861
+ function PremoveLayerInner({ model, orientation, pieces, from, to }) {
862
+ const cell = useSquareCell(model, from);
863
+ if (cell === 0) return null;
864
+ const origin = positionOf3(from, orientation);
865
+ const target = positionOf3(to, orientation);
866
+ return /* @__PURE__ */ jsxRuntime.jsxs(
867
+ "div",
868
+ {
869
+ "data-layer": "premove",
870
+ "aria-hidden": "true",
871
+ style: {
872
+ position: "absolute",
873
+ inset: 0,
874
+ pointerEvents: "none",
875
+ zIndex: 4
876
+ },
877
+ children: [
878
+ /* @__PURE__ */ jsxRuntime.jsx(
879
+ "div",
880
+ {
881
+ style: {
882
+ position: "absolute",
883
+ left: `${origin.x}%`,
884
+ top: `${origin.y}%`,
885
+ width: "12.5%",
886
+ height: "12.5%",
887
+ background: chunkI4BQGFP5_cjs.DEFAULT_PREMOVE_TINT,
888
+ opacity: 0.5
889
+ }
890
+ }
891
+ ),
892
+ /* @__PURE__ */ jsxRuntime.jsx(
893
+ "div",
894
+ {
895
+ style: {
896
+ position: "absolute",
897
+ left: `${target.x}%`,
898
+ top: `${target.y}%`,
899
+ width: "12.5%",
900
+ height: "12.5%",
901
+ background: chunkI4BQGFP5_cjs.DEFAULT_PREMOVE_TINT
902
+ }
903
+ }
904
+ ),
905
+ /* @__PURE__ */ jsxRuntime.jsx(
906
+ "div",
907
+ {
908
+ "data-premove-ghost": "true",
909
+ style: {
910
+ position: "absolute",
911
+ left: `${target.x}%`,
912
+ top: `${target.y}%`,
913
+ width: "12.5%",
914
+ height: "12.5%",
915
+ display: "flex",
916
+ alignItems: "center",
917
+ justifyContent: "center",
918
+ opacity: 0.55,
919
+ containerType: "size"
920
+ },
921
+ children: pieces({ cell, square: to })
922
+ }
923
+ )
924
+ ]
925
+ }
926
+ );
927
+ }
928
+ function positionOf4(index, orientation) {
929
+ const file = index & 7;
930
+ const rank = index >> 3;
931
+ const col = orientation === "white" ? file : 7 - file;
932
+ const row = orientation === "white" ? 7 - rank : rank;
933
+ return { x: col * 12.5, y: row * 12.5 };
934
+ }
935
+ var CHOICE_ORDER = [
936
+ chunkUUTEFNFO_cjs.PieceType.Queen,
937
+ chunkUUTEFNFO_cjs.PieceType.Knight,
938
+ chunkUUTEFNFO_cjs.PieceType.Rook,
939
+ chunkUUTEFNFO_cjs.PieceType.Bishop
940
+ ];
941
+ var SHORTCUTS = {
942
+ q: chunkUUTEFNFO_cjs.PieceType.Queen,
943
+ r: chunkUUTEFNFO_cjs.PieceType.Rook,
944
+ b: chunkUUTEFNFO_cjs.PieceType.Bishop,
945
+ n: chunkUUTEFNFO_cjs.PieceType.Knight,
946
+ Q: chunkUUTEFNFO_cjs.PieceType.Queen,
947
+ R: chunkUUTEFNFO_cjs.PieceType.Rook,
948
+ B: chunkUUTEFNFO_cjs.PieceType.Bishop,
949
+ N: chunkUUTEFNFO_cjs.PieceType.Knight
950
+ };
951
+ function PromotionOverlay({
952
+ from: _from,
953
+ to,
954
+ color,
955
+ orientation,
956
+ pieces,
957
+ onSelect,
958
+ onCancel,
959
+ getPromotionPieceAriaLabel
960
+ }) {
961
+ const buttonRefs = react.useRef([]);
962
+ const getLabel = react.useCallback(
963
+ (piece) => getPromotionPieceAriaLabel ? getPromotionPieceAriaLabel(piece, color) : pieceName2(piece),
964
+ [getPromotionPieceAriaLabel, color]
965
+ );
966
+ const [activeAnnouncement, setActiveAnnouncement] = react.useState(
967
+ () => getLabel(CHOICE_ORDER[0] ?? chunkUUTEFNFO_cjs.PieceType.Queen)
968
+ );
969
+ react.useEffect(() => {
970
+ buttonRefs.current[0]?.focus();
971
+ }, []);
972
+ react.useEffect(() => {
973
+ const onKey = (e) => {
974
+ if (e.key === "Escape") {
975
+ e.preventDefault();
976
+ onCancel();
977
+ return;
978
+ }
979
+ if (e.key === "Tab") {
980
+ e.preventDefault();
981
+ const activeEl = document.activeElement;
982
+ const currentIdx = buttonRefs.current.findIndex((el) => el === activeEl);
983
+ let nextIdx;
984
+ if (e.shiftKey) {
985
+ nextIdx = currentIdx <= 0 ? buttonRefs.current.length - 1 : currentIdx - 1;
986
+ } else {
987
+ nextIdx = currentIdx >= buttonRefs.current.length - 1 ? 0 : currentIdx + 1;
988
+ }
989
+ buttonRefs.current[nextIdx]?.focus();
990
+ return;
991
+ }
992
+ if (e.key === "ArrowDown" || e.key === "ArrowRight") {
993
+ e.preventDefault();
994
+ const activeEl = document.activeElement;
995
+ const currentIdx = buttonRefs.current.findIndex((el) => el === activeEl);
996
+ const nextIdx = currentIdx < 0 ? 0 : (currentIdx + 1) % CHOICE_ORDER.length;
997
+ buttonRefs.current[nextIdx]?.focus();
998
+ return;
999
+ }
1000
+ if (e.key === "ArrowUp" || e.key === "ArrowLeft") {
1001
+ e.preventDefault();
1002
+ const activeEl = document.activeElement;
1003
+ const currentIdx = buttonRefs.current.findIndex((el) => el === activeEl);
1004
+ const prevIdx = currentIdx <= 0 ? CHOICE_ORDER.length - 1 : currentIdx - 1;
1005
+ buttonRefs.current[prevIdx]?.focus();
1006
+ return;
1007
+ }
1008
+ if (e.key === "Home") {
1009
+ e.preventDefault();
1010
+ buttonRefs.current[0]?.focus();
1011
+ return;
1012
+ }
1013
+ if (e.key === "End") {
1014
+ e.preventDefault();
1015
+ buttonRefs.current[CHOICE_ORDER.length - 1]?.focus();
1016
+ return;
1017
+ }
1018
+ const choice = SHORTCUTS[e.key];
1019
+ if (choice !== void 0) {
1020
+ e.preventDefault();
1021
+ onSelect(choice);
1022
+ }
1023
+ };
1024
+ window.addEventListener("keydown", onKey);
1025
+ return () => window.removeEventListener("keydown", onKey);
1026
+ }, [onSelect, onCancel]);
1027
+ const targetPos = positionOf4(to, orientation);
1028
+ const promotingColorIsBlack = color === 1;
1029
+ const stackUpward = orientation === "white" ? promotingColorIsBlack : !promotingColorIsBlack;
1030
+ return /* @__PURE__ */ jsxRuntime.jsxs(
1031
+ "div",
1032
+ {
1033
+ role: "presentation",
1034
+ "data-layer": "promotion-overlay",
1035
+ onClick: onCancel,
1036
+ style: {
1037
+ position: "absolute",
1038
+ inset: 0,
1039
+ background: "rgba(0, 0, 0, 0.35)",
1040
+ zIndex: 30,
1041
+ cursor: "pointer"
1042
+ },
1043
+ children: [
1044
+ /* @__PURE__ */ jsxRuntime.jsx(
1045
+ "div",
1046
+ {
1047
+ role: "dialog",
1048
+ "aria-modal": "true",
1049
+ "aria-label": `Choose promotion piece at ${algebraicOf4(to)}`,
1050
+ onClick: (e) => e.stopPropagation(),
1051
+ style: {
1052
+ position: "absolute",
1053
+ left: `${targetPos.x}%`,
1054
+ top: `${stackUpward ? targetPos.y - 12.5 * 3 : targetPos.y}%`,
1055
+ width: "12.5%",
1056
+ height: `${12.5 * 4}%`,
1057
+ display: "flex",
1058
+ flexDirection: stackUpward ? "column-reverse" : "column",
1059
+ background: "#ffffff",
1060
+ boxShadow: "0 4px 16px rgba(0, 0, 0, 0.28)",
1061
+ borderRadius: "4px",
1062
+ overflow: "hidden"
1063
+ },
1064
+ children: CHOICE_ORDER.map((piece, index) => {
1065
+ const cell = chunkUUTEFNFO_cjs.encodeBoardCell(color, piece);
1066
+ const label = getLabel(piece);
1067
+ return /* @__PURE__ */ jsxRuntime.jsx(
1068
+ "button",
1069
+ {
1070
+ ref: (el) => {
1071
+ buttonRefs.current[index] = el;
1072
+ },
1073
+ type: "button",
1074
+ onClick: () => onSelect(piece),
1075
+ onFocus: () => setActiveAnnouncement(label),
1076
+ "aria-label": label,
1077
+ style: {
1078
+ flex: 1,
1079
+ display: "flex",
1080
+ alignItems: "center",
1081
+ justifyContent: "center",
1082
+ border: "none",
1083
+ background: "transparent",
1084
+ cursor: "pointer",
1085
+ padding: 0,
1086
+ margin: 0,
1087
+ font: "inherit",
1088
+ containerType: "size"
1089
+ },
1090
+ "data-promotion-piece": pieceName2(piece),
1091
+ children: pieces({ cell, square: to })
1092
+ },
1093
+ piece
1094
+ );
1095
+ })
1096
+ }
1097
+ ),
1098
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", "aria-live": "polite", "aria-atomic": "true", style: srOnlyStyle2, children: activeAnnouncement }),
1099
+ /* @__PURE__ */ jsxRuntime.jsx("span", { className: "sr-only", style: srOnlyStyle2, children: "Choose a promotion piece with Q, N, R, or B, or arrow keys. Press Escape to cancel." })
1100
+ ]
1101
+ }
1102
+ );
1103
+ }
1104
+ var srOnlyStyle2 = {
1105
+ position: "absolute",
1106
+ width: "1px",
1107
+ height: "1px",
1108
+ padding: 0,
1109
+ margin: "-1px",
1110
+ overflow: "hidden",
1111
+ clip: "rect(0, 0, 0, 0)",
1112
+ whiteSpace: "nowrap",
1113
+ border: 0
1114
+ };
1115
+ function pieceName2(t) {
1116
+ switch (t) {
1117
+ case chunkUUTEFNFO_cjs.PieceType.Queen:
1118
+ return "Queen";
1119
+ case chunkUUTEFNFO_cjs.PieceType.Rook:
1120
+ return "Rook";
1121
+ case chunkUUTEFNFO_cjs.PieceType.Bishop:
1122
+ return "Bishop";
1123
+ case chunkUUTEFNFO_cjs.PieceType.Knight:
1124
+ return "Knight";
1125
+ default:
1126
+ return "Piece";
1127
+ }
1128
+ }
1129
+ function algebraicOf4(index) {
1130
+ const file = index & 7;
1131
+ const rank = index >> 3;
1132
+ return `${String.fromCharCode(97 + file)}${rank + 1}`;
1133
+ }
1134
+ function StaticPieceLayer({ board, orientation, pieces }) {
1135
+ const slots = [];
1136
+ for (let i = 0; i < 64; i++) {
1137
+ const cell = board[i] ?? 0;
1138
+ if (cell === 0) continue;
1139
+ const index = i;
1140
+ const { col, row } = gridCoord(index, orientation);
1141
+ slots.push(
1142
+ /* @__PURE__ */ jsxRuntime.jsx(
1143
+ "div",
1144
+ {
1145
+ "data-piece-square": algebraicOf3(index),
1146
+ "data-piece-cell": cell,
1147
+ style: { ...SLOT_BASE_STYLE, gridColumnStart: col, gridRowStart: row },
1148
+ children: pieces({ cell, square: index })
1149
+ },
1150
+ i
1151
+ )
1152
+ );
1153
+ }
1154
+ return /* @__PURE__ */ jsxRuntime.jsx("div", { "aria-hidden": "true", style: CONTAINER_STYLE, children: slots });
1155
+ }
1156
+ var DEFAULT_DURATION_MS = 60;
1157
+ var DEFAULT_EASING = "cubic-bezier(0.22, 0.61, 0.36, 1)";
1158
+ function algebraicOf5(index) {
1159
+ const file = index & 7;
1160
+ const rank = index >> 3;
1161
+ return `${String.fromCharCode(97 + file)}${rank + 1}`;
1162
+ }
1163
+ function deltaFromTo(from, to, squareSize, orientation) {
1164
+ const fromFile = from & 7;
1165
+ const fromRank = from >> 3;
1166
+ const toFile = to & 7;
1167
+ const toRank = to >> 3;
1168
+ const fromCol = orientation === "white" ? fromFile : 7 - fromFile;
1169
+ const fromRow = orientation === "white" ? 7 - fromRank : fromRank;
1170
+ const toCol = orientation === "white" ? toFile : 7 - toFile;
1171
+ const toRow = orientation === "white" ? 7 - toRank : toRank;
1172
+ return {
1173
+ dx: (fromCol - toCol) * squareSize,
1174
+ dy: (fromRow - toRow) * squareSize
1175
+ };
1176
+ }
1177
+ function cancelAnimations(element) {
1178
+ if (typeof element.getAnimations !== "function") return;
1179
+ for (const anim of element.getAnimations()) anim.cancel();
1180
+ }
1181
+ function prefersReducedMotion2() {
1182
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
1183
+ return false;
1184
+ }
1185
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
1186
+ }
1187
+ function animateGlide(element, dx, dy, duration, easing) {
1188
+ cancelAnimations(element);
1189
+ if (duration <= 0 || typeof element.animate !== "function") return;
1190
+ element.animate(
1191
+ [{ transform: `translate(${dx}px, ${dy}px)` }, { transform: "translate(0, 0)" }],
1192
+ { duration, easing, fill: "none" }
1193
+ );
1194
+ }
1195
+ function runAnimations(descriptors, container, orientation, duration, easing) {
1196
+ const rect = container.getBoundingClientRect();
1197
+ const sqSize = rect.width / 8;
1198
+ if (sqSize <= 0) return;
1199
+ for (const d of descriptors) {
1200
+ switch (d.kind) {
1201
+ case "move":
1202
+ case "capture":
1203
+ case "en-passant":
1204
+ case "promotion": {
1205
+ const toEl = container.querySelector(
1206
+ `[data-piece-square="${algebraicOf5(d.to)}"]`
1207
+ );
1208
+ if (toEl === null) break;
1209
+ const { dx, dy } = deltaFromTo(d.from, d.to, sqSize, orientation);
1210
+ animateGlide(toEl, dx, dy, duration, easing);
1211
+ break;
1212
+ }
1213
+ case "castle": {
1214
+ const kingEl = container.querySelector(
1215
+ `[data-piece-square="${algebraicOf5(d.kingTo)}"]`
1216
+ );
1217
+ const rookEl = container.querySelector(
1218
+ `[data-piece-square="${algebraicOf5(d.rookTo)}"]`
1219
+ );
1220
+ if (kingEl !== null) {
1221
+ const { dx, dy } = deltaFromTo(d.kingFrom, d.kingTo, sqSize, orientation);
1222
+ animateGlide(kingEl, dx, dy, duration, easing);
1223
+ }
1224
+ if (rookEl !== null) {
1225
+ const { dx, dy } = deltaFromTo(d.rookFrom, d.rookTo, sqSize, orientation);
1226
+ animateGlide(rookEl, dx, dy, duration, easing);
1227
+ }
1228
+ break;
1229
+ }
1230
+ }
1231
+ }
1232
+ }
1233
+ function AnimationRunner({
1234
+ model,
1235
+ containerRef,
1236
+ orientation,
1237
+ options,
1238
+ runtime
1239
+ }) {
1240
+ const duration = options?.durationMs ?? DEFAULT_DURATION_MS;
1241
+ const easing = options?.easing ?? DEFAULT_EASING;
1242
+ const skipNextRef = runtime?.skipNextRef;
1243
+ const historyPly = useBoardSlice(model, (s) => s.historyPly);
1244
+ const lastAnimatedPlyRef = react.useRef(null);
1245
+ react.useLayoutEffect(() => {
1246
+ if (lastAnimatedPlyRef.current === historyPly) return;
1247
+ lastAnimatedPlyRef.current = historyPly;
1248
+ const container = containerRef.current;
1249
+ if (container === null) return;
1250
+ const descriptors = model.lastAnimations;
1251
+ if (descriptors.length === 0) return;
1252
+ if (skipNextRef?.current) {
1253
+ skipNextRef.current = false;
1254
+ return;
1255
+ }
1256
+ const effectiveDuration = prefersReducedMotion2() ? 0 : duration;
1257
+ if (effectiveDuration <= 0) return;
1258
+ runAnimations(descriptors, container, orientation, effectiveDuration, easing);
1259
+ }, [historyPly, model, containerRef, orientation, duration, easing, skipNextRef]);
1260
+ return null;
1261
+ }
1262
+
1263
+ // src/lib/geometry.ts
1264
+ function getSquareAtPoint(container, clientX, clientY, orientation) {
1265
+ const rect = container.getBoundingClientRect();
1266
+ const relX = clientX - rect.left;
1267
+ const relY = clientY - rect.top;
1268
+ if (relX < 0 || relY < 0 || relX >= rect.width || relY >= rect.height) return null;
1269
+ const col = Math.floor(relX / rect.width * 8);
1270
+ const row = Math.floor(relY / rect.height * 8);
1271
+ const file = orientation === "white" ? col : 7 - col;
1272
+ const rank = orientation === "white" ? 7 - row : row;
1273
+ return rank * 8 + file;
1274
+ }
1275
+ function getPointAtSquareCentre(container, square, orientation) {
1276
+ const rect = container.getBoundingClientRect();
1277
+ const sqW = rect.width / 8;
1278
+ const sqH = rect.height / 8;
1279
+ const file = square & 7;
1280
+ const rank = square >> 3;
1281
+ const col = orientation === "white" ? file : 7 - file;
1282
+ const row = orientation === "white" ? 7 - rank : rank;
1283
+ return {
1284
+ x: rect.left + sqW * col + sqW / 2,
1285
+ y: rect.top + sqH * row + sqH / 2
1286
+ };
1287
+ }
1288
+ function algebraicOf6(square) {
1289
+ const file = square & 7;
1290
+ const rank = square >> 3;
1291
+ return `${String.fromCharCode(97 + file)}${rank + 1}`;
1292
+ }
1293
+
1294
+ // src/hooks/use-arrow-gesture.ts
1295
+ function colorForModifiers(e, palette) {
1296
+ if (e.shiftKey) return palette.shift;
1297
+ if (e.altKey) return palette.alt;
1298
+ if (e.ctrlKey || e.metaKey) return palette.ctrl;
1299
+ return palette.default;
1300
+ }
1301
+ function useArrowGesture(options) {
1302
+ const { game, orientation, containerRef, arrowsLayerRef, enabled, palette, snapToValidMove } = options;
1303
+ react.useEffect(() => {
1304
+ if (!enabled) return;
1305
+ const container = containerRef.current;
1306
+ if (container === null || game === null) return;
1307
+ let origin = null;
1308
+ let activePointerId = null;
1309
+ const clearPreview = () => {
1310
+ arrowsLayerRef.current?.setPreview(null);
1311
+ };
1312
+ const snap = (candidate) => {
1313
+ if (!snapToValidMove) return candidate;
1314
+ if (origin === null) return candidate;
1315
+ if (candidate === origin) return candidate;
1316
+ const legals = game.legalFrom(origin);
1317
+ if (legals.size === 0 || legals.has(candidate)) return candidate;
1318
+ const cf = candidate & 7;
1319
+ const cr = candidate >> 3;
1320
+ let best = candidate;
1321
+ let bestDist = Infinity;
1322
+ for (const t of legals) {
1323
+ const df = (t & 7) - cf;
1324
+ const dr = (t >> 3) - cr;
1325
+ const d = df * df + dr * dr;
1326
+ if (d < bestDist) {
1327
+ bestDist = d;
1328
+ best = t;
1329
+ }
1330
+ }
1331
+ return best;
1332
+ };
1333
+ const setPreview = (to, color) => {
1334
+ if (origin === null) return;
1335
+ arrowsLayerRef.current?.setPreview({ from: origin, to: snap(to), color });
1336
+ };
1337
+ const onContextMenu = (e) => {
1338
+ e.preventDefault();
1339
+ };
1340
+ const onPointerDown = (e) => {
1341
+ if (e.button !== 2) return;
1342
+ const sq = getSquareAtPoint(container, e.clientX, e.clientY, orientation);
1343
+ if (sq === null) return;
1344
+ origin = sq;
1345
+ activePointerId = e.pointerId;
1346
+ setPreview(sq, colorForModifiers(e, palette));
1347
+ try {
1348
+ container.setPointerCapture(e.pointerId);
1349
+ } catch {
1350
+ }
1351
+ };
1352
+ const onPointerMove = (e) => {
1353
+ if (origin === null) return;
1354
+ if (activePointerId !== null && e.pointerId !== activePointerId) return;
1355
+ const sq = getSquareAtPoint(container, e.clientX, e.clientY, orientation);
1356
+ if (sq === null) {
1357
+ setPreview(origin, colorForModifiers(e, palette));
1358
+ return;
1359
+ }
1360
+ setPreview(sq, colorForModifiers(e, palette));
1361
+ };
1362
+ const onPointerUp = (e) => {
1363
+ if (origin === null) return;
1364
+ if (activePointerId !== null && e.pointerId !== activePointerId) return;
1365
+ const raw = getSquareAtPoint(container, e.clientX, e.clientY, orientation) ?? origin;
1366
+ const target = snap(raw);
1367
+ const color = colorForModifiers(e, palette);
1368
+ game.toggleArrow({ from: origin, to: target, color });
1369
+ origin = null;
1370
+ activePointerId = null;
1371
+ clearPreview();
1372
+ };
1373
+ const onPointerCancel = (e) => {
1374
+ if (origin === null) return;
1375
+ if (activePointerId !== null && e.pointerId !== activePointerId) return;
1376
+ origin = null;
1377
+ activePointerId = null;
1378
+ clearPreview();
1379
+ };
1380
+ container.addEventListener("contextmenu", onContextMenu);
1381
+ container.addEventListener("pointerdown", onPointerDown);
1382
+ container.addEventListener("pointermove", onPointerMove, { passive: true });
1383
+ container.addEventListener("pointerup", onPointerUp);
1384
+ container.addEventListener("pointercancel", onPointerCancel);
1385
+ return () => {
1386
+ container.removeEventListener("contextmenu", onContextMenu);
1387
+ container.removeEventListener("pointerdown", onPointerDown);
1388
+ container.removeEventListener("pointermove", onPointerMove);
1389
+ container.removeEventListener("pointerup", onPointerUp);
1390
+ container.removeEventListener("pointercancel", onPointerCancel);
1391
+ clearPreview();
1392
+ };
1393
+ }, [game, orientation, containerRef, arrowsLayerRef, enabled, palette, snapToValidMove]);
1394
+ }
1395
+ function wouldPromote(fromCell, toIndex) {
1396
+ if (fromCell === 0) return false;
1397
+ const pieceType = (fromCell - 1) % 6;
1398
+ if (pieceType !== chunkUUTEFNFO_cjs.PieceType.Pawn) return false;
1399
+ const toRank = toIndex >> 3;
1400
+ return toRank === 0 || toRank === 7;
1401
+ }
1402
+ function useClickToMove(model, onMove, onPromotionNeeded, enabled = true) {
1403
+ return react.useCallback(
1404
+ (index) => {
1405
+ if (!enabled) return;
1406
+ if (model === null) return;
1407
+ const snap = model.getSnapshot();
1408
+ const cell = snap.board[index] ?? 0;
1409
+ const pieceColour = cell === 0 ? -1 : cell > 6 ? chunkUUTEFNFO_cjs.Color.Black : chunkUUTEFNFO_cjs.Color.White;
1410
+ if (snap.selected === null) {
1411
+ if (pieceColour === snap.turn) model.selectSquare(index);
1412
+ return;
1413
+ }
1414
+ if (snap.selected === index) {
1415
+ model.selectSquare(null);
1416
+ return;
1417
+ }
1418
+ if (snap.legalTargets.has(index)) {
1419
+ const fromCell = snap.board[snap.selected] ?? 0;
1420
+ if (wouldPromote(fromCell, index)) {
1421
+ if (onPromotionNeeded !== void 0) {
1422
+ onPromotionNeeded(snap.selected, index);
1423
+ return;
1424
+ }
1425
+ const played2 = model.tryMove(snap.selected, index, chunkUUTEFNFO_cjs.PieceType.Queen);
1426
+ if (played2 !== null && onMove !== void 0) onMove(played2);
1427
+ return;
1428
+ }
1429
+ const played = model.tryMove(snap.selected, index);
1430
+ if (played !== null && onMove !== void 0) onMove(played);
1431
+ return;
1432
+ }
1433
+ if (pieceColour === snap.turn) {
1434
+ model.selectSquare(index);
1435
+ return;
1436
+ }
1437
+ model.selectSquare(null);
1438
+ },
1439
+ [model, onMove, onPromotionNeeded, enabled]
1440
+ );
1441
+ }
1442
+ var STYLE_ID = "gb-cursor-styles";
1443
+ function injectCursorStyles() {
1444
+ if (typeof document === "undefined") return;
1445
+ if (document.getElementById(STYLE_ID) !== null) return;
1446
+ const style = document.createElement("style");
1447
+ style.id = STYLE_ID;
1448
+ style.textContent = `
1449
+ [data-gb-square] {
1450
+ cursor: pointer;
1451
+ }
1452
+ [data-gb-turn="white"] [data-piece-cell="1"],
1453
+ [data-gb-turn="white"] [data-piece-cell="2"],
1454
+ [data-gb-turn="white"] [data-piece-cell="3"],
1455
+ [data-gb-turn="white"] [data-piece-cell="4"],
1456
+ [data-gb-turn="white"] [data-piece-cell="5"],
1457
+ [data-gb-turn="white"] [data-piece-cell="6"],
1458
+ [data-gb-turn="black"] [data-piece-cell="7"],
1459
+ [data-gb-turn="black"] [data-piece-cell="8"],
1460
+ [data-gb-turn="black"] [data-piece-cell="9"],
1461
+ [data-gb-turn="black"] [data-piece-cell="10"],
1462
+ [data-gb-turn="black"] [data-piece-cell="11"],
1463
+ [data-gb-turn="black"] [data-piece-cell="12"] {
1464
+ pointer-events: auto;
1465
+ cursor: grab;
1466
+ }
1467
+ [data-gb-premove="true"][data-gb-turn="white"] [data-piece-cell="7"],
1468
+ [data-gb-premove="true"][data-gb-turn="white"] [data-piece-cell="8"],
1469
+ [data-gb-premove="true"][data-gb-turn="white"] [data-piece-cell="9"],
1470
+ [data-gb-premove="true"][data-gb-turn="white"] [data-piece-cell="10"],
1471
+ [data-gb-premove="true"][data-gb-turn="white"] [data-piece-cell="11"],
1472
+ [data-gb-premove="true"][data-gb-turn="white"] [data-piece-cell="12"],
1473
+ [data-gb-premove="true"][data-gb-turn="black"] [data-piece-cell="1"],
1474
+ [data-gb-premove="true"][data-gb-turn="black"] [data-piece-cell="2"],
1475
+ [data-gb-premove="true"][data-gb-turn="black"] [data-piece-cell="3"],
1476
+ [data-gb-premove="true"][data-gb-turn="black"] [data-piece-cell="4"],
1477
+ [data-gb-premove="true"][data-gb-turn="black"] [data-piece-cell="5"],
1478
+ [data-gb-premove="true"][data-gb-turn="black"] [data-piece-cell="6"] {
1479
+ pointer-events: auto;
1480
+ cursor: grab;
1481
+ }
1482
+ [data-gb-dragging="true"] [data-gb-square],
1483
+ [data-gb-dragging="true"] [data-piece-cell] {
1484
+ cursor: grabbing;
1485
+ }
1486
+ `;
1487
+ document.head.appendChild(style);
1488
+ }
1489
+ function useCursorController(model, containerRef, allowDrag, allowPremove) {
1490
+ react.useEffect(() => {
1491
+ injectCursorStyles();
1492
+ }, []);
1493
+ react.useEffect(() => {
1494
+ if (model === null) return;
1495
+ let lastTurn = null;
1496
+ let lastPremove = null;
1497
+ const write = () => {
1498
+ const el = containerRef.current;
1499
+ if (el === null) return;
1500
+ if (!allowDrag) {
1501
+ if (lastTurn !== null) {
1502
+ delete el.dataset["gbTurn"];
1503
+ lastTurn = null;
1504
+ }
1505
+ if (lastPremove !== null) {
1506
+ delete el.dataset["gbPremove"];
1507
+ lastPremove = null;
1508
+ }
1509
+ return;
1510
+ }
1511
+ const turn = model.getSnapshot().turn === 0 ? "white" : "black";
1512
+ if (turn !== lastTurn) {
1513
+ el.dataset["gbTurn"] = turn;
1514
+ lastTurn = turn;
1515
+ }
1516
+ if (allowPremove !== lastPremove) {
1517
+ if (allowPremove) {
1518
+ el.dataset["gbPremove"] = "true";
1519
+ } else {
1520
+ delete el.dataset["gbPremove"];
1521
+ }
1522
+ lastPremove = allowPremove;
1523
+ }
1524
+ };
1525
+ write();
1526
+ const unsubscribe = model.subscribe(write);
1527
+ return () => {
1528
+ unsubscribe();
1529
+ const el = containerRef.current;
1530
+ if (el !== null) {
1531
+ delete el.dataset["gbTurn"];
1532
+ delete el.dataset["gbPremove"];
1533
+ }
1534
+ };
1535
+ }, [model, containerRef, allowDrag, allowPremove]);
1536
+ }
1537
+ var GHOST_OPACITY_VAR = chunkI4BQGFP5_cjs.CSS_VARS.DRAG_GHOST_OPACITY;
1538
+ function useDrag(options) {
1539
+ const {
1540
+ game,
1541
+ orientation,
1542
+ containerRef,
1543
+ dragLayerRef,
1544
+ enabled,
1545
+ activationDistance,
1546
+ onDragStart,
1547
+ onDragEnd,
1548
+ onDrop,
1549
+ canDragPiece
1550
+ } = options;
1551
+ const canDragPieceRef = react.useRef(canDragPiece);
1552
+ react.useEffect(() => {
1553
+ canDragPieceRef.current = canDragPiece;
1554
+ });
1555
+ react.useEffect(() => {
1556
+ if (!enabled) return;
1557
+ const container = containerRef.current;
1558
+ if (container === null || game === null) return;
1559
+ const controller = chunkUUTEFNFO_cjs.createDragController(
1560
+ activationDistance === void 0 ? {} : { activationDistance }
1561
+ );
1562
+ let originEl = null;
1563
+ let active = false;
1564
+ let activePointerId = null;
1565
+ const writeTransform = (clientX, clientY) => {
1566
+ const layer = dragLayerRef.current;
1567
+ if (layer === null) return;
1568
+ const rect = container.getBoundingClientRect();
1569
+ const sqSize = rect.width / 8;
1570
+ const x = clientX - rect.left - sqSize / 2;
1571
+ const y = clientY - rect.top - sqSize / 2;
1572
+ layer.setTransform(x, y);
1573
+ };
1574
+ const restoreOrigin = () => {
1575
+ if (originEl !== null) {
1576
+ originEl.style.opacity = "";
1577
+ originEl = null;
1578
+ }
1579
+ };
1580
+ const cleanup = () => {
1581
+ restoreOrigin();
1582
+ dragLayerRef.current?.hide();
1583
+ active = false;
1584
+ activePointerId = null;
1585
+ onDragEnd();
1586
+ };
1587
+ const onPointerDown = (e) => {
1588
+ if (e.button !== 0) return;
1589
+ const sq = getSquareAtPoint(container, e.clientX, e.clientY, orientation);
1590
+ if (sq === null) return;
1591
+ const cell = game.getSnapshot().board[sq] ?? 0;
1592
+ if (cell === 0) return;
1593
+ const canDrag = canDragPieceRef.current;
1594
+ if (canDrag !== void 0 && !canDrag({ square: sq, cell })) {
1595
+ return;
1596
+ }
1597
+ const consumed = controller.pointerDown(sq, e.pointerId, e.clientX, e.clientY);
1598
+ if (consumed) activePointerId = e.pointerId;
1599
+ };
1600
+ const onPointerMove = (e) => {
1601
+ const evt = controller.pointerMove(e.pointerId, e.clientX, e.clientY);
1602
+ if (evt === null) return;
1603
+ if (evt.kind === "drag-start") {
1604
+ const cell = game.getSnapshot().board[evt.from] ?? 0;
1605
+ if (cell === 0) {
1606
+ controller.cancel();
1607
+ return;
1608
+ }
1609
+ const originLabel = algebraicOf6(evt.from);
1610
+ originEl = container.querySelector(`[data-piece-square="${originLabel}"]`);
1611
+ if (originEl !== null) {
1612
+ originEl.style.opacity = `var(${GHOST_OPACITY_VAR}, 0.35)`;
1613
+ }
1614
+ active = true;
1615
+ try {
1616
+ container.setPointerCapture(e.pointerId);
1617
+ } catch {
1618
+ }
1619
+ dragLayerRef.current?.show(cell);
1620
+ writeTransform(e.clientX, e.clientY);
1621
+ onDragStart(evt.from, cell);
1622
+ return;
1623
+ }
1624
+ writeTransform(e.clientX, e.clientY);
1625
+ };
1626
+ const onPointerUp = (e) => {
1627
+ const evt = controller.pointerUp(e.pointerId, e.clientX, e.clientY);
1628
+ if (evt === null) return;
1629
+ if (evt.kind === "drop") {
1630
+ const target = getSquareAtPoint(container, e.clientX, e.clientY, orientation);
1631
+ if (target !== null && target !== evt.from) {
1632
+ onDrop(evt.from, target);
1633
+ }
1634
+ }
1635
+ if (active) cleanup();
1636
+ activePointerId = null;
1637
+ };
1638
+ const onPointerCancel = (e) => {
1639
+ if (activePointerId !== null && e.pointerId !== activePointerId) return;
1640
+ controller.cancel();
1641
+ if (active) cleanup();
1642
+ activePointerId = null;
1643
+ };
1644
+ container.addEventListener("pointerdown", onPointerDown);
1645
+ container.addEventListener("pointermove", onPointerMove, { passive: true });
1646
+ container.addEventListener("pointerup", onPointerUp);
1647
+ container.addEventListener("pointercancel", onPointerCancel);
1648
+ return () => {
1649
+ container.removeEventListener("pointerdown", onPointerDown);
1650
+ container.removeEventListener("pointermove", onPointerMove);
1651
+ container.removeEventListener("pointerup", onPointerUp);
1652
+ container.removeEventListener("pointercancel", onPointerCancel);
1653
+ if (active) cleanup();
1654
+ };
1655
+ }, [
1656
+ game,
1657
+ orientation,
1658
+ containerRef,
1659
+ dragLayerRef,
1660
+ enabled,
1661
+ activationDistance,
1662
+ onDragStart,
1663
+ onDragEnd,
1664
+ onDrop
1665
+ ]);
1666
+ }
1667
+ function useHoverSquare(options) {
1668
+ const { game, orientation, containerRef, enabled, onEnter, onLeave } = options;
1669
+ const onEnterRef = react.useRef(onEnter);
1670
+ const onLeaveRef = react.useRef(onLeave);
1671
+ react.useEffect(() => {
1672
+ onEnterRef.current = onEnter;
1673
+ onLeaveRef.current = onLeave;
1674
+ });
1675
+ react.useEffect(() => {
1676
+ if (!enabled) return;
1677
+ const container = containerRef.current;
1678
+ if (container === null) return;
1679
+ let current = null;
1680
+ const cellAt = (sq) => game === null ? 0 : game.getSnapshot().board[sq] ?? 0;
1681
+ const onPointerMove = (e) => {
1682
+ const sq = getSquareAtPoint(container, e.clientX, e.clientY, orientation);
1683
+ if (sq === current) return;
1684
+ if (current !== null) onLeaveRef.current?.({ square: current, cell: cellAt(current) });
1685
+ if (sq !== null) onEnterRef.current?.({ square: sq, cell: cellAt(sq) });
1686
+ current = sq;
1687
+ };
1688
+ const onPointerLeave = () => {
1689
+ if (current === null) return;
1690
+ onLeaveRef.current?.({ square: current, cell: cellAt(current) });
1691
+ current = null;
1692
+ };
1693
+ container.addEventListener("pointermove", onPointerMove, { passive: true });
1694
+ container.addEventListener("pointerleave", onPointerLeave);
1695
+ return () => {
1696
+ container.removeEventListener("pointermove", onPointerMove);
1697
+ container.removeEventListener("pointerleave", onPointerLeave);
1698
+ if (current !== null) {
1699
+ onLeaveRef.current?.({ square: current, cell: cellAt(current) });
1700
+ current = null;
1701
+ }
1702
+ };
1703
+ }, [game, orientation, containerRef, enabled]);
1704
+ }
1705
+ function moveFocus(current, direction, orientation) {
1706
+ const file = current & 7;
1707
+ const rank = current >> 3;
1708
+ const col = orientation === "white" ? file : 7 - file;
1709
+ const row = orientation === "white" ? 7 - rank : rank;
1710
+ let newCol = col;
1711
+ let newRow = row;
1712
+ switch (direction) {
1713
+ case "up":
1714
+ newRow = Math.max(0, row - 1);
1715
+ break;
1716
+ case "down":
1717
+ newRow = Math.min(7, row + 1);
1718
+ break;
1719
+ case "left":
1720
+ newCol = Math.max(0, col - 1);
1721
+ break;
1722
+ case "right":
1723
+ newCol = Math.min(7, col + 1);
1724
+ break;
1725
+ }
1726
+ const newFile = orientation === "white" ? newCol : 7 - newCol;
1727
+ const newRank = orientation === "white" ? 7 - newRow : newRow;
1728
+ return newRank * 8 + newFile;
1729
+ }
1730
+ function rankEnd(current, which, orientation) {
1731
+ const rank = current >> 3;
1732
+ if (orientation === "white") {
1733
+ return rank << 3 | (which === "home" ? 0 : 7);
1734
+ }
1735
+ return rank << 3 | (which === "home" ? 7 : 0);
1736
+ }
1737
+ function boardCorner(which, orientation) {
1738
+ if (orientation === "white") {
1739
+ return which === "home" ? 56 : 7;
1740
+ }
1741
+ return which === "home" ? 7 : 56;
1742
+ }
1743
+ function useKeyboardNav(options) {
1744
+ const {
1745
+ containerRef,
1746
+ orientation,
1747
+ focusedSquare,
1748
+ setFocusedSquare,
1749
+ onActivate,
1750
+ onEscape,
1751
+ enabled
1752
+ } = options;
1753
+ react.useEffect(() => {
1754
+ if (!enabled) return;
1755
+ const container = containerRef.current;
1756
+ if (container === null) return;
1757
+ const onKeyDown = (e) => {
1758
+ if (!container.contains(e.target)) return;
1759
+ const current = focusedSquare;
1760
+ if (current === null) return;
1761
+ switch (e.key) {
1762
+ case "ArrowUp":
1763
+ e.preventDefault();
1764
+ setFocusedSquare(moveFocus(current, "up", orientation));
1765
+ return;
1766
+ case "ArrowDown":
1767
+ e.preventDefault();
1768
+ setFocusedSquare(moveFocus(current, "down", orientation));
1769
+ return;
1770
+ case "ArrowLeft":
1771
+ e.preventDefault();
1772
+ setFocusedSquare(moveFocus(current, "left", orientation));
1773
+ return;
1774
+ case "ArrowRight":
1775
+ e.preventDefault();
1776
+ setFocusedSquare(moveFocus(current, "right", orientation));
1777
+ return;
1778
+ case "Home":
1779
+ e.preventDefault();
1780
+ setFocusedSquare(
1781
+ e.ctrlKey || e.metaKey ? boardCorner("home", orientation) : rankEnd(current, "home", orientation)
1782
+ );
1783
+ return;
1784
+ case "End":
1785
+ e.preventDefault();
1786
+ setFocusedSquare(
1787
+ e.ctrlKey || e.metaKey ? boardCorner("end", orientation) : rankEnd(current, "end", orientation)
1788
+ );
1789
+ return;
1790
+ case "Enter":
1791
+ case " ":
1792
+ e.preventDefault();
1793
+ onActivate(current);
1794
+ return;
1795
+ case "Escape":
1796
+ e.preventDefault();
1797
+ onEscape();
1798
+ return;
1799
+ default:
1800
+ return;
1801
+ }
1802
+ };
1803
+ container.addEventListener("keydown", onKeyDown);
1804
+ return () => container.removeEventListener("keydown", onKeyDown);
1805
+ }, [containerRef, orientation, focusedSquare, setFocusedSquare, onActivate, onEscape, enabled]);
1806
+ }
1807
+ var STYLE_ID2 = "gb-last-move-styles";
1808
+ function injectLastMoveStyles() {
1809
+ if (typeof document === "undefined") return;
1810
+ if (document.getElementById(STYLE_ID2) !== null) return;
1811
+ const style = document.createElement("style");
1812
+ style.id = STYLE_ID2;
1813
+ style.textContent = `
1814
+ [data-gb-square]::after {
1815
+ content: "";
1816
+ position: absolute;
1817
+ inset: 0;
1818
+ background: transparent;
1819
+ pointer-events: none;
1820
+ }
1821
+ [data-gb-square][data-gb-last-move]::after {
1822
+ background: var(--gb-last-move);
1823
+ }
1824
+ `;
1825
+ document.head.appendChild(style);
1826
+ }
1827
+ function useLastMoveController(model, squareRefs, enabled) {
1828
+ react.useEffect(() => {
1829
+ injectLastMoveStyles();
1830
+ }, []);
1831
+ react.useEffect(() => {
1832
+ if (model === null) return;
1833
+ let prevFrom = null;
1834
+ let prevTo = null;
1835
+ const write = (i, val) => {
1836
+ const el = squareRefs.current?.[i];
1837
+ if (el === null || el === void 0) return;
1838
+ if (val === null) {
1839
+ delete el.dataset["gbLastMove"];
1840
+ } else {
1841
+ el.dataset["gbLastMove"] = val;
1842
+ }
1843
+ };
1844
+ const clearAll = () => {
1845
+ if (prevFrom !== null) write(prevFrom, null);
1846
+ if (prevTo !== null) write(prevTo, null);
1847
+ prevFrom = null;
1848
+ prevTo = null;
1849
+ };
1850
+ if (!enabled) {
1851
+ return clearAll;
1852
+ }
1853
+ const update = () => {
1854
+ const snap = model.getSnapshot();
1855
+ const lastMove = snap.lastMove;
1856
+ let from = null;
1857
+ let to = null;
1858
+ if (lastMove !== null) {
1859
+ from = lastMove & 63;
1860
+ to = lastMove >> 6 & 63;
1861
+ }
1862
+ if (from === prevFrom && to === prevTo) return;
1863
+ if (prevFrom !== null && prevFrom !== from && prevFrom !== to) {
1864
+ write(prevFrom, null);
1865
+ }
1866
+ if (prevTo !== null && prevTo !== from && prevTo !== to) {
1867
+ write(prevTo, null);
1868
+ }
1869
+ if (from !== null && from !== to) write(from, "from");
1870
+ if (to !== null) write(to, "to");
1871
+ prevFrom = from;
1872
+ prevTo = to;
1873
+ };
1874
+ update();
1875
+ const unsubscribe = model.subscribe(update);
1876
+ return () => {
1877
+ unsubscribe();
1878
+ clearAll();
1879
+ };
1880
+ }, [model, squareRefs, enabled]);
1881
+ }
1882
+
1883
+ // src/lib/haptics.ts
1884
+ var COARSE_POINTER_QUERY = "(hover: none), (pointer: coarse)";
1885
+ var cached;
1886
+ var pending = null;
1887
+ function isCoarsePointer() {
1888
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
1889
+ return false;
1890
+ }
1891
+ return window.matchMedia(COARSE_POINTER_QUERY).matches;
1892
+ }
1893
+ function shouldRunHaptics(options) {
1894
+ if (options.enabled === false) return false;
1895
+ if (options.mobileOnly === false) return true;
1896
+ return isCoarsePointer();
1897
+ }
1898
+ async function getInstance() {
1899
+ if (cached !== void 0) {
1900
+ return cached;
1901
+ }
1902
+ if (pending !== null) {
1903
+ return pending;
1904
+ }
1905
+ if (typeof window === "undefined") {
1906
+ cached = null;
1907
+ return cached;
1908
+ }
1909
+ pending = import('web-haptics').then(({ WebHaptics }) => {
1910
+ try {
1911
+ cached = new WebHaptics();
1912
+ } catch {
1913
+ cached = null;
1914
+ }
1915
+ return cached;
1916
+ }).catch(() => {
1917
+ cached = null;
1918
+ return cached;
1919
+ }).finally(() => {
1920
+ pending = null;
1921
+ });
1922
+ return pending;
1923
+ }
1924
+ function triggerHaptic(input, options = {}) {
1925
+ if (!shouldRunHaptics(options)) {
1926
+ return;
1927
+ }
1928
+ const { enabled: _enabled, mobileOnly: _mobileOnly, ...triggerOptions } = options;
1929
+ void getInstance().then((instance) => {
1930
+ void instance?.trigger(input, triggerOptions);
1931
+ });
1932
+ }
1933
+ function warmupHaptics(options = {}) {
1934
+ if (!shouldRunHaptics(options)) {
1935
+ return;
1936
+ }
1937
+ void getInstance();
1938
+ }
1939
+
1940
+ // src/lib/move-feedback.ts
1941
+ var DEFAULT_MOVE_HAPTIC_PATTERNS = Object.freeze({
1942
+ moveSelf: "selection",
1943
+ moveOpponent: "selection",
1944
+ capture: "medium",
1945
+ castle: "medium",
1946
+ moveCheck: "success",
1947
+ promote: "success",
1948
+ gameEnd: "success"
1949
+ });
1950
+ function classifyMoveFeedback(animations, snapshot, perspective) {
1951
+ if (snapshot.isGameOver) return "gameEnd";
1952
+ let sawPromotion = false;
1953
+ let sawCastle = false;
1954
+ let sawCapture = false;
1955
+ for (const d of animations) {
1956
+ if (d.kind === "promotion") sawPromotion = true;
1957
+ else if (d.kind === "castle") sawCastle = true;
1958
+ else if (d.kind === "capture" || d.kind === "en-passant") sawCapture = true;
1959
+ }
1960
+ if (sawPromotion) return "promote";
1961
+ if (sawCastle) return "castle";
1962
+ if (snapshot.inCheck) return "moveCheck";
1963
+ if (sawCapture) return "capture";
1964
+ if (perspective !== void 0) {
1965
+ const moverWasMe = snapshot.turn !== perspective;
1966
+ return moverWasMe ? "moveSelf" : "moveOpponent";
1967
+ }
1968
+ return "moveSelf";
1969
+ }
1970
+
1971
+ // src/hooks/use-move-haptics.ts
1972
+ var DEFAULT_MIN_INTERVAL_MS = 14;
1973
+ function triggerMoveHaptic(key, {
1974
+ enabled = true,
1975
+ patterns,
1976
+ mobileOnly = true,
1977
+ intensity
1978
+ } = {}) {
1979
+ if (!enabled) {
1980
+ return;
1981
+ }
1982
+ const input = patterns?.[key] ?? DEFAULT_MOVE_HAPTIC_PATTERNS[key];
1983
+ triggerHaptic(input, {
1984
+ enabled,
1985
+ mobileOnly,
1986
+ ...intensity !== void 0 ? { intensity } : {}
1987
+ });
1988
+ }
1989
+ function useMoveHaptics(model, options = {}) {
1990
+ const {
1991
+ enabled = true,
1992
+ patterns,
1993
+ perspective,
1994
+ mobileOnly = true,
1995
+ minIntervalMs = DEFAULT_MIN_INTERVAL_MS,
1996
+ warmupOnFirstInteraction = true,
1997
+ intensity
1998
+ } = options;
1999
+ const patternsRef = react.useRef(patterns);
2000
+ react.useEffect(() => {
2001
+ patternsRef.current = patterns;
2002
+ });
2003
+ const perspectiveRef = react.useRef(perspective);
2004
+ react.useEffect(() => {
2005
+ perspectiveRef.current = perspective;
2006
+ });
2007
+ const mobileOnlyRef = react.useRef(mobileOnly);
2008
+ react.useEffect(() => {
2009
+ mobileOnlyRef.current = mobileOnly;
2010
+ });
2011
+ const intensityRef = react.useRef(intensity);
2012
+ react.useEffect(() => {
2013
+ intensityRef.current = intensity;
2014
+ });
2015
+ const lastTriggerAtRef = react.useRef(0);
2016
+ const trigger = react.useCallback(
2017
+ (key) => {
2018
+ triggerMoveHaptic(key, {
2019
+ enabled,
2020
+ ...patternsRef.current !== void 0 ? { patterns: patternsRef.current } : {},
2021
+ mobileOnly: mobileOnlyRef.current,
2022
+ ...intensityRef.current !== void 0 ? { intensity: intensityRef.current } : {}
2023
+ });
2024
+ },
2025
+ [enabled]
2026
+ );
2027
+ react.useEffect(() => {
2028
+ if (!enabled || !warmupOnFirstInteraction) {
2029
+ return;
2030
+ }
2031
+ let warmed = false;
2032
+ const warmup = () => {
2033
+ if (warmed) {
2034
+ return;
2035
+ }
2036
+ warmed = true;
2037
+ warmupHaptics({ mobileOnly: mobileOnlyRef.current });
2038
+ window.removeEventListener("pointerdown", warmup, true);
2039
+ window.removeEventListener("touchstart", warmup, true);
2040
+ window.removeEventListener("keydown", warmup, true);
2041
+ };
2042
+ window.addEventListener("pointerdown", warmup, { capture: true, passive: true });
2043
+ window.addEventListener("touchstart", warmup, { capture: true, passive: true });
2044
+ window.addEventListener("keydown", warmup, { capture: true });
2045
+ return () => {
2046
+ window.removeEventListener("pointerdown", warmup, true);
2047
+ window.removeEventListener("touchstart", warmup, true);
2048
+ window.removeEventListener("keydown", warmup, true);
2049
+ };
2050
+ }, [enabled, warmupOnFirstInteraction]);
2051
+ react.useEffect(() => {
2052
+ if (!enabled || model === null) return;
2053
+ let prevPly = model.getSnapshot().historyPly;
2054
+ return model.subscribe(() => {
2055
+ const snap = model.getSnapshot();
2056
+ if (snap.historyPly <= prevPly) {
2057
+ prevPly = snap.historyPly;
2058
+ return;
2059
+ }
2060
+ prevPly = snap.historyPly;
2061
+ const nowMs = performance.now();
2062
+ if (nowMs - lastTriggerAtRef.current < minIntervalMs) {
2063
+ return;
2064
+ }
2065
+ lastTriggerAtRef.current = nowMs;
2066
+ const key = classifyMoveFeedback(model.lastAnimations, snap, perspectiveRef.current);
2067
+ trigger(key);
2068
+ });
2069
+ }, [enabled, minIntervalMs, model, trigger]);
2070
+ return trigger;
2071
+ }
2072
+ var DEFAULT_MOVE_SOUND_SOURCES = Object.freeze({
2073
+ moveSelf: typeof (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) === "string" ? new URL("./sounds/move-self.mp3", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).href : "./sounds/move-self.mp3",
2074
+ moveOpponent: typeof (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) === "string" ? new URL("./sounds/move-opponent.mp3", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).href : "./sounds/move-opponent.mp3",
2075
+ capture: typeof (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) === "string" ? new URL("./sounds/capture.mp3", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).href : "./sounds/capture.mp3",
2076
+ castle: typeof (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) === "string" ? new URL("./sounds/castle.mp3", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).href : "./sounds/castle.mp3",
2077
+ moveCheck: typeof (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) === "string" ? new URL("./sounds/move-check.mp3", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).href : "./sounds/move-check.mp3",
2078
+ promote: typeof (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) === "string" ? new URL("./sounds/promote.mp3", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).href : "./sounds/promote.mp3",
2079
+ gameEnd: typeof (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href)) === "string" ? new URL("./sounds/game-end.mp3", (typeof document === 'undefined' ? require('u' + 'rl').pathToFileURL(__filename).href : (_documentCurrentScript && _documentCurrentScript.tagName.toUpperCase() === 'SCRIPT' && _documentCurrentScript.src || new URL('index.cjs', document.baseURI).href))).href : "./sounds/game-end.mp3"
2080
+ });
2081
+ var POOL_SIZE = 4;
2082
+ function createPools(sources, preload) {
2083
+ const pools = {};
2084
+ for (const key of Object.keys(sources)) {
2085
+ const url = sources[key];
2086
+ const elements = [];
2087
+ for (let i = 0; i < POOL_SIZE; i++) {
2088
+ const audio = new Audio(url);
2089
+ audio.preload = preload ?? "none";
2090
+ elements.push(audio);
2091
+ }
2092
+ pools[key] = { elements, cursor: 0 };
2093
+ }
2094
+ return pools;
2095
+ }
2096
+ function useMoveSound(model, options = {}) {
2097
+ const { enabled = true, volume = 1, preload = "none", sources, perspective } = options;
2098
+ const poolsRef = react.useRef(null);
2099
+ const sourcesRef = react.useRef(DEFAULT_MOVE_SOUND_SOURCES);
2100
+ const preloadRef = react.useRef(preload);
2101
+ const volumeRef = react.useRef(volume);
2102
+ react.useEffect(() => {
2103
+ volumeRef.current = volume;
2104
+ });
2105
+ const perspectiveRef = react.useRef(perspective);
2106
+ react.useEffect(() => {
2107
+ perspectiveRef.current = perspective;
2108
+ });
2109
+ react.useEffect(() => {
2110
+ sourcesRef.current = {
2111
+ ...DEFAULT_MOVE_SOUND_SOURCES,
2112
+ ...sources ?? {}
2113
+ };
2114
+ poolsRef.current = null;
2115
+ }, [sources]);
2116
+ react.useEffect(() => {
2117
+ preloadRef.current = preload;
2118
+ poolsRef.current = null;
2119
+ }, [preload]);
2120
+ react.useEffect(() => {
2121
+ if (!enabled) {
2122
+ poolsRef.current = null;
2123
+ }
2124
+ }, [enabled]);
2125
+ const triggerMoveSound = react.useCallback(
2126
+ (key) => {
2127
+ if (!enabled) return;
2128
+ if (typeof window === "undefined") return;
2129
+ if (typeof Audio === "undefined") return;
2130
+ const pools = poolsRef.current ?? createPools(sourcesRef.current, preloadRef.current);
2131
+ poolsRef.current = pools;
2132
+ const pool = pools[key];
2133
+ if (pool === void 0) return;
2134
+ pool.cursor = (pool.cursor + 1) % pool.elements.length;
2135
+ const el = pool.elements[pool.cursor];
2136
+ if (el === void 0) return;
2137
+ el.volume = Math.max(0, Math.min(1, volumeRef.current));
2138
+ el.currentTime = 0;
2139
+ void el.play().catch(() => {
2140
+ });
2141
+ },
2142
+ [enabled]
2143
+ );
2144
+ react.useEffect(() => {
2145
+ if (!enabled || model === null) return;
2146
+ let prevPly = model.getSnapshot().historyPly;
2147
+ return model.subscribe(() => {
2148
+ const snap = model.getSnapshot();
2149
+ if (snap.historyPly <= prevPly) {
2150
+ prevPly = snap.historyPly;
2151
+ return;
2152
+ }
2153
+ prevPly = snap.historyPly;
2154
+ const key = classifyMoveFeedback(model.lastAnimations, snap, perspectiveRef.current);
2155
+ triggerMoveSound(key);
2156
+ });
2157
+ }, [enabled, model, triggerMoveSound]);
2158
+ return triggerMoveSound;
2159
+ }
2160
+ var STYLE_ID3 = "gb-selection-styles";
2161
+ function injectSelectionStyles() {
2162
+ if (typeof document === "undefined") return;
2163
+ if (document.getElementById(STYLE_ID3) !== null) return;
2164
+ const style = document.createElement("style");
2165
+ style.id = STYLE_ID3;
2166
+ style.textContent = `
2167
+ [data-gb-square]::before {
2168
+ content: "";
2169
+ position: absolute;
2170
+ inset: 0;
2171
+ background: transparent;
2172
+ pointer-events: none;
2173
+ }
2174
+ [data-gb-square][data-gb-selection="selected"]::before {
2175
+ background: var(--gb-selected);
2176
+ }
2177
+ [data-gb-square][data-gb-selection="legal-quiet"]::before {
2178
+ background-image: radial-gradient(var(--gb-legal-target) 22%, transparent 24%);
2179
+ }
2180
+ [data-gb-square][data-gb-selection="legal-capture"]::before {
2181
+ background-image: radial-gradient(transparent 0 75%, var(--gb-legal-target-capture) 77% 83%, transparent 85%);
2182
+ }
2183
+ [data-gb-target-style="dots"] [data-gb-square][data-gb-selection="legal-quiet"]::before {
2184
+ background-image: radial-gradient(var(--gb-legal-target) 14%, transparent 16%);
2185
+ }
2186
+ [data-gb-target-style="dots"] [data-gb-square][data-gb-selection="legal-capture"]::before {
2187
+ background-image: radial-gradient(var(--gb-legal-target-capture) 20%, transparent 22%);
2188
+ }
2189
+ `;
2190
+ document.head.appendChild(style);
2191
+ }
2192
+ function useSelectionController(model, squareRefs, style) {
2193
+ react.useEffect(() => {
2194
+ injectSelectionStyles();
2195
+ }, []);
2196
+ react.useEffect(() => {
2197
+ if (model === null) return;
2198
+ let prevSelected = null;
2199
+ let prevTargets = /* @__PURE__ */ new Set();
2200
+ const write = (i, val) => {
2201
+ const el = squareRefs.current?.[i];
2202
+ if (el === null || el === void 0) return;
2203
+ if (val === "none") {
2204
+ delete el.dataset["gbSelection"];
2205
+ } else {
2206
+ el.dataset["gbSelection"] = val;
2207
+ }
2208
+ };
2209
+ const clear = () => {
2210
+ if (prevSelected !== null) write(prevSelected, "none");
2211
+ for (const sq of prevTargets) write(sq, "none");
2212
+ prevSelected = null;
2213
+ prevTargets = /* @__PURE__ */ new Set();
2214
+ };
2215
+ const update = () => {
2216
+ const snap = model.getSnapshot();
2217
+ const { selected, legalTargets, board } = snap;
2218
+ if (style === false) {
2219
+ for (const sq of prevTargets) {
2220
+ if (sq !== selected) write(sq, "none");
2221
+ }
2222
+ if (prevSelected !== null && prevSelected !== selected) {
2223
+ write(prevSelected, "none");
2224
+ }
2225
+ if (selected !== null) write(selected, "selected");
2226
+ prevSelected = selected;
2227
+ prevTargets = /* @__PURE__ */ new Set();
2228
+ return;
2229
+ }
2230
+ if (selected === prevSelected && legalTargets === prevTargets) return;
2231
+ if (prevSelected !== null && prevSelected !== selected && !legalTargets.has(prevSelected)) {
2232
+ write(prevSelected, "none");
2233
+ }
2234
+ for (const sq of prevTargets) {
2235
+ if (sq !== selected && !legalTargets.has(sq)) write(sq, "none");
2236
+ }
2237
+ if (selected !== null) write(selected, "selected");
2238
+ for (const sq of legalTargets) {
2239
+ if (sq === selected) continue;
2240
+ const targetCell = board[sq] ?? 0;
2241
+ write(sq, targetCell === 0 ? "legal-quiet" : "legal-capture");
2242
+ }
2243
+ prevSelected = selected;
2244
+ prevTargets = legalTargets;
2245
+ };
2246
+ update();
2247
+ const unsubscribe = model.subscribe(update);
2248
+ return () => {
2249
+ unsubscribe();
2250
+ clear();
2251
+ };
2252
+ }, [model, squareRefs, style]);
2253
+ }
2254
+ function colorOfCell(cell) {
2255
+ return cell > 6 ? 1 : 0;
2256
+ }
2257
+ function movePromotesPawn(fromCell, to) {
2258
+ if (fromCell === 0) return false;
2259
+ const pieceType = (fromCell - 1) % 6;
2260
+ if (pieceType !== 0) return false;
2261
+ const toRank = to >> 3;
2262
+ return toRank === 0 || toRank === 7;
2263
+ }
2264
+ var ILLEGAL_FLASH_HOLD_MS = 320;
2265
+ var DEFAULT_POSITION_SYNC_DURATION_MS = 60;
2266
+ var DEFAULT_POSITION_SYNC_EASING = "cubic-bezier(0.22, 0.61, 0.36, 1)";
2267
+ var UCI_TRANSITION_RE = /^([a-h][1-8])([a-h][1-8])([nbrq])?$/i;
2268
+ function prefersReducedMotion3() {
2269
+ if (typeof window === "undefined" || typeof window.matchMedia !== "function") {
2270
+ return false;
2271
+ }
2272
+ return window.matchMedia("(prefers-reduced-motion: reduce)").matches;
2273
+ }
2274
+ function cancelElementAnimations(element) {
2275
+ if (typeof element.getAnimations !== "function") return;
2276
+ for (const anim of element.getAnimations()) anim.cancel();
2277
+ }
2278
+ function queryPieceAt(container, square) {
2279
+ return container.querySelector(`[data-piece-square="${square}"]`);
2280
+ }
2281
+ function squareIndexOfName(square) {
2282
+ const file = square.charCodeAt(0) - 97;
2283
+ const rank = Number(square[1]) - 1;
2284
+ return rank << 3 | file;
2285
+ }
2286
+ function deltaFromSquareNames(from, to, squareSize, orientation) {
2287
+ const fromIndex = squareIndexOfName(from);
2288
+ const toIndex = squareIndexOfName(to);
2289
+ const fromFile = fromIndex & 7;
2290
+ const fromRank = fromIndex >> 3;
2291
+ const toFile = toIndex & 7;
2292
+ const toRank = toIndex >> 3;
2293
+ const fromCol = orientation === "white" ? fromFile : 7 - fromFile;
2294
+ const fromRow = orientation === "white" ? 7 - fromRank : fromRank;
2295
+ const toCol = orientation === "white" ? toFile : 7 - toFile;
2296
+ const toRow = orientation === "white" ? 7 - toRank : toRank;
2297
+ return {
2298
+ dx: (fromCol - toCol) * squareSize,
2299
+ dy: (fromRow - toRow) * squareSize
2300
+ };
2301
+ }
2302
+ function castleRookTransition(from, to) {
2303
+ const fromFile = from.charCodeAt(0);
2304
+ const toFile = to.charCodeAt(0);
2305
+ if (fromFile !== 101 || Math.abs(toFile - fromFile) !== 2) return null;
2306
+ const rank = from[1];
2307
+ if (rank !== "1" && rank !== "8") return null;
2308
+ const kingSide = toFile > fromFile;
2309
+ return {
2310
+ from: `${kingSide ? "h" : "a"}${rank}`,
2311
+ to: `${kingSide ? "f" : "d"}${rank}`
2312
+ };
2313
+ }
2314
+ function transitionSquares(transition) {
2315
+ const parsed = UCI_TRANSITION_RE.exec(transition.uci.trim().toLowerCase());
2316
+ if (parsed === null) return [];
2317
+ const moveFrom = parsed[1] ?? "";
2318
+ const moveTo = parsed[2] ?? "";
2319
+ const forward = [{ from: moveFrom, to: moveTo }];
2320
+ const rook = castleRookTransition(moveFrom, moveTo);
2321
+ if (rook !== null) forward.push(rook);
2322
+ return transition.direction === "forward" ? forward : forward.map((item) => ({ from: item.to, to: item.from }));
2323
+ }
2324
+ function parseTransitionMove(transition) {
2325
+ const parsed = UCI_TRANSITION_RE.exec(transition.uci.trim().toLowerCase());
2326
+ if (parsed === null) return null;
2327
+ const from = parsed[1];
2328
+ const to = parsed[2];
2329
+ if (from === void 0 || to === void 0) return null;
2330
+ return {
2331
+ from: squareIndexOfName(from),
2332
+ to: squareIndexOfName(to),
2333
+ promotion: parsed[3] ?? null
2334
+ };
2335
+ }
2336
+ function isPawn(cell) {
2337
+ return !chunkUUTEFNFO_cjs.isEmptyCell(cell) && chunkUUTEFNFO_cjs.pieceTypeOf(cell) === 0;
2338
+ }
2339
+ function controlledFeedbackDescriptors(transition, previousBoard, nextBoard) {
2340
+ if (transition === null || transition === void 0) return null;
2341
+ const parsed = parseTransitionMove(transition);
2342
+ if (parsed === null) return null;
2343
+ const before = transition.direction === "forward" ? previousBoard : nextBoard;
2344
+ const after = transition.direction === "forward" ? nextBoard : previousBoard;
2345
+ const movingPiece = before[parsed.from] ?? 0;
2346
+ if (chunkUUTEFNFO_cjs.isEmptyCell(movingPiece)) return null;
2347
+ const fromFile = parsed.from & 7;
2348
+ const toFile = parsed.to & 7;
2349
+ const targetBefore = before[parsed.to] ?? 0;
2350
+ const targetAfter = after[parsed.to] ?? 0;
2351
+ if (chunkUUTEFNFO_cjs.pieceTypeOf(movingPiece) === 5 && Math.abs(toFile - fromFile) === 2) {
2352
+ return [{ kind: "castle" }];
2353
+ }
2354
+ if (parsed.promotion !== null || isPawn(movingPiece) && (parsed.to >> 3 === 0 || parsed.to >> 3 === 7) && !chunkUUTEFNFO_cjs.isEmptyCell(targetAfter) && chunkUUTEFNFO_cjs.pieceTypeOf(targetAfter) !== 0) {
2355
+ return [{ kind: "promotion" }];
2356
+ }
2357
+ if (!chunkUUTEFNFO_cjs.isEmptyCell(targetBefore) && chunkUUTEFNFO_cjs.colorOf(targetBefore) !== chunkUUTEFNFO_cjs.colorOf(movingPiece)) {
2358
+ return [{ kind: "capture" }];
2359
+ }
2360
+ if (isPawn(movingPiece) && fromFile !== toFile && chunkUUTEFNFO_cjs.isEmptyCell(targetBefore)) {
2361
+ const capturedSquare = parsed.from & -8 | toFile;
2362
+ const capturedBefore = before[capturedSquare] ?? 0;
2363
+ const capturedAfter = after[capturedSquare] ?? 0;
2364
+ if (!chunkUUTEFNFO_cjs.isEmptyCell(capturedBefore) && chunkUUTEFNFO_cjs.colorOf(capturedBefore) !== chunkUUTEFNFO_cjs.colorOf(movingPiece) && chunkUUTEFNFO_cjs.isEmptyCell(capturedAfter)) {
2365
+ return [{ kind: "en-passant" }];
2366
+ }
2367
+ }
2368
+ return [{ kind: "move" }];
2369
+ }
2370
+ function schedulePositionAnimation(container, transitions, options, orientation) {
2371
+ const animate = () => {
2372
+ const rect = container.getBoundingClientRect();
2373
+ const squareSize = rect.width / 8;
2374
+ if (squareSize <= 0) return;
2375
+ for (const item of transitions) {
2376
+ const piece = queryPieceAt(container, item.to);
2377
+ if (piece === null || typeof piece.animate !== "function") continue;
2378
+ const { dx, dy } = deltaFromSquareNames(item.from, item.to, squareSize, orientation);
2379
+ if (Math.abs(dx) < 0.5 && Math.abs(dy) < 0.5) continue;
2380
+ cancelElementAnimations(piece);
2381
+ piece.animate(
2382
+ [{ transform: `translate(${dx}px, ${dy}px)` }, { transform: "translate(0, 0)" }],
2383
+ { duration: options.durationMs, easing: options.easing, fill: "none" }
2384
+ );
2385
+ }
2386
+ };
2387
+ if (typeof window.requestAnimationFrame === "function") {
2388
+ window.requestAnimationFrame(animate);
2389
+ } else {
2390
+ window.setTimeout(animate, 0);
2391
+ }
2392
+ }
2393
+ function syncPositionFen(game, targetFen, transition, managedArrows, preserveUserArrows, container, animation, orientation) {
2394
+ const positionChanged = game.engine.fen() !== targetFen;
2395
+ const durationMs = animation?.durationMs ?? DEFAULT_POSITION_SYNC_DURATION_MS;
2396
+ const easing = animation?.easing ?? DEFAULT_POSITION_SYNC_EASING;
2397
+ const shouldAnimate = positionChanged && transition !== null && transition !== void 0 && container !== null && durationMs > 0 && !prefersReducedMotion3();
2398
+ const transitions = [];
2399
+ if (shouldAnimate) {
2400
+ for (const item of transitionSquares(transition)) {
2401
+ if (queryPieceAt(container, item.from) !== null) transitions.push(item);
2402
+ }
2403
+ }
2404
+ try {
2405
+ if (managedArrows === void 0) {
2406
+ if (!positionChanged) return false;
2407
+ game.load(targetFen);
2408
+ } else {
2409
+ game.syncPosition({
2410
+ fen: targetFen,
2411
+ managedArrows,
2412
+ preserveUserArrows: preserveUserArrows === true
2413
+ });
2414
+ }
2415
+ } catch {
2416
+ return false;
2417
+ }
2418
+ if (transitions.length > 0 && container !== null) {
2419
+ schedulePositionAnimation(container, transitions, { durationMs, easing }, orientation);
2420
+ }
2421
+ return positionChanged;
2422
+ }
2423
+ function Chessboard(props) {
2424
+ const {
2425
+ game,
2426
+ fallbackFen,
2427
+ positionFen,
2428
+ positionTransition = null,
2429
+ managedArrows,
2430
+ preserveUserArrowsOnPositionSync,
2431
+ orientation = "white",
2432
+ theme = chunkI4BQGFP5_cjs.defaultTheme,
2433
+ pieces = chunkI4BQGFP5_cjs.defaultPieces,
2434
+ showCoordinates = true,
2435
+ ranksPosition = "left",
2436
+ showLegalTargets = "rings",
2437
+ highlightLastMove = true,
2438
+ onMove,
2439
+ onPromote,
2440
+ renderSquare,
2441
+ style,
2442
+ className,
2443
+ ariaLabel,
2444
+ animation,
2445
+ allowDrag = true,
2446
+ allowDrawingArrows = true,
2447
+ clearArrowsOnMove = true,
2448
+ clearArrowsOnClick = true,
2449
+ arrowColors,
2450
+ snapArrowsToValidMove = false,
2451
+ allowPremove = false,
2452
+ showCheckHighlight = true,
2453
+ showIllegalFlash = true,
2454
+ sound = true,
2455
+ haptics = false,
2456
+ viewOnly = false,
2457
+ canDragPiece,
2458
+ onSquareMouseEnter,
2459
+ onSquareMouseLeave,
2460
+ disableContextMenu = true,
2461
+ getSquareAriaLabel,
2462
+ onSquareFocus,
2463
+ getPromotionPieceAriaLabel
2464
+ } = props;
2465
+ const dragEnabled = allowDrag && !viewOnly;
2466
+ const arrowGestureEnabled = allowDrawingArrows && !viewOnly;
2467
+ const clickToMoveEnabled = !viewOnly;
2468
+ const keyboardNavEnabled = !viewOnly;
2469
+ const fallbackBoard = react.useMemo(() => {
2470
+ if (fallbackFen === void 0 || fallbackFen === "") return null;
2471
+ try {
2472
+ return chunkI4BQGFP5_cjs.parseFenPlacement(fallbackFen);
2473
+ } catch {
2474
+ return null;
2475
+ }
2476
+ }, [fallbackFen]);
2477
+ const soundOptions = react.useMemo(() => {
2478
+ if (sound === true) return { enabled: true };
2479
+ if (sound === false || sound === void 0) return { enabled: false };
2480
+ return sound;
2481
+ }, [sound]);
2482
+ const hapticOptions = react.useMemo(() => {
2483
+ if (haptics === true) return { enabled: true };
2484
+ if (haptics === false || haptics === void 0) return { enabled: false };
2485
+ return haptics;
2486
+ }, [haptics]);
2487
+ const containerRef = react.useRef(null);
2488
+ const dragLayerRef = react.useRef(null);
2489
+ const arrowsLayerRef = react.useRef(null);
2490
+ const lastControlledFeedbackFenRef = react.useRef(null);
2491
+ const squareRefs = react.useRef(new Array(64).fill(null));
2492
+ const setSquareRef = react.useCallback((index, el) => {
2493
+ squareRefs.current[index] = el;
2494
+ }, []);
2495
+ const skipNextAnimationRef = react.useRef(false);
2496
+ const onMoveRef = react.useRef(onMove);
2497
+ const onPromoteRef = react.useRef(onPromote);
2498
+ react.useEffect(() => {
2499
+ onMoveRef.current = onMove;
2500
+ onPromoteRef.current = onPromote;
2501
+ });
2502
+ const triggerMoveSound = useMoveSound(game, soundOptions);
2503
+ const triggerMoveHaptic2 = useMoveHaptics(game, hapticOptions);
2504
+ react.useLayoutEffect(() => {
2505
+ if (game === null || positionFen === void 0 || positionFen === "") return;
2506
+ const previousFeedbackFen = lastControlledFeedbackFenRef.current;
2507
+ const previousSnapshot = game.getSnapshot();
2508
+ if (game.engine.fen() === positionFen && managedArrows === void 0) {
2509
+ lastControlledFeedbackFenRef.current = positionFen;
2510
+ return;
2511
+ }
2512
+ const positionChanged = syncPositionFen(
2513
+ game,
2514
+ positionFen,
2515
+ positionTransition,
2516
+ managedArrows,
2517
+ preserveUserArrowsOnPositionSync,
2518
+ containerRef.current,
2519
+ animation,
2520
+ orientation
2521
+ );
2522
+ lastControlledFeedbackFenRef.current = positionFen;
2523
+ if (!positionChanged || previousFeedbackFen === null || previousFeedbackFen === positionFen) {
2524
+ return;
2525
+ }
2526
+ const snapshot = game.getSnapshot();
2527
+ const feedbackDescriptors = controlledFeedbackDescriptors(positionTransition, previousSnapshot.board, snapshot.board) ?? game.lastAnimations;
2528
+ const feedbackSnapshot = positionTransition?.direction === "backward" ? previousSnapshot : snapshot;
2529
+ triggerMoveSound(
2530
+ classifyMoveFeedback(feedbackDescriptors, feedbackSnapshot, soundOptions.perspective)
2531
+ );
2532
+ triggerMoveHaptic2(
2533
+ classifyMoveFeedback(feedbackDescriptors, feedbackSnapshot, hapticOptions.perspective)
2534
+ );
2535
+ }, [
2536
+ animation,
2537
+ game,
2538
+ hapticOptions.perspective,
2539
+ positionFen,
2540
+ positionTransition,
2541
+ managedArrows,
2542
+ preserveUserArrowsOnPositionSync,
2543
+ orientation,
2544
+ soundOptions.perspective,
2545
+ triggerMoveHaptic2,
2546
+ triggerMoveSound
2547
+ ]);
2548
+ const palette = react.useMemo(() => {
2549
+ const merged = {
2550
+ default: chunkI4BQGFP5_cjs.defaultArrowColors.default,
2551
+ shift: chunkI4BQGFP5_cjs.defaultArrowColors.shift,
2552
+ alt: chunkI4BQGFP5_cjs.defaultArrowColors.alt,
2553
+ ctrl: chunkI4BQGFP5_cjs.defaultArrowColors.ctrl
2554
+ };
2555
+ if (arrowColors !== void 0) {
2556
+ for (const k in arrowColors) {
2557
+ const v = arrowColors[k];
2558
+ if (v !== void 0) merged[k] = v;
2559
+ }
2560
+ }
2561
+ return merged;
2562
+ }, [arrowColors]);
2563
+ const [pendingPromotion, setPendingPromotion] = react.useState(null);
2564
+ const [focusedSquare, setFocusedSquare] = react.useState(null);
2565
+ const defaultKeyboardFocus = react.useCallback(
2566
+ () => orientation === "white" ? 12 : 52,
2567
+ [orientation]
2568
+ );
2569
+ const [flashSquare, setFlashSquare] = react.useState(null);
2570
+ const flashTimerRef = react.useRef(null);
2571
+ const triggerIllegalFlash = react.useCallback(
2572
+ (square) => {
2573
+ if (!showIllegalFlash) return;
2574
+ setFlashSquare(square);
2575
+ if (flashTimerRef.current !== null) clearTimeout(flashTimerRef.current);
2576
+ flashTimerRef.current = setTimeout(() => {
2577
+ setFlashSquare(null);
2578
+ flashTimerRef.current = null;
2579
+ }, ILLEGAL_FLASH_HOLD_MS);
2580
+ },
2581
+ [showIllegalFlash]
2582
+ );
2583
+ react.useEffect(
2584
+ () => () => {
2585
+ if (flashTimerRef.current !== null) clearTimeout(flashTimerRef.current);
2586
+ },
2587
+ []
2588
+ );
2589
+ react.useEffect(() => {
2590
+ if (game === null || !clearArrowsOnMove) return;
2591
+ let prevPly = game.getSnapshot().historyPly;
2592
+ return game.subscribe(() => {
2593
+ const snap = game.getSnapshot();
2594
+ if (snap.historyPly > prevPly && snap.arrows.length > 0) {
2595
+ game.clearUserArrows();
2596
+ }
2597
+ prevPly = snap.historyPly;
2598
+ });
2599
+ }, [game, clearArrowsOnMove]);
2600
+ const requestPromotion = react.useCallback(
2601
+ (from, to) => {
2602
+ if (game === null) return;
2603
+ const fromCell = game.getSnapshot().board[from] ?? 0;
2604
+ const color = colorOfCell(fromCell);
2605
+ const completeMove = (piece) => {
2606
+ const played = game.tryMove(from, to, piece);
2607
+ const cb = onMoveRef.current;
2608
+ if (played !== null && cb !== void 0) cb(played);
2609
+ };
2610
+ const promoteHandler = onPromoteRef.current;
2611
+ if (promoteHandler !== void 0) {
2612
+ Promise.resolve(promoteHandler({ from, to, color })).then(completeMove, () => {
2613
+ skipNextAnimationRef.current = false;
2614
+ });
2615
+ return;
2616
+ }
2617
+ setPendingPromotion({
2618
+ from,
2619
+ to,
2620
+ color,
2621
+ resolve: (piece) => {
2622
+ setPendingPromotion(null);
2623
+ completeMove(piece);
2624
+ },
2625
+ cancel: () => {
2626
+ skipNextAnimationRef.current = false;
2627
+ setPendingPromotion(null);
2628
+ }
2629
+ });
2630
+ },
2631
+ [game]
2632
+ );
2633
+ const clickToMove = useClickToMove(game, onMove, requestPromotion, clickToMoveEnabled);
2634
+ const maybeClearArrows = react.useCallback(() => {
2635
+ if (!clearArrowsOnClick || game === null) return;
2636
+ if (game.getSnapshot().arrows.length > 0) game.clearUserArrows();
2637
+ }, [clearArrowsOnClick, game]);
2638
+ const handleSquareClick = react.useCallback(
2639
+ (index) => {
2640
+ if (viewOnly) return;
2641
+ maybeClearArrows();
2642
+ clickToMove(index);
2643
+ },
2644
+ [viewOnly, maybeClearArrows, clickToMove]
2645
+ );
2646
+ react.useEffect(() => {
2647
+ if (!clearArrowsOnClick || game === null || viewOnly) return;
2648
+ const onKey = (e) => {
2649
+ if (e.key === "Escape") maybeClearArrows();
2650
+ };
2651
+ window.addEventListener("keydown", onKey);
2652
+ return () => window.removeEventListener("keydown", onKey);
2653
+ }, [clearArrowsOnClick, game, maybeClearArrows, viewOnly]);
2654
+ react.useEffect(() => {
2655
+ if (viewOnly || clickToMoveEnabled === false || game === null) return;
2656
+ const container = containerRef.current;
2657
+ if (container === null) return;
2658
+ const onContainerClick = (e) => {
2659
+ if (e.button !== 0) return;
2660
+ const target = e.target;
2661
+ if (target === null) return;
2662
+ if (target.closest("[data-piece-square]") === null) return;
2663
+ const sq = getSquareAtPoint(container, e.clientX, e.clientY, orientation);
2664
+ if (sq === null) return;
2665
+ handleSquareClick(sq);
2666
+ };
2667
+ container.addEventListener("click", onContainerClick);
2668
+ return () => container.removeEventListener("click", onContainerClick);
2669
+ }, [viewOnly, clickToMoveEnabled, game, orientation, handleSquareClick]);
2670
+ react.useEffect(() => {
2671
+ if (!disableContextMenu) return;
2672
+ const container = containerRef.current;
2673
+ if (container === null) return;
2674
+ const onContextMenu = (e) => {
2675
+ e.preventDefault();
2676
+ };
2677
+ container.addEventListener("contextmenu", onContextMenu);
2678
+ return () => container.removeEventListener("contextmenu", onContextMenu);
2679
+ }, [disableContextMenu]);
2680
+ const onDragStart = react.useCallback(
2681
+ (from, _cell) => {
2682
+ maybeClearArrows();
2683
+ const container = containerRef.current;
2684
+ if (container !== null) container.dataset["gbDragging"] = "true";
2685
+ game?.selectSquare(from);
2686
+ },
2687
+ [game, maybeClearArrows]
2688
+ );
2689
+ const onDragEnd = react.useCallback(() => {
2690
+ const container = containerRef.current;
2691
+ if (container !== null) delete container.dataset["gbDragging"];
2692
+ }, []);
2693
+ const onDrop = react.useCallback(
2694
+ (from, to) => {
2695
+ if (game === null) return;
2696
+ const snap = game.getSnapshot();
2697
+ const fromCell = snap.board[from] ?? 0;
2698
+ const pieceColour = colorOfCell(fromCell);
2699
+ if (fromCell !== 0 && pieceColour !== snap.turn) {
2700
+ if (!allowPremove) {
2701
+ triggerIllegalFlash(from);
2702
+ return;
2703
+ }
2704
+ const promotion = movePromotesPawn(fromCell, to) ? 4 : void 0;
2705
+ game.queuePremove(promotion === void 0 ? { from, to } : { from, to, promotion });
2706
+ return;
2707
+ }
2708
+ skipNextAnimationRef.current = true;
2709
+ if (movePromotesPawn(fromCell, to) && game.isLegal(
2710
+ from,
2711
+ to,
2712
+ 4
2713
+ /* Queen */
2714
+ )) {
2715
+ requestPromotion(from, to);
2716
+ return;
2717
+ }
2718
+ const played = game.tryMove(from, to);
2719
+ const cb = onMoveRef.current;
2720
+ if (played !== null) {
2721
+ if (cb !== void 0) cb(played);
2722
+ } else {
2723
+ skipNextAnimationRef.current = false;
2724
+ triggerIllegalFlash(from);
2725
+ game.selectSquare(from);
2726
+ }
2727
+ },
2728
+ [game, requestPromotion, allowPremove, triggerIllegalFlash]
2729
+ );
2730
+ useDrag({
2731
+ game,
2732
+ orientation,
2733
+ containerRef,
2734
+ dragLayerRef,
2735
+ enabled: dragEnabled && game !== null,
2736
+ onDragStart,
2737
+ onDragEnd,
2738
+ onDrop,
2739
+ ...canDragPiece !== void 0 ? { canDragPiece } : {}
2740
+ });
2741
+ react.useEffect(() => {
2742
+ if (!keyboardNavEnabled) {
2743
+ setFocusedSquare(null);
2744
+ return;
2745
+ }
2746
+ setFocusedSquare((current) => current ?? defaultKeyboardFocus());
2747
+ }, [defaultKeyboardFocus, keyboardNavEnabled]);
2748
+ const onKeyboardActivate = react.useCallback(
2749
+ (index) => {
2750
+ maybeClearArrows();
2751
+ clickToMove(index);
2752
+ },
2753
+ [maybeClearArrows, clickToMove]
2754
+ );
2755
+ const onKeyboardEscape = react.useCallback(() => {
2756
+ maybeClearArrows();
2757
+ game?.selectSquare(null);
2758
+ }, [maybeClearArrows, game]);
2759
+ useKeyboardNav({
2760
+ containerRef,
2761
+ orientation,
2762
+ focusedSquare,
2763
+ setFocusedSquare,
2764
+ onActivate: onKeyboardActivate,
2765
+ onEscape: onKeyboardEscape,
2766
+ enabled: game !== null && keyboardNavEnabled
2767
+ });
2768
+ useHoverSquare({
2769
+ game,
2770
+ orientation,
2771
+ containerRef,
2772
+ enabled: onSquareMouseEnter !== void 0 || onSquareMouseLeave !== void 0,
2773
+ ...onSquareMouseEnter !== void 0 ? { onEnter: onSquareMouseEnter } : {},
2774
+ ...onSquareMouseLeave !== void 0 ? { onLeave: onSquareMouseLeave } : {}
2775
+ });
2776
+ useArrowGesture({
2777
+ game,
2778
+ orientation,
2779
+ containerRef,
2780
+ arrowsLayerRef,
2781
+ enabled: arrowGestureEnabled && game !== null,
2782
+ palette,
2783
+ snapToValidMove: snapArrowsToValidMove
2784
+ });
2785
+ useSelectionController(game, squareRefs, showLegalTargets);
2786
+ useLastMoveController(game, squareRefs, highlightLastMove);
2787
+ useCursorController(game, containerRef, dragEnabled, allowPremove);
2788
+ const animationRuntime = react.useMemo(() => ({ skipNextRef: skipNextAnimationRef }), []);
2789
+ const containerStyle = {
2790
+ position: "relative",
2791
+ aspectRatio: "1 / 1",
2792
+ width: "100%",
2793
+ touchAction: "none",
2794
+ // let the drag layer own pan gestures
2795
+ ...theme,
2796
+ ...style
2797
+ };
2798
+ return /* @__PURE__ */ jsxRuntime.jsxs(
2799
+ "div",
2800
+ {
2801
+ ref: containerRef,
2802
+ className,
2803
+ style: containerStyle,
2804
+ "data-gb-orientation": orientation,
2805
+ "data-gb-target-style": showLegalTargets === false ? "off" : showLegalTargets,
2806
+ ...viewOnly ? { "data-gb-view-only": "true" } : {},
2807
+ children: [
2808
+ /* @__PURE__ */ jsxRuntime.jsx(
2809
+ BoardGrid,
2810
+ {
2811
+ orientation,
2812
+ onSquareClick: handleSquareClick,
2813
+ focusedSquare,
2814
+ setSquareRef,
2815
+ readOnly: viewOnly,
2816
+ model: game,
2817
+ getSquareAriaLabel,
2818
+ onSquareFocus,
2819
+ ...renderSquare !== void 0 ? { renderSquare } : {},
2820
+ ...ariaLabel !== void 0 ? { ariaLabel } : {}
2821
+ }
2822
+ ),
2823
+ game !== null && showCheckHighlight ? /* @__PURE__ */ jsxRuntime.jsx(CheckLayer, { model: game, orientation }) : null,
2824
+ game !== null ? /* @__PURE__ */ jsxRuntime.jsx(PremoveLayer, { model: game, orientation, pieces }) : null,
2825
+ game !== null ? /* @__PURE__ */ jsxRuntime.jsx(ArrowsLayer, { model: game, orientation, palette, below: true }) : null,
2826
+ game !== null ? /* @__PURE__ */ jsxRuntime.jsx(PieceLayer, { model: game, orientation, pieces }) : fallbackBoard !== null ? /* @__PURE__ */ jsxRuntime.jsx(StaticPieceLayer, { board: fallbackBoard, orientation, pieces }) : null,
2827
+ game !== null ? /* @__PURE__ */ jsxRuntime.jsx(
2828
+ AnimationRunner,
2829
+ {
2830
+ model: game,
2831
+ containerRef,
2832
+ orientation,
2833
+ ...animation !== void 0 ? { options: animation } : {},
2834
+ runtime: animationRuntime
2835
+ }
2836
+ ) : null,
2837
+ /* @__PURE__ */ jsxRuntime.jsx(DragLayer, { ref: dragLayerRef, pieces }),
2838
+ game !== null ? /* @__PURE__ */ jsxRuntime.jsx(
2839
+ ArrowsLayer,
2840
+ {
2841
+ ref: arrowsLayerRef,
2842
+ model: game,
2843
+ orientation,
2844
+ palette
2845
+ }
2846
+ ) : null,
2847
+ showCoordinates ? /* @__PURE__ */ jsxRuntime.jsx(Coordinates, { orientation, ranksPosition }) : null,
2848
+ /* @__PURE__ */ jsxRuntime.jsx(IllegalFlashLayer, { at: flashSquare, orientation }),
2849
+ game !== null ? /* @__PURE__ */ jsxRuntime.jsx(LiveRegion, { model: game }) : null,
2850
+ pendingPromotion !== null ? /* @__PURE__ */ jsxRuntime.jsx(
2851
+ PromotionOverlay,
2852
+ {
2853
+ from: pendingPromotion.from,
2854
+ to: pendingPromotion.to,
2855
+ color: pendingPromotion.color,
2856
+ orientation,
2857
+ pieces,
2858
+ onSelect: pendingPromotion.resolve,
2859
+ onCancel: pendingPromotion.cancel,
2860
+ getPromotionPieceAriaLabel
2861
+ }
2862
+ ) : null
2863
+ ]
2864
+ }
2865
+ );
2866
+ }
2867
+ function useChessGame(options = {}) {
2868
+ const { fen, ...modelOptions } = options;
2869
+ const [initialModelOptions] = react.useState(() => modelOptions);
2870
+ const [model, setModel] = react.useState(() => {
2871
+ const adapter = chunkUUTEFNFO_cjs.createGigachessAdapter(fen);
2872
+ return chunkUUTEFNFO_cjs.createBoardModel(adapter, modelOptions);
2873
+ });
2874
+ const prevFenRef = react.useRef(fen);
2875
+ react.useEffect(() => {
2876
+ if (prevFenRef.current !== fen) {
2877
+ prevFenRef.current = fen;
2878
+ const adapter = chunkUUTEFNFO_cjs.createGigachessAdapter(fen);
2879
+ const nextModel = chunkUUTEFNFO_cjs.createBoardModel(adapter, initialModelOptions);
2880
+ setModel((prev) => {
2881
+ prev.dispose();
2882
+ return nextModel;
2883
+ });
2884
+ }
2885
+ }, [fen, initialModelOptions]);
2886
+ react.useEffect(() => {
2887
+ return () => {
2888
+ model.dispose();
2889
+ };
2890
+ }, [model]);
2891
+ return model;
2892
+ }
2893
+
2894
+ // src/index.ts
2895
+ var PACKAGE_VERSION = "0.1.0-alpha";
2896
+
2897
+ Object.defineProperty(exports, "Color", {
2898
+ enumerable: true,
2899
+ get: function () { return chunkUUTEFNFO_cjs.Color; }
2900
+ });
2901
+ Object.defineProperty(exports, "PieceType", {
2902
+ enumerable: true,
2903
+ get: function () { return chunkUUTEFNFO_cjs.PieceType; }
2904
+ });
2905
+ Object.defineProperty(exports, "createBoardModel", {
2906
+ enumerable: true,
2907
+ get: function () { return chunkUUTEFNFO_cjs.createBoardModel; }
2908
+ });
2909
+ Object.defineProperty(exports, "createGigachessAdapter", {
2910
+ enumerable: true,
2911
+ get: function () { return chunkUUTEFNFO_cjs.createGigachessAdapter; }
2912
+ });
2913
+ Object.defineProperty(exports, "packMove", {
2914
+ enumerable: true,
2915
+ get: function () { return chunkUUTEFNFO_cjs.packMove; }
2916
+ });
2917
+ Object.defineProperty(exports, "preloadGigachessAdapter", {
2918
+ enumerable: true,
2919
+ get: function () { return chunkUUTEFNFO_cjs.preloadGigachessAdapter; }
2920
+ });
2921
+ Object.defineProperty(exports, "unpackMove", {
2922
+ enumerable: true,
2923
+ get: function () { return chunkUUTEFNFO_cjs.unpackMove; }
2924
+ });
2925
+ Object.defineProperty(exports, "CSS_VARS", {
2926
+ enumerable: true,
2927
+ get: function () { return chunkI4BQGFP5_cjs.CSS_VARS; }
2928
+ });
2929
+ Object.defineProperty(exports, "DEFAULT_ILLEGAL_FLASH_TINT", {
2930
+ enumerable: true,
2931
+ get: function () { return chunkI4BQGFP5_cjs.DEFAULT_ILLEGAL_FLASH_TINT; }
2932
+ });
2933
+ Object.defineProperty(exports, "DEFAULT_PREMOVE_TINT", {
2934
+ enumerable: true,
2935
+ get: function () { return chunkI4BQGFP5_cjs.DEFAULT_PREMOVE_TINT; }
2936
+ });
2937
+ Object.defineProperty(exports, "defaultArrowColors", {
2938
+ enumerable: true,
2939
+ get: function () { return chunkI4BQGFP5_cjs.defaultArrowColors; }
2940
+ });
2941
+ Object.defineProperty(exports, "defaultPieces", {
2942
+ enumerable: true,
2943
+ get: function () { return chunkI4BQGFP5_cjs.defaultPieces; }
2944
+ });
2945
+ Object.defineProperty(exports, "defaultTheme", {
2946
+ enumerable: true,
2947
+ get: function () { return chunkI4BQGFP5_cjs.defaultTheme; }
2948
+ });
2949
+ exports.AnimationRunner = AnimationRunner;
2950
+ exports.Chessboard = Chessboard;
2951
+ exports.DEFAULT_MOVE_HAPTIC_PATTERNS = DEFAULT_MOVE_HAPTIC_PATTERNS;
2952
+ exports.DEFAULT_MOVE_SOUND_SOURCES = DEFAULT_MOVE_SOUND_SOURCES;
2953
+ exports.PACKAGE_VERSION = PACKAGE_VERSION;
2954
+ exports.classifyMoveFeedback = classifyMoveFeedback;
2955
+ exports.getPointAtSquareCentre = getPointAtSquareCentre;
2956
+ exports.getSquareAtPoint = getSquareAtPoint;
2957
+ exports.triggerHaptic = triggerHaptic;
2958
+ exports.triggerMoveHaptic = triggerMoveHaptic;
2959
+ exports.useArrowGesture = useArrowGesture;
2960
+ exports.useBoardSlice = useBoardSlice;
2961
+ exports.useBoardSnapshot = useBoardSnapshot;
2962
+ exports.useChessGame = useChessGame;
2963
+ exports.useClickToMove = useClickToMove;
2964
+ exports.useDrag = useDrag;
2965
+ exports.useHoverSquare = useHoverSquare;
2966
+ exports.useKeyboardNav = useKeyboardNav;
2967
+ exports.useMoveHaptics = useMoveHaptics;
2968
+ exports.useMoveSound = useMoveSound;
2969
+ exports.useSquareCell = useSquareCell;
2970
+ exports.warmupHaptics = warmupHaptics;
2971
+ //# sourceMappingURL=index.cjs.map
2972
+ //# sourceMappingURL=index.cjs.map