chesspal 0.3.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/cli/cli.js +17 -4
- package/dist/cli/command-line.js +22 -12
- package/dist/cli/icon.d.ts +2 -0
- package/dist/cli/icon.js +11 -1
- package/dist/cli/prompt-hook.d.ts +17 -0
- package/dist/cli/prompt-hook.js +100 -0
- package/dist/commands/analysis/analyse.d.ts +2 -2
- package/dist/commands/analysis/analyse.js +10 -16
- package/dist/commands/analysis/annotate.d.ts +8 -0
- package/dist/commands/analysis/annotate.js +19 -3
- package/dist/commands/answers/agents.d.ts +17 -7
- package/dist/commands/answers/agents.js +46 -16
- package/dist/commands/answers/clock.js +2 -1
- package/dist/commands/answers/drawings.js +6 -2
- package/dist/commands/answers/index.d.ts +4 -3
- package/dist/commands/answers/index.js +4 -3
- package/dist/commands/answers/lines.d.ts +5 -3
- package/dist/commands/answers/lines.js +17 -9
- package/dist/commands/answers/opening-book.d.ts +13 -7
- package/dist/commands/answers/opening-book.js +34 -38
- package/dist/commands/answers/opening-tags.d.ts +4 -0
- package/dist/commands/answers/opening-tags.js +11 -0
- package/dist/commands/answers/play-move.d.ts +3 -1
- package/dist/commands/answers/play-move.js +9 -6
- package/dist/commands/answers/say.js +2 -1
- package/dist/commands/answers/summary.d.ts +7 -3
- package/dist/commands/answers/summary.js +16 -5
- package/dist/commands/answers/view.d.ts +1 -1
- package/dist/commands/answers/view.js +2 -2
- package/dist/commands/game/ai-move.js +4 -4
- package/dist/commands/game/coach.js +7 -6
- package/dist/commands/game/end.js +2 -2
- package/dist/commands/game/export.d.ts +1 -1
- package/dist/commands/game/index.d.ts +1 -1
- package/dist/commands/game/index.js +1 -1
- package/dist/commands/game/look.d.ts +7 -3
- package/dist/commands/game/look.js +9 -4
- package/dist/commands/game/new.js +42 -12
- package/dist/commands/game/player-move.d.ts +16 -0
- package/dist/commands/game/player-move.js +69 -19
- package/dist/commands/game/ponder.js +7 -4
- package/dist/commands/game/position.d.ts +2 -1
- package/dist/commands/game/position.js +9 -4
- package/dist/commands/game/status.d.ts +4 -2
- package/dist/commands/game/status.js +24 -14
- package/dist/commands/index.d.ts +3 -3
- package/dist/commands/index.js +3 -3
- package/dist/commands/profiles/past-games.d.ts +7 -2
- package/dist/commands/profiles/past-games.js +45 -18
- package/dist/commands/registry/context.d.ts +2 -0
- package/dist/commands/registry/context.js +5 -0
- package/dist/commands/registry/index.d.ts +1 -1
- package/dist/commands/registry/index.js +1 -1
- package/dist/commands/talk/ai-say.d.ts +1 -1
- package/dist/commands/talk/ai-say.js +7 -6
- package/dist/core/game.d.ts +13 -0
- package/dist/core/game.js +29 -3
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.js +3 -3
- package/dist/core/openings.d.ts +20 -6
- package/dist/core/openings.js +45 -22
- package/dist/core/pgn.d.ts +4 -0
- package/dist/core/pgn.js +35 -6
- package/dist/games/analysis.d.ts +7 -4
- package/dist/games/analysis.js +5 -3
- package/dist/games/archive.d.ts +25 -5
- package/dist/games/archive.js +52 -19
- package/dist/games/chat-start.d.ts +2 -2
- package/dist/games/chat-start.js +6 -1
- package/dist/games/current-game.d.ts +2 -2
- package/dist/games/current-game.js +3 -2
- package/dist/games/index.d.ts +3 -2
- package/dist/games/index.js +3 -2
- package/dist/games/position-shown.d.ts +24 -0
- package/dist/games/position-shown.js +51 -0
- package/dist/games/thoughts.d.ts +12 -4
- package/dist/games/thoughts.js +27 -10
- package/dist/home/index.d.ts +1 -0
- package/dist/home/index.js +1 -0
- package/dist/home/same-path.d.ts +7 -0
- package/dist/home/same-path.js +24 -0
- package/dist/host-files/host-files.d.ts +5 -0
- package/dist/host-files/host-files.js +21 -7
- package/dist/host-files/index.d.ts +1 -1
- package/dist/host-files/index.js +1 -1
- package/dist/page/app/anchor.js +15 -11
- package/dist/page/app/app.js +49 -14
- package/dist/page/app/served.js +30 -38
- package/dist/page/app/watch.js +97 -0
- package/dist/page/avatars/torretta.css +108 -0
- package/dist/page/base.css +1 -1
- package/dist/page/board/board.css +11 -8
- package/dist/page/board/marks.js +2 -2
- package/dist/page/board/pointer.js +2 -2
- package/dist/page/dialogs/analyses.js +5 -3
- package/dist/page/dialogs/dialogs.css +3 -1
- package/dist/page/dialogs/rows.js +72 -0
- package/dist/page/dialogs/site-games.js +3 -1
- package/dist/page/game/column.js +10 -8
- package/dist/page/game/game.css +137 -16
- package/dist/page/game/move-list.js +7 -5
- package/dist/page/game/plies.js +4 -0
- package/dist/page/game/review-progress.js +11 -0
- package/dist/page/guide/drawings/drawings.css +0 -1
- package/dist/page/guide/en.md +2 -2
- package/dist/page/guide/it.md +2 -2
- package/dist/page/helps/helps.css +5 -5
- package/dist/page/helps/helps.js +3 -181
- package/dist/page/helps/rows.js +16 -0
- package/dist/page/helps/strategy.js +105 -0
- package/dist/page/helps/tactics.js +69 -0
- package/dist/page/layout/header.js +49 -6
- package/dist/page/layout/icon.ico +0 -0
- package/dist/page/layout/layout.css +49 -17
- package/dist/page/layout/window.js +45 -6
- package/dist/page/lead/lead.js +41 -4
- package/dist/page/legend/legend.css +4 -16
- package/dist/page/legend/legend.js +28 -15
- package/dist/page/profile/settings.js +1 -0
- package/dist/page/sounds/sounds.js +1 -1
- package/dist/page/style.css +1 -0
- package/dist/page/table/action.js +42 -0
- package/dist/page/table/table.css +57 -39
- package/dist/page/table/window.js +67 -67
- package/dist/page/talk/talk.css +52 -118
- package/dist/page/talk/talk.js +65 -20
- package/dist/page/talk/voice.js +92 -21
- package/dist/page/text.js +34 -8
- package/dist/page/theme.css +13 -0
- package/dist/profiles/profiles.js +5 -1
- package/dist/server/bell/bell.d.ts +24 -29
- package/dist/server/bell/bell.js +79 -133
- package/dist/server/bell/index.d.ts +1 -1
- package/dist/server/bell/index.js +2 -2
- package/dist/server/mcp/mcp.js +49 -7
- package/dist/server/page/analyses.js +15 -4
- package/dist/server/page/chat.d.ts +5 -1
- package/dist/server/page/chat.js +5 -1
- package/dist/server/page/files.js +3 -1
- package/dist/server/page/game.d.ts +5 -0
- package/dist/server/page/game.js +95 -15
- package/dist/server/page/http.d.ts +1 -1
- package/dist/server/page/page.d.ts +14 -9
- package/dist/server/page/page.js +111 -26
- package/dist/server/page/table.d.ts +40 -5
- package/dist/server/page/table.js +65 -25
- package/dist/server/page/update.d.ts +1 -1
- package/dist/server/page/update.js +4 -4
- package/dist/server/page/windows.d.ts +22 -0
- package/dist/server/page/windows.js +45 -0
- package/dist/server/ports/index.d.ts +1 -0
- package/dist/server/ports/index.js +2 -0
- package/dist/server/ports/ports.d.ts +78 -0
- package/dist/server/ports/ports.js +222 -0
- package/dist/server/table/browser.d.ts +22 -6
- package/dist/server/table/browser.js +98 -20
- package/dist/server/table/hosts/chats.d.ts +5 -0
- package/dist/server/table/hosts/chats.js +11 -4
- package/dist/server/table/hosts/hooks.d.ts +6 -0
- package/dist/server/table/hosts/hooks.js +38 -0
- package/dist/server/table/hosts/hosts.d.ts +13 -5
- package/dist/server/table/hosts/hosts.js +16 -10
- package/dist/server/table/hosts/index.d.ts +1 -0
- package/dist/server/table/hosts/index.js +1 -0
- package/dist/server/table/hosts/play.d.ts +7 -3
- package/dist/server/table/hosts/play.js +63 -9
- package/dist/server/table/icon/index.d.ts +2 -0
- package/dist/server/table/icon/index.js +16 -2
- package/dist/server/table/index.d.ts +2 -2
- package/dist/server/table/index.js +1 -1
- package/dist/server/table/launch.d.ts +5 -4
- package/dist/server/table/launch.js +18 -5
- package/dist/server/table/link.js +4 -1
- package/dist/server/table/table.d.ts +7 -0
- package/dist/server/table/table.js +40 -12
- package/dist/sites/index.d.ts +1 -1
- package/dist/sites/index.js +1 -1
- package/dist/sites/{chesscom.d.ts → ratings/chesscom.d.ts} +2 -2
- package/dist/sites/{chesscom.js → ratings/chesscom.js} +1 -1
- package/dist/sites/{choice.d.ts → ratings/choice.d.ts} +1 -1
- package/dist/sites/{choice.js → ratings/choice.js} +1 -1
- package/dist/sites/ratings/index.d.ts +1 -0
- package/dist/sites/ratings/index.js +2 -0
- package/dist/sites/{lichess.d.ts → ratings/lichess.d.ts} +2 -2
- package/dist/sites/{lichess.js → ratings/lichess.js} +1 -1
- package/dist/sites/{sites.d.ts → ratings/ratings.d.ts} +2 -2
- package/dist/sites/{sites.js → ratings/ratings.js} +1 -1
- package/instructions/opponent.md +1 -1
- package/instructions/start.md +1 -1
- package/package.json +1 -1
package/dist/page/base.css
CHANGED
|
@@ -375,23 +375,23 @@
|
|
|
375
375
|
}
|
|
376
376
|
|
|
377
377
|
.badge.crown {
|
|
378
|
-
background:
|
|
379
|
-
color:
|
|
378
|
+
background: var(--king-crown);
|
|
379
|
+
color: var(--king-crown-ink);
|
|
380
380
|
}
|
|
381
381
|
|
|
382
382
|
/* The mate white on the red square, edged in red */
|
|
383
383
|
.badge.mate {
|
|
384
384
|
background: #fff;
|
|
385
|
-
color:
|
|
386
|
-
box-shadow: 0 0 0 max(1.5px, 0.25cqw)
|
|
385
|
+
color: var(--king-mate);
|
|
386
|
+
box-shadow: 0 0 0 max(1.5px, 0.25cqw) var(--king-mate), 0 0.2cqw 0.6cqw rgba(0, 0, 0, 0.4);
|
|
387
387
|
}
|
|
388
388
|
|
|
389
389
|
.badge.flag {
|
|
390
|
-
background:
|
|
390
|
+
background: var(--king-flag);
|
|
391
391
|
}
|
|
392
392
|
|
|
393
393
|
.badge.half {
|
|
394
|
-
background:
|
|
394
|
+
background: var(--king-draw);
|
|
395
395
|
}
|
|
396
396
|
|
|
397
397
|
/* A move's badge (D-69): 16px beside a move, a third of a square in the corner of its arrival, edged in white as the kings' badges */
|
|
@@ -789,7 +789,7 @@
|
|
|
789
789
|
.notice.right,
|
|
790
790
|
.notice.left {
|
|
791
791
|
top: calc((var(--row) + 0.5) * 12.5cqw);
|
|
792
|
-
max-width: min(calc(
|
|
792
|
+
max-width: min(calc(var(--room) * 12.5cqw - 12px), 340px);
|
|
793
793
|
}
|
|
794
794
|
|
|
795
795
|
.notice.right {
|
|
@@ -842,6 +842,7 @@
|
|
|
842
842
|
left: 50%;
|
|
843
843
|
top: 50%;
|
|
844
844
|
transform: translate(-50%, -50%);
|
|
845
|
+
text-wrap: balance;
|
|
845
846
|
}
|
|
846
847
|
|
|
847
848
|
.notice.centre::after {
|
|
@@ -886,7 +887,9 @@
|
|
|
886
887
|
}
|
|
887
888
|
|
|
888
889
|
.promotion-piece:hover {
|
|
889
|
-
box-shadow:
|
|
890
|
+
box-shadow:
|
|
891
|
+
var(--shadow),
|
|
892
|
+
inset 0 0 0 max(2px, 0.625cqw) var(--hover-edge);
|
|
890
893
|
}
|
|
891
894
|
|
|
892
895
|
/* White inside dark reads on every square and every theme. */
|
package/dist/page/board/marks.js
CHANGED
|
@@ -110,8 +110,8 @@ export function PromotionChoice({ side, column, down, onPick, text }) {
|
|
|
110
110
|
onPick(undefined);
|
|
111
111
|
}
|
|
112
112
|
};
|
|
113
|
-
addEventListener("keydown", escape);
|
|
114
|
-
return () => removeEventListener("keydown", escape);
|
|
113
|
+
window.addEventListener("keydown", escape);
|
|
114
|
+
return () => window.removeEventListener("keydown", escape);
|
|
115
115
|
}, []);
|
|
116
116
|
return (_jsxs("div", { class: "promotion", role: "group", "aria-label": text.promoteTo, children: [_jsx("div", { class: "promotion-shade", onPointerDown: () => onPick(undefined) }), promotionPieces.map((piece, index) => (_jsx("button", { ref: index === 0 ? first : undefined, type: "button", class: "promotion-piece", style: { "--column": column, "--row": down ? index : 7 - index }, "aria-label": text.piece(piece.toUpperCase(), side), onClick: () => onPick(piece), children: _jsx(Piece, { letter: side === "white" ? piece.toUpperCase() : piece }) }, piece)))] }));
|
|
117
117
|
}
|
|
@@ -96,10 +96,10 @@ export function useBoardPointer({ columns, ranks, pieces, player, selected, targ
|
|
|
96
96
|
}
|
|
97
97
|
};
|
|
98
98
|
const landed = drag.back ? setTimeout(() => setDrag(undefined), reducedMotion() ? 0 : 260) : undefined;
|
|
99
|
-
addEventListener("keydown", escape);
|
|
99
|
+
window.addEventListener("keydown", escape);
|
|
100
100
|
document.documentElement.classList.add("dragging");
|
|
101
101
|
return () => {
|
|
102
|
-
removeEventListener("keydown", escape);
|
|
102
|
+
window.removeEventListener("keydown", escape);
|
|
103
103
|
document.documentElement.classList.remove("dragging");
|
|
104
104
|
clearTimeout(landed);
|
|
105
105
|
};
|
|
@@ -6,6 +6,7 @@ import { accountSide } from "../../core/index.js";
|
|
|
6
6
|
import { ResultTile } from "../game/result-tile.js";
|
|
7
7
|
import { signature } from "../players/model-name.js";
|
|
8
8
|
import { OutcomeBadge, outcomeOf } from "./outcome.js";
|
|
9
|
+
import { useRowKeys } from "./rows.js";
|
|
9
10
|
import { searched } from "./search.js";
|
|
10
11
|
import { playerIn, studiedSides } from "./studied.js";
|
|
11
12
|
/** The list, read anew at each opening: the search, the row that loads a game, then a row for each game found, with its menu or its question. */
|
|
@@ -17,13 +18,14 @@ export function Analyses({ onStudy, onLoad, you, accounts, others, ai, text }) {
|
|
|
17
18
|
const [asking, setAsking] = useState(undefined);
|
|
18
19
|
const [busy, setBusy] = useState(false);
|
|
19
20
|
const [problem, setProblem] = useState(undefined);
|
|
21
|
+
const rowKeys = useRowKeys();
|
|
20
22
|
useClosing(menu !== undefined, () => setMenu(undefined));
|
|
21
23
|
useClosing(asking !== undefined, () => setAsking(undefined));
|
|
22
24
|
const act = async (name, asked) => {
|
|
23
25
|
setBusy(true);
|
|
24
26
|
const refused = asked === "remove" ? await deletion("/api/delete-game", name) : await deletion("/api/delete-review", name);
|
|
25
|
-
// Analysing again opens the game without its review: the chat's analyst goes over it (D-89).
|
|
26
|
-
const after = refused === undefined && asked === "redo" ? await onStudy({ game: name }) : refused;
|
|
27
|
+
// Analysing again opens the game without its review: the chat's analyst goes over it (D-89), asked for by the player.
|
|
28
|
+
const after = refused === undefined && asked === "redo" ? await onStudy({ game: name, redo: true }) : refused;
|
|
27
29
|
setBusy(false);
|
|
28
30
|
setAsking(undefined);
|
|
29
31
|
setProblem(after);
|
|
@@ -44,7 +46,7 @@ export function Analyses({ onStudy, onLoad, you, accounts, others, ai, text }) {
|
|
|
44
46
|
return { item, named, outcome };
|
|
45
47
|
});
|
|
46
48
|
const found = searched(rows, query, ({ item, named, outcome }) => searchText(item, named.sides, outcome, text));
|
|
47
|
-
return (_jsxs("div", { class: "analyses", children: [_jsxs("label", { class: "search", children: [_jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M10.5 17A6.5 6.5 0 1 0 10.5 4A6.5 6.5 0 0 0 10.5 17ZM15.5 15.5L20 20" }) }), _jsx("input", { type: "search", value: query, placeholder: words.search, "aria-label": words.search, spellcheck: false, onInput: (event) => setQuery(event.currentTarget.value) })] }), _jsxs("button", { class: "load-row", type: "button", onClick: onLoad, children: [_jsx("span", { class: "plus", "aria-hidden": "true", children: _jsx("svg", { class: "icon", viewBox: "0 0 24 24", children: _jsx("path", { d: "M12 5V19M5 12H19" }) }) }), _jsxs("span", { children: [_jsx("b", { children: words.bring }), _jsx("small", { children: words.bringNote })] })] }), problem === undefined ? null : _jsx("p", { class: "problem", role: "alert", children: problem }), analyses.length > 0 && found.length === 0 ? _jsx("p", { class: "none", children: words.noMatch }) : null, found.length === 0 ? null : (_jsx("ul", { class: "games", "aria-label": words.yours, children: found.map(({ item, named, outcome }) => asking?.name === item.name
|
|
49
|
+
return (_jsxs("div", { class: "analyses", children: [_jsxs("label", { class: "search", children: [_jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M10.5 17A6.5 6.5 0 1 0 10.5 4A6.5 6.5 0 0 0 10.5 17ZM15.5 15.5L20 20" }) }), _jsx("input", { type: "search", value: query, placeholder: words.search, "aria-label": words.search, spellcheck: false, onInput: (event) => setQuery(event.currentTarget.value) })] }), _jsxs("button", { class: "load-row", type: "button", onClick: onLoad, children: [_jsx("span", { class: "plus", "aria-hidden": "true", children: _jsx("svg", { class: "icon", viewBox: "0 0 24 24", children: _jsx("path", { d: "M12 5V19M5 12H19" }) }) }), _jsxs("span", { children: [_jsx("b", { children: words.bring }), _jsx("small", { children: words.bringNote })] })] }), problem === undefined ? null : _jsx("p", { class: "problem", role: "alert", children: problem }), analyses.length > 0 && found.length === 0 ? _jsx("p", { class: "none", children: words.noMatch }) : null, found.length === 0 ? null : (_jsx("ul", { class: "games", "aria-label": words.yours, ...rowKeys, children: found.map(({ item, named, outcome }) => asking?.name === item.name
|
|
48
50
|
? (_jsx(Question, { asked: asking.asked, game: named.sides, when: dayOf(item.date, text.language), by: by(item), busy: busy, onYes: () => void act(item.name, asking.asked), onNo: () => setAsking(undefined), words: words }, item.name))
|
|
49
51
|
: (_jsxs("li", { class: "game", children: [_jsxs("button", { class: "reopen", type: "button", disabled: busy, onClick: () => open(item.name), children: [_jsx(ResultTile, { result: item.result }), _jsxs("span", { class: "what", children: [_jsx("b", { class: "who", children: named.sides }), _jsx("span", { class: "meta", children: [dayOf(item.date, text.language), words.moves(item.moves), item.opening].filter((part) => part !== undefined).join(" · ") }), item.reviewed ? _jsx(Reviewed, { by: by(item), words: words }) : null] }), _jsxs("span", { class: "score", children: [_jsx("b", { children: scoreOf(item.result) }), outcome === undefined ? _jsx("span", {}) : _jsx(OutcomeBadge, { outcome: outcome, words: words })] })] }), _jsx("span", { class: "row-actions", children: _jsx("button", { class: `icon-button${menu?.name === item.name ? " on" : ""}`, type: "button", title: words.more, "aria-label": words.more, "aria-expanded": menu?.name === item.name, disabled: busy, onClick: (event) => setMenu(menu?.name === item.name ? undefined : { name: item.name, anchor: event.currentTarget }), children: _jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M5 12H5.01M12 12H12.01M19 12H19.01" }) }) }) }), menu?.name === item.name
|
|
50
52
|
? (_jsx(RowMenu, { anchor: menu.anchor, withReview: item.reviewed || item.begun === true, onPick: (asked) => {
|
|
@@ -622,7 +622,9 @@ h3 + .switch {
|
|
|
622
622
|
|
|
623
623
|
/* A game without a result: its star on a coin of the card's colour, readable where the four squares meet */
|
|
624
624
|
.analyses .tile.open {
|
|
625
|
-
background:
|
|
625
|
+
background:
|
|
626
|
+
radial-gradient(circle, var(--card) 0 11px, transparent 11.5px),
|
|
627
|
+
conic-gradient(var(--dark-square) 0 25%, var(--light-square) 0 50%, var(--dark-square) 0 75%, var(--light-square) 0);
|
|
626
628
|
color: var(--soft);
|
|
627
629
|
font-size: 18px;
|
|
628
630
|
font-weight: 700;
|
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
import { useLayoutEffect, useRef } from "preact/hooks";
|
|
2
|
+
/**
|
|
3
|
+
* Makes the list's rows one stop of the Tab: the row's button focused last, or the first row's, takes the Tab, the others only the arrows. Up and down go
|
|
4
|
+
* to the row above and below, Home and End to the first and the last, left and right between a row's own buttons. A row that asks a question in its place
|
|
5
|
+
* keeps its buttons in the Tab's order.
|
|
6
|
+
*/
|
|
7
|
+
export function useRowKeys() {
|
|
8
|
+
const ref = useRef(null);
|
|
9
|
+
const current = useRef(undefined);
|
|
10
|
+
// After each render the rows may have changed, by a search or a game deleted: the one stop is the button focused last, if still there, or the first.
|
|
11
|
+
useLayoutEffect(() => {
|
|
12
|
+
if (ref.current !== null) {
|
|
13
|
+
setStop(rowsOf(ref.current).flat(), current.current);
|
|
14
|
+
}
|
|
15
|
+
});
|
|
16
|
+
const onFocusIn = (event) => {
|
|
17
|
+
const buttons = ref.current === null ? [] : rowsOf(ref.current).flat();
|
|
18
|
+
if (buttons.includes(event.target)) {
|
|
19
|
+
current.current = event.target;
|
|
20
|
+
setStop(buttons, current.current);
|
|
21
|
+
}
|
|
22
|
+
};
|
|
23
|
+
const onKeyDown = (event) => {
|
|
24
|
+
const rows = ref.current === null ? [] : rowsOf(ref.current);
|
|
25
|
+
const row = rows.findIndex((buttons) => buttons.includes(event.target));
|
|
26
|
+
if (row === -1) {
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const next = target(event.key, rows, row, rows[row].indexOf(event.target));
|
|
30
|
+
if (next !== undefined) {
|
|
31
|
+
event.preventDefault();
|
|
32
|
+
next.focus();
|
|
33
|
+
}
|
|
34
|
+
};
|
|
35
|
+
return { ref, onKeyDown, onFocusIn };
|
|
36
|
+
}
|
|
37
|
+
/** Gives the Tab to the button wanted, if it is still in the list, else to the first. */
|
|
38
|
+
function setStop(buttons, wanted) {
|
|
39
|
+
const stop = wanted !== undefined && buttons.includes(wanted) ? wanted : buttons[0];
|
|
40
|
+
for (const button of buttons) {
|
|
41
|
+
button.tabIndex = button === stop ? 0 : -1;
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
/** The button a key goes to from the row and column focused, or none for a key the list leaves to the page. */
|
|
45
|
+
function target(key, rows, row, column) {
|
|
46
|
+
const at = (index) => {
|
|
47
|
+
const buttons = rows[Math.max(0, Math.min(rows.length - 1, index))];
|
|
48
|
+
return buttons[Math.min(column, buttons.length - 1)];
|
|
49
|
+
};
|
|
50
|
+
switch (key) {
|
|
51
|
+
case "ArrowDown":
|
|
52
|
+
return at(row + 1);
|
|
53
|
+
case "ArrowUp":
|
|
54
|
+
return at(row - 1);
|
|
55
|
+
case "Home":
|
|
56
|
+
return at(0);
|
|
57
|
+
case "End":
|
|
58
|
+
return at(rows.length - 1);
|
|
59
|
+
case "ArrowRight":
|
|
60
|
+
return rows[row][Math.min(column + 1, rows[row].length - 1)];
|
|
61
|
+
case "ArrowLeft":
|
|
62
|
+
return rows[row][Math.max(column - 1, 0)];
|
|
63
|
+
default:
|
|
64
|
+
return undefined;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
/** The rows' own buttons, row by row, those that can take the focus: the row's, and its menu's button, never a menu's or a question's. */
|
|
68
|
+
function rowsOf(list) {
|
|
69
|
+
return [...list.querySelectorAll(":scope > li:not([role])")]
|
|
70
|
+
.map((row) => [...row.querySelectorAll(":scope > button, :scope > .row-actions > button")].filter((button) => !button.disabled))
|
|
71
|
+
.filter((buttons) => buttons.length > 0);
|
|
72
|
+
}
|
|
@@ -3,6 +3,7 @@ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "preact/jsx-ru
|
|
|
3
3
|
import { useEffect, useState } from "preact/hooks";
|
|
4
4
|
import { Piece } from "../pieces/pieces.js";
|
|
5
5
|
import { OutcomeBadge, outcomeOf } from "./outcome.js";
|
|
6
|
+
import { useRowKeys } from "./rows.js";
|
|
6
7
|
/**
|
|
7
8
|
* The list, asked of the server when the step opens and the profile has a name on a site. `picked` is the game being sent, whose row keeps the accent and
|
|
8
9
|
* a spinner while the others wait; `problem`, why the server refused it, shown under the list; `onProfile` opens the profile, where the names go.
|
|
@@ -11,6 +12,7 @@ export function SiteGameList({ accounts, picked, problem, onPick, onProfile, tex
|
|
|
11
12
|
const words = text.study;
|
|
12
13
|
const named = accounts?.lichess !== undefined || accounts?.chesscom !== undefined;
|
|
13
14
|
const answer = useSiteGames(named, words.siteGamesRefused);
|
|
15
|
+
const rowKeys = useRowKeys();
|
|
14
16
|
if (!named) {
|
|
15
17
|
return (_jsxs("div", { class: "field analyses site-games", children: [_jsx("span", { children: words.siteGames }), _jsxs("p", { class: "none", children: [`${words.siteGamesNoAccount} `, _jsx("button", { class: "add-names", type: "button", onClick: onProfile, children: words.siteGamesProfile })] })] }));
|
|
16
18
|
}
|
|
@@ -20,7 +22,7 @@ export function SiteGameList({ accounts, picked, problem, onPick, onProfile, tex
|
|
|
20
22
|
? _jsx("p", { class: "site-problem", children: answer.refusal })
|
|
21
23
|
: (_jsxs(_Fragment, { children: [_jsx(SiteProblems, { answer: answer, text: text }), answer.games.length === 0
|
|
22
24
|
? (answer.answered.length === 0 ? null : _jsx("p", { class: "none", children: words.siteGamesNone }))
|
|
23
|
-
: (_jsx("ul", { class: `games${picked === undefined ? "" : " picking"}`, "aria-label": words.siteGames, children: answer.games.map((game) => _jsx(GameRow, { game: game, picked: picked, onPick: onPick, text: text }, game.name)) })), problem === undefined ? null : _jsx("p", { class: "problem", role: "alert", children: problem })] }))] }));
|
|
25
|
+
: (_jsx("ul", { class: `games${picked === undefined ? "" : " picking"}`, "aria-label": words.siteGames, ...rowKeys, children: answer.games.map((game) => _jsx(GameRow, { game: game, picked: picked, onPick: onPick, text: text }, game.name)) })), problem === undefined ? null : _jsx("p", { class: "problem", role: "alert", children: problem })] }))] }));
|
|
24
26
|
}
|
|
25
27
|
const siteNames = { "lichess": "Lichess", "chess.com": "Chess.com" };
|
|
26
28
|
/** The sites that did not answer, both in one line, and those without a player by the profile's name, in the profile window's words. */
|
package/dist/page/game/column.js
CHANGED
|
@@ -1,20 +1,21 @@
|
|
|
1
1
|
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
|
-
import {
|
|
2
|
+
import {} from "../../core/index.js";
|
|
3
3
|
import { LeadBar, LeadChart } from "../lead/lead.js";
|
|
4
4
|
import { standingAt } from "../lead/view.js";
|
|
5
5
|
import { Piece } from "../pieces/pieces.js";
|
|
6
6
|
import { FoldButton, useCardFold } from "./card-fold.js";
|
|
7
7
|
import { MoveList, Steps } from "./move-list.js";
|
|
8
8
|
import { ResultTile } from "./result-tile.js";
|
|
9
|
+
import { ReviewProgress } from "./review-progress.js";
|
|
9
10
|
/**
|
|
10
11
|
* The column, which the player folds with its tab; under the board, where three columns would leave the board small, always open. A game in analysis
|
|
11
12
|
* (D-69) says above the moves that the AI is going over it, until its word on the whole game comes, which the talk shows.
|
|
12
13
|
*/
|
|
13
|
-
export function GameColumn({ plies, result, how, lit, started, move, toMove, opening, helps, legend, standings, analysis, shownPlies, onShow, onStep, onDownload, onSaveMoves, onAnalyse, text }) {
|
|
14
|
+
export function GameColumn({ plies, result, how, lit, started, move, toMove, opening, outOfBook, helps, legend, standings, analysis, shownPlies, onShow, onStep, onDownload, onSaveMoves, onAnalyse, text }) {
|
|
14
15
|
const bar = standings === undefined ? undefined : _jsx(LeadBar, { standing: standingAt(standings, shownPlies), shown: shownPlies, text: text });
|
|
15
16
|
return (_jsx("aside", { class: "side", id: "game", "aria-label": text.game, children: started === undefined && analysis === undefined
|
|
16
17
|
? (_jsxs(_Fragment, { children: [_jsx("p", { class: "card empty", children: text.noGame }), onAnalyse === undefined ? null : _jsx(AnalyseButton, { label: text.study.analyseLast, onAnalyse: onAnalyse })] }))
|
|
17
|
-
: (_jsxs(_Fragment, { children: [_jsxs("div", { class: "side-cards", children: [_jsx(GameCard, { plies: plies, result: result, how: how, lit: lit, started: started, move: move, toMove: toMove, bar: bar, text: text }), analysis?.reviewing !== true ? null : (_jsxs("
|
|
18
|
+
: (_jsxs(_Fragment, { children: [_jsxs("div", { class: "side-cards", children: [_jsx(GameCard, { plies: plies, result: result, how: how, lit: lit, started: started, move: move, toMove: toMove, bar: bar, text: text }), analysis?.reviewing !== true ? null : (_jsxs("div", { class: "card review waiting", children: [_jsx("p", { role: "status", children: text.reviewing(analysis.by) }), analysis.count === undefined ? null : _jsx(ReviewProgress, { count: analysis.count, label: text.reviewing(analysis.by) })] })), analysis?.onComplete === undefined ? null : (_jsxs("div", { class: "card review incomplete", children: [_jsxs("p", { children: [_jsx("span", { role: "status", children: text.incomplete }), _jsx("button", { type: "button", title: text.completeHint, "aria-label": text.completeHint, onClick: analysis.onComplete, children: text.complete })] }), analysis.count === undefined || analysis.count.done === 0 ? null : _jsx(ReviewProgress, { count: analysis.count, label: text.incomplete, stopped: true })] })), analysis === undefined || standings === undefined ? null : _jsx(LeadChart, { plies: plies, standings: standings, shown: shownPlies, onShow: onShow, text: text }), opening === undefined ? null : _jsx(OpeningCard, { opening: opening, ...(outOfBook === undefined ? {} : { outOfBook }), text: text }), helps] }), _jsxs("div", { class: "side-foot", children: [result !== undefined && plies.length === 0 ? null : _jsx(MoveList, { plies: plies, result: result, shown: shownPlies, onShow: onShow, tools: legend, text: text }), onDownload === undefined && onSaveMoves === undefined ? null : (_jsxs("div", { class: "card analysis-actions", children: [onDownload === undefined ? null : _jsx(SaveButton, { label: text.study.download, hint: text.study.downloadHint, onSave: onDownload }), onSaveMoves === undefined ? null : _jsx(SaveButton, { label: text.saveMoves, hint: text.saveMovesHint, onSave: onSaveMoves })] })), onAnalyse === undefined ? null : _jsx(AnalyseButton, { label: text.study.analyseIt, onAnalyse: onAnalyse }), _jsx(Steps, { shown: shownPlies, total: plies.length, onStep: onStep, text: text })] })] })) }));
|
|
18
19
|
}
|
|
19
20
|
/** A button that saves the game as a file in the player's downloads, its tooltip saying what the file holds. */
|
|
20
21
|
function SaveButton({ label, hint, onSave }) {
|
|
@@ -48,12 +49,13 @@ function GameCard({ plies, result, how, lit, started, move, toMove, bar, text })
|
|
|
48
49
|
* The opening by Lichess's book: a book, crossed out once the game left it, and the family, which fold the card to them (Stefano); the variation under them,
|
|
49
50
|
* and the move the game left the book at.
|
|
50
51
|
*/
|
|
51
|
-
function OpeningCard({ opening, text }) {
|
|
52
|
+
function OpeningCard({ opening, outOfBook, text }) {
|
|
52
53
|
const [foldedHere, toggleFold] = useCardFold("opening");
|
|
53
|
-
const
|
|
54
|
+
const inBook = outOfBook === undefined;
|
|
55
|
+
const book = inBook ? text.inBook : `${text.outOfBook} ${outOfBook}`;
|
|
54
56
|
// The family alone has nothing under it to fold: no button, no chevron (Stefano).
|
|
55
|
-
const foldable = opening.variation !== undefined || !
|
|
57
|
+
const foldable = opening.variation !== undefined || !inBook;
|
|
56
58
|
const folded = foldable && foldedHere;
|
|
57
|
-
const title = (_jsxs(_Fragment, { children: [_jsx("span", { class: `book${
|
|
58
|
-
return (_jsxs("section", { class: `card opening${folded ? " folded" : ""}`, "aria-label": text.opening, children: [_jsx("h3", { class: "family", children: foldable ? _jsx(FoldButton, { folded: folded, onFold: toggleFold, text: text, children: title }) : title }), folded || opening.variation === undefined ? null : _jsx("p", { class: "variation", children: opening.variation }), folded ||
|
|
59
|
+
const title = (_jsxs(_Fragment, { children: [_jsx("span", { class: `book${inBook ? "" : " out"}`, title: book, role: "img", "aria-label": book, children: _jsxs("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: [_jsx("path", { d: "M12 6.5C10 5 7 4.5 4 5V18C7 17.5 10 18 12 19.5C14 18 17 17.5 20 18V5C17 4.5 14 5 12 6.5ZM12 6.5V19.5" }), inBook ? null : _jsx("path", { d: "M3.5 3.5L20.5 20.5" })] }) }), _jsx("b", { children: opening.family })] }));
|
|
60
|
+
return (_jsxs("section", { class: `card opening${folded ? " folded" : ""}`, "aria-label": text.opening, children: [_jsx("h3", { class: "family", children: foldable ? _jsx(FoldButton, { folded: folded, onFold: toggleFold, text: text, children: title }) : title }), folded || opening.variation === undefined ? null : _jsx("p", { class: "variation", children: opening.variation }), folded || inBook ? null : _jsxs("p", { class: "out", children: [text.outOfBook, " ", outOfBook] })] }));
|
|
59
61
|
}
|
package/dist/page/game/game.css
CHANGED
|
@@ -129,8 +129,8 @@
|
|
|
129
129
|
height: 14px;
|
|
130
130
|
margin-top: 6px;
|
|
131
131
|
border-radius: 7px;
|
|
132
|
-
background:
|
|
133
|
-
box-shadow: 0 0 0 1px
|
|
132
|
+
background: var(--side-black);
|
|
133
|
+
box-shadow: 0 0 0 1px var(--side-edge);
|
|
134
134
|
overflow: hidden;
|
|
135
135
|
}
|
|
136
136
|
|
|
@@ -139,7 +139,7 @@
|
|
|
139
139
|
position: absolute;
|
|
140
140
|
inset: 3px auto 3px calc(50% - 0.5px);
|
|
141
141
|
width: 1px;
|
|
142
|
-
background:
|
|
142
|
+
background: var(--side-both);
|
|
143
143
|
}
|
|
144
144
|
|
|
145
145
|
/* On a position after the one the AI estimated: the player's move, until the coach speaks again */
|
|
@@ -150,7 +150,7 @@
|
|
|
150
150
|
.lead-bar .white {
|
|
151
151
|
position: absolute;
|
|
152
152
|
inset: 0 auto 0 0;
|
|
153
|
-
background:
|
|
153
|
+
background: var(--side-white);
|
|
154
154
|
transition: width 400ms ease;
|
|
155
155
|
}
|
|
156
156
|
|
|
@@ -172,12 +172,12 @@
|
|
|
172
172
|
.lead-bar .figure.white,
|
|
173
173
|
.lead-bar .figure.even {
|
|
174
174
|
left: 6px;
|
|
175
|
-
color:
|
|
175
|
+
color: var(--fixed-ink);
|
|
176
176
|
}
|
|
177
177
|
|
|
178
178
|
.lead-bar .figure.black {
|
|
179
179
|
right: 6px;
|
|
180
|
-
color:
|
|
180
|
+
color: var(--side-white);
|
|
181
181
|
}
|
|
182
182
|
|
|
183
183
|
/* The chart of the game in analysis: the white area is White's share, the line in the middle an even game; a dot on a move a badge marks, a line on the move
|
|
@@ -191,10 +191,13 @@
|
|
|
191
191
|
position: relative;
|
|
192
192
|
height: 72px;
|
|
193
193
|
border-radius: 6px;
|
|
194
|
-
background:
|
|
195
|
-
box-shadow: 0 0 0 1px
|
|
194
|
+
background: var(--side-black);
|
|
195
|
+
box-shadow: 0 0 0 1px var(--side-edge);
|
|
196
196
|
overflow: hidden;
|
|
197
197
|
cursor: pointer;
|
|
198
|
+
/* A finger drawn across it runs through the game; up and down it scrolls the page */
|
|
199
|
+
touch-action: pan-y;
|
|
200
|
+
user-select: none;
|
|
198
201
|
}
|
|
199
202
|
|
|
200
203
|
.lead-chart svg {
|
|
@@ -204,7 +207,7 @@
|
|
|
204
207
|
}
|
|
205
208
|
|
|
206
209
|
.lead-chart .white-area {
|
|
207
|
-
fill:
|
|
210
|
+
fill: var(--side-white);
|
|
208
211
|
}
|
|
209
212
|
|
|
210
213
|
.lead-chart .pending {
|
|
@@ -212,7 +215,7 @@
|
|
|
212
215
|
}
|
|
213
216
|
|
|
214
217
|
.lead-chart .even {
|
|
215
|
-
stroke:
|
|
218
|
+
stroke: var(--side-both);
|
|
216
219
|
stroke-width: 1;
|
|
217
220
|
stroke-dasharray: 3 3;
|
|
218
221
|
fill: none;
|
|
@@ -225,10 +228,15 @@
|
|
|
225
228
|
width: 2px;
|
|
226
229
|
margin-left: -1px;
|
|
227
230
|
background: #ffc450;
|
|
228
|
-
box-shadow: 0 0 0 1px
|
|
231
|
+
box-shadow: 0 0 0 1px rgba(20, 23, 27, 0.6);
|
|
229
232
|
pointer-events: none;
|
|
230
233
|
}
|
|
231
234
|
|
|
235
|
+
/* The move a click would show, under the mouse: the same line, fainter (Zoe) */
|
|
236
|
+
.lead-chart .ghost {
|
|
237
|
+
opacity: 0.5;
|
|
238
|
+
}
|
|
239
|
+
|
|
232
240
|
.lead-chart .dot {
|
|
233
241
|
position: absolute;
|
|
234
242
|
width: 8px;
|
|
@@ -311,24 +319,104 @@
|
|
|
311
319
|
line-height: 1.45;
|
|
312
320
|
}
|
|
313
321
|
|
|
322
|
+
/* The line that says the AI reviews, and under it how far it has got */
|
|
314
323
|
.review.waiting,
|
|
315
324
|
.say.reviewing {
|
|
316
|
-
display:
|
|
317
|
-
align-items: center;
|
|
325
|
+
display: grid;
|
|
318
326
|
gap: 8px;
|
|
319
327
|
margin: 0;
|
|
320
328
|
color: var(--soft);
|
|
321
329
|
}
|
|
322
330
|
|
|
323
|
-
/* A review nobody is finishing: the note, and beside it the button that brings the game back to the AI (D-109); neutral, as no error (Zoe)
|
|
331
|
+
/* A review nobody is finishing: the note, and beside it the button that brings the game back to the AI (D-109); neutral, as no error (Zoe). Under them,
|
|
332
|
+
how far it got */
|
|
324
333
|
.review.incomplete,
|
|
325
334
|
.say.incomplete {
|
|
335
|
+
display: grid;
|
|
336
|
+
gap: 8px;
|
|
337
|
+
margin: 0;
|
|
338
|
+
color: var(--soft);
|
|
339
|
+
}
|
|
340
|
+
|
|
341
|
+
.review.incomplete > p,
|
|
342
|
+
.say.incomplete > p {
|
|
326
343
|
display: flex;
|
|
327
344
|
align-items: center;
|
|
328
345
|
justify-content: space-between;
|
|
329
346
|
gap: 8px;
|
|
330
|
-
|
|
331
|
-
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
/* How far a review has got: a thin bar in the accent and the count, 12/33. Before the first move a short run goes back and forth; full, while the word on
|
|
350
|
+
the whole game is written, it breathes; stopped unfinished, it stands still in the soft ink */
|
|
351
|
+
.review-progress {
|
|
352
|
+
display: flex;
|
|
353
|
+
align-items: center;
|
|
354
|
+
gap: 8px;
|
|
355
|
+
}
|
|
356
|
+
|
|
357
|
+
.review-progress .track {
|
|
358
|
+
flex: 1;
|
|
359
|
+
height: 4px;
|
|
360
|
+
overflow: hidden;
|
|
361
|
+
border-radius: 2px;
|
|
362
|
+
background: color-mix(in srgb, var(--accent) 20%, transparent);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
.review-progress .fill {
|
|
366
|
+
display: block;
|
|
367
|
+
width: 0;
|
|
368
|
+
height: 100%;
|
|
369
|
+
border-radius: inherit;
|
|
370
|
+
background: var(--accent);
|
|
371
|
+
transition: width 300ms ease-out;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
.review-progress .count {
|
|
375
|
+
flex: none;
|
|
376
|
+
font-size: 12px;
|
|
377
|
+
font-variant-numeric: tabular-nums;
|
|
378
|
+
}
|
|
379
|
+
|
|
380
|
+
.review-progress.waiting .fill {
|
|
381
|
+
width: 30%;
|
|
382
|
+
animation: review-run 1400ms ease-in-out infinite alternate;
|
|
383
|
+
}
|
|
384
|
+
|
|
385
|
+
.review-progress.writing .fill {
|
|
386
|
+
animation: review-breath 1600ms ease-in-out infinite;
|
|
387
|
+
}
|
|
388
|
+
|
|
389
|
+
.review-progress.stopped .track {
|
|
390
|
+
background: color-mix(in srgb, var(--soft) 20%, transparent);
|
|
391
|
+
}
|
|
392
|
+
|
|
393
|
+
.review-progress.stopped .fill {
|
|
394
|
+
background: var(--soft);
|
|
395
|
+
}
|
|
396
|
+
|
|
397
|
+
@keyframes review-run {
|
|
398
|
+
to {
|
|
399
|
+
translate: 233%;
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
@keyframes review-breath {
|
|
404
|
+
50% {
|
|
405
|
+
opacity: 0.45;
|
|
406
|
+
}
|
|
407
|
+
}
|
|
408
|
+
|
|
409
|
+
/* Still: before the first move a faint full bar, the count then grows without sliding */
|
|
410
|
+
@media (prefers-reduced-motion: reduce) {
|
|
411
|
+
.review-progress .fill {
|
|
412
|
+
transition: none;
|
|
413
|
+
animation: none;
|
|
414
|
+
}
|
|
415
|
+
|
|
416
|
+
.review-progress.waiting .fill {
|
|
417
|
+
width: 100%;
|
|
418
|
+
opacity: 0.35;
|
|
419
|
+
}
|
|
332
420
|
}
|
|
333
421
|
|
|
334
422
|
.review.incomplete button,
|
|
@@ -394,6 +482,8 @@
|
|
|
394
482
|
overflow: hidden auto;
|
|
395
483
|
scrollbar-width: none;
|
|
396
484
|
font-size: 14px;
|
|
485
|
+
/* A row too long for a phone never makes the page wider */
|
|
486
|
+
contain: inline-size;
|
|
397
487
|
}
|
|
398
488
|
|
|
399
489
|
.moves ol {
|
|
@@ -438,6 +528,11 @@
|
|
|
438
528
|
cursor: pointer;
|
|
439
529
|
}
|
|
440
530
|
|
|
531
|
+
/* A move never breaks, its piece always beside its square: a long one takes room from its neighbour in the row */
|
|
532
|
+
.moves .ply {
|
|
533
|
+
white-space: nowrap;
|
|
534
|
+
}
|
|
535
|
+
|
|
441
536
|
/* The move under the pointer in a neutral grey, never the accent, so it never reads as the move shown (Stefano) */
|
|
442
537
|
.moves .ply:hover {
|
|
443
538
|
background: color-mix(in oklab, var(--ink) 9%, transparent);
|
|
@@ -552,6 +647,32 @@
|
|
|
552
647
|
}
|
|
553
648
|
}
|
|
554
649
|
|
|
650
|
+
/* On a phone the rows give the moves all the room they can, so that two columns hold a move with its badge on one line (Zoe) */
|
|
651
|
+
@media (max-width: 479px) {
|
|
652
|
+
.moves ol {
|
|
653
|
+
column-gap: 8px;
|
|
654
|
+
}
|
|
655
|
+
|
|
656
|
+
.moves li {
|
|
657
|
+
grid-template-columns: 22px 1fr 1fr;
|
|
658
|
+
column-gap: 4px;
|
|
659
|
+
padding: 3px 4px;
|
|
660
|
+
}
|
|
661
|
+
|
|
662
|
+
.moves .ply {
|
|
663
|
+
gap: 3px;
|
|
664
|
+
padding: 1px 4px;
|
|
665
|
+
margin-left: -4px;
|
|
666
|
+
}
|
|
667
|
+
}
|
|
668
|
+
|
|
669
|
+
/* On the narrowest phones two columns would leave a move no room: one column */
|
|
670
|
+
@media (max-width: 359px) {
|
|
671
|
+
.moves ol {
|
|
672
|
+
columns: 1;
|
|
673
|
+
}
|
|
674
|
+
}
|
|
675
|
+
|
|
555
676
|
/* A card's title in the game's column, its icon in the accent as the opening's book: the helps' lifebuoy, the moves' score sheet (Stefano) */
|
|
556
677
|
.card-title {
|
|
557
678
|
display: flex;
|
|
@@ -48,14 +48,16 @@ function Move({ ply, current, onShow, text }) {
|
|
|
48
48
|
// A screen reader reads the move in SAN and the AI's badge by its name; the eye sees each piece's letter as its figure, and the badge's disc (D-69).
|
|
49
49
|
return (_jsxs("button", { type: "button", class: current ? "ply current" : "ply", "aria-current": current ? "step" : undefined, onClick: onShow, children: [_jsx("span", { class: "visually-hidden", children: ply.review?.badge === undefined ? ply.san : `${ply.san}, ${text.badges[ply.review.badge]}` }), _jsx("span", { "aria-hidden": "true", children: _jsx(Figurines, { san: ply.san, side: ply.side }) }), ply.review?.badge !== undefined ? _jsx(BadgeIcon, { badge: ply.review.badge }) : ply.review?.note !== undefined ? _jsx(NoteIcon, {}) : null] }));
|
|
50
50
|
}
|
|
51
|
-
/** Scrolls the list, and only the list, as little as it takes to show its current move. */
|
|
51
|
+
/** Scrolls the list, and only the list, as little as it takes to show the whole row of its current move, and the result under the last one. */
|
|
52
52
|
function keepInSight(list) {
|
|
53
|
-
const
|
|
54
|
-
|
|
55
|
-
if (box === undefined || move === undefined) {
|
|
53
|
+
const row = list?.querySelector(".current")?.closest("li");
|
|
54
|
+
if (list === null || !row) {
|
|
56
55
|
return;
|
|
57
56
|
}
|
|
58
|
-
|
|
57
|
+
const box = list.getBoundingClientRect();
|
|
58
|
+
const top = row.getBoundingClientRect().top;
|
|
59
|
+
const bottom = (row.nextElementSibling === null ? list.lastElementChild ?? row : row).getBoundingClientRect().bottom;
|
|
60
|
+
list.scrollTop += Math.min(0, top - box.top) + Math.max(0, bottom - box.bottom);
|
|
59
61
|
}
|
|
60
62
|
/** The plies in rows of one number: White's move and Black's. */
|
|
61
63
|
function rowsOf(plies) {
|
package/dist/page/game/plies.js
CHANGED
|
@@ -91,3 +91,7 @@ export function playerSpent(plies, ai, talk) {
|
|
|
91
91
|
function judged({ review }) {
|
|
92
92
|
return review?.note !== undefined || (review?.badge !== undefined && review.badge !== "book");
|
|
93
93
|
}
|
|
94
|
+
/** How far a review has got, as status tells the AI: the moves the analyst has said who is ahead after, of all the game's. */
|
|
95
|
+
export function reviewCount(plies) {
|
|
96
|
+
return { done: plies.filter((ply) => ply.lead !== undefined).length, total: plies.length };
|
|
97
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
|
+
/**
|
|
3
|
+
* The line that says the analyst works, and under it a thin bar with the count, 12/33: a bar that runs back and forth before the first move is said, full
|
|
4
|
+
* and breathing while the word on the whole game is written; still, in the soft ink, when the review stopped unfinished.
|
|
5
|
+
*/
|
|
6
|
+
export function ReviewProgress({ count, label, stopped = false }) {
|
|
7
|
+
const { done, total } = count;
|
|
8
|
+
const started = done > 0;
|
|
9
|
+
const state = stopped ? " stopped" : !started ? " waiting" : done >= total ? " writing" : "";
|
|
10
|
+
return (_jsxs("span", { class: `review-progress${state}`, role: "progressbar", "aria-label": label, "aria-valuemin": 0, "aria-valuemax": total, ...(started || stopped ? { "aria-valuenow": done, "aria-valuetext": `${done}/${total}` } : {}), children: [_jsx("span", { class: "track", "aria-hidden": "true", children: _jsx("span", { class: "fill", style: started ? { width: `${(100 * done) / Math.max(total, 1)}%` } : undefined }) }), started || stopped ? _jsxs("span", { class: "count", "aria-hidden": "true", children: [done, "/", total] }) : null] }));
|
|
11
|
+
}
|
package/dist/page/guide/en.md
CHANGED
|
@@ -18,7 +18,7 @@ No chess engine plays for the AI: every move and every judgment is its own. Ches
|
|
|
18
18
|
|
|
19
19
|
### Getting started
|
|
20
20
|
|
|
21
|
-
1. Click the **AI's name**, at the top left, and choose its chat among those on your computer: **Claude Code** or **Codex**. It opens in a window of its own: keep it open while you play.
|
|
21
|
+
1. Click the **AI's name**, at the top left, and choose its chat among those on your computer: **Claude Code** or **Codex**. It opens in a window of its own: keep it open while you play. The first time it asks some questions. Whether you trust the folder: **Yes** (in Claude Code No is chosen already: go down to Yes with the arrow keys, then Enter). With Codex, about the hooks, Chesspal's two helpers: **Trust all and continue**. On Windows Codex also asks whether to set up its sandbox, the protected space it works in: choose the first option, and yes when Windows asks for permission.
|
|
22
22
|
2. Press **New game**, choose your colour, the clock and the AI's level, and press **Start**.
|
|
23
23
|
3. Move: the AI answers on the board, and with a few words under its name.
|
|
24
24
|
|
|
@@ -35,7 +35,7 @@ No chess engine plays for the AI: every move and every judgment is its own. Ches
|
|
|
35
35
|
|
|
36
36
|

|
|
37
37
|
|
|
38
|
-
- You talk with the AI in the **box under its name**, a few words with each move: it reads them with your move. You can also write to it in its window.
|
|
38
|
+
- You talk with the AI in the **box under its name**, a few words with each move: it reads them with your move. You can also write to it in its window. With Codex, if you write there while it waits for your move, press **Esc** after Enter: else it reads your message only after your move.
|
|
39
39
|
- A **move written** in the chat counts too: `e4`, `Nf3`, with the English letters of the pieces (K Q R B N), or in words.
|
|
40
40
|
- **The AI's name** opens the choice of its chat: Claude Code or Codex, the model (which AI: some are faster, others stronger) and how much it reasons. The more it reasons, the slower it plays. If you change them, the chat starts afresh in the same window: the AI forgets what you talked about, and the game stays as it is.
|
|
41
41
|
- At each **new game**, the chat the page opened starts afresh by itself, once the AI has stopped thinking: a long chat makes the AI slower.
|
package/dist/page/guide/it.md
CHANGED
|
@@ -18,7 +18,7 @@ Nessun motore di scacchi gioca al posto dell'AI: ogni mossa e ogni giudizio sono
|
|
|
18
18
|
|
|
19
19
|
### Per cominciare
|
|
20
20
|
|
|
21
|
-
1. Clicca il **nome dell'AI**, in alto a sinistra, e scegli la chat fra quelle che hai sul computer: **Claude Code** o **Codex**. Si apre in una finestra sua: tienila aperta mentre giochi.
|
|
21
|
+
1. Clicca il **nome dell'AI**, in alto a sinistra, e scegli la chat fra quelle che hai sul computer: **Claude Code** o **Codex**. Si apre in una finestra sua: tienila aperta mentre giochi. La prima volta fa qualche domanda. Se ti fidi della cartella: **Sì** (in Claude Code è già scelto No: scendi su Yes con le frecce, poi Invio). Con Codex, sugli hook, i due aiutanti di Chesspal: **Trust all and continue**. Su Windows Codex chiede anche se preparare la sua sandbox, lo spazio protetto dove lavora: scegli la prima opzione, e sì quando Windows chiede il permesso.
|
|
22
22
|
2. Premi **Nuova partita**, scegli il colore, l'orologio e il livello dell'AI, e premi **Inizia**.
|
|
23
23
|
3. Muovi: l'AI risponde sulla scacchiera, e con qualche parola sotto il suo nome.
|
|
24
24
|
|
|
@@ -35,7 +35,7 @@ Nessun motore di scacchi gioca al posto dell'AI: ogni mossa e ogni giudizio sono
|
|
|
35
35
|
|
|
36
36
|

|
|
37
37
|
|
|
38
|
-
- Parli con l'AI nella **casella sotto il suo nome**, qualche parola a ogni mossa: le legge con la tua mossa. Puoi scriverle anche nella sua finestra.
|
|
38
|
+
- Parli con l'AI nella **casella sotto il suo nome**, qualche parola a ogni mossa: le legge con la tua mossa. Puoi scriverle anche nella sua finestra. Con Codex, se gli scrivi lì mentre aspetta la tua mossa, dopo Invio premi **Esc**: se no legge il messaggio solo dopo la tua mossa.
|
|
39
39
|
- Anche una **mossa scritta** nella chat vale: `e4`, `Nf3`, con le lettere inglesi dei pezzi (K Q R B N), o a parole.
|
|
40
40
|
- **Il nome dell'AI** apre la scelta della chat: Claude Code o Codex, il modello (quale AI: alcune sono più veloci, altre più forti) e quanto ragiona. Più ragiona, più gioca lenta. Se cambi, la chat riparte da capo nella stessa finestra: l'AI dimentica la chiacchierata di prima, la partita resta com'è.
|
|
41
41
|
- A ogni **nuova partita** la chat aperta dalla pagina riparte da capo da sola, se l'AI ha finito di pensare: una chat lunga rende l'AI più lenta.
|