chesspal 0.2.4 → 0.3.1
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 +11 -12
- package/dist/cli/cli.js +83 -3
- package/dist/cli/command-line.js +26 -9
- package/dist/cli/icon.d.ts +25 -0
- package/dist/cli/icon.js +80 -0
- package/dist/commands/agents.d.ts +19 -2
- package/dist/commands/agents.js +39 -13
- package/dist/commands/ai-say.js +3 -4
- package/dist/commands/analyse-game.d.ts +4 -0
- package/dist/commands/analyse-game.js +46 -13
- package/dist/commands/annotate-game.d.ts +2 -0
- package/dist/commands/annotate-game.js +40 -18
- package/dist/commands/answer-lines.d.ts +2 -0
- package/dist/commands/answer-lines.js +13 -3
- package/dist/commands/coach.js +4 -2
- package/dist/commands/context.d.ts +11 -1
- package/dist/commands/context.js +5 -0
- package/dist/commands/delete-review.js +9 -5
- package/dist/commands/game-summary.d.ts +2 -0
- package/dist/commands/game-summary.js +4 -0
- package/dist/commands/index.d.ts +3 -2
- package/dist/commands/index.js +7 -4
- package/dist/commands/init.js +2 -4
- package/dist/commands/instructions.d.ts +9 -0
- package/dist/commands/instructions.js +30 -0
- package/dist/commands/lead-input.d.ts +1 -1
- package/dist/commands/lead-input.js +7 -4
- package/dist/commands/page-link.js +6 -5
- package/dist/commands/past-games.d.ts +1 -1
- package/dist/commands/past-games.js +12 -5
- package/dist/commands/player-move.js +2 -3
- package/dist/commands/profiles.d.ts +23 -9
- package/dist/commands/profiles.js +76 -35
- package/dist/commands/ratings.d.ts +8 -0
- package/dist/commands/ratings.js +45 -0
- package/dist/commands/registry.d.ts +1 -1
- package/dist/commands/registry.js +3 -2
- package/dist/commands/say.d.ts +1 -1
- package/dist/commands/say.js +1 -1
- package/dist/commands/status.d.ts +3 -1
- package/dist/commands/status.js +46 -27
- package/dist/commands/study.js +8 -5
- package/dist/core/index.d.ts +1 -1
- package/dist/core/index.js +1 -1
- package/dist/core/review.d.ts +2 -0
- package/dist/core/review.js +5 -1
- package/dist/games/ai-model.d.ts +4 -2
- package/dist/games/ai-model.js +10 -1
- package/dist/games/analysis.d.ts +26 -3
- package/dist/games/analysis.js +41 -6
- package/dist/games/archive.d.ts +6 -3
- package/dist/games/archive.js +13 -5
- package/dist/games/current-game.d.ts +10 -1
- package/dist/games/current-game.js +49 -15
- package/dist/games/files.d.ts +5 -0
- package/dist/games/files.js +42 -1
- package/dist/games/index.d.ts +3 -3
- package/dist/games/index.js +3 -3
- package/dist/home/data-folder.d.ts +2 -0
- package/dist/home/data-folder.js +5 -1
- package/dist/home/index.d.ts +1 -1
- package/dist/home/index.js +1 -1
- package/dist/host-files/agents.d.ts +2 -2
- package/dist/host-files/agents.js +12 -6
- package/dist/host-files/host-files.d.ts +5 -3
- package/dist/host-files/host-files.js +15 -8
- package/dist/host-files/index.d.ts +2 -2
- package/dist/host-files/index.js +2 -2
- package/dist/page/app/app.js +49 -20
- package/dist/page/app/title.js +17 -0
- package/dist/page/avatars/torretta.js +4 -4
- package/dist/page/dialogs/analyses.js +4 -4
- package/dist/page/dialogs/dialog.js +4 -1
- package/dist/page/dialogs/dialogs.css +84 -10
- package/dist/page/dialogs/profiles.js +96 -43
- package/dist/page/game/column.js +5 -1
- package/dist/page/game/plies.js +7 -4
- package/dist/page/guide/blocks.js +67 -0
- package/dist/page/guide/en.md +66 -0
- package/dist/page/guide/foot.js +18 -0
- package/dist/page/guide/guide.css +200 -0
- package/dist/page/guide/guide.js +89 -0
- package/dist/page/guide/it.md +66 -0
- package/dist/page/guide/markdown.js +67 -0
- package/dist/page/helps/helps.css +7 -10
- package/dist/page/layout/favicon.svg +13 -0
- package/dist/page/layout/header.js +3 -3
- package/dist/page/layout/icon.icns +0 -0
- package/dist/page/layout/icon.ico +0 -0
- package/dist/page/layout/icon.png +0 -0
- package/dist/page/layout/layout.css +85 -4
- package/dist/page/layout/window.js +45 -0
- package/dist/page/main.js +4 -1
- package/dist/page/players/card.js +1 -1
- package/dist/page/profile/profile.js +14 -9
- package/dist/page/profile/settings.js +9 -0
- package/dist/page/style.css +2 -0
- package/dist/page/table/table.css +123 -0
- package/dist/page/table/window.js +130 -0
- package/dist/page/talk/chat.css +23 -5
- package/dist/page/talk/talk.css +38 -26
- package/dist/page/talk/talk.js +6 -4
- package/dist/page/talk/voice.js +35 -9
- package/dist/page/text.js +131 -17
- package/dist/page/theme.css +7 -0
- package/dist/profiles/index.d.ts +2 -1
- package/dist/profiles/index.js +2 -1
- package/dist/profiles/profiles.d.ts +10 -15
- package/dist/profiles/profiles.js +30 -15
- package/dist/profiles/ratings.d.ts +35 -0
- package/dist/profiles/ratings.js +61 -0
- package/dist/server/bell.d.ts +24 -3
- package/dist/server/bell.js +84 -10
- package/dist/server/caller.d.ts +2 -1
- package/dist/server/caller.js +3 -2
- package/dist/server/mcp.d.ts +7 -3
- package/dist/server/mcp.js +100 -52
- package/dist/server/page/analyses.d.ts +2 -0
- package/dist/server/page/analyses.js +20 -5
- package/dist/server/page/attach.d.ts +4 -0
- package/dist/server/page/attach.js +112 -0
- package/dist/server/page/chat.d.ts +65 -0
- package/dist/server/page/chat.js +87 -0
- package/dist/server/page/events.d.ts +7 -0
- package/dist/server/page/events.js +20 -0
- package/dist/server/page/files.js +10 -1
- package/dist/server/page/game.js +43 -23
- package/dist/server/page/page.d.ts +51 -7
- package/dist/server/page/page.js +74 -48
- package/dist/server/page/profiles.d.ts +5 -0
- package/dist/server/page/profiles.js +26 -10
- package/dist/server/page/table.d.ts +58 -0
- package/dist/server/page/table.js +210 -0
- package/dist/server/table/browser.d.ts +6 -0
- package/dist/server/table/browser.js +48 -0
- package/dist/server/table/hosts/chats.d.ts +7 -0
- package/dist/server/table/hosts/chats.js +34 -0
- package/dist/server/table/hosts/find.d.ts +2 -0
- package/dist/server/table/hosts/find.js +20 -0
- package/dist/server/table/hosts/hosts.d.ts +34 -0
- package/dist/server/table/hosts/hosts.js +49 -0
- package/dist/server/table/hosts/index.d.ts +3 -0
- package/dist/server/table/hosts/index.js +4 -0
- package/dist/server/table/hosts/play.d.ts +29 -0
- package/dist/server/table/hosts/play.js +180 -0
- package/dist/server/table/hosts/seat.d.ts +33 -0
- package/dist/server/table/hosts/seat.js +80 -0
- package/dist/server/table/hosts/terminal.d.ts +6 -0
- package/dist/server/table/hosts/terminal.js +63 -0
- package/dist/server/table/icon/index.d.ts +32 -0
- package/dist/server/table/icon/index.js +83 -0
- package/dist/server/table/icon/linux.d.ts +5 -0
- package/dist/server/table/icon/linux.js +47 -0
- package/dist/server/table/icon/macos.d.ts +3 -0
- package/dist/server/table/icon/macos.js +38 -0
- package/dist/server/table/icon/windows.d.ts +3 -0
- package/dist/server/table/icon/windows.js +48 -0
- package/dist/server/table/index.d.ts +6 -0
- package/dist/server/table/index.js +7 -0
- package/dist/server/table/launch.d.ts +19 -0
- package/dist/server/table/launch.js +78 -0
- package/dist/server/table/link.d.ts +10 -0
- package/dist/server/table/link.js +231 -0
- package/dist/server/table/table.d.ts +35 -0
- package/dist/server/table/table.js +94 -0
- package/dist/sites/chesscom.d.ts +4 -0
- package/dist/sites/chesscom.js +25 -0
- package/dist/sites/choice.d.ts +17 -0
- package/dist/sites/choice.js +13 -0
- package/dist/sites/index.d.ts +2 -0
- package/dist/sites/index.js +4 -0
- package/dist/sites/lichess.d.ts +4 -0
- package/dist/sites/lichess.js +32 -0
- package/dist/sites/request.d.ts +11 -0
- package/dist/sites/request.js +15 -0
- package/dist/sites/sites.d.ts +6 -0
- package/dist/sites/sites.js +33 -0
- package/instructions/analysis.md +1 -1
- package/instructions/analyst-agent.md +1 -1
- package/instructions/coach.md +2 -1
- package/instructions/opponent.md +6 -6
- package/instructions/ponder.md +1 -0
- package/instructions/start.md +1 -0
- package/package.json +1 -1
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
/* The guide: a tall dialog, an index of chips, each part folding under its title, and the foot with who made Chesspal and the credits (Zoe) */
|
|
2
|
+
|
|
3
|
+
.dialog:has(> .guide) {
|
|
4
|
+
height: calc(100vh - 48px);
|
|
5
|
+
max-height: 90vh;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
.guide {
|
|
9
|
+
font-size: 14px;
|
|
10
|
+
line-height: 1.55;
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
.guide p,
|
|
14
|
+
.guide ul,
|
|
15
|
+
.guide ol {
|
|
16
|
+
margin: 0 0 10px;
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
.guide ul,
|
|
20
|
+
.guide ol {
|
|
21
|
+
padding-inline-start: 22px;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
.guide li + li {
|
|
25
|
+
margin-top: 4px;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
.guide a {
|
|
29
|
+
color: var(--accent);
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
.guide code {
|
|
33
|
+
padding: 1px 5px;
|
|
34
|
+
border-radius: 4px;
|
|
35
|
+
background: var(--quote);
|
|
36
|
+
font-size: 13px;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/* A heading a chip or a link brings into sight stays under the dialog's own title */
|
|
40
|
+
.guide [id] {
|
|
41
|
+
scroll-margin-top: 64px;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.guide-index {
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-wrap: wrap;
|
|
47
|
+
gap: 8px;
|
|
48
|
+
margin: 4px 0 14px;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
.guide-chip {
|
|
52
|
+
min-height: 32px;
|
|
53
|
+
padding: 5px 12px;
|
|
54
|
+
border: 1px solid var(--line);
|
|
55
|
+
border-radius: 999px;
|
|
56
|
+
background: var(--card);
|
|
57
|
+
color: var(--ink);
|
|
58
|
+
font: inherit;
|
|
59
|
+
font-size: 13px;
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
.guide-chip:hover {
|
|
63
|
+
border-color: var(--accent);
|
|
64
|
+
color: var(--accent);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/* A part: its title, a chevron that turns when it opens */
|
|
68
|
+
.guide-part {
|
|
69
|
+
border-top: 1px solid var(--line);
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
.guide-part summary {
|
|
73
|
+
display: flex;
|
|
74
|
+
align-items: center;
|
|
75
|
+
gap: 8px;
|
|
76
|
+
padding: 10px 0;
|
|
77
|
+
cursor: pointer;
|
|
78
|
+
list-style: none;
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
.guide-part summary::-webkit-details-marker {
|
|
82
|
+
display: none;
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
.guide-part summary::before,
|
|
86
|
+
.credits summary::before {
|
|
87
|
+
content: "";
|
|
88
|
+
width: 7px;
|
|
89
|
+
height: 7px;
|
|
90
|
+
border-right: 1.7px solid var(--soft);
|
|
91
|
+
border-bottom: 1.7px solid var(--soft);
|
|
92
|
+
transform: rotate(-45deg);
|
|
93
|
+
transition: transform 0.15s;
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
.guide-part[open] summary::before,
|
|
97
|
+
.credits[open] summary::before {
|
|
98
|
+
transform: rotate(45deg);
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
.guide-part h3 {
|
|
102
|
+
margin: 0;
|
|
103
|
+
font-size: 16px;
|
|
104
|
+
font-weight: 600;
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
.guide-part[open] {
|
|
108
|
+
padding-bottom: 6px;
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
.guide-table {
|
|
112
|
+
overflow-x: auto;
|
|
113
|
+
margin: 0 0 10px;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
.guide-table table {
|
|
117
|
+
border-collapse: collapse;
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
.guide-table th,
|
|
121
|
+
.guide-table td {
|
|
122
|
+
padding: 6px 10px;
|
|
123
|
+
border-bottom: 1px solid var(--line);
|
|
124
|
+
text-align: start;
|
|
125
|
+
vertical-align: top;
|
|
126
|
+
}
|
|
127
|
+
|
|
128
|
+
/* The foot: who made Chesspal, where it lives, the heart that supports it; the credits folded under it */
|
|
129
|
+
.guide-foot {
|
|
130
|
+
display: grid;
|
|
131
|
+
gap: 6px;
|
|
132
|
+
margin-top: 18px;
|
|
133
|
+
padding-top: 14px;
|
|
134
|
+
border-top: 1px solid var(--line);
|
|
135
|
+
color: var(--soft);
|
|
136
|
+
font-size: 12.5px;
|
|
137
|
+
text-align: center;
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
.guide-foot p {
|
|
141
|
+
margin: 0;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
.foot-own {
|
|
145
|
+
display: flex;
|
|
146
|
+
flex-wrap: wrap;
|
|
147
|
+
align-items: center;
|
|
148
|
+
justify-content: center;
|
|
149
|
+
gap: 6px 14px;
|
|
150
|
+
}
|
|
151
|
+
|
|
152
|
+
.guide .foot-own a {
|
|
153
|
+
display: inline-flex;
|
|
154
|
+
align-items: center;
|
|
155
|
+
gap: 5px;
|
|
156
|
+
color: inherit;
|
|
157
|
+
text-decoration: none;
|
|
158
|
+
}
|
|
159
|
+
|
|
160
|
+
.guide .foot-own a:hover,
|
|
161
|
+
.guide .foot-own a:focus-visible {
|
|
162
|
+
color: var(--ink);
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
.foot-own .icon {
|
|
166
|
+
width: 16px;
|
|
167
|
+
height: 16px;
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
.foot-support .icon {
|
|
171
|
+
color: var(--low-time);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
.credits summary {
|
|
175
|
+
display: inline-flex;
|
|
176
|
+
align-items: center;
|
|
177
|
+
gap: 6px;
|
|
178
|
+
cursor: pointer;
|
|
179
|
+
list-style: none;
|
|
180
|
+
}
|
|
181
|
+
|
|
182
|
+
.credits summary::-webkit-details-marker {
|
|
183
|
+
display: none;
|
|
184
|
+
}
|
|
185
|
+
|
|
186
|
+
.credits ul {
|
|
187
|
+
margin: 8px auto 0;
|
|
188
|
+
max-width: 60ch;
|
|
189
|
+
text-align: start;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
@media (max-width: 600px) {
|
|
193
|
+
.dialog:has(> .guide) {
|
|
194
|
+
width: 100vw;
|
|
195
|
+
max-width: 100vw;
|
|
196
|
+
height: 100vh;
|
|
197
|
+
max-height: 100vh;
|
|
198
|
+
border-radius: 0;
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
|
+
// The guide, from the book in the header: how to play, in the page's language (guide/<code>.md), an index of chips, its parts folding, the first two
|
|
3
|
+
// open, and its foot; its anchors are the English guide's, so that a link finds its heading in both languages.
|
|
4
|
+
import { useEffect, useMemo, useState } from "preact/hooks";
|
|
5
|
+
import { Dialog } from "../dialogs/dialog.js";
|
|
6
|
+
import { anchorOf, markdownBlocks } from "./blocks.js";
|
|
7
|
+
import { GuideFoot } from "./foot.js";
|
|
8
|
+
import { Blocks, Inline } from "./markdown.js";
|
|
9
|
+
/** The guide in its dialog. */
|
|
10
|
+
export function GuideDialog({ open, onClose, text }) {
|
|
11
|
+
return (_jsx(Dialog, { open: open, title: text.guide.title, onClose: onClose, text: text, children: _jsx(Guide, { text: text }) }));
|
|
12
|
+
}
|
|
13
|
+
function Guide({ text }) {
|
|
14
|
+
const guide = useGuide(text.language);
|
|
15
|
+
const [open, setOpen] = useState(() => new Set([0, 1]));
|
|
16
|
+
const [sought, setSought] = useState();
|
|
17
|
+
const { lead, parts, anchors } = useMemo(() => guideParts(guide.own, guide.english), [guide]);
|
|
18
|
+
const toggle = (index, event) => {
|
|
19
|
+
event.preventDefault();
|
|
20
|
+
setOpen((before) => new Set(before.has(index) ? [...before].filter((one) => one !== index) : [...before, index]));
|
|
21
|
+
};
|
|
22
|
+
// A chip of the index, or a link to a heading, opens its part and brings the heading into sight.
|
|
23
|
+
const go = (id) => {
|
|
24
|
+
const index = parts.findIndex((part) => [part.heading, ...part.blocks].some((block) => anchors.get(block) === id));
|
|
25
|
+
if (index >= 0) {
|
|
26
|
+
setOpen((before) => new Set([...before, index]));
|
|
27
|
+
setSought(id);
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
const goToAnchor = (anchor) => go(idOf(anchor));
|
|
31
|
+
useEffect(() => {
|
|
32
|
+
if (sought === undefined) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
setSought(undefined);
|
|
36
|
+
document.getElementById(sought)?.scrollIntoView({ block: "start", behavior: matchMedia("(prefers-reduced-motion: reduce)").matches ? "auto" : "smooth" });
|
|
37
|
+
}, [sought]);
|
|
38
|
+
return (_jsxs("div", { class: "guide", children: [_jsx(Blocks, { blocks: lead, anchors: anchors, go: goToAnchor }), _jsx("nav", { class: "guide-index", "aria-label": text.guide.contents, children: parts.map((part) => (_jsx("button", { type: "button", class: "guide-chip", onClick: () => go(anchors.get(part.heading) ?? ""), children: _jsx(Inline, { text: part.heading.text, go: goToAnchor }) }, part.heading.text))) }), parts.map((part, index) => (_jsxs("details", { id: anchors.get(part.heading), class: "guide-part", open: open.has(index), children: [_jsx("summary", { onClick: (event) => toggle(index, event), children: _jsx("h3", { children: part.heading.text }) }), _jsx(Blocks, { blocks: part.blocks, anchors: anchors, go: goToAnchor })] }, part.heading.text))), _jsx(GuideFoot, { text: text })] }));
|
|
39
|
+
}
|
|
40
|
+
/** The guide in a language, and in English for its anchors; empty until they are read. */
|
|
41
|
+
function useGuide(language) {
|
|
42
|
+
const [guide, setGuide] = useState({ own: "", english: "" });
|
|
43
|
+
useEffect(() => {
|
|
44
|
+
let current = true;
|
|
45
|
+
void Promise.all([guideIn("en"), language === "en" ? Promise.resolve("") : guideIn(language)]).then(([english, own]) => {
|
|
46
|
+
if (current) {
|
|
47
|
+
setGuide({ own: own === "" ? english : own, english });
|
|
48
|
+
}
|
|
49
|
+
});
|
|
50
|
+
return () => {
|
|
51
|
+
current = false;
|
|
52
|
+
};
|
|
53
|
+
}, [language]);
|
|
54
|
+
return guide;
|
|
55
|
+
}
|
|
56
|
+
// Each language's guide is read once while the page lives; one the server did not give is asked again the next time.
|
|
57
|
+
const read = new Map();
|
|
58
|
+
function guideIn(language) {
|
|
59
|
+
const known = read.get(language) ?? fetch(`/page/guide/${language}.md`).then((response) => (response.ok ? response.text() : Promise.reject(new Error(response.statusText))));
|
|
60
|
+
read.set(language, known);
|
|
61
|
+
return known.catch(() => {
|
|
62
|
+
read.delete(language);
|
|
63
|
+
return "";
|
|
64
|
+
});
|
|
65
|
+
}
|
|
66
|
+
/** The guide's lead, the lines before its first part, its title aside; its parts; and each heading's anchor, the English one at its place. */
|
|
67
|
+
function guideParts(own, english) {
|
|
68
|
+
const blocks = markdownBlocks(own);
|
|
69
|
+
const headings = blocks.filter((block) => block.kind === "heading");
|
|
70
|
+
const englishHeadings = markdownBlocks(english).filter((block) => block.kind === "heading");
|
|
71
|
+
const sameShape = headings.length === englishHeadings.length && headings.every((heading, index) => heading.level === englishHeadings[index]?.level);
|
|
72
|
+
const anchors = new Map(headings.map((heading, index) => [heading, idOf(anchorOf((sameShape ? englishHeadings[index] : heading)?.text ?? heading.text))]));
|
|
73
|
+
const first = blocks.findIndex((block) => block.kind === "heading" && block.level === 2);
|
|
74
|
+
const lead = (first < 0 ? blocks : blocks.slice(0, first)).filter((block) => !(block.kind === "heading" && block.level === 1));
|
|
75
|
+
const parts = [];
|
|
76
|
+
for (const block of first < 0 ? [] : blocks.slice(first)) {
|
|
77
|
+
if (block.kind === "heading" && block.level === 2) {
|
|
78
|
+
parts.push({ heading: block, blocks: [] });
|
|
79
|
+
}
|
|
80
|
+
else {
|
|
81
|
+
parts.at(-1)?.blocks.push(block);
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
return { lead, parts, anchors };
|
|
85
|
+
}
|
|
86
|
+
// A heading's id on the page, apart from the page's own.
|
|
87
|
+
function idOf(anchor) {
|
|
88
|
+
return `guide-${anchor}`;
|
|
89
|
+
}
|
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# Chesspal: la guida
|
|
2
|
+
|
|
3
|
+
Con **Chesspal** giochi a scacchi contro un'**AI**, su una scacchiera vera nel browser, e impari con lei. Questa guida è per chi gioca: non serve saper programmare.
|
|
4
|
+
|
|
5
|
+
## Come funziona
|
|
6
|
+
|
|
7
|
+
| Chi | Cosa fa |
|
|
8
|
+
|---|---|
|
|
9
|
+
| **Tu** | Muovi sulla scacchiera e parli con l'AI. |
|
|
10
|
+
| **L'AI** | Gioca, ragiona, giudica le mosse e ti risponde, nella sua chat. |
|
|
11
|
+
| **Chesspal** | Tiene le regole, la scacchiera, l'orologio e le tue partite, e passa all'AI i fatti della posizione. |
|
|
12
|
+
|
|
13
|
+
Le finestre sono due: questa **pagina**, con la scacchiera, e la **chat** dell'AI, in una finestra a parte. L'AI gioca da lì, e Chesspal la collega alla pagina.
|
|
14
|
+
|
|
15
|
+
Nessun motore di scacchi gioca al posto dell'AI: ogni mossa e ogni giudizio sono suoi. Chesspal controlla che ogni mossa sia legale e dice all'AI cosa c'è sulla scacchiera: i pezzi attaccati e difesi, le inchiodature, il nome dell'apertura.
|
|
16
|
+
|
|
17
|
+
## Avvio rapido
|
|
18
|
+
|
|
19
|
+
1. Installa **Node.js** da [nodejs.org](https://nodejs.org), la versione consigliata (LTS), e un'AI col tuo abbonamento: **Claude Code** o **Codex**.
|
|
20
|
+
2. Apri un terminale, la finestra dove si scrivono i comandi (su Windows il Prompt dei comandi: tasto Start, scrivi `cmd`, Invio; su macOS il Terminale), e scrivi `npm install -g chesspal`, poi `chesspal`: si apre questa pagina. La prima volta ti propone un'icona sul desktop, per aprirla con un doppio clic.
|
|
21
|
+
3. Premi **Claude Code** o **Codex**: la chat dell'AI si apre in una finestra sua. Tienila aperta mentre giochi.
|
|
22
|
+
4. Premi **Nuova partita**, scegli il colore, l'orologio e il livello dell'AI, e premi **Inizia**.
|
|
23
|
+
|
|
24
|
+
## Se ti sei perso
|
|
25
|
+
|
|
26
|
+
- **L'AI non risponde**: guarda la sua finestra. Se ti sta chiedendo qualcosa, rispondi lì; se l'hai chiusa, premi di nuovo **Claude Code** o **Codex**.
|
|
27
|
+
- **La pagina non si apre**: fai doppio clic sull'icona di Chesspal, o aprila come nell'[Avvio rapido](#quick-start).
|
|
28
|
+
- **Le tue partite** stanno in una cartella sul tuo computer: **Chi gioca?**, dalla tua immagine in alto a destra, dice dove, e **Apri la cartella** la apre.
|
|
29
|
+
|
|
30
|
+
## La chat dell'AI
|
|
31
|
+
|
|
32
|
+
- La pagina apre la **chat** dell'AI coi suoi pulsanti, in una finestra a parte: lì l'AI pensa e scrive. Accanto ai pulsanti scegli il modello (la versione dell'AI) e quanto ragiona: più ragiona, più gioca lenta.
|
|
33
|
+
- 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.
|
|
34
|
+
- Anche una **mossa scritta** nella chat vale: `e4`, `Nf3`, con le lettere inglesi dei pezzi (K Q R B N), o a parole.
|
|
35
|
+
- A ogni **nuova partita** la chat aperta dalla pagina riparte pulita da sola, se l'AI ha finito di pensare: una chat lunga rende l'AI più lenta.
|
|
36
|
+
|
|
37
|
+
## La partita
|
|
38
|
+
|
|
39
|
+
- **Muovi** trascinando un pezzo, o cliccando il pezzo e poi la sua casa. I pallini segnano le mosse legali; quando una mossa è vietata, la pagina dice perché.
|
|
40
|
+
- **L'orologio**, se ne hai scelto uno, parte con **Inizia**. Anche l'AI ha il suo, e sa quanto tempo le resta.
|
|
41
|
+
- **Proponi patta** nel tuo turno, dopo una mossa a testa; **Abbandona** chiude la partita.
|
|
42
|
+
- **Gli aiuti**: con «L'AI ti insegna mentre giochi», nelle impostazioni, la scheda **Aiuti** mostra i pezzi in pericolo, le inchiodature e i pedoni, e l'AI, da allenatore, ti dà un'idea. L'AI disegna frecce colorate sulla scacchiera: il **?** accanto alle mosse spiega ogni segno.
|
|
43
|
+
- **Gira** volta la scacchiera; **PGN** salva le mosse nel file che leggono tutti i siti di scacchi, da aprire su Lichess o Chess.com.
|
|
44
|
+
|
|
45
|
+
## Il ripasso e l'analisi
|
|
46
|
+
|
|
47
|
+
- A partita finita premi **Analizza la partita**: l'AI la ripassa e mette sulle mosse i suoi giudizi e le sue note. Le leggi scorrendo le mosse con le frecce.
|
|
48
|
+
- **Analisi**, nell'intestazione, apre le tue analisi e carica una partita giocata altrove: incolla il suo PGN, o scegli un file .pgn.
|
|
49
|
+
- Chiedi all'AI della partita nella casella sotto il suo nome.
|
|
50
|
+
- **PGN annotato** salva la partita coi commenti.
|
|
51
|
+
|
|
52
|
+
## I giocatori e il profilo
|
|
53
|
+
|
|
54
|
+
- **Chi gioca?**: la tua immagine in alto a destra sceglie un giocatore o ne aggiunge uno. Ognuno ha le sue partite e le sue impostazioni.
|
|
55
|
+
- **Il tuo profilo**: un clic sulla tua immagine accanto alla scacchiera, o la matita in **Chi gioca?**, cambia la tua immagine, il nome, come giochi, i tuoi nomi su Lichess e Chess.com e il tuo punteggio FIDE.
|
|
56
|
+
- **Il tuo punteggio**: dai tuoi nomi lì, Chesspal legge il tuo punteggio pubblico, quello delle partite rapide se le giochi, e lo dice all'AI. Niente password: quel punteggio lo vede chiunque sul sito. Senza un nome lì, l'AI sa come giochi da **Come giochi?**
|
|
57
|
+
- **Il punteggio FIDE**, se giochi tornei, lo scrivi tu: nessun sito lo dà a Chesspal. L'AI lo legge accanto a quello del sito, ognuno per sé.
|
|
58
|
+
- Se carichi una partita giocata su Lichess o Chess.com, Chesspal sa da che colore giocavi tu.
|
|
59
|
+
|
|
60
|
+
## Domande e problemi
|
|
61
|
+
|
|
62
|
+
- **L'AI è lenta**: ogni mossa la pensa davvero. Scegli un modello più veloce, o meno ragionamento, accanto ai pulsanti della chat.
|
|
63
|
+
- **Quanto costa**: Chesspal è gratis; l'AI usa il tuo abbonamento, e non paghi niente in più.
|
|
64
|
+
- **L'AI può fare una mossa illegale?** No: Chesspal controlla ogni mossa. I suoi giudizi però sono suoi, e un'AI può sbagliare, come una persona.
|
|
65
|
+
- **Un backup**: copia la cartella delle tue partite, con la chat e la pagina chiuse.
|
|
66
|
+
- **Qualcosa non va?** Raccontacelo su [GitHub](https://github.com/stefanocaronia/chesspal/issues): cosa hai fatto, cosa ti aspettavi, cosa è successo. GitHub è il sito dove si fa Chesspal: ci scrivi con un account gratuito, e chiunque può leggere.
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
|
+
/** Blocks drawn as the page's elements: headings one level under the part's, each with the anchor given by its place among them; a link to an anchor calls go. */
|
|
3
|
+
export function Blocks({ blocks, anchors, go }) {
|
|
4
|
+
return _jsx(_Fragment, { children: blocks.map((block, index) => _jsx(BlockView, { block: block, anchors: anchors, go: go }, index)) });
|
|
5
|
+
}
|
|
6
|
+
/** A line's text with its bold, italic, code and links. */
|
|
7
|
+
export function Inline({ text, go }) {
|
|
8
|
+
const parts = [];
|
|
9
|
+
const marks = /\*\*(.+?)\*\*|\*(.+?)\*|`([^`]+)`|\[([^\]]+)\]\(([^)\s]+)\)/g;
|
|
10
|
+
let from = 0;
|
|
11
|
+
for (const match of text.matchAll(marks)) {
|
|
12
|
+
parts.push(text.slice(from, match.index));
|
|
13
|
+
const [, bold, italic, code, label, target] = match;
|
|
14
|
+
const key = match.index;
|
|
15
|
+
if (bold !== undefined) {
|
|
16
|
+
parts.push(_jsx("strong", { children: _jsx(Inline, { text: bold, go: go }) }, key));
|
|
17
|
+
}
|
|
18
|
+
else if (italic !== undefined) {
|
|
19
|
+
parts.push(_jsx("em", { children: _jsx(Inline, { text: italic, go: go }) }, key));
|
|
20
|
+
}
|
|
21
|
+
else if (code !== undefined) {
|
|
22
|
+
parts.push(_jsx("code", { children: code }, key));
|
|
23
|
+
}
|
|
24
|
+
else {
|
|
25
|
+
parts.push(_jsx(LinkView, { label: label ?? "", target: target ?? "", go: go }, key));
|
|
26
|
+
}
|
|
27
|
+
from = match.index + match[0].length;
|
|
28
|
+
}
|
|
29
|
+
parts.push(text.slice(from));
|
|
30
|
+
return _jsx(_Fragment, { children: parts });
|
|
31
|
+
}
|
|
32
|
+
function BlockView({ block, anchors, go }) {
|
|
33
|
+
switch (block.kind) {
|
|
34
|
+
case "heading": {
|
|
35
|
+
const Tag = `h${Math.min(block.level + 1, 6)}`;
|
|
36
|
+
return (_jsx(Tag, { id: anchors.get(block), children: _jsx(Inline, { text: block.text, go: go }) }));
|
|
37
|
+
}
|
|
38
|
+
case "paragraph":
|
|
39
|
+
return (_jsx("p", { children: _jsx(Inline, { text: block.text, go: go }) }));
|
|
40
|
+
case "list": {
|
|
41
|
+
const items = block.items.map((item, index) => (_jsx("li", { children: _jsx(Inline, { text: item, go: go }) }, index)));
|
|
42
|
+
return block.ordered ? _jsx("ol", { children: items }) : _jsx("ul", { children: items });
|
|
43
|
+
}
|
|
44
|
+
case "table":
|
|
45
|
+
return (_jsx("div", { class: "guide-table", children: _jsxs("table", { children: [_jsx("thead", { children: _jsx("tr", { children: block.head.map((cell, index) => (_jsx("th", { scope: "col", children: _jsx(Inline, { text: cell, go: go }) }, index))) }) }), _jsx("tbody", { children: block.rows.map((row, index) => (_jsx("tr", { children: row.map((cell, column) => (_jsx("td", { children: _jsx(Inline, { text: cell, go: go }) }, column))) }, index))) })] }) }));
|
|
46
|
+
case "quote":
|
|
47
|
+
return (_jsx("blockquote", { children: _jsx(Blocks, { blocks: block.blocks, anchors: anchors, go: go }) }));
|
|
48
|
+
case "code":
|
|
49
|
+
return _jsx("pre", { children: _jsx("code", { children: block.text }) });
|
|
50
|
+
case "rule":
|
|
51
|
+
return null;
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
/** A link: to a heading of the guide, which go brings into sight, leaving the address as it is (it names the game); to a page of the web, in a new tab. */
|
|
55
|
+
function LinkView({ label, target, go }) {
|
|
56
|
+
if (target.startsWith("#")) {
|
|
57
|
+
return (_jsx("a", { href: target, onClick: (event) => {
|
|
58
|
+
event.preventDefault();
|
|
59
|
+
go(target.slice(1));
|
|
60
|
+
}, children: _jsx(Inline, { text: label, go: go }) }));
|
|
61
|
+
}
|
|
62
|
+
// Only a web address is a link, never a script.
|
|
63
|
+
if (!/^https?:\/\//i.test(target)) {
|
|
64
|
+
return _jsx(Inline, { text: label, go: go });
|
|
65
|
+
}
|
|
66
|
+
return (_jsx("a", { href: target, target: "_blank", rel: "noreferrer", children: _jsx(Inline, { text: label, go: go }) }));
|
|
67
|
+
}
|
|
@@ -146,8 +146,9 @@
|
|
|
146
146
|
stroke-dasharray: 0.22 0.11 0 0.11;
|
|
147
147
|
}
|
|
148
148
|
|
|
149
|
-
/* The map (Zoe,
|
|
150
|
-
|
|
149
|
+
/* The map (Zoe's veil, Stefano's choice): a grey veil flattens the board, sand where only White reaches, slate where only Black does; the squares both reach
|
|
150
|
+
and those none does stay grey, and their counts speak, together in the middle as a domino. Never an opaque ground: the square shows through. The drawings
|
|
151
|
+
and the pieces step back */
|
|
151
152
|
.board.mapping ~ .arrows,
|
|
152
153
|
.board.mapping .move-badge {
|
|
153
154
|
opacity: 0.12;
|
|
@@ -158,26 +159,22 @@
|
|
|
158
159
|
}
|
|
159
160
|
|
|
160
161
|
.board.mapping .piece {
|
|
161
|
-
opacity: 0.
|
|
162
|
+
opacity: 0.5;
|
|
162
163
|
}
|
|
163
164
|
|
|
164
165
|
.control-ground {
|
|
165
166
|
position: absolute;
|
|
166
167
|
inset: 0;
|
|
167
168
|
pointer-events: none;
|
|
168
|
-
background:
|
|
169
|
+
background: var(--map-veil);
|
|
169
170
|
}
|
|
170
171
|
|
|
171
172
|
.control-ground.white {
|
|
172
|
-
background:
|
|
173
|
+
background: var(--map-white);
|
|
173
174
|
}
|
|
174
175
|
|
|
175
176
|
.control-ground.black {
|
|
176
|
-
background:
|
|
177
|
-
}
|
|
178
|
-
|
|
179
|
-
.control-ground.both {
|
|
180
|
-
background: linear-gradient(135deg, #eef1f4 50%, #2b323c 50%);
|
|
177
|
+
background: var(--map-black);
|
|
181
178
|
}
|
|
182
179
|
|
|
183
180
|
.control-count {
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 32 32">
|
|
2
|
+
<!-- Chesspal, the favicon: the Torretta with its neutral face, on a grid of 16 pixels. © 2026 Stefano Caronia, Chesspal (https://github.com/stefanocaronia/chesspal), under the licence CC BY 4.0 (https://creativecommons.org/licenses/by/4.0/), crediting "Stefano Caronia, Chesspal". -->
|
|
3
|
+
<rect x="15" y="4" width="2" height="4" fill="#4b7cb0"/>
|
|
4
|
+
<rect x="14" y="0" width="4" height="4" rx="2" fill="#f4c84c"/>
|
|
5
|
+
<rect x="2" y="18" width="4" height="8" rx="1" fill="#4b7cb0"/>
|
|
6
|
+
<rect x="26" y="18" width="4" height="8" rx="1" fill="#4b7cb0"/>
|
|
7
|
+
<path d="M4 8H10V12H14V8H18V12H22V8H28V16L26 18V28H6V18L4 16Z" fill="#6d9fd1"/>
|
|
8
|
+
<rect x="2" y="28" width="28" height="4" rx="1" fill="#4b7cb0"/>
|
|
9
|
+
<rect x="8" y="14" width="16" height="12" rx="1.5" fill="#16263a"/>
|
|
10
|
+
<rect x="10" y="16" width="4" height="4" rx="1" fill="#f2f7fc"/>
|
|
11
|
+
<rect x="18" y="16" width="4" height="4" rx="1" fill="#f2f7fc"/>
|
|
12
|
+
<rect x="14" y="22" width="4" height="2" rx="1" fill="#f2f7fc"/>
|
|
13
|
+
</svg>
|
|
@@ -4,10 +4,10 @@ import { Torretta } from "../avatars/torretta.js";
|
|
|
4
4
|
/**
|
|
5
5
|
* The header, with how a game over ended, or that none goes on (D-77), or the AI's draw offer with the player's answer, the player's draw
|
|
6
6
|
* and resignation in a game going on (D-65), a new game once the page has the folder's, the command that turns the board round and says whether it is
|
|
7
|
-
* turned, the switches of the sounds and the theme,
|
|
7
|
+
* turned, the switches of the sounds and the theme, the gear of the settings (D-56) and the book of the guide.
|
|
8
8
|
*/
|
|
9
|
-
export function Header({ text, verdict, kind, who, actions, onNewGame, standOut = false, onStudy, flipped, onFlip, sound, onSound, onTheme, onSettings, profile, onProfile }) {
|
|
10
|
-
return (_jsxs("header", { class: "top", children: [_jsx(Torretta, { size: 34 }), _jsx("b", { class: "title", children: "Chesspal" }), _jsxs("p", { class: `verdict${kind === undefined ? "" : ` ${kind}`}`, role: "status", children: [verdict === undefined || kind === undefined ? null : _jsx(Icon, { class: "kind", children: kindIcons[kind] }), who === undefined || verdict === undefined ? verdict : (_jsxs(_Fragment, { children: [_jsx("span", { class: "ai-name", children: who }), _jsx("span", { children: verdict })] }))] }), actions === undefined ? null : _jsx(Actions, { actions: actions, text: text }), onNewGame === undefined ? null : (_jsxs("button", { class: standOut ? "command primary" : "command", type: "button", title: text.newGame, onClick: onNewGame, children: [_jsx(Icon, { class: "new", children: _jsx("path", { d: "M12 5V19M5 12H19" }) }), _jsx("span", { children: text.newGame })] })), onStudy === undefined ? null : (_jsxs("button", { class: "command", type: "button", title: text.study.title, onClick: onStudy, children: [_jsxs(Icon, { class: "study", children: [_jsx("circle", { cx: "10.5", cy: "10.5", r: "5.5" }), _jsx("path", { d: "M14.5 14.5L20 20" })] }), _jsx("span", { children: text.study.title })] })), _jsxs("button", { class: "command", type: "button", title: text.flip, "aria-pressed": flipped, onClick: onFlip, children: [_jsx(Icon, { class: "flip", children: _jsx("path", { d: "M7 4V17M7 17L4 14M7 17L10 14M17 20V7M17 7L14 10M17 7L20 10" }) }), _jsx("span", { children: text.flip })] }), _jsx("button", { class: "icon-button", type: "button", title: text.sounds, "aria-label": text.sounds, "aria-pressed": sound, onClick: onSound, children: _jsxs(Icon, { class: "speaker", children: [_jsx("path", { d: "M5 9.5H8.5L13 5.5V18.5L8.5 14.5H5Z" }), sound ? _jsx("path", { d: "M16.5 9A4 4 0 0 1 16.5 15M19 6.5A7.5 7.5 0 0 1 19 17.5" }) : _jsx("path", { d: "M15.5 9L21.5 15M21.5 9L15.5 15" })] }) }), _jsxs("button", { class: "icon-button", type: "button", title: text.theme, "aria-label": text.theme, onClick: onTheme, children: [_jsxs(Icon, { class: "sun", children: [_jsx("circle", { cx: "12", cy: "12", r: "4" }), _jsx("path", { d: "M12 2V4M12 20V22M2 12H4M20 12H22M4.9 4.9L6.3 6.3M17.7 17.7L19.1 19.1M4.9 19.1L6.3 17.7M17.7 6.3L19.1 4.9" })] }), _jsx(Icon, { class: "moon", children: _jsx("path", { d: "M20 14.5A8 8 0 1 1 9.5 4A6.5 6.5 0 0 0 20 14.5Z" }) })] }), _jsx("button", { class: "icon-button", type: "button", title: text.settings, "aria-label": text.settings, onClick: onSettings, children: _jsxs(Icon, { class: "gear", children: [_jsx("circle", { cx: "12", cy: "12", r: "3" }), _jsx("path", { d: "M9.72 5.38L10.19 2.67L13.81 2.67L14.28 5.38L15.07 5.71L17.31 4.12L19.88 6.69L18.29 8.93L18.62 9.72L21.33 10.19L21.33 13.81L18.62 14.28L18.29 15.07L19.88 17.31L17.31 19.88L15.07 18.29L14.28 18.62L13.81 21.33L10.19 21.33L9.72 18.62L8.93 18.29L6.69 19.88L4.12 17.31L5.71 15.07L5.38 14.28L2.67 13.81L2.67 10.19L5.38 9.72L5.71 8.93L4.12 6.69L6.69 4.12L8.93 5.71Z" })] }) }), profile === undefined || onProfile === undefined ? null : (_jsx("button", { class: "icon-button player", type: "button", title: `${profile.name}: ${text.profile.profiles}`, "aria-label": `${profile.name}: ${text.profile.profiles}`, "aria-haspopup": "dialog", onClick: onProfile, children: _jsx(PlayerPicture, { profile: profile, size: 28 }) }))] }));
|
|
9
|
+
export function Header({ text, verdict, kind, who, actions, onNewGame, standOut = false, onStudy, flipped, onFlip, sound, onSound, onTheme, onSettings, onGuide, profile, onProfile }) {
|
|
10
|
+
return (_jsxs("header", { class: "top", children: [_jsx(Torretta, { size: 34 }), _jsx("b", { class: "title", children: "Chesspal" }), _jsxs("p", { class: `verdict${kind === undefined ? "" : ` ${kind}`}`, role: "status", children: [verdict === undefined || kind === undefined ? null : _jsx(Icon, { class: "kind", children: kindIcons[kind] }), who === undefined || verdict === undefined ? verdict : (_jsxs(_Fragment, { children: [_jsx("span", { class: "ai-name", children: who }), _jsx("span", { children: verdict })] }))] }), actions === undefined ? null : _jsx(Actions, { actions: actions, text: text }), onNewGame === undefined ? null : (_jsxs("button", { class: standOut ? "command primary" : "command", type: "button", title: text.newGame, onClick: onNewGame, children: [_jsx(Icon, { class: "new", children: _jsx("path", { d: "M12 5V19M5 12H19" }) }), _jsx("span", { children: text.newGame })] })), onStudy === undefined ? null : (_jsxs("button", { class: "command", type: "button", title: text.study.title, onClick: onStudy, children: [_jsxs(Icon, { class: "study", children: [_jsx("circle", { cx: "10.5", cy: "10.5", r: "5.5" }), _jsx("path", { d: "M14.5 14.5L20 20" })] }), _jsx("span", { children: text.study.title })] })), _jsxs("button", { class: "command", type: "button", title: text.flip, "aria-pressed": flipped, onClick: onFlip, children: [_jsx(Icon, { class: "flip", children: _jsx("path", { d: "M7 4V17M7 17L4 14M7 17L10 14M17 20V7M17 7L14 10M17 7L20 10" }) }), _jsx("span", { children: text.flip })] }), _jsx("button", { class: "icon-button sound-button", type: "button", title: text.sounds, "aria-label": text.sounds, "aria-pressed": sound, onClick: onSound, children: _jsxs(Icon, { class: "speaker", children: [_jsx("path", { d: "M5 9.5H8.5L13 5.5V18.5L8.5 14.5H5Z" }), sound ? _jsx("path", { d: "M16.5 9A4 4 0 0 1 16.5 15M19 6.5A7.5 7.5 0 0 1 19 17.5" }) : _jsx("path", { d: "M15.5 9L21.5 15M21.5 9L15.5 15" })] }) }), _jsxs("button", { class: "icon-button theme-button", type: "button", title: text.theme, "aria-label": text.theme, onClick: onTheme, children: [_jsxs(Icon, { class: "sun", children: [_jsx("circle", { cx: "12", cy: "12", r: "4" }), _jsx("path", { d: "M12 2V4M12 20V22M2 12H4M20 12H22M4.9 4.9L6.3 6.3M17.7 17.7L19.1 19.1M4.9 19.1L6.3 17.7M17.7 6.3L19.1 4.9" })] }), _jsx(Icon, { class: "moon", children: _jsx("path", { d: "M20 14.5A8 8 0 1 1 9.5 4A6.5 6.5 0 0 0 20 14.5Z" }) })] }), _jsx("button", { class: "icon-button", type: "button", title: text.settings, "aria-label": text.settings, onClick: onSettings, children: _jsxs(Icon, { class: "gear", children: [_jsx("circle", { cx: "12", cy: "12", r: "3" }), _jsx("path", { d: "M9.72 5.38L10.19 2.67L13.81 2.67L14.28 5.38L15.07 5.71L17.31 4.12L19.88 6.69L18.29 8.93L18.62 9.72L21.33 10.19L21.33 13.81L18.62 14.28L18.29 15.07L19.88 17.31L17.31 19.88L15.07 18.29L14.28 18.62L13.81 21.33L10.19 21.33L9.72 18.62L8.93 18.29L6.69 19.88L4.12 17.31L5.71 15.07L5.38 14.28L2.67 13.81L2.67 10.19L5.38 9.72L5.71 8.93L4.12 6.69L6.69 4.12L8.93 5.71Z" })] }) }), _jsx("button", { class: "icon-button guide-button", type: "button", title: text.guide.title, "aria-label": text.guide.title, "aria-haspopup": "dialog", onClick: onGuide, children: _jsx(Icon, { class: "book", children: _jsx("path", { d: "M12 6.5C10 5 7 4.5 3.5 5V18.5C7 18 10 18.5 12 20C14 18.5 17 18 20.5 18.5V5C17 4.5 14 5 12 6.5ZM12 6.5V20" }) }) }), profile === undefined || onProfile === undefined ? null : (_jsx("button", { class: "icon-button player", type: "button", title: `${profile.name}: ${text.profile.profiles}`, "aria-label": `${profile.name}: ${text.profile.profiles}`, "aria-haspopup": "dialog", onClick: onProfile, children: _jsx(PlayerPicture, { profile: profile, size: 28 }) }))] }));
|
|
11
11
|
}
|
|
12
12
|
/** The answer to the AI's draw offer while it stands, else the player's own offer; then the resignation. */
|
|
13
13
|
function Actions({ actions, text }) {
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
|
@@ -553,6 +553,57 @@
|
|
|
553
553
|
flex: none;
|
|
554
554
|
}
|
|
555
555
|
|
|
556
|
+
/* Beside the board the talk is a row a side, each half the column, or what its card, note and box need when that is more: only the words give way, and
|
|
557
|
+
never spill over the other side (Stefano, Zoe). Turned round, the player's row comes first. In analysis the AI's note keeps its height while the thread
|
|
558
|
+
gives way, and scrolls by itself only when nothing else is left to give */
|
|
559
|
+
@media (min-width: 841px) {
|
|
560
|
+
.talk {
|
|
561
|
+
display: grid;
|
|
562
|
+
grid-template-rows: minmax(min-content, 1fr) minmax(min-content, 1fr);
|
|
563
|
+
container: talk / size;
|
|
564
|
+
}
|
|
565
|
+
|
|
566
|
+
.ai-block,
|
|
567
|
+
.you-block {
|
|
568
|
+
max-height: none;
|
|
569
|
+
}
|
|
570
|
+
|
|
571
|
+
/* The player's card stays by the board's edge when the words above it go */
|
|
572
|
+
.you-block {
|
|
573
|
+
justify-content: flex-end;
|
|
574
|
+
}
|
|
575
|
+
|
|
576
|
+
.flipped .ai-block {
|
|
577
|
+
grid-row: 2;
|
|
578
|
+
}
|
|
579
|
+
|
|
580
|
+
.flipped .you-block {
|
|
581
|
+
grid-row: 1;
|
|
582
|
+
}
|
|
583
|
+
|
|
584
|
+
/* The words take the room left, and ask none of their own */
|
|
585
|
+
.says,
|
|
586
|
+
.sent {
|
|
587
|
+
flex: 1 1 0;
|
|
588
|
+
contain: size;
|
|
589
|
+
}
|
|
590
|
+
|
|
591
|
+
.talk.studied {
|
|
592
|
+
grid-template-rows: minmax(0, max-content) minmax(min-content, 1fr);
|
|
593
|
+
overflow: hidden;
|
|
594
|
+
}
|
|
595
|
+
|
|
596
|
+
.flipped .talk.studied {
|
|
597
|
+
grid-template-rows: minmax(min-content, 1fr) minmax(0, max-content);
|
|
598
|
+
}
|
|
599
|
+
|
|
600
|
+
.talk.studied > .ai-block > .ai-note {
|
|
601
|
+
flex: 0 1 auto;
|
|
602
|
+
min-height: 0;
|
|
603
|
+
overflow-y: auto;
|
|
604
|
+
}
|
|
605
|
+
}
|
|
606
|
+
|
|
556
607
|
/* The game's column, which folds from its tab */
|
|
557
608
|
.side {
|
|
558
609
|
grid-area: side;
|
|
@@ -580,15 +631,22 @@
|
|
|
580
631
|
align-items: center;
|
|
581
632
|
}
|
|
582
633
|
|
|
583
|
-
/* Between games
|
|
634
|
+
/* Between games and in analysis the Torretta alone stands for the name, at every width, to leave the chip its room */
|
|
584
635
|
@media (min-width: 841px) {
|
|
585
|
-
.top:has(.verdict.idle, .verdict.over) .title {
|
|
636
|
+
.top:has(.verdict.idle, .verdict.over, .verdict.analysis) .title {
|
|
586
637
|
display: none;
|
|
587
638
|
}
|
|
588
639
|
|
|
589
|
-
.top:has(.verdict.idle, .verdict.over) .verdict {
|
|
640
|
+
.top:has(.verdict.idle, .verdict.over, .verdict.analysis) .verdict {
|
|
590
641
|
margin-right: auto;
|
|
591
642
|
}
|
|
643
|
+
|
|
644
|
+
/* Over a board of 760 px or less the sound and the theme leave their room to the chip, and stay in the settings; in a game they come back */
|
|
645
|
+
@container (max-width: 760px) {
|
|
646
|
+
.top:has(.verdict.idle, .verdict.over, .verdict.analysis) :is(.sound-button, .theme-button) {
|
|
647
|
+
display: none;
|
|
648
|
+
}
|
|
649
|
+
}
|
|
592
650
|
}
|
|
593
651
|
|
|
594
652
|
/* Beside the board, a column's content keeps its width and its outer edge while the column folds: it slides under the board, the text never flows again */
|
|
@@ -800,6 +858,23 @@
|
|
|
800
858
|
grid-template-areas: "head" "you" "board" "steps" "asleep" "note" "chat" "ai" "side";
|
|
801
859
|
}
|
|
802
860
|
|
|
861
|
+
/* With the helps on, their map under the steps: an area the grid lacks would put it in a column of its own, off the screen */
|
|
862
|
+
.app:has(.talk.studied):has(.board-map) {
|
|
863
|
+
grid-template-areas: "head" "ai" "board" "steps" "map" "note" "chat" "you" "side";
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
.app.flipped:has(.talk.studied):has(.board-map) {
|
|
867
|
+
grid-template-areas: "head" "you" "board" "steps" "map" "note" "chat" "ai" "side";
|
|
868
|
+
}
|
|
869
|
+
|
|
870
|
+
.app:has(.talk.studied > .ai-block > .asleep):has(.board-map) {
|
|
871
|
+
grid-template-areas: "head" "ai" "board" "steps" "map" "asleep" "note" "chat" "you" "side";
|
|
872
|
+
}
|
|
873
|
+
|
|
874
|
+
.app.flipped:has(.talk.studied > .ai-block > .asleep):has(.board-map) {
|
|
875
|
+
grid-template-areas: "head" "you" "board" "steps" "map" "asleep" "note" "chat" "ai" "side";
|
|
876
|
+
}
|
|
877
|
+
|
|
803
878
|
.talk.studied > .ai-block > .asleep {
|
|
804
879
|
grid-area: asleep;
|
|
805
880
|
margin: 0;
|
|
@@ -834,12 +909,18 @@
|
|
|
834
909
|
box-shadow: 0 8px 24px rgb(0 0 0 / 16%);
|
|
835
910
|
}
|
|
836
911
|
|
|
837
|
-
/* On a phone the header's first line holds the Torretta, which stands for the name, and every button down to the player's picture, a little closer
|
|
912
|
+
/* On a phone the header's first line holds the Torretta, which stands for the name, and every button down to the player's picture, a little closer; the
|
|
913
|
+
sound and the theme stay in the settings */
|
|
838
914
|
@media (max-width: 420px) {
|
|
839
915
|
.top {
|
|
840
916
|
column-gap: 4px;
|
|
841
917
|
}
|
|
842
918
|
|
|
919
|
+
.top .sound-button,
|
|
920
|
+
.top .theme-button {
|
|
921
|
+
display: none;
|
|
922
|
+
}
|
|
923
|
+
|
|
843
924
|
.top .title {
|
|
844
925
|
display: none;
|
|
845
926
|
}
|