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.
Files changed (184) hide show
  1. package/README.md +11 -12
  2. package/dist/cli/cli.js +83 -3
  3. package/dist/cli/command-line.js +26 -9
  4. package/dist/cli/icon.d.ts +25 -0
  5. package/dist/cli/icon.js +80 -0
  6. package/dist/commands/agents.d.ts +19 -2
  7. package/dist/commands/agents.js +39 -13
  8. package/dist/commands/ai-say.js +3 -4
  9. package/dist/commands/analyse-game.d.ts +4 -0
  10. package/dist/commands/analyse-game.js +46 -13
  11. package/dist/commands/annotate-game.d.ts +2 -0
  12. package/dist/commands/annotate-game.js +40 -18
  13. package/dist/commands/answer-lines.d.ts +2 -0
  14. package/dist/commands/answer-lines.js +13 -3
  15. package/dist/commands/coach.js +4 -2
  16. package/dist/commands/context.d.ts +11 -1
  17. package/dist/commands/context.js +5 -0
  18. package/dist/commands/delete-review.js +9 -5
  19. package/dist/commands/game-summary.d.ts +2 -0
  20. package/dist/commands/game-summary.js +4 -0
  21. package/dist/commands/index.d.ts +3 -2
  22. package/dist/commands/index.js +7 -4
  23. package/dist/commands/init.js +2 -4
  24. package/dist/commands/instructions.d.ts +9 -0
  25. package/dist/commands/instructions.js +30 -0
  26. package/dist/commands/lead-input.d.ts +1 -1
  27. package/dist/commands/lead-input.js +7 -4
  28. package/dist/commands/page-link.js +6 -5
  29. package/dist/commands/past-games.d.ts +1 -1
  30. package/dist/commands/past-games.js +12 -5
  31. package/dist/commands/player-move.js +2 -3
  32. package/dist/commands/profiles.d.ts +23 -9
  33. package/dist/commands/profiles.js +76 -35
  34. package/dist/commands/ratings.d.ts +8 -0
  35. package/dist/commands/ratings.js +45 -0
  36. package/dist/commands/registry.d.ts +1 -1
  37. package/dist/commands/registry.js +3 -2
  38. package/dist/commands/say.d.ts +1 -1
  39. package/dist/commands/say.js +1 -1
  40. package/dist/commands/status.d.ts +3 -1
  41. package/dist/commands/status.js +46 -27
  42. package/dist/commands/study.js +8 -5
  43. package/dist/core/index.d.ts +1 -1
  44. package/dist/core/index.js +1 -1
  45. package/dist/core/review.d.ts +2 -0
  46. package/dist/core/review.js +5 -1
  47. package/dist/games/ai-model.d.ts +4 -2
  48. package/dist/games/ai-model.js +10 -1
  49. package/dist/games/analysis.d.ts +26 -3
  50. package/dist/games/analysis.js +41 -6
  51. package/dist/games/archive.d.ts +6 -3
  52. package/dist/games/archive.js +13 -5
  53. package/dist/games/current-game.d.ts +10 -1
  54. package/dist/games/current-game.js +49 -15
  55. package/dist/games/files.d.ts +5 -0
  56. package/dist/games/files.js +42 -1
  57. package/dist/games/index.d.ts +3 -3
  58. package/dist/games/index.js +3 -3
  59. package/dist/home/data-folder.d.ts +2 -0
  60. package/dist/home/data-folder.js +5 -1
  61. package/dist/home/index.d.ts +1 -1
  62. package/dist/home/index.js +1 -1
  63. package/dist/host-files/agents.d.ts +2 -2
  64. package/dist/host-files/agents.js +12 -6
  65. package/dist/host-files/host-files.d.ts +5 -3
  66. package/dist/host-files/host-files.js +15 -8
  67. package/dist/host-files/index.d.ts +2 -2
  68. package/dist/host-files/index.js +2 -2
  69. package/dist/page/app/app.js +49 -20
  70. package/dist/page/app/title.js +17 -0
  71. package/dist/page/avatars/torretta.js +4 -4
  72. package/dist/page/dialogs/analyses.js +4 -4
  73. package/dist/page/dialogs/dialog.js +4 -1
  74. package/dist/page/dialogs/dialogs.css +84 -10
  75. package/dist/page/dialogs/profiles.js +96 -43
  76. package/dist/page/game/column.js +5 -1
  77. package/dist/page/game/plies.js +7 -4
  78. package/dist/page/guide/blocks.js +67 -0
  79. package/dist/page/guide/en.md +66 -0
  80. package/dist/page/guide/foot.js +18 -0
  81. package/dist/page/guide/guide.css +200 -0
  82. package/dist/page/guide/guide.js +89 -0
  83. package/dist/page/guide/it.md +66 -0
  84. package/dist/page/guide/markdown.js +67 -0
  85. package/dist/page/helps/helps.css +7 -10
  86. package/dist/page/layout/favicon.svg +13 -0
  87. package/dist/page/layout/header.js +3 -3
  88. package/dist/page/layout/icon.icns +0 -0
  89. package/dist/page/layout/icon.ico +0 -0
  90. package/dist/page/layout/icon.png +0 -0
  91. package/dist/page/layout/layout.css +85 -4
  92. package/dist/page/layout/window.js +45 -0
  93. package/dist/page/main.js +4 -1
  94. package/dist/page/players/card.js +1 -1
  95. package/dist/page/profile/profile.js +14 -9
  96. package/dist/page/profile/settings.js +9 -0
  97. package/dist/page/style.css +2 -0
  98. package/dist/page/table/table.css +123 -0
  99. package/dist/page/table/window.js +130 -0
  100. package/dist/page/talk/chat.css +23 -5
  101. package/dist/page/talk/talk.css +38 -26
  102. package/dist/page/talk/talk.js +6 -4
  103. package/dist/page/talk/voice.js +35 -9
  104. package/dist/page/text.js +131 -17
  105. package/dist/page/theme.css +7 -0
  106. package/dist/profiles/index.d.ts +2 -1
  107. package/dist/profiles/index.js +2 -1
  108. package/dist/profiles/profiles.d.ts +10 -15
  109. package/dist/profiles/profiles.js +30 -15
  110. package/dist/profiles/ratings.d.ts +35 -0
  111. package/dist/profiles/ratings.js +61 -0
  112. package/dist/server/bell.d.ts +24 -3
  113. package/dist/server/bell.js +84 -10
  114. package/dist/server/caller.d.ts +2 -1
  115. package/dist/server/caller.js +3 -2
  116. package/dist/server/mcp.d.ts +7 -3
  117. package/dist/server/mcp.js +100 -52
  118. package/dist/server/page/analyses.d.ts +2 -0
  119. package/dist/server/page/analyses.js +20 -5
  120. package/dist/server/page/attach.d.ts +4 -0
  121. package/dist/server/page/attach.js +112 -0
  122. package/dist/server/page/chat.d.ts +65 -0
  123. package/dist/server/page/chat.js +87 -0
  124. package/dist/server/page/events.d.ts +7 -0
  125. package/dist/server/page/events.js +20 -0
  126. package/dist/server/page/files.js +10 -1
  127. package/dist/server/page/game.js +43 -23
  128. package/dist/server/page/page.d.ts +51 -7
  129. package/dist/server/page/page.js +74 -48
  130. package/dist/server/page/profiles.d.ts +5 -0
  131. package/dist/server/page/profiles.js +26 -10
  132. package/dist/server/page/table.d.ts +58 -0
  133. package/dist/server/page/table.js +210 -0
  134. package/dist/server/table/browser.d.ts +6 -0
  135. package/dist/server/table/browser.js +48 -0
  136. package/dist/server/table/hosts/chats.d.ts +7 -0
  137. package/dist/server/table/hosts/chats.js +34 -0
  138. package/dist/server/table/hosts/find.d.ts +2 -0
  139. package/dist/server/table/hosts/find.js +20 -0
  140. package/dist/server/table/hosts/hosts.d.ts +34 -0
  141. package/dist/server/table/hosts/hosts.js +49 -0
  142. package/dist/server/table/hosts/index.d.ts +3 -0
  143. package/dist/server/table/hosts/index.js +4 -0
  144. package/dist/server/table/hosts/play.d.ts +29 -0
  145. package/dist/server/table/hosts/play.js +180 -0
  146. package/dist/server/table/hosts/seat.d.ts +33 -0
  147. package/dist/server/table/hosts/seat.js +80 -0
  148. package/dist/server/table/hosts/terminal.d.ts +6 -0
  149. package/dist/server/table/hosts/terminal.js +63 -0
  150. package/dist/server/table/icon/index.d.ts +32 -0
  151. package/dist/server/table/icon/index.js +83 -0
  152. package/dist/server/table/icon/linux.d.ts +5 -0
  153. package/dist/server/table/icon/linux.js +47 -0
  154. package/dist/server/table/icon/macos.d.ts +3 -0
  155. package/dist/server/table/icon/macos.js +38 -0
  156. package/dist/server/table/icon/windows.d.ts +3 -0
  157. package/dist/server/table/icon/windows.js +48 -0
  158. package/dist/server/table/index.d.ts +6 -0
  159. package/dist/server/table/index.js +7 -0
  160. package/dist/server/table/launch.d.ts +19 -0
  161. package/dist/server/table/launch.js +78 -0
  162. package/dist/server/table/link.d.ts +10 -0
  163. package/dist/server/table/link.js +231 -0
  164. package/dist/server/table/table.d.ts +35 -0
  165. package/dist/server/table/table.js +94 -0
  166. package/dist/sites/chesscom.d.ts +4 -0
  167. package/dist/sites/chesscom.js +25 -0
  168. package/dist/sites/choice.d.ts +17 -0
  169. package/dist/sites/choice.js +13 -0
  170. package/dist/sites/index.d.ts +2 -0
  171. package/dist/sites/index.js +4 -0
  172. package/dist/sites/lichess.d.ts +4 -0
  173. package/dist/sites/lichess.js +32 -0
  174. package/dist/sites/request.d.ts +11 -0
  175. package/dist/sites/request.js +15 -0
  176. package/dist/sites/sites.d.ts +6 -0
  177. package/dist/sites/sites.js +33 -0
  178. package/instructions/analysis.md +1 -1
  179. package/instructions/analyst-agent.md +1 -1
  180. package/instructions/coach.md +2 -1
  181. package/instructions/opponent.md +6 -6
  182. package/instructions/ponder.md +1 -0
  183. package/instructions/start.md +1 -0
  184. 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, after the round): every square on one neutral ground, then light where only White reaches, dark where only Black does, half and half
150
- when both do; the two counts together in the middle, as a domino. The drawings and the pieces step back */
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.45;
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: #8d96a1;
169
+ background: var(--map-veil);
169
170
  }
170
171
 
171
172
  .control-ground.white {
172
- background: #eef1f4;
173
+ background: var(--map-white);
173
174
  }
174
175
 
175
176
  .control-ground.black {
176
- background: #2b323c;
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, and the gear of the settings (D-56).
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 the chip is never cut: beside it the Torretta stands for the name at every width, and the flip keeps only its icon */
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
  }