mkdocs-chess-plugin 0.1.0__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,565 @@
1
+ const SVG = 'http://www.w3.org/2000/svg';
2
+ const PROMOTIONS = {q: '后', r: '车', b: '象', n: '马'};
3
+ const CHESS_NAMES = {k: '王', q: '后', r: '车', b: '象', n: '马', p: '兵'};
4
+ const mounted = new WeakSet();
5
+ const arrowDirections = {ArrowUp: [0, -1], ArrowDown: [0, 1], ArrowLeft: [-1, 0], ArrowRight: [1, 0]};
6
+ const pointerMoved = (event, gesture) => Math.hypot(event.clientX - gesture.startX, event.clientY - gesture.startY) >= 5;
7
+ const pieceSide = piece => piece === piece.toUpperCase() ? 'w' : 'b';
8
+
9
+ async function xiangEngine(position) {
10
+ const rules = await import('./xiangqi-rules.mjs');
11
+ const states = [rules.parseFen(position)];
12
+ const current = () => states.at(-1);
13
+ return {
14
+ turn: () => current().turn,
15
+ position: () => rules.fen(current()),
16
+ board: () => current().board,
17
+ name: piece => rules.names[piece],
18
+ square: rules.coord,
19
+ candidates: from => rules.moves(current(), rules.index(from)).map(move => ({from, to: move.slice(2)})),
20
+ play: ({from, to}) => {
21
+ const move = from + to;
22
+ const label = rules.notation(current(), move);
23
+ const capture = Boolean(current().board[rules.index(to)]);
24
+ states.push(rules.play(current(), move));
25
+ return {label, capture};
26
+ },
27
+ undo: () => states.pop(),
28
+ status: () => {
29
+ if (rules.moves(current()).length === 0) return rules.inCheck(current()) ? '将死' : '困毙';
30
+ return rules.inCheck(current()) ? '将军' : '';
31
+ },
32
+ };
33
+ }
34
+
35
+ async function chessEngine(position) {
36
+ const {Chess} = await import('./vendor/chess-1.4.0.mjs');
37
+ const game = new Chess(position);
38
+ const turn = game.turn();
39
+ const opposingKing = game.findPiece({type: 'k', color: turn === 'w' ? 'b' : 'w'})[0];
40
+ if (game.isAttacked(opposingKing, turn)) throw new Error('非行棋方处于被将军状态');
41
+ const [, , rights, ep] = position.split(' ');
42
+ for (const [flag, color, rank, rook] of [['K', 'w', 1, 'h'], ['Q', 'w', 1, 'a'], ['k', 'b', 8, 'h'], ['q', 'b', 8, 'a']]) {
43
+ if (!rights.includes(flag)) continue;
44
+ const king = game.get('e' + rank);
45
+ const piece = game.get(rook + rank);
46
+ if (king?.type !== 'k' || king.color !== color || piece?.type !== 'r' || piece.color !== color) {
47
+ throw new Error('易位权与王、车位置不符');
48
+ }
49
+ }
50
+ if (ep !== '-') {
51
+ const pawn = game.get(ep[0] + (turn === 'w' ? '5' : '4'));
52
+ const origin = ep[0] + (turn === 'w' ? '7' : '2');
53
+ if (game.get(ep) || game.get(origin) || pawn?.type !== 'p' || pawn.color === turn) {
54
+ throw new Error('过路兵目标格与兵的位置不符');
55
+ }
56
+ }
57
+ return {
58
+ turn: () => game.turn(),
59
+ position: () => game.fen({forceEnpassantSquare: true}),
60
+ board: () => game.board().flat().map(piece => piece?.color === 'w' ? piece.type.toUpperCase() : piece?.type),
61
+ name: piece => CHESS_NAMES[piece.toLowerCase()],
62
+ square: i => String.fromCharCode(97 + i % 8) + (8 - Math.floor(i / 8)),
63
+ candidates: square => game.moves({square, verbose: true}),
64
+ play: ({from, to, promotion}) => {
65
+ const {san, captured} = game.move({from, to, promotion});
66
+ return {label: san, capture: Boolean(captured)};
67
+ },
68
+ undo: () => game.undo(),
69
+ status: () => {
70
+ if (game.isCheckmate()) return '将死';
71
+ if (game.isStalemate()) return '逼和';
72
+ return game.isCheck() ? '将军' : '';
73
+ },
74
+ };
75
+ }
76
+
77
+ const engines = {xiang: xiangEngine, chess: chessEngine};
78
+
79
+ function makeButton(text) {
80
+ const button = document.createElement('button');
81
+ button.type = 'button';
82
+ button.textContent = text;
83
+ return button;
84
+ }
85
+
86
+ function makeSvg(tag, attributes) {
87
+ const node = document.createElementNS(SVG, tag);
88
+ for (const [name, value] of Object.entries(attributes)) node.setAttribute(name, value);
89
+ return node;
90
+ }
91
+
92
+ function addFenExport(controls, figure) {
93
+ const button = makeButton('导出 FEN');
94
+ button.dataset.action = 'export-fen';
95
+ const feedback = document.createElement('span');
96
+ feedback.className = 'fen-board-status';
97
+ feedback.setAttribute('role', 'status');
98
+ const field = document.createElement('input');
99
+ field.type = 'text';
100
+ field.className = 'fen-board-fen-export';
101
+ field.readOnly = true;
102
+ field.hidden = true;
103
+ field.setAttribute('aria-label', '导出的 FEN');
104
+ controls.append(button, feedback, field);
105
+ button.addEventListener('click', async () => {
106
+ const position = figure.dataset.fen;
107
+ try {
108
+ await navigator.clipboard.writeText(position);
109
+ field.hidden = true;
110
+ feedback.textContent = '已复制 FEN';
111
+ } catch {
112
+ field.value = position;
113
+ field.hidden = false;
114
+ field.focus();
115
+ field.select();
116
+ feedback.textContent = '请手动复制 FEN';
117
+ }
118
+ });
119
+ }
120
+
121
+ function showUnavailable(figure, error) {
122
+ const message = document.createElement('p');
123
+ message.className = 'fen-board-status';
124
+ message.textContent = '此局面仅供展示,无法开始推演。' + error.message;
125
+ figure.append(message);
126
+ }
127
+
128
+ async function mount(figure) {
129
+ const {game, fen, prefix, grid, label, light} = figure.dataset;
130
+ const controls = document.createElement('div');
131
+ controls.className = 'fen-board-controls';
132
+ let engine;
133
+ try {
134
+ engine = await engines[game](fen);
135
+ } catch (error) {
136
+ if (figure.isConnected) {
137
+ showUnavailable(figure, error);
138
+ addFenExport(controls, figure);
139
+ figure.append(controls);
140
+ }
141
+ return;
142
+ }
143
+ if (!figure.isConnected) return;
144
+
145
+ const block = figure.parentElement;
146
+ const heading = block.firstElementChild.querySelector('.fen-board-title');
147
+ const surface = figure.querySelector('.fen-board-surface');
148
+ const svg = surface.querySelector('svg');
149
+ const svgPoint = event => new DOMPoint(event.clientX, event.clientY).matrixTransform(svg.getScreenCTM().inverse());
150
+ const pieces = svg.querySelector('.fen-board-pieces');
151
+ const desc = svg.querySelector('desc');
152
+ const [columns, rows, x, y, dx, dy, width, height] = grid.split(' ').map(Number);
153
+ const center = index => ({x: x + index % columns * dx, y: y + Math.floor(index / columns) * dy});
154
+ const cell = Math.min(dx, dy);
155
+ const history = [];
156
+ const future = [];
157
+ const arrows = new Map();
158
+ const sounds = new Map();
159
+ const arrowLayer = makeSvg('g', {class: 'fen-board-arrows', 'aria-hidden': 'true'});
160
+ const draftLayer = makeSvg('g', {class: 'fen-board-arrow-draft', 'aria-hidden': 'true'});
161
+ svg.append(arrowLayer, draftLayer);
162
+ let selection = -1;
163
+ let targets = [];
164
+ let tabStop = 0;
165
+ let drag = null;
166
+ let drawing = null;
167
+ let suppressClick = false;
168
+
169
+ const undo = makeButton('悔棋');
170
+ undo.dataset.action = 'undo';
171
+ const redo = makeButton('撤销悔棋');
172
+ redo.dataset.action = 'redo';
173
+ const clearArrows = makeButton('清除箭头');
174
+ clearArrows.dataset.action = 'clear-arrows';
175
+ const status = document.createElement('span');
176
+ status.className = 'fen-board-status';
177
+ status.setAttribute('role', 'status');
178
+ controls.append(undo, redo, clearArrows);
179
+ addFenExport(controls, figure);
180
+ controls.append(status);
181
+ const promotion = document.createElement('div');
182
+ promotion.className = 'fen-board-controls fen-board-promotion';
183
+ promotion.hidden = true;
184
+ figure.append(controls, promotion);
185
+
186
+ const buttons = Array.from({length: columns * rows}, (_, i) => {
187
+ const button = makeButton('');
188
+ button.className = 'fen-board-square';
189
+ button.dataset.square = engine.square(i);
190
+ const point = center(i);
191
+ button.style.left = point.x / width * 100 + '%';
192
+ button.style.top = point.y / height * 100 + '%';
193
+ button.style.width = dx / width * 100 + '%';
194
+ button.style.height = dy / height * 100 + '%';
195
+ button.tabIndex = i === tabStop ? 0 : -1;
196
+ surface.append(button);
197
+ button.addEventListener('focus', () => {
198
+ buttons[tabStop].tabIndex = -1;
199
+ tabStop = i;
200
+ button.tabIndex = 0;
201
+ });
202
+ button.addEventListener('keydown', event => {
203
+ const direction = arrowDirections[event.key];
204
+ if (!direction) return;
205
+ event.preventDefault();
206
+ const col = i % columns + direction[0];
207
+ const row = Math.floor(i / columns) + direction[1];
208
+ if (col >= 0 && col < columns && row >= 0 && row < rows) buttons[row * columns + col].focus();
209
+ });
210
+ button.addEventListener('click', event => {
211
+ if (drawing || drag?.ghost || (suppressClick && event.detail)) return;
212
+ select(i);
213
+ });
214
+ return button;
215
+ });
216
+
217
+ function sideLabel(turn) {
218
+ return turn === 'w' ? light : '黑';
219
+ }
220
+
221
+ function resetAfterMove() {
222
+ endDrag();
223
+ endDrawing();
224
+ arrows.clear();
225
+ selection = -1;
226
+ targets = [];
227
+ promotion.hidden = true;
228
+ promotion.replaceChildren();
229
+ }
230
+
231
+ function drawSelection() {
232
+ const destinations = new Set(targets.map(move => move.to));
233
+ buttons.forEach((button, i) => {
234
+ button.setAttribute('aria-pressed', String(i === selection));
235
+ button.classList.toggle('fen-board-target', destinations.has(button.dataset.square));
236
+ });
237
+ }
238
+
239
+ function pick(index) {
240
+ selection = index;
241
+ targets = index === -1 ? [] : engine.candidates(buttons[index].dataset.square);
242
+ drawSelection();
243
+ }
244
+
245
+ function makeArrow({from, to, side, occupied}, number) {
246
+ const a = center(from);
247
+ const b = center(to);
248
+ const length = Math.hypot(b.x - a.x, b.y - a.y);
249
+ const ux = (b.x - a.x) / length;
250
+ const uy = (b.y - a.y) / length;
251
+ // The reference board has a 67.5 px intersection spacing.
252
+ const scale = cell / 67.5;
253
+ const radius = 31.5 * scale;
254
+ const headLength = (occupied ? 24 : 32) * scale;
255
+ const head = {x: b.x - ux * headLength, y: b.y - uy * headLength};
256
+ const halfHead = (occupied ? 18 : 24) * scale;
257
+ const start = occupied ? radius : 0;
258
+ const badgeOffset = 22.5 * scale;
259
+ const badge = {x: b.x - ux * badgeOffset, y: b.y - uy * badgeOffset};
260
+ const group = makeSvg('g', {class: 'fen-board-arrow', 'data-side': side});
261
+ const ink = makeSvg('g', {class: 'fen-board-arrow-ink'});
262
+ ink.append(
263
+ makeSvg('line', {
264
+ class: 'fen-board-arrow-shaft', x1: a.x + ux * start, y1: a.y + uy * start,
265
+ x2: head.x, y2: head.y, 'stroke-width': 12 * scale,
266
+ }),
267
+ makeSvg('polygon', {
268
+ class: 'fen-board-arrow-head',
269
+ points: `${b.x},${b.y} ${head.x - uy * halfHead},${head.y + ux * halfHead} ${head.x + uy * halfHead},${head.y - ux * halfHead}`,
270
+ }),
271
+ occupied
272
+ ? makeSvg('circle', {class: 'fen-board-arrow-origin', cx: a.x, cy: a.y, r: radius, 'stroke-width': 4 * scale})
273
+ : makeSvg('circle', {cx: a.x, cy: a.y, r: 11 * scale}),
274
+ );
275
+ const numberBadge = makeSvg('circle', {class: 'fen-board-arrow-badge', cx: badge.x, cy: badge.y, r: 12 * scale});
276
+ const text = makeSvg('text', {x: badge.x, y: badge.y, 'font-size': (number < 10 ? 20 : 15.5) * scale});
277
+ text.textContent = number;
278
+ group.append(ink, numberBadge, text);
279
+ return group;
280
+ }
281
+
282
+ function renderArrows() {
283
+ const marks = [...arrows].map(([key, arrow], index) => {
284
+ const mark = makeArrow(arrow, index + 1);
285
+ mark.dataset.arrowKey = key;
286
+ return mark;
287
+ });
288
+ arrowLayer.replaceChildren(...marks);
289
+ clearArrows.disabled = arrows.size === 0;
290
+ }
291
+
292
+ function removeArrowAt(event) {
293
+ if (!arrows.size) return;
294
+ const point = svgPoint(event);
295
+ // Match the visible head or badge, from the topmost arrow down.
296
+ for (const mark of Array.from(arrowLayer.children).reverse()) {
297
+ const head = mark.querySelector('.fen-board-arrow-head');
298
+ const badge = mark.querySelector('.fen-board-arrow-badge');
299
+ if (!head.isPointInFill(point) && !badge.isPointInFill(point)) continue;
300
+ arrows.delete(mark.dataset.arrowKey);
301
+ renderArrows();
302
+ return;
303
+ }
304
+ }
305
+
306
+ function render() {
307
+ const turn = engine.turn();
308
+ const title = label + ' · ' + sideLabel(turn) + '方走棋';
309
+ if (block.tagName !== 'DETAILS') heading.textContent = title;
310
+ block.dataset.turn = turn;
311
+ svg.setAttribute('aria-label', title);
312
+ figure.dataset.fen = engine.position();
313
+ desc.textContent = 'FEN: ' + figure.dataset.fen;
314
+ const layer = document.createDocumentFragment();
315
+ const last = history.at(-1);
316
+ const board = engine.board();
317
+ board.forEach((piece, i) => {
318
+ const button = buttons[i];
319
+ const square = button.dataset.square;
320
+ const side = piece && pieceSide(piece);
321
+ const name = piece ? sideLabel(side) + engine.name(piece) : '空位';
322
+ button.setAttribute('aria-label', square + ' ' + name);
323
+ button.classList.toggle('fen-board-draggable', side === turn);
324
+ const from = last?.move.from === square;
325
+ const to = last?.move.to === square;
326
+ button.classList.toggle('fen-board-last', from || to);
327
+ button.classList.toggle('fen-board-last-from', from);
328
+ button.classList.toggle('fen-board-last-to', to);
329
+ if (!piece) return;
330
+ const group = document.createElementNS(SVG, 'g');
331
+ group.dataset.square = square;
332
+ group.dataset.piece = piece;
333
+ const point = center(i);
334
+ group.setAttribute('transform', `translate(${point.x} ${point.y})`);
335
+ const shape = document.createElementNS(SVG, 'use');
336
+ shape.setAttribute('href', `#${prefix}-${piece}`);
337
+ group.append(shape);
338
+ layer.append(group);
339
+ });
340
+ pieces.replaceChildren(layer);
341
+ drawSelection();
342
+ renderArrows();
343
+ undo.disabled = history.length === 0;
344
+ redo.disabled = future.length === 0;
345
+ status.textContent = [last?.label, engine.status()].filter(Boolean).join(' · ') || '点选或拖动棋子开始推演';
346
+ }
347
+
348
+ function playSound(capture) {
349
+ const file = capture ? figure.dataset.soundCapture : figure.dataset.soundMove;
350
+ if (!file) return;
351
+ if (!sounds.has(file)) sounds.set(file, new Audio(new URL('sounds/' + file, import.meta.url)));
352
+ const sound = sounds.get(file);
353
+ sound.currentTime = 0;
354
+ sound.play().catch(() => {});
355
+ }
356
+
357
+ function play(move, restoring = false) {
358
+ const {label, capture} = engine.play(move);
359
+ history.push({move, label});
360
+ if (restoring) future.pop();
361
+ else future.length = 0;
362
+ resetAfterMove();
363
+ render();
364
+ playSound(capture);
365
+ }
366
+
367
+ function choosePromotion(candidates, index) {
368
+ promotion.hidden = false;
369
+ const label = document.createElement('span');
370
+ label.textContent = '升变为';
371
+ promotion.append(label);
372
+ for (const move of candidates) {
373
+ const button = makeButton(PROMOTIONS[move.promotion]);
374
+ button.addEventListener('click', () => {
375
+ play(move);
376
+ buttons[index].focus();
377
+ });
378
+ promotion.append(button);
379
+ }
380
+ const cancel = makeButton('取消');
381
+ cancel.addEventListener('click', () => {
382
+ promotion.hidden = true;
383
+ promotion.replaceChildren();
384
+ buttons[selection].focus();
385
+ });
386
+ promotion.append(cancel);
387
+ promotion.querySelector('button').focus();
388
+ }
389
+
390
+ function tryDestination(index) {
391
+ const square = buttons[index].dataset.square;
392
+ const candidates = targets.filter(move => move.to === square);
393
+ if (candidates.length > 1) {
394
+ choosePromotion(candidates, index);
395
+ } else if (candidates.length === 1) {
396
+ play(candidates[0]);
397
+ } else {
398
+ return false;
399
+ }
400
+ return true;
401
+ }
402
+
403
+ function select(index) {
404
+ if (!promotion.hidden || tryDestination(index)) return;
405
+ const ownPiece = buttons[index].classList.contains('fen-board-draggable');
406
+ pick(ownPiece && selection !== index ? index : -1);
407
+ }
408
+
409
+ function endDrag() {
410
+ if (!drag) return;
411
+ const {button, pointerId, source, ghost} = drag;
412
+ drag = null;
413
+ if (ghost) {
414
+ ghost.remove();
415
+ source.removeAttribute('visibility');
416
+ surface.classList.remove('fen-board-dragging');
417
+ // Pointer capture can still produce a click on the source button.
418
+ suppressClick = true;
419
+ }
420
+ if (button.hasPointerCapture(pointerId)) button.releasePointerCapture(pointerId);
421
+ }
422
+
423
+ function squareFrom(element) {
424
+ const hit = element?.closest('.fen-board-square');
425
+ return hit?.parentElement === surface ? buttons.indexOf(hit) : -1;
426
+ }
427
+
428
+ function squareAt(event) {
429
+ return squareFrom(document.elementFromPoint(event.clientX, event.clientY));
430
+ }
431
+
432
+ function endDrawing() {
433
+ if (!drawing) return;
434
+ const {pointerId} = drawing;
435
+ drawing = null;
436
+ draftLayer.replaceChildren();
437
+ if (surface.hasPointerCapture(pointerId)) surface.releasePointerCapture(pointerId);
438
+ }
439
+
440
+ surface.addEventListener('contextmenu', event => {
441
+ if (event.button === 2) event.preventDefault();
442
+ });
443
+
444
+ surface.addEventListener('pointerdown', event => {
445
+ if (drag || drawing || !event.isPrimary) return;
446
+ if (event.button === 2) {
447
+ const from = squareFrom(event.target);
448
+ event.preventDefault();
449
+ surface.setPointerCapture(event.pointerId);
450
+ const piece = engine.board()[from];
451
+ drawing = {
452
+ from, to: -1, pointerId: event.pointerId,
453
+ side: piece ? pieceSide(piece) : engine.turn(), occupied: Boolean(piece),
454
+ startX: event.clientX, startY: event.clientY, moved: false,
455
+ };
456
+ return;
457
+ }
458
+ if (event.button !== 0) return;
459
+ suppressClick = false;
460
+ const button = event.target.closest('.fen-board-draggable');
461
+ if (!button || !promotion.hidden) return;
462
+ const source = pieces.querySelector(`[data-square="${button.dataset.square}"]`);
463
+ if (!source) return;
464
+ button.setPointerCapture(event.pointerId);
465
+ drag = {
466
+ button, source, pointerId: event.pointerId, index: buttons.indexOf(button),
467
+ startX: event.clientX, startY: event.clientY, ghost: null,
468
+ };
469
+ });
470
+
471
+ surface.addEventListener('pointermove', event => {
472
+ if (drawing?.pointerId === event.pointerId) {
473
+ drawing.moved ||= pointerMoved(event, drawing);
474
+ const to = squareAt(event);
475
+ if (to === drawing.to) return;
476
+ drawing.to = to;
477
+ const {from} = drawing;
478
+ draftLayer.replaceChildren();
479
+ if (from !== -1 && to !== -1 && to !== from) {
480
+ draftLayer.append(makeArrow(drawing, arrows.size + 1));
481
+ }
482
+ return;
483
+ }
484
+ if (drag?.pointerId !== event.pointerId) return;
485
+ if (!drag.ghost) {
486
+ if (!pointerMoved(event, drag)) return;
487
+ const ghost = drag.source.firstElementChild.cloneNode(true);
488
+ ghost.setAttribute('class', 'fen-board-drag-piece');
489
+ ghost.setAttribute('aria-hidden', 'true');
490
+ svg.append(ghost);
491
+ drag.ghost = ghost;
492
+ drag.source.setAttribute('visibility', 'hidden');
493
+ surface.classList.add('fen-board-dragging');
494
+ pick(drag.index);
495
+ }
496
+ const point = svgPoint(event);
497
+ drag.ghost.setAttribute('transform', `translate(${point.x} ${point.y})`);
498
+ });
499
+
500
+ surface.addEventListener('pointerup', event => {
501
+ if (drawing?.pointerId === event.pointerId) {
502
+ const {from, side, occupied} = drawing;
503
+ const moved = drawing.moved || pointerMoved(event, drawing);
504
+ const to = squareAt(event);
505
+ endDrawing();
506
+ if (event.button !== 2) return;
507
+ if (!moved) {
508
+ removeArrowAt(event);
509
+ return;
510
+ }
511
+ if (from === -1 || to === -1 || to === from) return;
512
+ const key = `${from}:${to}`;
513
+ if (!arrows.delete(key)) arrows.set(key, {from, to, side, occupied});
514
+ renderArrows();
515
+ return;
516
+ }
517
+ if (drag?.pointerId !== event.pointerId) return;
518
+ const moved = Boolean(drag.ghost);
519
+ endDrag();
520
+ if (!moved) return;
521
+ const to = squareAt(event);
522
+ if (to !== -1 && tryDestination(to)) return;
523
+ pick(-1);
524
+ });
525
+
526
+ function cancelPointer(event) {
527
+ if (drawing?.pointerId === event.pointerId) endDrawing();
528
+ if (drag?.pointerId !== event.pointerId) return;
529
+ const moved = Boolean(drag.ghost);
530
+ endDrag();
531
+ if (moved) pick(-1);
532
+ }
533
+ surface.addEventListener('pointercancel', cancelPointer);
534
+ surface.addEventListener('lostpointercapture', cancelPointer);
535
+
536
+ clearArrows.addEventListener('click', () => {
537
+ endDrawing();
538
+ arrows.clear();
539
+ renderArrows();
540
+ });
541
+
542
+ undo.addEventListener('click', () => {
543
+ if (history.length === 0) return;
544
+ engine.undo();
545
+ future.push(history.pop().move);
546
+ resetAfterMove();
547
+ render();
548
+ });
549
+ redo.addEventListener('click', () => {
550
+ if (future.length) play(future.at(-1), true);
551
+ });
552
+ render();
553
+ }
554
+
555
+ export function mountBoards() {
556
+ for (const figure of document.querySelectorAll('.fen-board-figure')) {
557
+ if (mounted.has(figure) || figure.closest('details:not([open])')) continue;
558
+ mounted.add(figure);
559
+ mount(figure);
560
+ }
561
+ }
562
+
563
+ document.addEventListener('toggle', event => {
564
+ if (event.target.tagName === 'DETAILS' && event.target.open) mountBoards();
565
+ }, true);
@@ -0,0 +1,13 @@
1
+ (() => {
2
+ const moduleUrl = new URL('board.mjs', document.currentScript.src);
3
+
4
+ function loadBoards() {
5
+ if (document.querySelector('.fen-board-figure')) {
6
+ import(moduleUrl.href).then(({mountBoards}) => mountBoards()).catch(console.error);
7
+ }
8
+ }
9
+
10
+ if (typeof document$ !== 'undefined') document$.subscribe(loadBoards);
11
+ else if (document.readyState === 'loading') document.addEventListener('DOMContentLoaded', loadBoards);
12
+ else loadBoards();
13
+ })();