chesspal 0.3.5 → 0.3.6
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 +10 -2
- package/dist/cli/command-line.js +17 -9
- package/dist/cli/icon.d.ts +2 -0
- package/dist/cli/icon.js +11 -1
- package/dist/commands/analysis/analyse.d.ts +2 -2
- package/dist/commands/analysis/analyse.js +3 -3
- package/dist/commands/answers/agents.d.ts +10 -2
- package/dist/commands/answers/agents.js +30 -7
- package/dist/commands/answers/index.d.ts +1 -1
- package/dist/commands/answers/index.js +1 -1
- package/dist/commands/answers/lines.js +3 -2
- package/dist/commands/answers/summary.d.ts +2 -1
- package/dist/commands/answers/view.d.ts +1 -1
- package/dist/commands/answers/view.js +2 -2
- package/dist/commands/game/look.d.ts +7 -3
- package/dist/commands/game/look.js +9 -4
- package/dist/commands/game/new.js +1 -1
- package/dist/commands/game/ponder.js +3 -3
- package/dist/commands/game/position.d.ts +2 -1
- package/dist/commands/game/position.js +9 -4
- package/dist/commands/game/status.d.ts +3 -1
- package/dist/commands/game/status.js +19 -9
- package/dist/commands/profiles/past-games.d.ts +7 -2
- package/dist/commands/profiles/past-games.js +26 -10
- package/dist/commands/talk/ai-say.js +1 -1
- package/dist/games/analysis.d.ts +7 -4
- package/dist/games/analysis.js +5 -3
- package/dist/games/index.d.ts +1 -1
- package/dist/games/index.js +1 -1
- 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 +2 -0
- package/dist/home/same-path.js +5 -0
- package/dist/host-files/host-files.js +8 -3
- package/dist/page/app/anchor.js +15 -11
- package/dist/page/app/app.js +20 -8
- package/dist/page/app/served.js +23 -36
- package/dist/page/app/watch.js +97 -0
- package/dist/page/avatars/torretta.css +108 -0
- package/dist/page/board/board.css +7 -6
- 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/rows.js +72 -0
- package/dist/page/dialogs/site-games.js +3 -1
- package/dist/page/game/game.css +51 -10
- package/dist/page/game/move-list.js +7 -5
- package/dist/page/guide/en.md +1 -1
- package/dist/page/guide/it.md +1 -1
- 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/layout.css +17 -2
- package/dist/page/layout/window.js +4 -4
- package/dist/page/lead/lead.js +41 -4
- package/dist/page/sounds/sounds.js +1 -1
- package/dist/page/style.css +1 -0
- package/dist/page/talk/talk.css +0 -109
- package/dist/page/text.js +10 -0
- package/dist/page/theme.css +13 -0
- package/dist/profiles/profiles.js +5 -1
- package/dist/server/bell/bell.d.ts +0 -2
- package/dist/server/bell/bell.js +17 -7
- package/dist/server/bell/index.d.ts +1 -1
- package/dist/server/bell/index.js +1 -1
- package/dist/server/page/chat.d.ts +5 -1
- package/dist/server/page/chat.js +5 -1
- package/dist/server/page/game.js +18 -3
- package/dist/server/page/table.d.ts +2 -1
- package/dist/server/page/table.js +4 -2
- package/dist/server/page/update.d.ts +1 -1
- package/dist/server/page/update.js +4 -4
- package/dist/server/table/hosts/chats.d.ts +5 -0
- package/dist/server/table/hosts/chats.js +11 -4
- package/dist/server/table/hosts/play.d.ts +3 -1
- package/dist/server/table/hosts/play.js +17 -5
- 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 +1 -1
- package/dist/server/table/index.js +1 -1
- package/dist/server/table/link.js +3 -1
- package/dist/server/table/table.js +2 -2
- 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/start.md +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
/* The Torretta, whose eyes blink now and then, and its moods (D-70): a new face that pops in, never fading out, a red light when worried, out when sad, a light that breathes when
|
|
2
|
+
thoughtful */
|
|
3
|
+
.torretta .antenna {
|
|
4
|
+
fill: none;
|
|
5
|
+
stroke: var(--torretta-shade);
|
|
6
|
+
stroke-width: 4;
|
|
7
|
+
stroke-linecap: round;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
.torretta .light {
|
|
11
|
+
fill: var(--torretta-light);
|
|
12
|
+
transition: fill 0.2s;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
.torretta.mood-worried .light {
|
|
16
|
+
fill: var(--torretta-alarm);
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.torretta.mood-thoughtful .light {
|
|
20
|
+
animation: ponder 2.4s ease-in-out infinite;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
.torretta.mood-sad .light {
|
|
24
|
+
fill: var(--torretta-shade);
|
|
25
|
+
}
|
|
26
|
+
|
|
27
|
+
@keyframes ponder {
|
|
28
|
+
50% {
|
|
29
|
+
fill: var(--torretta-ponder);
|
|
30
|
+
}
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
.torretta .expression.changed {
|
|
34
|
+
transform-box: fill-box;
|
|
35
|
+
transform-origin: center;
|
|
36
|
+
animation: face-in 0.16s ease-out;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
@keyframes face-in {
|
|
40
|
+
from {
|
|
41
|
+
transform: scale(0.8);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
/* Its bust on a player's card: a disc of the player's picture's colour, which cuts what falls out of it */
|
|
46
|
+
.torretta.bust {
|
|
47
|
+
clip-path: circle(50%);
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
.torretta .back {
|
|
51
|
+
fill: var(--say);
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
.torretta .shade {
|
|
55
|
+
fill: var(--torretta-shade);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
.torretta .body {
|
|
59
|
+
fill: var(--torretta-body);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.torretta .screen {
|
|
63
|
+
fill: var(--torretta-screen);
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
.torretta .eyes,
|
|
67
|
+
.torretta .mouth {
|
|
68
|
+
fill: var(--torretta-face);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
.torretta .expression .line,
|
|
72
|
+
.torretta .brows {
|
|
73
|
+
fill: none;
|
|
74
|
+
stroke: var(--torretta-face);
|
|
75
|
+
stroke-width: 3.4;
|
|
76
|
+
stroke-linecap: round;
|
|
77
|
+
stroke-linejoin: round;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
.torretta .eyes {
|
|
81
|
+
transform-box: fill-box;
|
|
82
|
+
transform-origin: center;
|
|
83
|
+
animation: blink 6s infinite;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
@keyframes blink {
|
|
87
|
+
0%,
|
|
88
|
+
95%,
|
|
89
|
+
100% {
|
|
90
|
+
transform: scaleY(1);
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
97% {
|
|
94
|
+
transform: scaleY(0.1);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
|
|
98
|
+
@media (prefers-reduced-motion: reduce) {
|
|
99
|
+
.torretta .eyes,
|
|
100
|
+
.torretta .expression.changed,
|
|
101
|
+
.torretta.mood-thoughtful .light {
|
|
102
|
+
animation: none;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
.torretta .light {
|
|
106
|
+
transition: none;
|
|
107
|
+
}
|
|
108
|
+
}
|
|
@@ -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 */
|
|
@@ -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 {
|
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) => {
|
|
@@ -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/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;
|
|
@@ -229,6 +232,11 @@
|
|
|
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;
|
|
@@ -394,6 +402,8 @@
|
|
|
394
402
|
overflow: hidden auto;
|
|
395
403
|
scrollbar-width: none;
|
|
396
404
|
font-size: 14px;
|
|
405
|
+
/* A row too long for a phone never makes the page wider */
|
|
406
|
+
contain: inline-size;
|
|
397
407
|
}
|
|
398
408
|
|
|
399
409
|
.moves ol {
|
|
@@ -438,6 +448,11 @@
|
|
|
438
448
|
cursor: pointer;
|
|
439
449
|
}
|
|
440
450
|
|
|
451
|
+
/* A move never breaks, its piece always beside its square: a long one takes room from its neighbour in the row */
|
|
452
|
+
.moves .ply {
|
|
453
|
+
white-space: nowrap;
|
|
454
|
+
}
|
|
455
|
+
|
|
441
456
|
/* The move under the pointer in a neutral grey, never the accent, so it never reads as the move shown (Stefano) */
|
|
442
457
|
.moves .ply:hover {
|
|
443
458
|
background: color-mix(in oklab, var(--ink) 9%, transparent);
|
|
@@ -552,6 +567,32 @@
|
|
|
552
567
|
}
|
|
553
568
|
}
|
|
554
569
|
|
|
570
|
+
/* 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) */
|
|
571
|
+
@media (max-width: 479px) {
|
|
572
|
+
.moves ol {
|
|
573
|
+
column-gap: 8px;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.moves li {
|
|
577
|
+
grid-template-columns: 22px 1fr 1fr;
|
|
578
|
+
column-gap: 4px;
|
|
579
|
+
padding: 3px 4px;
|
|
580
|
+
}
|
|
581
|
+
|
|
582
|
+
.moves .ply {
|
|
583
|
+
gap: 3px;
|
|
584
|
+
padding: 1px 4px;
|
|
585
|
+
margin-left: -4px;
|
|
586
|
+
}
|
|
587
|
+
}
|
|
588
|
+
|
|
589
|
+
/* On the narrowest phones two columns would leave a move no room: one column */
|
|
590
|
+
@media (max-width: 359px) {
|
|
591
|
+
.moves ol {
|
|
592
|
+
columns: 1;
|
|
593
|
+
}
|
|
594
|
+
}
|
|
595
|
+
|
|
555
596
|
/* 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
597
|
.card-title {
|
|
557
598
|
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/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**. 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
|
|
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ì**. 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
|
|
|
@@ -801,26 +801,26 @@
|
|
|
801
801
|
/* The thread that keeps the black half in sight on the dark theme, as the lead bar's */
|
|
802
802
|
.control-bar,
|
|
803
803
|
.row-tile {
|
|
804
|
-
border: 1px solid
|
|
804
|
+
border: 1px solid var(--side-edge);
|
|
805
805
|
}
|
|
806
806
|
|
|
807
807
|
/* The bar's colours, which the lines' tiles share */
|
|
808
808
|
.control-bar .white,
|
|
809
809
|
.row-tile .white {
|
|
810
|
-
background:
|
|
810
|
+
background: var(--side-white);
|
|
811
811
|
color: var(--fixed-ink);
|
|
812
812
|
}
|
|
813
813
|
|
|
814
814
|
.control-bar .both,
|
|
815
815
|
.row-tile .both {
|
|
816
|
-
background:
|
|
816
|
+
background: var(--side-both);
|
|
817
817
|
color: var(--fixed-ink);
|
|
818
818
|
}
|
|
819
819
|
|
|
820
820
|
.control-bar .black,
|
|
821
821
|
.row-tile .black {
|
|
822
|
-
background: var(--
|
|
823
|
-
color:
|
|
822
|
+
background: var(--side-black);
|
|
823
|
+
color: var(--side-white);
|
|
824
824
|
}
|
|
825
825
|
|
|
826
826
|
/* A line's tile: a half for each side, the player's first; the open files in one half as wide as the two */
|