chesspal 0.1.0 → 0.2.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 (211) hide show
  1. package/README.md +45 -82
  2. package/dist/cli/chat-hook.js +1 -1
  3. package/dist/cli/command-line.d.ts +1 -2
  4. package/dist/cli/command-line.js +2 -3
  5. package/dist/commands/agents.d.ts +26 -0
  6. package/dist/commands/agents.js +72 -0
  7. package/dist/commands/ai-move.d.ts +5 -14
  8. package/dist/commands/ai-move.js +28 -35
  9. package/dist/commands/ai-say.d.ts +3 -1
  10. package/dist/commands/ai-say.js +24 -6
  11. package/dist/commands/analyse-game.d.ts +4 -0
  12. package/dist/commands/analyse-game.js +43 -16
  13. package/dist/commands/annotate-game.d.ts +4 -0
  14. package/dist/commands/annotate-game.js +65 -12
  15. package/dist/commands/answer-lines.d.ts +28 -0
  16. package/dist/commands/answer-lines.js +144 -0
  17. package/dist/commands/coach.d.ts +45 -0
  18. package/dist/commands/coach.js +344 -0
  19. package/dist/commands/context.d.ts +16 -5
  20. package/dist/commands/context.js +1 -0
  21. package/dist/commands/delete-game.d.ts +7 -0
  22. package/dist/commands/delete-game.js +30 -0
  23. package/dist/commands/delete-review.d.ts +8 -0
  24. package/dist/commands/delete-review.js +34 -0
  25. package/dist/commands/drawings.d.ts +0 -2
  26. package/dist/commands/drawings.js +5 -3
  27. package/dist/commands/end-game.js +5 -2
  28. package/dist/commands/export-game.d.ts +1 -1
  29. package/dist/commands/export-game.js +4 -3
  30. package/dist/commands/game-summary.d.ts +21 -38
  31. package/dist/commands/game-summary.js +30 -167
  32. package/dist/commands/index.d.ts +15 -0
  33. package/dist/commands/index.js +47 -2
  34. package/dist/commands/init.d.ts +3 -3
  35. package/dist/commands/init.js +4 -4
  36. package/dist/commands/lead-input.d.ts +8 -0
  37. package/dist/commands/lead-input.js +21 -0
  38. package/dist/commands/look.js +4 -1
  39. package/dist/commands/new-game.d.ts +2 -3
  40. package/dist/commands/new-game.js +7 -4
  41. package/dist/commands/play-move.d.ts +8 -15
  42. package/dist/commands/play-move.js +5 -13
  43. package/dist/commands/player-move.d.ts +2 -6
  44. package/dist/commands/player-move.js +13 -6
  45. package/dist/commands/player-say.d.ts +1 -1
  46. package/dist/commands/player-say.js +1 -1
  47. package/dist/commands/ponder.d.ts +10 -0
  48. package/dist/commands/ponder.js +33 -21
  49. package/dist/commands/position.d.ts +1 -1
  50. package/dist/commands/position.js +3 -1
  51. package/dist/commands/profiles.d.ts +13 -4
  52. package/dist/commands/profiles.js +49 -15
  53. package/dist/commands/registry.d.ts +8 -3
  54. package/dist/commands/registry.js +12 -11
  55. package/dist/commands/say.d.ts +1 -1
  56. package/dist/commands/say.js +16 -16
  57. package/dist/commands/speech.d.ts +8 -0
  58. package/dist/commands/speech.js +20 -0
  59. package/dist/commands/status.d.ts +9 -3
  60. package/dist/commands/status.js +40 -15
  61. package/dist/commands/study-games.js +1 -1
  62. package/dist/commands/study.js +3 -19
  63. package/dist/core/facts.js +15 -15
  64. package/dist/core/game.d.ts +3 -1
  65. package/dist/core/game.js +8 -4
  66. package/dist/core/illegal.js +1 -1
  67. package/dist/core/index.d.ts +5 -4
  68. package/dist/core/index.js +5 -4
  69. package/dist/core/lead.d.ts +15 -0
  70. package/dist/core/lead.js +40 -0
  71. package/dist/core/levels.d.ts +1 -1
  72. package/dist/core/levels.js +1 -1
  73. package/dist/core/moods.d.ts +0 -2
  74. package/dist/core/moods.js +2 -2
  75. package/dist/core/openings.d.ts +0 -2
  76. package/dist/core/openings.js +1 -1
  77. package/dist/core/profile.js +1 -1
  78. package/dist/core/record.d.ts +1 -1
  79. package/dist/core/record.js +1 -1
  80. package/dist/core/review.d.ts +18 -3
  81. package/dist/core/review.js +52 -9
  82. package/dist/games/ai-model.d.ts +4 -0
  83. package/dist/games/ai-model.js +9 -0
  84. package/dist/games/analysis.d.ts +1 -1
  85. package/dist/games/archive.d.ts +25 -4
  86. package/dist/games/archive.js +99 -25
  87. package/dist/games/coach.d.ts +57 -0
  88. package/dist/games/coach.js +144 -0
  89. package/dist/games/current-game.d.ts +2 -0
  90. package/dist/games/current-game.js +7 -1
  91. package/dist/games/files.js +1 -0
  92. package/dist/games/index.d.ts +5 -4
  93. package/dist/games/index.js +5 -4
  94. package/dist/games/talk.d.ts +1 -5
  95. package/dist/games/talk.js +14 -14
  96. package/dist/home/open-folder.js +1 -1
  97. package/dist/host-files/{ponder-agents.d.ts → agents.d.ts} +10 -2
  98. package/dist/host-files/agents.js +53 -0
  99. package/dist/host-files/host-files.d.ts +7 -2
  100. package/dist/host-files/host-files.js +27 -12
  101. package/dist/host-files/index.d.ts +2 -2
  102. package/dist/host-files/index.js +3 -3
  103. package/dist/index.d.ts +1 -3
  104. package/dist/index.js +1 -0
  105. package/dist/page/app/anchor.js +88 -0
  106. package/dist/page/{app.js → app/app.js} +116 -231
  107. package/dist/page/app/keys.js +94 -0
  108. package/dist/page/app/phase.js +30 -0
  109. package/dist/page/app/served.js +73 -0
  110. package/dist/page/avatars/avatars.js +19 -0
  111. package/dist/page/avatars/thought.js +10 -0
  112. package/dist/page/base.css +160 -0
  113. package/dist/page/board/board.css +959 -0
  114. package/dist/page/board/board.js +125 -0
  115. package/dist/page/{explain.js → board/explain.js} +1 -1
  116. package/dist/page/board/marks.js +160 -0
  117. package/dist/page/board/motion.js +71 -0
  118. package/dist/page/board/pointer.js +239 -0
  119. package/dist/page/board/squares.js +26 -0
  120. package/dist/page/dialogs/analyses.js +195 -0
  121. package/dist/page/{dialog.js → dialogs/dialog.js} +1 -1
  122. package/dist/page/dialogs/dialogs.css +1228 -0
  123. package/dist/page/{new-game-dialog.js → dialogs/new-game.js} +3 -3
  124. package/dist/page/{profile-dialogs.js → dialogs/profiles.js} +40 -11
  125. package/dist/page/dialogs/search.js +31 -0
  126. package/dist/page/dialogs/settings.js +42 -0
  127. package/dist/page/dialogs/studied.js +39 -0
  128. package/dist/page/dialogs/study.js +51 -0
  129. package/dist/page/{badges.js → game/badges.js} +1 -1
  130. package/dist/page/{browse.js → game/browse.js} +1 -1
  131. package/dist/page/game/card-fold.js +40 -0
  132. package/dist/page/game/column.js +55 -0
  133. package/dist/page/{ending.js → game/ending.js} +2 -2
  134. package/dist/page/game/game.css +577 -0
  135. package/dist/page/{move-list.js → game/move-list.js} +10 -6
  136. package/dist/page/{plies.js → game/plies.js} +30 -10
  137. package/dist/page/game/result-tile.js +14 -0
  138. package/dist/page/helps/helps.css +689 -0
  139. package/dist/page/helps/helps.js +220 -0
  140. package/dist/page/helps/view.js +127 -0
  141. package/dist/page/layout/fold-tab.js +11 -0
  142. package/dist/page/{folds.js → layout/folds.js} +1 -1
  143. package/dist/page/{header.js → layout/header.js} +5 -4
  144. package/dist/page/layout/layout.css +834 -0
  145. package/dist/page/lead/lead.js +82 -0
  146. package/dist/page/lead/view.js +38 -0
  147. package/dist/page/main.js +2 -2
  148. package/dist/page/pieces/CREDITS.md +12 -29
  149. package/dist/page/pieces/capablanca-small.svg +20 -0
  150. package/dist/page/pieces/capablanca.svg +22 -0
  151. package/dist/page/pieces/nitida-small.svg +3 -2
  152. package/dist/page/pieces/nitida.svg +3 -2
  153. package/dist/page/{pieces.js → pieces/pieces.js} +2 -2
  154. package/dist/page/pieces/torretta-small.svg +15 -14
  155. package/dist/page/pieces/torretta.svg +17 -15
  156. package/dist/page/{player-card.js → players/card.js} +2 -6
  157. package/dist/page/{clock-time.js → players/clock-time.js} +2 -2
  158. package/dist/page/{profile.js → profile/profile.js} +9 -5
  159. package/dist/page/{settings.js → profile/settings.js} +5 -4
  160. package/dist/page/style.css +9 -4398
  161. package/dist/page/talk/talk.css +958 -0
  162. package/dist/page/{talk.js → talk/talk.js} +39 -46
  163. package/dist/page/text.js +137 -19
  164. package/dist/page/theme.css +292 -0
  165. package/dist/profiles/index.d.ts +1 -1
  166. package/dist/profiles/profiles.d.ts +15 -4
  167. package/dist/profiles/profiles.js +24 -7
  168. package/dist/server/bell.d.ts +10 -2
  169. package/dist/server/bell.js +28 -12
  170. package/dist/server/caller.d.ts +14 -3
  171. package/dist/server/caller.js +44 -11
  172. package/dist/server/mcp.d.ts +2 -3
  173. package/dist/server/mcp.js +53 -19
  174. package/dist/server/page/analyses.d.ts +23 -0
  175. package/dist/server/page/analyses.js +65 -0
  176. package/dist/server/page/files.d.ts +5 -0
  177. package/dist/server/page/files.js +92 -0
  178. package/dist/server/page/game.d.ts +6 -0
  179. package/dist/server/page/game.js +381 -0
  180. package/dist/server/{http.d.ts → page/http.d.ts} +13 -0
  181. package/dist/server/{http.js → page/http.js} +11 -0
  182. package/dist/server/{page.d.ts → page/page.d.ts} +8 -13
  183. package/dist/server/page/page.js +229 -0
  184. package/dist/server/{profile-routes.d.ts → page/profiles.d.ts} +2 -2
  185. package/dist/server/{profile-routes.js → page/profiles.js} +47 -18
  186. package/instructions/analysis.md +4 -0
  187. package/instructions/analyst-agent.md +8 -0
  188. package/instructions/analyst.md +3 -7
  189. package/instructions/coach.md +11 -0
  190. package/instructions/opponent.md +4 -4
  191. package/package.json +1 -1
  192. package/dist/host-files/ponder-agents.js +0 -40
  193. package/dist/page/avatars.js +0 -16
  194. package/dist/page/board.js +0 -566
  195. package/dist/page/fold-tab.js +0 -9
  196. package/dist/page/game-column.js +0 -44
  197. package/dist/page/helps-view.js +0 -51
  198. package/dist/page/helps.js +0 -90
  199. package/dist/page/pieces/cburnett-small.svg +0 -20
  200. package/dist/page/pieces/cburnett.svg +0 -20
  201. package/dist/page/settings-panel.js +0 -32
  202. package/dist/page/studied.js +0 -14
  203. package/dist/page/study-dialog.js +0 -58
  204. package/dist/server/analyses.d.ts +0 -17
  205. package/dist/server/analyses.js +0 -46
  206. package/dist/server/page.js +0 -657
  207. /package/dist/page/{torretta.js → avatars/torretta.js} +0 -0
  208. /package/dist/page/{opening-book.js → game/opening-book.js} +0 -0
  209. /package/dist/page/{model-name.js → players/model-name.js} +0 -0
  210. /package/dist/page/{sounds.js → sounds/sounds.js} +0 -0
  211. /package/dist/page/{voice.js → talk/voice.js} +0 -0
@@ -0,0 +1,834 @@
1
+ /* The page's grid: the header, the board between the two columns, how they fold away, and the grid at each width. Its widths, 840 and 1099px, are also
2
+ in layout/folds.ts: change them together */
3
+
4
+ /* The header */
5
+ .top {
6
+ grid-area: head;
7
+ height: var(--bar);
8
+ margin-top: var(--between);
9
+ container-type: inline-size;
10
+ display: flex;
11
+ align-items: center;
12
+ gap: 10px;
13
+ padding: 0 var(--gap);
14
+ }
15
+
16
+ /* In the page's grid the header spans the board, edge to edge, as far from it as the cards */
17
+ .app .top {
18
+ margin: 0;
19
+ padding: 0;
20
+ }
21
+
22
+ .title {
23
+ flex: 1;
24
+ font-size: 17px;
25
+ font-weight: 600;
26
+ letter-spacing: 0.01em;
27
+ }
28
+
29
+ /* A game over says how it ended by the new game, which stands out in the accent */
30
+ .verdict {
31
+ margin: 0;
32
+ min-width: 0;
33
+ overflow: hidden;
34
+ white-space: nowrap;
35
+ text-overflow: ellipsis;
36
+ border-radius: 999px;
37
+ padding: 6px 12px;
38
+ background: color-mix(in srgb, var(--accent) 14%, transparent);
39
+ color: var(--ink);
40
+ font-size: 14px;
41
+ font-weight: 600;
42
+ animation: appear 200ms;
43
+ }
44
+
45
+ .verdict:empty {
46
+ display: none;
47
+ }
48
+
49
+ /* The chip's icon says what it is about; the game in analysis stands out in the accent's colour (D-69) */
50
+ .verdict:has(.icon) {
51
+ display: flex;
52
+ align-items: center;
53
+ gap: 6px;
54
+ }
55
+
56
+ .verdict .icon {
57
+ flex: none;
58
+ width: 16px;
59
+ height: 16px;
60
+ stroke-width: 2;
61
+ }
62
+
63
+ .verdict.analysis {
64
+ background: color-mix(in srgb, var(--accent) 22%, transparent);
65
+ color: var(--ink);
66
+ }
67
+
68
+ /* The answer to the AI's offer: accepting and declining weigh the same */
69
+ .command.answer {
70
+ color: var(--ink);
71
+ border-color: var(--line);
72
+ }
73
+
74
+ /* While the AI's draw offer stands, the answer keeps its words and the other commands only their icons; in a game going on, a narrow board keeps icons alone */
75
+ .top:has(.answer) .command:not(.answer) span {
76
+ display: none;
77
+ }
78
+
79
+ @container (max-width: 800px) {
80
+ .top:has(.icon.resign) .command:not(.answer) span {
81
+ display: none;
82
+ }
83
+
84
+ /* Beside a narrow board the offer and its answer take the room of the other commands, which come back once it is answered */
85
+ .top:has(.answer) .command:not(.answer) {
86
+ display: none;
87
+ }
88
+ }
89
+
90
+ /* The AI's name gives way before its verb (D-65) */
91
+ .verdict:has(.ai-name) {
92
+ display: flex;
93
+ gap: 0.3em;
94
+ }
95
+
96
+ .verdict .ai-name {
97
+ min-width: 0;
98
+ overflow: hidden;
99
+ text-overflow: ellipsis;
100
+ }
101
+
102
+ .verdict .ai-name + span {
103
+ flex-shrink: 0;
104
+ }
105
+
106
+ .top .torretta {
107
+ flex-shrink: 0;
108
+ }
109
+
110
+ /* The header's buttons never wrap or shrink: the word on a game over gives way */
111
+ .top .command,
112
+ .top .icon-button {
113
+ flex-shrink: 0;
114
+ white-space: nowrap;
115
+ }
116
+
117
+ /* The player's picture, round and borderless, at the end of the header: it opens the menu of the players */
118
+ .top .player {
119
+ border-color: transparent;
120
+ border-radius: 50%;
121
+ padding: 0;
122
+ }
123
+
124
+ .top .player:hover,
125
+ .top .player:focus-visible {
126
+ box-shadow: 0 0 0 2px var(--line);
127
+ }
128
+
129
+ /* The header's chip when no game goes on: neutral, not the accent, with a pause sign (D-77) */
130
+ .verdict.idle {
131
+ background: var(--card);
132
+ box-shadow: inset 0 0 0 1px var(--line);
133
+ color: var(--ink);
134
+ }
135
+
136
+ .verdict.idle .icon {
137
+ color: var(--soft);
138
+ stroke-width: 2.4;
139
+ }
140
+
141
+ /* Beside the word on a game over, the Torretta alone stands for the name, and the flip keeps only its icon; a phone gives the word a line of its own */
142
+ @media (min-width: 841px) {
143
+ @container (max-width: 720px) {
144
+ .top:has(.verdict:not(:empty)) .title,
145
+ .top:has(.verdict:not(:empty)) .command:not(.primary):not(.answer) span {
146
+ display: none;
147
+ }
148
+
149
+ .top:has(.verdict:not(:empty)) .verdict {
150
+ margin-right: auto;
151
+ }
152
+ }
153
+ }
154
+
155
+ .top:has(.verdict.idle, .verdict.over) .command:has(.icon.flip) span {
156
+ display: none;
157
+ }
158
+
159
+ /* On a phone the player's card is under the board, below the fold: between games the header's new game stands out too, on the first screen */
160
+ @media (max-width: 840px) {
161
+ .top:has(.verdict.idle, .verdict.over) .command:has(.icon.new) {
162
+ background: var(--accent);
163
+ border-color: var(--accent);
164
+ color: var(--on-accent);
165
+ }
166
+ }
167
+
168
+ /* Three columns: the talk, the board, the game, under the header, which spans the board. The side columns take the board's height and scroll inside it; folded,
169
+ they slide under the board, which grows into the room */
170
+ .app {
171
+ --left-now: var(--left);
172
+ --right-now: var(--right);
173
+ --rows: var(--between);
174
+ display: grid;
175
+ gap: var(--rows) var(--between);
176
+ padding: var(--between) var(--gap);
177
+ grid-template-columns: var(--left-now) auto var(--right-now);
178
+ transition: grid-template-columns 200ms cubic-bezier(0.2, 0, 0, 1);
179
+ grid-template-rows: auto minmax(var(--least), auto);
180
+ grid-template-areas: ". head ." "talk board side";
181
+ justify-content: center;
182
+ }
183
+
184
+ /* Beside a narrow board the header's buttons keep only their icons */
185
+ @container (max-width: 560px) {
186
+ .command span {
187
+ display: none;
188
+ }
189
+ }
190
+
191
+ .app.talk-rail {
192
+ --left-now: var(--rail);
193
+ }
194
+
195
+ .app.talk-folded {
196
+ --left-now: 0px;
197
+ }
198
+
199
+ .app.folded {
200
+ --right-now: 0px;
201
+ }
202
+
203
+ .board-wrap {
204
+ grid-area: board;
205
+ position: relative;
206
+ /* 12.5cqw is a square, for the lifted piece too */
207
+ container-type: inline-size;
208
+ width: min(calc(100vw - var(--left-now) - var(--right-now) - 2 * var(--gap) - 2 * var(--between)), max(var(--least), calc(100vh - var(--bar) - 3 * var(--between))));
209
+ transition: width 200ms cubic-bezier(0.2, 0, 0, 1);
210
+ }
211
+
212
+ .talk {
213
+ grid-area: talk;
214
+ display: flex;
215
+ flex-direction: column;
216
+ justify-content: space-between;
217
+ gap: var(--between);
218
+ contain: size;
219
+ min-height: 0;
220
+ }
221
+
222
+ /* Narrowing, the talk fades into its card of faces while it slides under the board, in the same 200ms */
223
+ .app.talk-rail .talk,
224
+ .app.talk-folded .talk {
225
+ visibility: hidden;
226
+ opacity: 0;
227
+ transition: opacity 120ms 80ms, visibility 0s 200ms;
228
+ }
229
+
230
+ /* A column's tab, a drawer's handle at the top: open, on the outer edge of the column's first card; folded, the column slides under the board and the
231
+ tab peeks out from under it */
232
+ .fold-tab {
233
+ align-self: start;
234
+ position: relative;
235
+ display: flex;
236
+ width: var(--tab);
237
+ height: 48px;
238
+ margin-top: calc(var(--ear) - 24px);
239
+ transition: width 200ms cubic-bezier(0.2, 0, 0, 1), margin 200ms cubic-bezier(0.2, 0, 0, 1);
240
+ }
241
+
242
+ .fold-tab.folded {
243
+ width: calc(var(--tab) + var(--tuck));
244
+ }
245
+
246
+ .fold-tab.of-talk {
247
+ grid-area: talk;
248
+ justify-self: start;
249
+ margin-left: calc(-1 * var(--tab));
250
+ }
251
+
252
+ .fold-tab.of-talk.folded {
253
+ margin-left: calc(var(--between) - var(--tab));
254
+ }
255
+
256
+ .fold-tab.of-game {
257
+ grid-area: side;
258
+ justify-self: end;
259
+ margin-right: calc(-1 * var(--tab));
260
+ }
261
+
262
+ .fold-tab.of-game.folded {
263
+ margin-right: calc(var(--between) - var(--tab));
264
+ }
265
+
266
+ .fold-tab button {
267
+ flex: 1;
268
+ position: relative;
269
+ display: grid;
270
+ place-items: center;
271
+ padding: 0;
272
+ background: var(--card);
273
+ border: 1px solid var(--line);
274
+ box-shadow: var(--shadow);
275
+ color: var(--soft);
276
+ }
277
+
278
+ .fold-tab button:hover {
279
+ color: var(--ink);
280
+ }
281
+
282
+ .fold-tab .icon {
283
+ width: 14px;
284
+ height: 14px;
285
+ stroke-width: 2;
286
+ }
287
+
288
+ /* Rounded on the outer side, its icon clear of the board; the card it leans on gives its inner edge */
289
+ .fold-tab.of-talk button {
290
+ border-right: 0;
291
+ border-radius: var(--radius) 0 0 var(--radius);
292
+ }
293
+
294
+ .fold-tab.of-game button {
295
+ border-left: 0;
296
+ border-radius: 0 var(--radius) var(--radius) 0;
297
+ }
298
+
299
+ .fold-tab.of-talk.folded button {
300
+ padding-right: var(--tuck);
301
+ }
302
+
303
+ .fold-tab.of-game.folded button {
304
+ padding-left: var(--tuck);
305
+ }
306
+
307
+ /* The talk narrowed to a card beside the board, with two equal coins where the faces sat in the cards: the Torretta above and the player below, once the talk
308
+ has slid under the board */
309
+ .faces {
310
+ grid-area: talk;
311
+ justify-self: start;
312
+ display: none;
313
+ flex-direction: column;
314
+ justify-content: space-between;
315
+ align-items: center;
316
+ gap: 16px;
317
+ width: var(--rail);
318
+ min-height: 0;
319
+ overflow: hidden;
320
+ padding: calc(var(--ear) - var(--coin) / 2 - 1px) 0;
321
+ background: var(--card);
322
+ border: 1px solid var(--line);
323
+ border-radius: var(--radius);
324
+ box-shadow: var(--shadow);
325
+ }
326
+
327
+ .app.talk-rail .faces {
328
+ display: flex;
329
+ animation: faces-in 120ms 80ms both;
330
+ }
331
+
332
+ /* Folding, the card of faces slides under the board in its turn */
333
+ .app.talk-folded .faces {
334
+ display: flex;
335
+ visibility: hidden;
336
+ transition: visibility 0s 200ms;
337
+ }
338
+
339
+ .app.flipped .faces {
340
+ flex-direction: column-reverse;
341
+ }
342
+
343
+ @keyframes faces-in {
344
+ from {
345
+ opacity: 0;
346
+ }
347
+ }
348
+
349
+ /* Each face with the name and side written down the card from it: the lower one's run up to it */
350
+ .rail-player {
351
+ display: flex;
352
+ flex-direction: column;
353
+ align-items: center;
354
+ gap: 10px;
355
+ min-height: 0;
356
+ }
357
+
358
+ .rail-player.you,
359
+ .app.flipped .rail-player.ai {
360
+ flex-direction: column-reverse;
361
+ }
362
+
363
+ .app.flipped .rail-player.you {
364
+ flex-direction: column;
365
+ }
366
+
367
+ /* The lower name reads upwards, its letters' tops to the left */
368
+ .rail-player.you .rail-name,
369
+ .app.flipped .rail-player.ai .rail-name {
370
+ rotate: 180deg;
371
+ }
372
+
373
+ .app.flipped .rail-player.you .rail-name {
374
+ rotate: none;
375
+ }
376
+
377
+ .rail-name {
378
+ min-height: 0;
379
+ overflow: hidden;
380
+ writing-mode: vertical-rl;
381
+ white-space: nowrap;
382
+ text-overflow: ellipsis;
383
+ font-size: 13px;
384
+ font-weight: 600;
385
+ }
386
+
387
+ .face {
388
+ flex: none;
389
+ position: relative;
390
+ display: grid;
391
+ place-items: center;
392
+ width: var(--coin);
393
+ height: var(--coin);
394
+ border-radius: 50%;
395
+ background: var(--say);
396
+ }
397
+
398
+ /* Both faces fill their circle, with no edge */
399
+ .face svg,
400
+ .face img {
401
+ width: var(--coin);
402
+ height: var(--coin);
403
+ clip-path: circle(50%);
404
+ }
405
+
406
+ /* Who moves: their name in the accent */
407
+ .rail-player.moving .rail-name {
408
+ color: var(--accent);
409
+ }
410
+
411
+ /* The AI spoke while the talk was narrowed or folded: on the Torretta, or on the tab */
412
+ .face.unread::after,
413
+ .fold-tab button.unread::after {
414
+ content: "";
415
+ position: absolute;
416
+ top: 0;
417
+ right: 0;
418
+ width: 10px;
419
+ height: 10px;
420
+ border-radius: 50%;
421
+ background: var(--accent);
422
+ box-shadow: 0 0 0 2px var(--page);
423
+ }
424
+
425
+ .fold-tab button.unread::after {
426
+ top: -4px;
427
+ right: auto;
428
+ left: -4px;
429
+ }
430
+
431
+ /* The AI thinks: a cloud at the Torretta's top left corner, by its battlements, its trail towards the antenna (Zoe). Filled in the accent on its move, as
432
+ the clock that runs; outlined and quieter on the player's time (D-72). Shape tells them apart, not colour; a halo of the card keeps it off the Torretta. */
433
+ .thinker {
434
+ position: relative;
435
+ display: inline-grid;
436
+ flex: none;
437
+ }
438
+
439
+ /* Its trail clear of the battlements' corner, which the halo would bite */
440
+ .thought {
441
+ position: absolute;
442
+ top: -8px;
443
+ left: -10px;
444
+ width: 24px;
445
+ height: 20px;
446
+ overflow: visible;
447
+ animation: thought-in 150ms ease-out;
448
+ }
449
+
450
+ .face .thought {
451
+ top: -8px;
452
+ left: -6px;
453
+ width: 20px;
454
+ height: 16px;
455
+ clip-path: none;
456
+ }
457
+
458
+ /* On the folded talk's tab, at its outer corner below the dot: the rest of the tab is under the board. No trail, with no Torretta to point at; it would
459
+ read as a second dot */
460
+ .fold-tab .thought {
461
+ top: auto;
462
+ bottom: -8px;
463
+ left: -12px;
464
+ width: 20px;
465
+ height: 16px;
466
+ }
467
+
468
+ .fold-tab .thought circle.cloud {
469
+ display: none;
470
+ }
471
+
472
+ .thought .cloud {
473
+ paint-order: stroke;
474
+ stroke: var(--card);
475
+ stroke-width: 3;
476
+ }
477
+
478
+ .thought.move .cloud {
479
+ fill: var(--accent);
480
+ }
481
+
482
+ .thought.move .dot {
483
+ fill: var(--on-accent);
484
+ animation: thought-dot 1.2s ease-in-out infinite;
485
+ }
486
+
487
+ /* In order from the left: nth-of-type would count the trail, a circle too */
488
+ .thought.move .dot + .dot {
489
+ animation-delay: 0.2s;
490
+ }
491
+
492
+ .thought.move .dot + .dot + .dot {
493
+ animation-delay: 0.4s;
494
+ }
495
+
496
+ .thought.ponder .cloud {
497
+ fill: var(--card);
498
+ stroke: var(--soft);
499
+ stroke-width: 1.8;
500
+ }
501
+
502
+ .thought.ponder .dot {
503
+ fill: var(--soft);
504
+ animation: thought-breath 3s ease-in-out infinite;
505
+ }
506
+
507
+ @keyframes thought-in {
508
+ from {
509
+ opacity: 0;
510
+ }
511
+ }
512
+
513
+ @keyframes thought-dot {
514
+ 0%,
515
+ 100% {
516
+ opacity: 0.35;
517
+ }
518
+
519
+ 40% {
520
+ opacity: 1;
521
+ }
522
+ }
523
+
524
+ @keyframes thought-breath {
525
+ 50% {
526
+ opacity: 0.4;
527
+ }
528
+ }
529
+
530
+ /* Still dots: the shape alone tells the two apart */
531
+ @media (prefers-reduced-motion: reduce) {
532
+ .thought,
533
+ .thought.move .dot,
534
+ .thought.ponder .dot {
535
+ animation: none;
536
+ }
537
+ }
538
+
539
+ /* A long name or model goes to its ellipsis instead of widening the page on a phone (Zoe). Each side's talk has half the column at most:
540
+ past it, its words scroll; the box and the cards never give way. */
541
+ .ai-block,
542
+ .you-block {
543
+ display: flex;
544
+ flex-direction: column;
545
+ min-height: 0;
546
+ min-width: 0;
547
+ flex: 0 1 auto;
548
+ max-height: calc(50% - var(--between) / 2);
549
+ }
550
+
551
+ .you-block > .compose,
552
+ .you-block > .player {
553
+ flex: none;
554
+ }
555
+
556
+ /* The game's column, which folds from its tab */
557
+ .side {
558
+ grid-area: side;
559
+ display: flex;
560
+ flex-direction: column;
561
+ gap: var(--between);
562
+ contain: size;
563
+ min-height: 0;
564
+ }
565
+
566
+ .board-steps,
567
+ .board-map {
568
+ display: none;
569
+ }
570
+
571
+ .app.folded .side {
572
+ visibility: hidden;
573
+ transition: visibility 0s 200ms;
574
+ }
575
+
576
+ /* With no game, the game's column opens with this note, as tall as the game's card, so the column's tab sits in its middle */
577
+ .side > .card.empty {
578
+ min-height: 74px;
579
+ display: flex;
580
+ align-items: center;
581
+ }
582
+
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 */
584
+ @media (min-width: 841px) {
585
+ .top:has(.verdict.idle, .verdict.over) .title {
586
+ display: none;
587
+ }
588
+
589
+ .top:has(.verdict.idle, .verdict.over) .verdict {
590
+ margin-right: auto;
591
+ }
592
+ }
593
+
594
+ /* 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 */
595
+ @media (min-width: 841px) {
596
+ .talk > * {
597
+ width: var(--left);
598
+ }
599
+ }
600
+
601
+ @media (min-width: 1100px) {
602
+ .side {
603
+ align-items: flex-end;
604
+ }
605
+
606
+ .side > * {
607
+ width: var(--right);
608
+ }
609
+ }
610
+
611
+ /* Where three columns would leave the board small, the game's column goes under the board, always open */
612
+ @media (min-width: 841px) and (max-width: 1099px) {
613
+ .app,
614
+ .app.folded {
615
+ --right-now: 0px;
616
+ grid-template-columns: var(--left-now) auto;
617
+ grid-template-rows: auto minmax(var(--least), auto) auto;
618
+ grid-template-areas: ". head" "talk board" ". side";
619
+ /* The page scrolls here: the board measures the page without its scroll bar */
620
+ container-type: inline-size;
621
+ }
622
+
623
+ .board-wrap {
624
+ width: min(calc(100cqw - var(--left-now) - var(--between)), max(var(--least), calc(100vh - var(--bar) - 3 * var(--between))));
625
+ }
626
+
627
+ .side,
628
+ .app.folded .side {
629
+ visibility: visible;
630
+ contain: none;
631
+ }
632
+
633
+ .fold-tab.of-game {
634
+ display: none;
635
+ }
636
+ }
637
+
638
+ @media (prefers-reduced-motion: reduce) {
639
+ .app,
640
+ .board-wrap,
641
+ .fold-tab,
642
+ .app.talk-rail .talk {
643
+ transition: none;
644
+ }
645
+
646
+ .app.talk-rail .faces,
647
+ .verdict {
648
+ animation: none;
649
+ }
650
+ }
651
+
652
+ /* A phone, or a narrow window: one column. The AI above the board, the player's words and card below it, then the game */
653
+ @media (max-width: 840px) {
654
+ :root {
655
+ --gap: 12px;
656
+ }
657
+
658
+ .command span {
659
+ display: none;
660
+ }
661
+
662
+ /* The header has no room for the word on a game over: it takes a line of its own under the buttons */
663
+ .top {
664
+ height: auto;
665
+ min-height: var(--bar);
666
+ flex-wrap: wrap;
667
+ gap: var(--between) 8px;
668
+ }
669
+
670
+ /* The name gives way to the buttons, which keep to the first line */
671
+ .title {
672
+ min-width: 0;
673
+ overflow: hidden;
674
+ white-space: nowrap;
675
+ text-overflow: ellipsis;
676
+ }
677
+
678
+ .verdict {
679
+ order: 1;
680
+ flex-basis: 100%;
681
+ text-align: center;
682
+ justify-content: center;
683
+ }
684
+
685
+ /* The AI's draw offer and its answer share the second line */
686
+ .answer {
687
+ order: 1;
688
+ }
689
+
690
+ .top:has(.answer) .verdict {
691
+ flex: 1 0 60%;
692
+ }
693
+
694
+ .app,
695
+ .app.folded,
696
+ .app.talk-rail,
697
+ .app.talk-folded {
698
+ grid-template-columns: 1fr;
699
+ grid-template-rows: none;
700
+ grid-template-areas: "head" "ai" "board" "you" "side";
701
+ }
702
+
703
+ .app.flipped {
704
+ grid-template-areas: "head" "you" "board" "ai" "side";
705
+ }
706
+
707
+ /* The helps' map right under the board, by the thumb, so the board stays in sight while it shows (D-78, Zoe) */
708
+ .app:has(.board-map) {
709
+ grid-template-areas: "head" "ai" "board" "map" "you" "side";
710
+ }
711
+
712
+ .app.flipped:has(.board-map) {
713
+ grid-template-areas: "head" "you" "board" "map" "ai" "side";
714
+ }
715
+
716
+ .talk,
717
+ .app.talk-rail .talk,
718
+ .app.talk-folded .talk {
719
+ display: contents;
720
+ visibility: visible;
721
+ opacity: 1;
722
+ }
723
+
724
+ .ai-block {
725
+ grid-area: ai;
726
+ }
727
+
728
+ .you-block {
729
+ grid-area: you;
730
+ }
731
+
732
+ .board-wrap {
733
+ width: 100%;
734
+ }
735
+
736
+ .says,
737
+ .sent {
738
+ max-height: 30vh;
739
+ }
740
+
741
+ .side,
742
+ .app.folded .side {
743
+ display: flex;
744
+ visibility: visible;
745
+ contain: none;
746
+ }
747
+
748
+ .fold-tab,
749
+ .app.talk-rail .faces,
750
+ .app.talk-folded .faces {
751
+ display: none;
752
+ }
753
+
754
+ /* A game in analysis, turned round or not: the card on top, the board, its steps under the thumb, the Torretta's note, the card below, the moves (Zoe) */
755
+ .app:has(.talk.studied),
756
+ .app.flipped:has(.talk.studied) {
757
+ grid-template-areas: "head" "ai" "board" "steps" "note" "you" "side";
758
+ }
759
+
760
+ .talk.studied > .ai-block {
761
+ display: contents;
762
+ }
763
+
764
+ .talk.studied > .ai-block > :not(.ai-note) {
765
+ grid-area: ai;
766
+ }
767
+
768
+ .talk.studied .ai-note {
769
+ grid-area: note;
770
+ margin-top: 0;
771
+ }
772
+
773
+ /* With no chat listening, its note comes before the Torretta's (Zoe) */
774
+ .app:has(.talk.studied > .ai-block > .asleep),
775
+ .app.flipped:has(.talk.studied > .ai-block > .asleep) {
776
+ grid-template-areas: "head" "ai" "board" "steps" "asleep" "note" "you" "side";
777
+ }
778
+
779
+ .talk.studied > .ai-block > .asleep {
780
+ grid-area: asleep;
781
+ margin: 0;
782
+ }
783
+
784
+ .board-steps {
785
+ display: block;
786
+ grid-area: steps;
787
+ }
788
+
789
+ .app:has(.talk.studied) .side .steps {
790
+ display: none;
791
+ }
792
+ }
793
+
794
+ /* The player's folder was set up by an older Chesspal: a strip at the foot of the window until they run init again */
795
+ .outdated {
796
+ position: fixed;
797
+ right: 16px;
798
+ bottom: 16px;
799
+ left: 16px;
800
+ z-index: 15;
801
+ max-width: 640px;
802
+ margin: 0 auto;
803
+ padding: 10px 14px;
804
+ border: 1px solid var(--line);
805
+ border-radius: var(--radius);
806
+ background: var(--warn-back);
807
+ color: var(--ink);
808
+ font-size: 14px;
809
+ line-height: 1.45;
810
+ box-shadow: 0 8px 24px rgb(0 0 0 / 16%);
811
+ }
812
+
813
+ /* 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 */
814
+ @media (max-width: 420px) {
815
+ .top {
816
+ column-gap: 4px;
817
+ }
818
+
819
+ .top .title {
820
+ display: none;
821
+ }
822
+
823
+ .top .torretta {
824
+ margin-right: auto;
825
+ }
826
+
827
+ .top .command,
828
+ .top .icon-button {
829
+ width: 32px;
830
+ height: 32px;
831
+ padding: 0;
832
+ justify-content: center;
833
+ }
834
+ }