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,577 @@
1
+ /* The game's column: the moves, the lead, the game's card and its commands */
2
+
3
+ /* The game's card, as tall as a player's across the board: a little board where the player's card has their face, round people, a square game */
4
+ .game-card {
5
+ margin: 0;
6
+ min-height: 74px;
7
+ display: flex;
8
+ align-items: center;
9
+ gap: 12px;
10
+ font-size: 13px;
11
+ }
12
+
13
+ .tile {
14
+ position: relative;
15
+ flex: none;
16
+ display: grid;
17
+ place-items: center;
18
+ width: 48px;
19
+ height: 48px;
20
+ border-radius: var(--radius);
21
+ /* A board of four squares in the skin's colours, the dark one at the bottom left as on a board */
22
+ background: conic-gradient(var(--dark-square) 0 25%, var(--light-square) 0 50%, var(--dark-square) 0 75%, var(--light-square) 0);
23
+ overflow: hidden;
24
+ }
25
+
26
+ .tile .piece {
27
+ --piece-edge-width: 7px;
28
+ width: 40px;
29
+ height: 40px;
30
+ }
31
+
32
+ /* The king to move, edged in the accent like the coin of whoever moves */
33
+ .tile.moving {
34
+ box-shadow: 0 0 0 2px var(--card), 0 0 0 4px var(--accent);
35
+ }
36
+
37
+ /* At the end both kings: side by side in a draw, the loser laid at the winner's feet */
38
+ .tile.over {
39
+ display: flex;
40
+ justify-content: center;
41
+ align-items: flex-end;
42
+ padding-bottom: 4px;
43
+ }
44
+
45
+ .tile.over .piece {
46
+ --piece-edge-width: 8px;
47
+ width: 30px;
48
+ height: 30px;
49
+ }
50
+
51
+ .tile.draw .piece + .piece {
52
+ margin-left: -12px;
53
+ }
54
+
55
+ .tile.win > span {
56
+ position: absolute;
57
+ display: grid;
58
+ }
59
+
60
+ .tile.win .piece {
61
+ width: 100%;
62
+ height: 100%;
63
+ }
64
+
65
+ .tile.win .up {
66
+ right: 5px;
67
+ bottom: 4px;
68
+ width: 28px;
69
+ height: 28px;
70
+ }
71
+
72
+ /* Laid down, the king is as wide as it was tall: it drops 5px to rest where the other stands */
73
+ .tile.win .down {
74
+ left: 4px;
75
+ bottom: -1px;
76
+ width: 24px;
77
+ height: 24px;
78
+ rotate: -90deg;
79
+ }
80
+
81
+ .game-card .what {
82
+ flex: 1;
83
+ min-width: 0;
84
+ line-height: 1.25;
85
+ }
86
+
87
+ .game-card p {
88
+ margin: 0;
89
+ }
90
+
91
+ .game-card .head,
92
+ .game-card .when {
93
+ color: var(--soft);
94
+ }
95
+
96
+ .game-card .big {
97
+ color: var(--ink);
98
+ font-size: 20px;
99
+ font-weight: 600;
100
+ font-variant-numeric: tabular-nums;
101
+ letter-spacing: -0.01em;
102
+ }
103
+
104
+ .game-card .when {
105
+ margin-top: 2px;
106
+ }
107
+
108
+ /* The game ended under the player's eyes: once the board has shown it, with the kings' badges, the card lights up in the accent and fades back */
109
+ .game-card.lit {
110
+ animation: lit 1200ms ease-out 1350ms;
111
+ }
112
+
113
+ @keyframes lit {
114
+ 20% {
115
+ box-shadow: 0 0 0 2px var(--accent), 0 0 18px color-mix(in srgb, var(--accent) 55%, transparent);
116
+ }
117
+ }
118
+
119
+ @media (prefers-reduced-motion: reduce) {
120
+ .game-card.lit {
121
+ animation: none;
122
+ }
123
+ }
124
+
125
+ /* Who is ahead, as the AI estimates it (D-90): White's share from the left, Black's from the right, the figure on the side ahead, as chess.com's bar; an edge
126
+ and a mark of the even game in the middle keep it a bar of two sides in both themes, never one of progress (Zoe) */
127
+ .lead-bar {
128
+ position: relative;
129
+ height: 14px;
130
+ margin-top: 6px;
131
+ border-radius: 7px;
132
+ background: #3b4048;
133
+ box-shadow: 0 0 0 1px rgb(138 144 153 / 0.6);
134
+ overflow: hidden;
135
+ }
136
+
137
+ .lead-bar::after {
138
+ content: "";
139
+ position: absolute;
140
+ inset: 3px auto 3px calc(50% - 0.5px);
141
+ width: 1px;
142
+ background: #8a9099;
143
+ }
144
+
145
+ /* On a position after the one the AI estimated: the player's move, until the coach speaks again */
146
+ .lead-bar.earlier {
147
+ opacity: 0.55;
148
+ }
149
+
150
+ .lead-bar .white {
151
+ position: absolute;
152
+ inset: 0 auto 0 0;
153
+ background: #f5f5f2;
154
+ transition: width 400ms ease;
155
+ }
156
+
157
+ @media (prefers-reduced-motion: reduce) {
158
+ .lead-bar .white {
159
+ transition: none;
160
+ }
161
+ }
162
+
163
+ .lead-bar .figure {
164
+ position: absolute;
165
+ top: 0;
166
+ font-size: 10px;
167
+ font-weight: 700;
168
+ line-height: 14px;
169
+ font-variant-numeric: tabular-nums;
170
+ }
171
+
172
+ .lead-bar .figure.white,
173
+ .lead-bar .figure.even {
174
+ left: 6px;
175
+ color: #2a2f36;
176
+ }
177
+
178
+ .lead-bar .figure.black {
179
+ right: 6px;
180
+ color: #f5f5f2;
181
+ }
182
+
183
+ /* The chart of the game in analysis: the white area is White's share, the line in the middle an even game; a dot on a move a badge marks, a line on the move
184
+ shown, gold as on the board and in the moves (Zoe) */
185
+ .lead-card {
186
+ display: grid;
187
+ gap: 8px;
188
+ }
189
+
190
+ .lead-chart {
191
+ position: relative;
192
+ height: 72px;
193
+ border-radius: 6px;
194
+ background: #3b4048;
195
+ box-shadow: 0 0 0 1px rgb(138 144 153 / 0.6);
196
+ overflow: hidden;
197
+ cursor: pointer;
198
+ }
199
+
200
+ .lead-chart svg {
201
+ display: block;
202
+ width: 100%;
203
+ height: 100%;
204
+ }
205
+
206
+ .lead-chart .white-area {
207
+ fill: #f5f5f2;
208
+ }
209
+
210
+ .lead-chart .pending {
211
+ fill: #5a606a;
212
+ }
213
+
214
+ .lead-chart .even {
215
+ stroke: #8a9099;
216
+ stroke-width: 1;
217
+ stroke-dasharray: 3 3;
218
+ fill: none;
219
+ }
220
+
221
+ .lead-chart .shown {
222
+ position: absolute;
223
+ top: 0;
224
+ bottom: 0;
225
+ width: 2px;
226
+ margin-left: -1px;
227
+ background: #ffc450;
228
+ box-shadow: 0 0 0 1px rgb(20 23 27 / 0.6);
229
+ pointer-events: none;
230
+ }
231
+
232
+ .lead-chart .dot {
233
+ position: absolute;
234
+ width: 8px;
235
+ height: 8px;
236
+ margin: -4px 0 0 -4px;
237
+ border-radius: 50%;
238
+ box-shadow: 0 0 0 1.5px #ffffff;
239
+ pointer-events: none;
240
+ }
241
+
242
+ .lead-chart .dot.brilliant {
243
+ background: var(--badge-brilliant);
244
+ }
245
+
246
+ .lead-chart .dot.great {
247
+ background: var(--badge-great);
248
+ }
249
+
250
+ .lead-chart .dot.miss {
251
+ background: var(--badge-miss);
252
+ }
253
+
254
+ .lead-chart .dot.mistake {
255
+ background: var(--badge-mistake);
256
+ }
257
+
258
+ .lead-chart .dot.blunder {
259
+ background: var(--badge-blunder);
260
+ }
261
+
262
+ .opening {
263
+ display: grid;
264
+ gap: 2px;
265
+ font-size: 13px;
266
+ }
267
+
268
+ .opening p {
269
+ margin: 0;
270
+ }
271
+
272
+ .opening .family {
273
+ display: flex;
274
+ align-items: center;
275
+ gap: 7px;
276
+ margin: 0;
277
+ font-size: inherit;
278
+ font-weight: inherit;
279
+ }
280
+
281
+ .opening .book {
282
+ display: grid;
283
+ align-self: center;
284
+ color: var(--accent);
285
+ }
286
+
287
+ .opening .book .icon {
288
+ width: 21px;
289
+ height: 21px;
290
+ stroke-width: 2.3;
291
+ }
292
+
293
+ .opening .book.out {
294
+ color: var(--ink);
295
+ }
296
+
297
+ .opening .variation {
298
+ color: var(--ink);
299
+ }
300
+
301
+ .opening .out {
302
+ color: var(--soft);
303
+ font-size: 12px;
304
+ }
305
+
306
+ /* A game in analysis (D-69): the AI's word on the whole game above the moves, and its note on the move shown in the talk, under the Torretta */
307
+ .review {
308
+ display: grid;
309
+ gap: 4px;
310
+ font-size: 13.5px;
311
+ line-height: 1.45;
312
+ }
313
+
314
+ .review.waiting,
315
+ .say.reviewing {
316
+ display: flex;
317
+ align-items: center;
318
+ gap: 8px;
319
+ margin: 0;
320
+ color: var(--soft);
321
+ }
322
+
323
+ .review ul {
324
+ margin: 0;
325
+ padding-left: 18px;
326
+ }
327
+
328
+ .review p,
329
+ .ai-note p {
330
+ margin: 0;
331
+ }
332
+
333
+ .review .head {
334
+ display: flex;
335
+ align-items: center;
336
+ gap: 6px;
337
+ color: var(--soft);
338
+ font-size: 12px;
339
+ }
340
+
341
+ /* The moves' card takes the column's room left: its title, then the list */
342
+ .moves-card {
343
+ flex: 1;
344
+ min-height: 120px;
345
+ display: flex;
346
+ flex-direction: column;
347
+ gap: 6px;
348
+ padding: 8px 10px;
349
+ }
350
+
351
+ .moves-card .card-title {
352
+ padding: 2px 4px 0;
353
+ }
354
+
355
+ /* Positioned, so that the moves' hidden names scroll with the list instead of stretching the page */
356
+ .moves {
357
+ position: relative;
358
+ flex: 1;
359
+ min-height: 0;
360
+ /* It scrolls with no scrollbar, kept on the move shown */
361
+ overflow: hidden auto;
362
+ scrollbar-width: none;
363
+ font-size: 14px;
364
+ }
365
+
366
+ .moves ol {
367
+ list-style: none;
368
+ margin: 0;
369
+ padding: 0;
370
+ }
371
+
372
+ .moves li {
373
+ display: grid;
374
+ grid-template-columns: 30px 1fr 1fr;
375
+ padding: 3px 6px;
376
+ border-radius: 5px;
377
+ }
378
+
379
+ .moves .result {
380
+ margin: 6px;
381
+ font-weight: 600;
382
+ }
383
+
384
+ .moves li:nth-child(even) {
385
+ background: var(--page);
386
+ }
387
+
388
+ .moves .number {
389
+ color: var(--soft);
390
+ }
391
+
392
+ /* A move is a button that shows it on the board; shown or not it keeps the same room, so stepping with the arrows never shifts the line (Stefano) */
393
+ .moves .ply {
394
+ justify-self: start;
395
+ display: inline-flex;
396
+ align-items: center;
397
+ gap: 4px;
398
+ padding: 1px 6px;
399
+ margin-left: -6px;
400
+ background: none;
401
+ border: 0;
402
+ border-radius: 5px;
403
+ color: inherit;
404
+ font: inherit;
405
+ cursor: pointer;
406
+ }
407
+
408
+ /* The move under the pointer in a neutral grey, never the accent, so it never reads as the move shown (Stefano) */
409
+ .moves .ply:hover {
410
+ background: color-mix(in oklab, var(--ink) 9%, transparent);
411
+ }
412
+
413
+ /* The move shown in the board's gold of the last move, laid on the light square as there, with dark ink in both themes: the yellow move in the list is the yellow
414
+ move on the board, and it follows the skin (Stefano) */
415
+ .moves .ply.current {
416
+ background: var(--light-square) linear-gradient(var(--last-move), var(--last-move));
417
+ color: var(--fixed-ink);
418
+ }
419
+
420
+ .moves .current .takes {
421
+ color: #4d5866;
422
+ }
423
+
424
+ /* A ring in the card's colour keeps a badge's disc whole on the gold */
425
+ .moves .current .move-badge {
426
+ border-radius: 50%;
427
+ box-shadow: 0 0 0 1.5px var(--card);
428
+ }
429
+
430
+ /* The buttons that step through the moves, under them as on Lichess and chess.com */
431
+ .steps {
432
+ display: flex;
433
+ gap: 4px;
434
+ padding: 4px;
435
+ }
436
+
437
+ .steps button {
438
+ flex: 1;
439
+ display: grid;
440
+ place-items: center;
441
+ padding: 6px 0;
442
+ background: none;
443
+ border: 1px solid transparent;
444
+ border-radius: 6px;
445
+ color: var(--ink);
446
+ cursor: pointer;
447
+ }
448
+
449
+ .steps button:hover:not(:disabled) {
450
+ background: var(--page);
451
+ border-color: var(--line);
452
+ }
453
+
454
+ .steps button:disabled {
455
+ color: var(--soft);
456
+ opacity: 0.5;
457
+ cursor: default;
458
+ }
459
+
460
+ .steps .icon {
461
+ width: 20px;
462
+ height: 20px;
463
+ stroke-width: 2;
464
+ }
465
+
466
+ /* A piece in a move: the set's small one, White's white and Black's black, edged so either shows on either theme */
467
+ .figurine {
468
+ display: inline-block;
469
+ width: 1.15em;
470
+ height: 1.15em;
471
+ margin-right: 0.05em;
472
+ vertical-align: -0.22em;
473
+ }
474
+
475
+ /* A capture's x is grey, so the squares of a move read first */
476
+ .takes {
477
+ color: var(--soft);
478
+ font-weight: 400;
479
+ }
480
+
481
+ /* In analysis, the game's own commands in the right column: save its PGN with the review, open the other analyses */
482
+ .analysis-actions {
483
+ display: flex;
484
+ gap: 8px;
485
+ padding: 8px;
486
+ }
487
+
488
+ .analysis-actions button {
489
+ flex: 1;
490
+ display: inline-flex;
491
+ align-items: center;
492
+ justify-content: center;
493
+ gap: 6px;
494
+ padding: 7px 10px;
495
+ border: 1px solid var(--line);
496
+ border-radius: 8px;
497
+ background: transparent;
498
+ color: var(--ink);
499
+ font: inherit;
500
+ font-size: 13px;
501
+ cursor: pointer;
502
+ }
503
+
504
+ .analysis-actions button:hover {
505
+ border-color: var(--accent);
506
+ }
507
+
508
+ /* Where the game's column runs under the board, the moves in two columns */
509
+ @media (max-width: 1099px) {
510
+ .moves ol {
511
+ columns: 2;
512
+ column-gap: 12px;
513
+ }
514
+
515
+ .moves li {
516
+ break-inside: avoid;
517
+ }
518
+ }
519
+
520
+ /* A card's title in the game's column, its icon in the accent as the opening's book: the helps' lifebuoy, the moves' score sheet (Stefano) */
521
+ .card-title {
522
+ display: flex;
523
+ align-items: center;
524
+ gap: 7px;
525
+ margin: 0;
526
+ font-size: 13px;
527
+ font-weight: 700;
528
+ color: var(--ink);
529
+ }
530
+
531
+ .card-title .icon {
532
+ width: 19px;
533
+ height: 19px;
534
+ stroke-width: 2.1;
535
+ color: var(--accent);
536
+ }
537
+
538
+ /* A card's title is the button that folds the card to it and opens it again, a chevron after the name (Stefano) */
539
+ .card-fold {
540
+ display: inline-flex;
541
+ align-items: center;
542
+ gap: 7px;
543
+ padding: 0;
544
+ border: 0;
545
+ background: none;
546
+ color: inherit;
547
+ font: inherit;
548
+ text-align: left;
549
+ cursor: pointer;
550
+ }
551
+
552
+ .card-fold .chevron {
553
+ flex: none;
554
+ width: 14px;
555
+ height: 14px;
556
+ fill: none;
557
+ stroke: var(--soft);
558
+ stroke-width: 1.8;
559
+ stroke-linecap: round;
560
+ stroke-linejoin: round;
561
+ }
562
+
563
+ .card-fold:hover .chevron,
564
+ .card-fold:focus-visible .chevron {
565
+ stroke: var(--accent);
566
+ }
567
+
568
+ /* A folded card is its title alone: the moves' gives the column's room back to the cards above */
569
+ .moves-card.folded {
570
+ flex: none;
571
+ min-height: 0;
572
+ }
573
+
574
+ /* The saves and the steps stay at the foot of the column, with the moves folded too (Stefano) */
575
+ .side > .moves-card + * {
576
+ margin-top: auto;
577
+ }
@@ -1,13 +1,15 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "preact/jsx-runtime";
2
2
  import { useEffect, useRef } from "preact/hooks";
3
+ import { Figurine } from "../pieces/pieces.js";
3
4
  import { BadgeIcon, NoteIcon } from "./badges.js";
4
- import { Figurine } from "./pieces.js";
5
+ import { FoldButton, useCardFold } from "./card-fold.js";
5
6
  /**
6
7
  * The moves numbered in pairs, the one the board shows marked (`shown`, how many moves it shows), and the result once the game is over; a click on a move
7
8
  * shows it. The list keeps its last move in sight, and the one shown while the player looks back.
8
9
  */
9
10
  export function MoveList({ plies, result, shown, onShow, text }) {
10
11
  const list = useRef(null);
12
+ const [folded, toggleFold] = useCardFold("moves");
11
13
  const rows = rowsOf(plies);
12
14
  const current = plies[shown - 1];
13
15
  useEffect(() => {
@@ -26,13 +28,19 @@ export function MoveList({ plies, result, shown, onShow, text }) {
26
28
  return () => observer.disconnect();
27
29
  }, []);
28
30
  const move = (ply) => _jsx(Move, { ply: ply, current: ply !== undefined && ply === current, onShow: () => onShow(plies.indexOf(ply) + 1), text: text });
29
- return (_jsxs("div", { ref: list, class: "moves card", role: "region", "aria-label": text.moves, children: [_jsx("ol", { children: rows.map(({ number, white, black }) => (_jsxs("li", { children: [_jsxs("span", { class: "number", children: [number, "."] }), move(white), move(black)] }, number))) }), result === undefined ? null : _jsx("p", { class: "result", children: result })] }));
31
+ // The card's title with its icon, a score sheet, as the other cards of the column, which folds the card to it (Stefano); only the list under it
32
+ // scrolls, and it stays in the page while folded, so that it keeps its move in sight.
33
+ return (_jsxs("section", { class: `card moves-card${folded ? " folded" : ""}`, "aria-label": text.moves, children: [_jsx("h3", { class: "card-title", children: _jsxs(FoldButton, { folded: folded, onFold: toggleFold, text: text, children: [_jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M6 3H15L19 7V21H6ZM15 3V7H19M9 11H16M9 14.5H16M9 18H13" }) }), text.moves] }) }), _jsxs("div", { ref: list, class: "moves", hidden: folded, children: [_jsx("ol", { children: rows.map(({ number, white, black }) => (_jsxs("li", { children: [_jsxs("span", { class: "number", children: [number, "."] }), move(white), move(black)] }, number))) }), result === undefined ? null : _jsx("p", { class: "result", children: result })] })] }));
30
34
  }
31
35
  /** The buttons that step through the moves: to the start, back, forward, and to the game as it is; each one off where it could not go. */
32
36
  export function Steps({ shown, total, onStep, text }) {
33
37
  const button = (step, path, off) => (_jsx("button", { type: "button", title: text.steps[step], "aria-label": text.steps[step], disabled: off, onClick: () => onStep(step), children: _jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: path }) }) }));
34
38
  return (_jsxs("nav", { class: "steps card", "aria-label": text.moves, children: [button("first", "M7 6V18M17 6L11 12L17 18", shown === 0), button("back", "M14.5 6L8.5 12L14.5 18", shown === 0), button("forward", "M9.5 6L15.5 12L9.5 18", shown === total), button("last", "M17 6V18M7 6L13 12L7 18", shown === total)] }));
35
39
  }
40
+ /** A move in SAN with each piece's letter drawn as its figure, the same in every language (D-50), and a capture's x in grey, so the squares stand out. */
41
+ export function Figurines({ san, side }) {
42
+ return (_jsx(_Fragment, { children: san.split(/([KQRBNx])/).map((part, index) => index % 2 === 0 ? part : part === "x" ? _jsx("span", { class: "takes", children: "x" }, index) : _jsx(Figurine, { letter: part, side: side }, index)) }));
43
+ }
36
44
  function Move({ ply, current, onShow, text }) {
37
45
  if (ply === undefined) {
38
46
  return _jsx("span", { children: "\u2026" });
@@ -40,10 +48,6 @@ function Move({ ply, current, onShow, text }) {
40
48
  // A screen reader reads the move in SAN and the AI's badge by its name; the eye sees each piece's letter as its figure, and the badge's disc (D-69).
41
49
  return (_jsxs("button", { type: "button", class: current ? "ply current" : "ply", "aria-current": current ? "step" : undefined, onClick: onShow, children: [_jsx("span", { class: "visually-hidden", children: ply.review?.badge === undefined ? ply.san : `${ply.san}, ${text.badges[ply.review.badge]}` }), _jsx("span", { "aria-hidden": "true", children: _jsx(Figurines, { san: ply.san, side: ply.side }) }), ply.review?.badge !== undefined ? _jsx(BadgeIcon, { badge: ply.review.badge }) : ply.review?.note !== undefined ? _jsx(NoteIcon, {}) : null] }));
42
50
  }
43
- /** A move in SAN with each piece's letter drawn as its figure, the same in every language (D-50), and a capture's x in grey, so the squares stand out. */
44
- export function Figurines({ san, side }) {
45
- return (_jsx(_Fragment, { children: san.split(/([KQRBNx])/).map((part, index) => index % 2 === 0 ? part : part === "x" ? _jsx("span", { class: "takes", children: "x" }, index) : _jsx(Figurine, { letter: part, side: side }, index)) }));
46
- }
47
51
  /** Scrolls the list, and only the list, as little as it takes to show its current move. */
48
52
  function keepInSight(list) {
49
53
  const box = list?.getBoundingClientRect();
@@ -1,6 +1,6 @@
1
1
  // The moves of a game as the page shows them: each with its side and number, the comment the player may read and the AI's review; and the words said on
2
2
  // the page.
3
- import { drawingsIn, reviewOf } from "../core/index.js";
3
+ import { drawingsIn, leadOf, reviewOf } from "../../core/index.js";
4
4
  /**
5
5
  * The moves of a record, each with its side and number: a game may start from a position where Black moves first. A comment keeps only its words: the
6
6
  * commands in it, such as the AI's plan that an ended game shows, and the (=) of a draw offer (D-65), are not for the page.
@@ -14,34 +14,51 @@ export function pliesOf(record) {
14
14
  // The AI's drawings with its own move, with the helps on, come without a badge or a note (D-78).
15
15
  const drawings = drawingsIn(move.comment);
16
16
  const review = reviewOf(move.comment) ?? (Object.keys(drawings).length === 0 ? undefined : drawings);
17
+ const lead = leadOf(move.comment);
17
18
  const ply = {
18
19
  san: move.san,
19
20
  side: (index + offset) % 2 === 0 ? "white" : "black",
20
21
  number: first + Math.floor((index + offset) / 2),
21
22
  ...(review === undefined ? {} : { review }),
23
+ ...(lead === undefined ? {} : { lead }),
22
24
  };
23
25
  return comment === undefined || comment === "" ? ply : { ...ply, comment };
24
26
  });
25
27
  }
28
+ /** The moves with the coach's judgments on the player's, as the analyst's review shows on them (D-88), and who it saw ahead after the moves it was given (D-90). */
29
+ export function withJudgments(plies, { judgments, leads = [] }) {
30
+ return plies.map((ply, index) => {
31
+ const judgment = judgments.find((one) => one.ply === index + 1);
32
+ const lead = leads.find((one) => one.position === index + 1)?.lead;
33
+ const judged = judgment === undefined
34
+ ? ply
35
+ : { ...ply, review: { ...(judgment.badge === undefined ? {} : { badge: judgment.badge }), ...(judgment.note === undefined ? {} : { note: judgment.note }) } };
36
+ return lead === undefined ? judged : { ...judged, lead };
37
+ });
38
+ }
26
39
  /** The moves with the book's badge on the first `book` of them that have no review of the AI's. */
27
40
  export function withBook(plies, book) {
28
41
  // A move the AI only drew on keeps its drawings beside the book's badge (D-78).
29
42
  return book === 0 ? plies : plies.map((ply, index) => index < book && ply.review?.badge === undefined && ply.review?.note === undefined ? { ...ply, review: { ...ply.review, badge: "book" } } : ply);
30
43
  }
31
- /** The moves without the AI's review: during a game with the helps off the player sees neither its judgments nor its drawings (D-78). */
44
+ /** The moves without the AI's review: during a game with the helps off the player sees neither its judgments nor its drawings (D-78), nor who is ahead (D-90). */
32
45
  export function withoutReviews(plies) {
33
- return plies.map(({ review, ...ply }) => ply);
46
+ return plies.map(({ review, lead, ...ply }) => ply);
34
47
  }
35
- /** The player's last move when the AI judged it, with a badge of its own or a note, and has moved since (D-78); none else. */
36
- export function judgmentOf(plies, player) {
37
- const judged = plies.at(-2);
38
- const review = judged?.review;
39
- const answered = plies.at(-1)?.side !== player && judged?.side === player;
40
- return answered && review !== undefined && (review.note !== undefined || (review.badge !== undefined && review.badge !== "book")) ? judged : undefined;
48
+ /**
49
+ * The coach's judgments the card shows (D-78, D-88): first one whose note was held until the player's last move let it go, however far back its move (Zoe); then the latest, of the player's last move the AI has answered, else of the one before (Zoe and Federico). Only
50
+ * those with a badge of their own or a note.
51
+ */
52
+ export function judgmentsOf(plies, player, { judgments }) {
53
+ const answered = plies.at(-1)?.side === player ? plies.slice(0, -1) : plies;
54
+ const latest = answered.filter((ply) => ply.side === player).slice(-2).reverse().find(judged);
55
+ const released = judgments.filter((judgment) => judgment.released === true).map(({ ply }) => answered[ply - 1])
56
+ .filter((ply) => ply !== undefined && ply !== latest && judged(ply));
57
+ return [...released, ...(latest === undefined ? [] : [latest])];
41
58
  }
42
59
  /** How long a line of the player's may be: the server's limit (D-54), which a test keeps equal. */
43
60
  export const talkLimits = { line: 200 };
44
- /** How many times the AI has spoken in the game, its comments on its moves and its lines on the page: the page sees it speak when the count grows (T-84). */
61
+ /** How many times the AI has spoken in the game, its comments on its moves and its lines on the page: the page sees it speak when the count grows. */
45
62
  export function aiSpoke(plies, ai, talk) {
46
63
  return plies.filter((ply) => ply.side === ai && ply.comment !== undefined).length + talk.filter((line) => line.who === "ai").length;
47
64
  }
@@ -60,3 +77,6 @@ export function playerSpent(plies, ai, talk) {
60
77
  const moved = plies.findLastIndex((ply) => ply.side === ai);
61
78
  return talk.filter((line) => line.who === "player" && line.ply > moved).reduce((sum, line) => sum + line.text.length, 0);
62
79
  }
80
+ function judged({ review }) {
81
+ return review?.note !== undefined || (review?.badge !== undefined && review.badge !== "book");
82
+ }