chesspal 0.3.0 → 0.3.2

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 (188) hide show
  1. package/README.md +1 -1
  2. package/dist/cli/cli.js +6 -6
  3. package/dist/commands/{analyse-game.d.ts → analysis/analyse.d.ts} +2 -2
  4. package/dist/commands/{analyse-game.js → analysis/analyse.js} +7 -11
  5. package/dist/commands/{annotate-game.d.ts → analysis/annotate.d.ts} +1 -1
  6. package/dist/commands/{annotate-game.js → analysis/annotate.js} +6 -9
  7. package/dist/commands/{delete-review.d.ts → analysis/delete-review.d.ts} +1 -1
  8. package/dist/commands/{delete-review.js → analysis/delete-review.js} +5 -5
  9. package/dist/commands/analysis/index.d.ts +4 -0
  10. package/dist/commands/analysis/index.js +5 -0
  11. package/dist/commands/{study-games.d.ts → analysis/study-games.d.ts} +1 -1
  12. package/dist/commands/{study-games.js → analysis/study-games.js} +4 -5
  13. package/dist/commands/{agents.d.ts → answers/agents.d.ts} +3 -3
  14. package/dist/commands/{agents.js → answers/agents.js} +5 -5
  15. package/dist/commands/{clock.d.ts → answers/clock.d.ts} +2 -2
  16. package/dist/commands/{clock.js → answers/clock.js} +2 -2
  17. package/dist/commands/{drawings.d.ts → answers/drawings.d.ts} +1 -1
  18. package/dist/commands/{drawings.js → answers/drawings.js} +1 -1
  19. package/dist/commands/answers/index.d.ts +12 -0
  20. package/dist/commands/answers/index.js +14 -0
  21. package/dist/commands/{lead-input.d.ts → answers/lead-input.d.ts} +1 -1
  22. package/dist/commands/{lead-input.js → answers/lead-input.js} +1 -1
  23. package/dist/commands/{answer-lines.d.ts → answers/lines.d.ts} +2 -2
  24. package/dist/commands/{answer-lines.js → answers/lines.js} +4 -4
  25. package/dist/commands/{opening-book.d.ts → answers/opening-book.d.ts} +1 -1
  26. package/dist/commands/{opening-book.js → answers/opening-book.js} +3 -3
  27. package/dist/commands/{play-move.d.ts → answers/play-move.d.ts} +2 -2
  28. package/dist/commands/{play-move.js → answers/play-move.js} +3 -3
  29. package/dist/commands/{say.d.ts → answers/say.d.ts} +3 -3
  30. package/dist/commands/{say.js → answers/say.js} +4 -4
  31. package/dist/commands/{speech.js → answers/speech.js} +1 -1
  32. package/dist/commands/{study.d.ts → answers/study.d.ts} +3 -3
  33. package/dist/commands/{study.js → answers/study.js} +3 -3
  34. package/dist/commands/{game-summary.d.ts → answers/summary.d.ts} +2 -2
  35. package/dist/commands/{game-summary.js → answers/summary.js} +4 -4
  36. package/dist/commands/{view.d.ts → answers/view.d.ts} +1 -1
  37. package/dist/commands/{ai-move.d.ts → game/ai-move.d.ts} +2 -2
  38. package/dist/commands/{ai-move.js → game/ai-move.js} +6 -11
  39. package/dist/commands/{coach.d.ts → game/coach.d.ts} +1 -1
  40. package/dist/commands/{coach.js → game/coach.js} +7 -13
  41. package/dist/commands/{draw-offer.d.ts → game/draw-offer.d.ts} +1 -1
  42. package/dist/commands/{draw-offer.js → game/draw-offer.js} +4 -4
  43. package/dist/commands/{end-game.d.ts → game/end.d.ts} +2 -2
  44. package/dist/commands/{end-game.js → game/end.js} +5 -9
  45. package/dist/commands/{export-game.d.ts → game/export.d.ts} +1 -1
  46. package/dist/commands/{export-game.js → game/export.js} +4 -4
  47. package/dist/commands/game/index.d.ts +11 -0
  48. package/dist/commands/game/index.js +13 -0
  49. package/dist/commands/{levels.d.ts → game/levels.d.ts} +3 -3
  50. package/dist/commands/{levels.js → game/levels.js} +3 -3
  51. package/dist/commands/{look.d.ts → game/look.d.ts} +3 -4
  52. package/dist/commands/{look.js → game/look.js} +5 -10
  53. package/dist/commands/{new-game.d.ts → game/new.d.ts} +3 -4
  54. package/dist/commands/{new-game.js → game/new.js} +17 -13
  55. package/dist/commands/{page-link.d.ts → game/page-link.d.ts} +1 -1
  56. package/dist/commands/{player-move.d.ts → game/player-move.d.ts} +2 -2
  57. package/dist/commands/{player-move.js → game/player-move.js} +4 -8
  58. package/dist/commands/{ponder.d.ts → game/ponder.d.ts} +3 -3
  59. package/dist/commands/{ponder.js → game/ponder.js} +6 -9
  60. package/dist/commands/{position.d.ts → game/position.d.ts} +2 -2
  61. package/dist/commands/{position.js → game/position.js} +5 -7
  62. package/dist/commands/{status.d.ts → game/status.d.ts} +2 -4
  63. package/dist/commands/{status.js → game/status.js} +12 -17
  64. package/dist/commands/index.d.ts +6 -17
  65. package/dist/commands/index.js +11 -38
  66. package/dist/commands/{delete-game.d.ts → profiles/delete-game.d.ts} +1 -1
  67. package/dist/commands/{delete-game.js → profiles/delete-game.js} +3 -3
  68. package/dist/commands/profiles/index.d.ts +4 -0
  69. package/dist/commands/profiles/index.js +5 -0
  70. package/dist/commands/{past-games.d.ts → profiles/past-games.d.ts} +1 -1
  71. package/dist/commands/{past-games.js → profiles/past-games.js} +5 -5
  72. package/dist/commands/{profiles.d.ts → profiles/profiles.d.ts} +7 -7
  73. package/dist/commands/{profiles.js → profiles/profiles.js} +4 -4
  74. package/dist/commands/{ratings.d.ts → profiles/ratings.d.ts} +1 -1
  75. package/dist/commands/{ratings.js → profiles/ratings.js} +2 -2
  76. package/dist/commands/{context.d.ts → registry/context.d.ts} +2 -0
  77. package/dist/commands/{context.js → registry/context.js} +2 -2
  78. package/dist/commands/registry/index.d.ts +2 -0
  79. package/dist/commands/registry/index.js +3 -0
  80. package/dist/commands/setup/index.d.ts +2 -0
  81. package/dist/commands/setup/index.js +3 -0
  82. package/dist/commands/{init.d.ts → setup/init.d.ts} +1 -1
  83. package/dist/commands/{init.js → setup/init.js} +2 -3
  84. package/dist/commands/{instructions.d.ts → setup/instructions.d.ts} +2 -2
  85. package/dist/commands/{instructions.js → setup/instructions.js} +3 -3
  86. package/dist/commands/{ai-say.d.ts → talk/ai-say.d.ts} +2 -3
  87. package/dist/commands/{ai-say.js → talk/ai-say.js} +5 -9
  88. package/dist/commands/talk/index.d.ts +2 -0
  89. package/dist/commands/talk/index.js +3 -0
  90. package/dist/commands/{player-say.d.ts → talk/player-say.d.ts} +1 -1
  91. package/dist/commands/{player-say.js → talk/player-say.js} +4 -4
  92. package/dist/page/app/app.js +44 -20
  93. package/dist/page/app/served.js +7 -0
  94. package/dist/page/avatars/torretta.js +10 -3
  95. package/dist/page/dialogs/dialogs.css +10 -0
  96. package/dist/page/dialogs/profiles.js +3 -7
  97. package/dist/page/dialogs/settings.js +18 -4
  98. package/dist/page/game/column.js +1 -1
  99. package/dist/page/game/game.css +1 -1
  100. package/dist/page/guide/blocks.js +13 -2
  101. package/dist/page/guide/credits.js +6 -0
  102. package/dist/page/guide/drawings/analysis.js +13 -0
  103. package/dist/page/guide/drawings/drawings.css +329 -0
  104. package/dist/page/guide/drawings/drawings.js +15 -0
  105. package/dist/page/guide/drawings/game.js +15 -0
  106. package/dist/page/guide/drawings/helps.js +13 -0
  107. package/dist/page/guide/drawings/how-it-works.js +13 -0
  108. package/dist/page/guide/drawings/parts.js +103 -0
  109. package/dist/page/guide/drawings/players.js +19 -0
  110. package/dist/page/guide/drawings/talk.js +9 -0
  111. package/dist/page/guide/en.md +39 -25
  112. package/dist/page/guide/guide.css +123 -91
  113. package/dist/page/guide/guide.js +47 -25
  114. package/dist/page/guide/it.md +39 -25
  115. package/dist/page/guide/markdown.js +5 -0
  116. package/dist/page/helps/helps.css +127 -83
  117. package/dist/page/helps/helps.js +45 -26
  118. package/dist/page/helps/view.js +13 -3
  119. package/dist/page/{guide/foot.js → layout/footer.js} +7 -8
  120. package/dist/page/layout/header.js +5 -3
  121. package/dist/page/layout/layout.css +114 -36
  122. package/dist/page/layout/window.js +6 -26
  123. package/dist/page/main.js +1 -1
  124. package/dist/page/pieces/pieces.js +5 -2
  125. package/dist/page/style.css +2 -1
  126. package/dist/page/table/table.css +192 -0
  127. package/dist/page/table/update.js +82 -0
  128. package/dist/page/table/window.js +130 -0
  129. package/dist/page/talk/chat.css +19 -8
  130. package/dist/page/talk/talk.css +17 -67
  131. package/dist/page/talk/talk.js +7 -7
  132. package/dist/page/talk/voice.js +47 -9
  133. package/dist/page/text.js +118 -22
  134. package/dist/page/theme.css +2 -0
  135. package/dist/server/{bell.js → bell/bell.js} +3 -3
  136. package/dist/server/bell/index.d.ts +1 -0
  137. package/dist/server/bell/index.js +2 -0
  138. package/dist/server/{caller.d.ts → mcp/caller.d.ts} +1 -1
  139. package/dist/server/{caller.js → mcp/caller.js} +1 -1
  140. package/dist/server/mcp/index.d.ts +1 -0
  141. package/dist/server/mcp/index.js +2 -0
  142. package/dist/server/{mcp.d.ts → mcp/mcp.d.ts} +1 -1
  143. package/dist/server/{mcp.js → mcp/mcp.js} +9 -9
  144. package/dist/server/page/chat.d.ts +5 -0
  145. package/dist/server/page/chat.js +9 -1
  146. package/dist/server/page/game.js +16 -3
  147. package/dist/server/page/http.d.ts +1 -1
  148. package/dist/server/page/index.d.ts +3 -0
  149. package/dist/server/page/index.js +3 -0
  150. package/dist/server/page/page.d.ts +16 -3
  151. package/dist/server/page/page.js +18 -4
  152. package/dist/server/page/table.d.ts +40 -15
  153. package/dist/server/page/table.js +203 -19
  154. package/dist/server/page/update.d.ts +18 -0
  155. package/dist/server/page/update.js +40 -0
  156. package/dist/server/table/browser.d.ts +1 -1
  157. package/dist/server/table/browser.js +1 -1
  158. package/dist/server/table/hosts/chats.d.ts +1 -1
  159. package/dist/server/table/hosts/chats.js +7 -4
  160. package/dist/server/table/hosts/index.d.ts +1 -1
  161. package/dist/server/table/hosts/index.js +1 -1
  162. package/dist/server/table/hosts/play.d.ts +15 -4
  163. package/dist/server/table/hosts/play.js +75 -11
  164. package/dist/server/table/hosts/seat.d.ts +17 -5
  165. package/dist/server/table/hosts/seat.js +21 -15
  166. package/dist/server/table/index.d.ts +3 -2
  167. package/dist/server/table/index.js +3 -2
  168. package/dist/server/table/launch.d.ts +5 -2
  169. package/dist/server/table/launch.js +6 -3
  170. package/dist/server/table/link.d.ts +1 -1
  171. package/dist/server/table/link.js +4 -3
  172. package/dist/server/table/table.d.ts +4 -5
  173. package/dist/server/table/table.js +15 -12
  174. package/dist/server/table/update.d.ts +17 -0
  175. package/dist/server/table/update.js +77 -0
  176. package/dist/sites/index.d.ts +1 -1
  177. package/dist/sites/index.js +2 -2
  178. package/dist/version.d.ts +4 -0
  179. package/dist/version.js +12 -1
  180. package/package.json +1 -1
  181. package/dist/page/table/opener.css +0 -65
  182. package/dist/page/table/opener.js +0 -59
  183. /package/dist/commands/{speech.d.ts → answers/speech.d.ts} +0 -0
  184. /package/dist/commands/{view.js → answers/view.js} +0 -0
  185. /package/dist/commands/{page-link.js → game/page-link.js} +0 -0
  186. /package/dist/commands/{registry.d.ts → registry/registry.d.ts} +0 -0
  187. /package/dist/commands/{registry.js → registry/registry.js} +0 -0
  188. /package/dist/server/{bell.d.ts → bell/bell.d.ts} +0 -0
@@ -2,11 +2,11 @@
2
2
  import { readFileSync } from "node:fs";
3
3
  import { resolve } from "node:path";
4
4
  import { z } from "zod";
5
- import { endOf } from "../games/index.js";
6
- import { activateProfile, activeProfile, createProfile, drawnAvatars, fideHighest, fideLowest, gameFolder, levels, listProfiles, nameLimit, removeOwnImage, saveOwnImage, updateProfile, } from "../profiles/index.js";
7
- import { gameAt } from "./clock.js";
5
+ import { endOf } from "../../games/index.js";
6
+ import { activateProfile, activeProfile, createProfile, drawnAvatars, fideHighest, fideLowest, gameFolder, levels, listProfiles, nameLimit, removeOwnImage, saveOwnImage, updateProfile, } from "../../profiles/index.js";
7
+ import { gameAt } from "../answers/index.js";
8
+ import { defineCommand } from "../registry/index.js";
8
9
  import { refreshRatings } from "./ratings.js";
9
- import { defineCommand } from "./registry.js";
10
10
  /** A change of profile refused while a game goes on: the page says it in the player's language. */
11
11
  export class GameGoingOn extends Error {
12
12
  constructor() {
@@ -1,4 +1,4 @@
1
- import type { Context } from "./context.ts";
1
+ import type { Context } from "../registry/index.ts";
2
2
  /** A day, in milliseconds: the page reads the ratings again once they are older. */
3
3
  export declare const ratingsAge: number;
4
4
  /**
@@ -1,7 +1,7 @@
1
1
  // The player's ratings read again from the sites (D-105, D-106): when their names there change, and when the page opens a day after the last reading;
2
2
  // never from a tool of the AI's.
3
- import { listProfiles, profileFolder, saveReadings } from "../profiles/index.js";
4
- import { readingsStale, readSites } from "../sites/index.js";
3
+ import { listProfiles, profileFolder, saveReadings } from "../../profiles/index.js";
4
+ import { readingsStale, readSites } from "../../sites/index.js";
5
5
  /** A day, in milliseconds: the page reads the ratings again once they are older. */
6
6
  export const ratingsAge = 24 * 60 * 60 * 1000;
7
7
  /**
@@ -41,6 +41,8 @@ export interface Context {
41
41
  * (D-71); null in a chat no tool has shown the game yet, none outside a chat.
42
42
  */
43
43
  positionSeen?: SeenPosition | null | undefined;
44
+ /** The first position a tool showed the chat: a chat that has seen no other since has only greeted the player, and a new game keeps it (D-57). */
45
+ positionFirstSeen?: SeenPosition | undefined;
44
46
  /** The instructions the MCP server gave in this chat, by their name and the chat's start: each comes once a chat (D-68); none on the command line. */
45
47
  told?: Set<string>;
46
48
  /** Who makes this tool call, as far as the host ties it to the call (D-81); none on the command line. */
@@ -1,7 +1,7 @@
1
1
  // What every command runs with besides its input, injected by the command line, the MCP server or the tests: the folders, the time, the randomness.
2
2
  import { userInfo } from "node:os";
3
- import { dataFolder, openFolder } from "../home/index.js";
4
- import { ensureProfile } from "../profiles/index.js";
3
+ import { dataFolder, openFolder } from "../../home/index.js";
4
+ import { ensureProfile } from "../../profiles/index.js";
5
5
  /** The context of a process started in the player's folder: the data folder is found at the first command that needs it, once. */
6
6
  export function defaultContext() {
7
7
  let found;
@@ -0,0 +1,2 @@
1
+ export { type Caller, type Context, defaultContext, type SeenPosition, speaksItalian } from "./context.ts";
2
+ export { type AnyCommand, baseType, type Command, commandLineFieldProblem, defineCommand, inputProblems, isBoolean, isList, sentence, takesNumbers } from "./registry.ts";
@@ -0,0 +1,3 @@
1
+ // What a command is and what it runs with: the definition and the reading of its input, and the context of folders, time and randomness.
2
+ export { defaultContext, speaksItalian } from "./context.js";
3
+ export { baseType, commandLineFieldProblem, defineCommand, inputProblems, isBoolean, isList, sentence, takesNumbers } from "./registry.js";
@@ -0,0 +1,2 @@
1
+ export { init } from "./init.ts";
2
+ export { instructions, instructionsRead } from "./instructions.ts";
@@ -0,0 +1,3 @@
1
+ // The setting up of a player's folder for the hosts, and the opponent's instructions the MCP server gives.
2
+ export { init } from "./init.js";
3
+ export { instructions, instructionsRead } from "./instructions.js";
@@ -9,7 +9,7 @@ interface FolderSetUp {
9
9
  hooksChanged?: true;
10
10
  }
11
11
  /** Makes a folder the player's chess table: the files through which Claude Code and Codex find Chesspal and the opponent's instructions (D-31). */
12
- export declare const init: import("./registry.ts").Command<z.ZodObject<{
12
+ export declare const init: import("../registry/registry.ts").Command<z.ZodObject<{
13
13
  folder: z.ZodOptional<z.ZodString>;
14
14
  }, z.core.$strip>, FolderSetUp>;
15
15
  export {};
@@ -3,9 +3,8 @@ import { relative, resolve } from "node:path";
3
3
  // Colours only on a terminal that shows them: none in a file, a pipe or with NO_COLOR.
4
4
  import { styleText } from "node:util";
5
5
  import { z } from "zod";
6
- import { codexHooksPath, setUpHosts } from "../host-files/index.js";
7
- import { speaksItalian } from "./context.js";
8
- import { defineCommand } from "./registry.js";
6
+ import { codexHooksPath, setUpHosts } from "../../host-files/index.js";
7
+ import { defineCommand, speaksItalian } from "../registry/index.js";
9
8
  /** Makes a folder the player's chess table: the files through which Claude Code and Codex find Chesspal and the opponent's instructions (D-31). */
10
9
  export const init = defineCommand({
11
10
  name: "init",
@@ -1,7 +1,7 @@
1
1
  import { z } from "zod";
2
- import type { Context } from "./context.ts";
2
+ import { type Context } from "../registry/index.ts";
3
3
  /** How to play as the player's opponent, which the chat reads before its first reply and again after a summary (D-102). */
4
- export declare const instructions: import("./registry.ts").Command<z.ZodObject<{}, z.core.$strip>, string>;
4
+ export declare const instructions: import("../registry/registry.ts").Command<z.ZodObject<{}, z.core.$strip>, string>;
5
5
  /**
6
6
  * Whether the chat has read the opponent's instructions since it started, or was cleared or summarized, as its start keys them (D-57); without the host's
7
7
  * note of it, since the MCP server started. Always on the command line, which keeps nothing.
@@ -1,8 +1,8 @@
1
1
  // The opponent's instructions, which the MCP server gives so that they follow the version installed (D-102).
2
2
  import { z } from "zod";
3
- import { lastChatStart } from "../games/index.js";
4
- import { opponentInstructions } from "../host-files/index.js";
5
- import { defineCommand } from "./registry.js";
3
+ import { lastChatStart } from "../../games/index.js";
4
+ import { opponentInstructions } from "../../host-files/index.js";
5
+ import { defineCommand } from "../registry/index.js";
6
6
  /** How to play as the player's opponent, which the chat reads before its first reply and again after a summary (D-102). */
7
7
  export const instructions = defineCommand({
8
8
  name: "instructions",
@@ -1,6 +1,5 @@
1
1
  import { z } from "zod";
2
- import { type GameSummary } from "./game-summary.ts";
3
- import { type Written } from "./say.ts";
2
+ import { type GameSummary, type Written } from "../answers/index.ts";
4
3
  interface Said {
5
4
  /** The game, none when the AI spoke before the first (D-77). */
6
5
  game?: GameSummary;
@@ -9,7 +8,7 @@ interface Said {
9
8
  coach?: true;
10
9
  }
11
10
  /** The AI's words on the board page without a move, read only there, on the player's time: an answer, or a word it has to say (D-54, D-72). */
12
- export declare const aiSay: import("./registry.ts").Command<z.ZodObject<{
11
+ export declare const aiSay: import("../registry/registry.ts").Command<z.ZodObject<{
13
12
  text: z.ZodString;
14
13
  mood: z.ZodOptional<z.ZodEnum<{
15
14
  neutral: "neutral";
@@ -1,13 +1,9 @@
1
1
  import { z } from "zod";
2
- import { moods } from "../core/index.js";
3
- import { archivedName, talkLimits } from "../games/index.js";
4
- import { gameFolder } from "../profiles/index.js";
5
- import { analystLine, coachOnEnd, coachOnEndLine, reviewedLine, withAnalyst } from "./agents.js";
6
- import { newGameStep, nextStep, wordLines } from "./answer-lines.js";
7
- import { summaryOf } from "./game-summary.js";
8
- import { refuseOtherGame } from "./play-move.js";
9
- import { defineCommand } from "./registry.js";
10
- import { give, say, waitingWords } from "./say.js";
2
+ import { moods } from "../../core/index.js";
3
+ import { archivedName, talkLimits } from "../../games/index.js";
4
+ import { gameFolder } from "../../profiles/index.js";
5
+ import { analystLine, coachOnEnd, coachOnEndLine, give, newGameStep, nextStep, refuseOtherGame, reviewedLine, say, summaryOf, waitingWords, withAnalyst, wordLines } from "../answers/index.js";
6
+ import { defineCommand } from "../registry/index.js";
11
7
  /** The AI's words on the board page without a move, read only there, on the player's time: an answer, or a word it has to say (D-54, D-72). */
12
8
  export const aiSay = defineCommand({
13
9
  name: "ai_say",
@@ -0,0 +1,2 @@
1
+ export { aiSay } from "./ai-say.ts";
2
+ export { playerSay } from "./player-say.ts";
@@ -0,0 +1,3 @@
1
+ // The talk at the table: the AI's words and the player's, said beside the moves.
2
+ export { aiSay } from "./ai-say.js";
3
+ export { playerSay } from "./player-say.js";
@@ -1,5 +1,5 @@
1
1
  import { z } from "zod";
2
2
  /** The player's words to the AI, as the board page sends them: on the command line only, since the AI never writes for the player. */
3
- export declare const playerSay: import("./registry.ts").Command<z.ZodObject<{
3
+ export declare const playerSay: import("../registry/registry.ts").Command<z.ZodObject<{
4
4
  text: z.ZodString;
5
5
  }, z.core.$strip>, string>;
@@ -1,8 +1,8 @@
1
1
  import { z } from "zod";
2
- import { talkLimits } from "../games/index.js";
3
- import { gameFolder } from "../profiles/index.js";
4
- import { defineCommand } from "./registry.js";
5
- import { say } from "./say.js";
2
+ import { talkLimits } from "../../games/index.js";
3
+ import { gameFolder } from "../../profiles/index.js";
4
+ import { say } from "../answers/index.js";
5
+ import { defineCommand } from "../registry/index.js";
6
6
  /** The player's words to the AI, as the board page sends them: on the command line only, since the AI never writes for the player. */
7
7
  export const playerSay = defineCommand({
8
8
  name: "player_say",
@@ -9,7 +9,7 @@ import { explain } from "../board/explain.js";
9
9
  import { kingSigns } from "../board/signs.js";
10
10
  import { piecesOf } from "../board/squares.js";
11
11
  import { NewGameDialog } from "../dialogs/new-game.js";
12
- import { NameButton, PictureButton, ProfileDialog, ProfilesDialog, Wizard } from "../dialogs/profiles.js";
12
+ import { NameButton, ProfileDialog, ProfilesDialog, Wizard } from "../dialogs/profiles.js";
13
13
  import { SettingsPanel } from "../dialogs/settings.js";
14
14
  import { playerIn, studiedSides } from "../dialogs/studied.js";
15
15
  import { StudyDialog } from "../dialogs/study.js";
@@ -25,6 +25,7 @@ import { BareButton, HelpsCard, HintLine, MapButton } from "../helps/helps.js";
25
25
  import { helpsOf } from "../helps/view.js";
26
26
  import { FoldTab } from "../layout/fold-tab.js";
27
27
  import { nextFold, rememberFold, startFolds } from "../layout/folds.js";
28
+ import { PageFoot } from "../layout/footer.js";
28
29
  import { Header } from "../layout/header.js";
29
30
  import { standingsOf } from "../lead/view.js";
30
31
  import { MarksLegend } from "../legend/legend.js";
@@ -35,7 +36,8 @@ import { signature } from "../players/model-name.js";
35
36
  import { keepSettings } from "../profile/profile.js";
36
37
  import { languagesOf, saveSettings, settingsFrom, showSettings } from "../profile/settings.js";
37
38
  import { endSoundOf, playSound, soundOf, wakeSounds } from "../sounds/sounds.js";
38
- import { ChatOpener } from "../table/opener.js";
39
+ import { UpdateDialog, UpdateNotice } from "../table/update.js";
40
+ import { AiNameButton, ChatDialog, tableNote } from "../table/window.js";
39
41
  import { AiNote, AiWords, Compose, PointSquare, TalkFaces } from "../talk/talk.js";
40
42
  import { textFor } from "../text.js";
41
43
  import { anchorName, useAnchor } from "./anchor.js";
@@ -67,6 +69,18 @@ export function App({ start }) {
67
69
  };
68
70
  const [dialog, setDialog] = useState(undefined);
69
71
  const { data: served, problem, offline, reload } = useServed(() => setDialog("new-game"));
72
+ // Nuova partita with no chat at the table asks for one first (D-107): the chat's window, then the new game's, however the first closes.
73
+ const [chatFirst, setChatFirst] = useState(false);
74
+ const askNewGame = () => {
75
+ const table = offline ? undefined : served?.table;
76
+ const first = table !== undefined && !table.chat && table.opening === undefined;
77
+ setChatFirst(first);
78
+ setDialog(first ? "chat" : "new-game");
79
+ };
80
+ const closeChat = () => {
81
+ setDialog(chatFirst ? "new-game" : undefined);
82
+ setChatFirst(false);
83
+ };
70
84
  const [selected, setSelected] = useState(undefined);
71
85
  const [refusal, setRefusal] = useState(undefined);
72
86
  // A pawn taken to the last rank waits for the player to choose what it becomes.
@@ -237,9 +251,9 @@ export function App({ start }) {
237
251
  },
238
252
  };
239
253
  const { kind, verdict } = aborted && phase === "over" ? { kind: "over", verdict: text.aborted } : verdictOf(phase, how, winner, offer, declined, text);
240
- const header = (_jsx(Header, { text: text, kind: kind, verdict: verdict, ...(!ended && (offer === "ai" || declined) ? { who: name } : {}), actions: actions, onNewGame: served === undefined ? undefined : () => setDialog("new-game"),
254
+ const header = (_jsx(Header, { text: text, kind: kind, verdict: verdict, ...(!ended && (offer === "ai" || declined) ? { who: name } : {}), actions: actions, onNewGame: served === undefined ? undefined : askNewGame,
241
255
  // Between games the new one stands out in the player's card (D-77): in the header only when the talk, and the card with it, is folded away.
242
- standOut: studied !== undefined || (between && folds.talk !== "open"), onStudy: served === undefined || going ? undefined : () => setDialog("study"), flipped: flipped, onFlip: () => setFlipped(!flipped), sound: sound, onSound: () => change({ ...settings, sound: !sound }), onTheme: switchTheme, onSettings: () => setSetting(true), onGuide: () => setDialog("guide"), ...(served === undefined ? {} : { profile: served.profile, onProfile: () => setDialog("profiles") }) }));
256
+ standOut: studied !== undefined || (between && folds.talk !== "open"), onStudy: served === undefined || going ? undefined : () => setDialog("study"), flipped: flipped, onFlip: () => setFlipped(!flipped), sound: sound, onSound: () => change({ ...settings, sound: !sound }), onTheme: switchTheme, onSettings: () => setSetting(true), onGuide: () => setDialog("guide"), ...(served === undefined ? {} : { profile: served.profile, onProfile: () => setDialog("profiles") }), ...(served?.update === undefined || served.table === undefined || offline ? {} : { update: { version: served.update.version, installing: served.update.installing === true }, onUpdate: () => setDialog("update") }) }));
243
257
  const panel = _jsx(SettingsPanel, { open: setting, settings: settings, onChange: change, onClose: () => setSetting(false), text: text });
244
258
  // The guide opens before the page has its data too.
245
259
  const guide = _jsx(GuideDialog, { open: dialog === "guide", onClose: () => setDialog(undefined), text: text });
@@ -286,6 +300,8 @@ export function App({ start }) {
286
300
  const legendAt = (under) => (_jsx(MarksLegend, { rows: legendRows, open: legendOpen, onOpen: setLegendOpen, drawn: { words: drawnByCoach ? text.helpsCard.drawn : text.drawn, by: drawer }, onPoint: setPointed, under: under, text: text }));
287
301
  const legend = legendAt(false);
288
302
  const summary = studied === undefined ? undefined : gameReviewOf(live.record);
303
+ // An AI is going over the game only when the server says so: with no chat at it, nobody is (Stefano).
304
+ const reviewing = summary === undefined && studied?.reviewing === true && !offline;
289
305
  const closing = studied === undefined ? undefined : closingOf(live.record);
290
306
  // In analysis the AI's card says who played that game, as its PGN keeps it (D-82), not who plays now.
291
307
  const playedBy = studied === undefined ? undefined : aiModelIn(live.record);
@@ -297,21 +313,25 @@ export function App({ start }) {
297
313
  const asleep = served.listening === false && !offline;
298
314
  // What the AI thinks on, a cloud at the Torretta's corner (Zoe): only in a game going on, and never while no chat hears the player, which the note says.
299
315
  const thinking = going && !asleep && !offline && studied === undefined ? served.thinking : undefined;
300
- const thinker = (size) => (_jsx(Thinker, { thinking: thinking, words: text.thinking, children: _jsx(Torretta, { size: size, mood: mood }) }));
316
+ const thinker = (size) => (_jsx(Thinker, { thinking: thinking, words: text.thinking, children: _jsx(Torretta, { size: size, mood: mood, bust: true }) }));
301
317
  // The clocks wait for the player from the game's start: when the AI moves first, a button gives it the start; else the player's first move does (Stefano).
302
318
  const clockWaits = clock?.waiting === true && !ended && studied === undefined && viewing === undefined && live.position.turn === ai;
303
319
  const clockOf = (side) => clock === undefined ? undefined : (_jsx(ClockFace, { clock: clock, side: side, toMove: !ended && live.position.turn === side, held: pending !== undefined || (asleep && side === ai), onFlag: reload, text: text }));
304
320
  // Where to wake the chat, under the AI's card (over the board when the talk is folded); the button that starts the clocks, under the player's (Zoe).
305
- // On the table with no chat at it, the note opens one instead (D-104).
321
+ // On the table, what its chat is doing stands there, and in a game with no chat at it a line that opens the chat's window (D-107).
306
322
  const table = offline ? undefined : served.table;
307
- const opener = table !== undefined && !table.chat ? _jsx(ChatOpener, { table: table, settings: settings, onChange: change, name: name, text: text }) : undefined;
308
- const asleepNote = opener ?? (asleep ? _jsx("p", { class: "asleep", role: "status", children: sides === undefined ? text.asleep(name) : text.asleepInAnalysis(name) }) : null);
323
+ const atTable = table === undefined ? null : tableNote({ table, name, inGame: going, onOpen: () => setDialog("chat"), text });
324
+ const asleepNote = atTable !== null
325
+ ? atTable
326
+ : asleep && table?.chat !== false
327
+ ? _jsx("p", { class: "asleep", role: "status", children: sides === undefined ? text.asleep(name) : text.asleepInAnalysis(name) })
328
+ : null;
309
329
  const startClocks = clockWaits
310
330
  ? (_jsxs("button", { type: "button", class: "command primary start-clocks", onClick: () => act("/api/start", {}), children: [_jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M8 5.5v13l10.5-6.5z" }) }), text.startClocks] }))
311
331
  : null;
312
332
  // Between games the next starts from the player's card, where the clock and the button that starts it stand in a game (D-77).
313
333
  const nextGame = between
314
- ? (_jsxs("button", { type: "button", class: "command primary next-game", onClick: () => setDialog("new-game"), children: [_jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M12 5V19M5 12H19" }) }), text.newGame] }))
334
+ ? (_jsxs("button", { type: "button", class: "command primary next-game", onClick: askNewGame, children: [_jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M12 5V19M5 12H19" }) }), text.newGame] }))
315
335
  : null;
316
336
  // Words sent leave the box; what the player wrote on while they went stays.
317
337
  const clearSent = (refused, words) => {
@@ -381,9 +401,7 @@ export function App({ start }) {
381
401
  act("/api/new", { ai_colour: colour === "random" ? "random" : colour === "white" ? "black" : "white", level: chosen, ...(control === undefined ? {} : { clock: control }) });
382
402
  };
383
403
  // In analysis the AI's note goes with the other side's card and the talk with the player's, turned round with the board as in a game (D-101, Zoe).
384
- const studiedCard = (side, place) => sides === undefined ? null : (_jsx(PlayerCard, { place: place,
385
- // The player opens their profile in analysis too, from their own side's card.
386
- picture: sides[side].who === "player" ? _jsx(PictureButton, { profile: profile, onOpen: () => setDialog("profile"), text: text }) : pictureOf(sides[side], profile, 48, mood), name: sides[side].name, signed: sides[side].who === "ai" && playedBy !== undefined ? signature(playedBy.model, playedBy.effort) : undefined, side: side, taken: material.taken[side], ahead: ahead(side), badges: _jsx(BadgeCounts, { plies: plies, side: side, names: text.badges }), text: text }));
404
+ const studiedCard = (side, place) => sides === undefined ? null : (_jsx(PlayerCard, { place: place, picture: pictureOf(sides[side], profile, 48, mood), name: sides[side].name, signed: sides[side].who === "ai" && playedBy !== undefined ? signature(playedBy.model, playedBy.effort) : undefined, side: side, taken: material.taken[side], ahead: ahead(side), badges: _jsx(BadgeCounts, { plies: plies, side: side, names: text.badges }), text: text }));
387
405
  // A game brought to analyse: the page shows it once the server has it in analysis; a refusal stays in the dialog.
388
406
  const studyGame = async (studied) => {
389
407
  const refused = await post("/api/study", studied, text, name);
@@ -392,29 +410,35 @@ export function App({ start }) {
392
410
  }
393
411
  return refused?.text;
394
412
  };
395
- return (_jsx(ChosenSet.Provider, { value: settings.set, children: _jsxs(PointSquare.Provider, { value: pointSquare, children: [panel, _jsx(ProfileDialog, { open: dialog === "profile", profile: profile, onClose: () => setDialog(undefined), text: text }), _jsx(ProfilesDialog, { open: dialog === "profiles", profiles: served.profiles, active: profile, locked: going ? text.profile.whilePlaying : undefined, dataFolder: served.dataFolder, onEdit: () => setDialog("profile"), onClose: () => setDialog(undefined), text: text }), _jsx(NewGameDialog, { open: dialog === "new-game", choice: choice, going: going, onStart: startNewGame, onClose: () => setDialog(undefined), text: text }), guide, _jsx(StudyDialog, { open: dialog === "study", onStudy: studyGame, onClose: () => setDialog(undefined), you: profile.name, accounts: profile.accounts, ai: name, text: text }), offline ? _jsx("p", { class: "offline", role: "alert", children: text.offline(name) }) : null, served.outdated === true ? _jsx("p", { class: "outdated", role: "status", children: text.outdated(name) }) : null, _jsxs("main", { class: `app${flipped ? " flipped" : ""}${folds.talk === "open" ? "" : ` talk-${folds.talk}`}${folds.game === "folded" ? " folded" : ""}`, inert: offline, children: [header, sides === undefined
413
+ return (_jsx(ChosenSet.Provider, { value: settings.set, children: _jsxs(PointSquare.Provider, { value: pointSquare, children: [panel, _jsx(ProfileDialog, { open: dialog === "profile", profile: profile, onClose: () => setDialog(undefined), text: text }), _jsx(ProfilesDialog, { open: dialog === "profiles", profiles: served.profiles, active: profile, locked: going ? text.profile.whilePlaying : undefined, dataFolder: served.dataFolder, onEdit: () => setDialog("profile"), onClose: () => setDialog(undefined), text: text }), table === undefined ? null : _jsx(ChatDialog, { open: dialog === "chat", table: table, settings: settings, onChange: change, name: name, onClose: closeChat, text: text }), _jsx(NewGameDialog, { open: dialog === "new-game", choice: choice, going: going, onStart: startNewGame, onClose: () => setDialog(undefined), text: text }), guide, _jsx(StudyDialog, { open: dialog === "study", onStudy: studyGame, onClose: () => setDialog(undefined), you: profile.name, accounts: profile.accounts, ai: name, text: text }), served.update === undefined || served.table === undefined
414
+ ? null
415
+ : (_jsx(UpdateDialog, { open: dialog === "update", version: served.update.version, current: served.version, installing: served.update.installing === true, installed: served.update.installed === true, going: going, chat: served.table.chat ? (served.table.held ? "held" : "byHand") : undefined, name: name, onClose: () => setDialog(undefined), text: text })), offline ? _jsx("p", { class: "offline", role: "alert", children: text.offline(name) }) : null, served.outdated === true
416
+ ? _jsx("p", { class: "outdated", role: "status", children: text.outdated(name) })
417
+ : served.update !== undefined && served.table === undefined
418
+ ? _jsx(UpdateNotice, { version: served.update.version, name: name, text: text })
419
+ : null, _jsxs("main", { class: `app${flipped ? " flipped" : ""}${folds.talk === "open" ? "" : ` talk-${folds.talk}`}${folds.game === "folded" ? " folded" : ""}`, inert: offline, children: [header, sides === undefined
396
420
  ? (_jsx(TalkFaces, { name: name, aiPicture: thinker(40), you: profile.name, picture: picture, ...(idle ? {} : { ai, player }), aiMoves: going && !playerToMove, playerMoves: playerToMove, unread: unread || asleep, text: text }))
397
421
  : (_jsx(TalkFaces, { name: sides[ai].name, aiPicture: pictureOf(sides[ai], profile, 40, mood), you: sides[player].name, picture: pictureOf(sides[player], profile, 40, mood), ai: ai, player: player, aiMoves: false, playerMoves: false, unread: asleep, text: text })), _jsx(FoldTab, { column: "talk", folded: folds.talk === "folded", label: thinking === undefined || folds.talk !== "folded" ? text.talk : `${text.talk}: ${text.thinking[thinking]}`, unread: (unread || asleep) && folds.talk === "folded", ...(thinking === undefined || folds.talk !== "folded" ? {} : { mark: _jsx(Thought, { thinking: thinking, words: text.thinking }) }), onFold: fold }), _jsx(FoldTab, { column: "game", folded: folds.game === "folded", label: text.game, onFold: fold }), _jsx("span", { class: "visually-hidden", "aria-live": "polite", children: unread ? text.wrote(name) : "" }), _jsx("div", { class: sides === undefined ? "talk" : "talk studied", id: "talk", children: sides !== undefined && studied !== undefined
398
- ? (_jsxs(_Fragment, { children: [_jsxs("section", { class: "ai-block", "aria-label": sides[ai].name, children: [studiedCard(ai, "ai"), asleepNote, _jsx(AiNote, { ...(reviewed === undefined ? {} : { ply: reviewed }), ...(shownOpening === undefined ? {} : { opening: shownOpening }), ...(at === total && closing !== undefined ? { closing } : {}), ...(at === 0 && summary !== undefined ? { summary } : {}), ...(summary === undefined ? { reviewing: true } : {}), name: analyst, by: annotator, mood: mood, ...(settings.voice ? { voice: text.language } : {}), text: text })] }), _jsxs("section", { class: "you-block", "aria-label": sides[player].name, children: [_jsx("div", { class: "chat", children: _jsx(Compose, { lines: analysisTalk(talk, studied.name), newestFirst: false, spent: 0, draft: draft, onDraft: (words) => {
422
+ ? (_jsxs(_Fragment, { children: [_jsxs("section", { class: "ai-block", "aria-label": sides[ai].name, children: [studiedCard(ai, "ai"), asleepNote, _jsx(AiNote, { ...(reviewed === undefined ? {} : { ply: reviewed }), ...(shownOpening === undefined ? {} : { opening: shownOpening }), ...(at === total && closing !== undefined ? { closing } : {}), ...(at === 0 && summary !== undefined ? { summary } : {}), ...(reviewing ? { reviewing: true } : {}), name: analyst, by: annotator, mood: mood, ...(settings.voice ? { voice: text } : {}), text: text })] }), _jsxs("section", { class: "you-block", "aria-label": sides[player].name, children: [_jsx("div", { class: "chat", children: _jsx(Compose, { lines: analysisTalk(talk, studied.name), newestFirst: false, spent: 0, draft: draft, onDraft: (words) => {
399
423
  setDraft(words);
400
424
  setUnsent(undefined);
401
425
  }, onSend: () => sendWords({ game: studied.name, at }), canMove: false, name: name, problem: unsent, about: { ...(at === 0 ? {} : { ply: plies[at - 1] }), onShow: goTo }, text: text }) }), studiedCard(player, "you")] })] }))
402
- : (_jsxs(_Fragment, { children: [_jsxs("section", { class: "ai-block", "aria-label": name, children: [_jsx(PlayerCard, { place: "ai", picture: thinker(48), name: name, signed: signed, side: idle ? undefined : ai, ...(idle ? {} : { level: text.start.levels[level] }), taken: material.taken[ai], ahead: ahead(ai), clock: clockOf(ai), text: text }), asleepNote, _jsx(AiWords, { words: aiWords(plies, ai, talk), newestFirst: !flipped, ...(settings.voice ? { voice: text.language } : {}) })] }), _jsxs("section", { class: "you-block", "aria-label": profile.name, children: [_jsx(Compose, { lines: said, newestFirst: flipped,
426
+ : (_jsxs(_Fragment, { children: [_jsxs("section", { class: "ai-block", "aria-label": name, children: [_jsx(PlayerCard, { place: "ai", picture: thinker(48), name: table === undefined ? name : _jsx(AiNameButton, { name: name, table: table, onOpen: () => setDialog("chat"), text: text }), signed: signed, side: idle ? undefined : ai, ...(idle ? {} : { level: text.start.levels[level] }), taken: material.taken[ai], ahead: ahead(ai), clock: clockOf(ai), text: text }), asleepNote, _jsx(AiWords, { words: aiWords(plies, ai, talk), newestFirst: !flipped, ...(settings.voice ? { voice: text } : {}) })] }), _jsxs("section", { class: "you-block", "aria-label": profile.name, children: [_jsx(Compose, { lines: said, newestFirst: flipped,
403
427
  // Between games, and before the first, the player writes freely, a line at a time, as the AI does (D-74, D-77).
404
428
  spent: going ? playerSpent(plies, ai, talk) : 0, draft: draft, onDraft: (words) => {
405
429
  setDraft(words);
406
430
  setUnsent(undefined);
407
- }, onSend: sendWords, canMove: playerToMove, name: name, problem: unsent, text: text }), _jsx(PlayerCard, { place: "you", picture: _jsx(PictureButton, { profile: profile, onOpen: () => setDialog("profile"), text: text }), name: _jsx(NameButton, { profile: profile, onOpen: () => setDialog("profiles"), text: text }), side: idle ? undefined : player, taken: material.taken[player], ahead: ahead(player), turn: playerToMove, clock: _jsxs(_Fragment, { children: [clockOf(player), startClocks, nextGame] }), text: text })] })] })) }), _jsxs("div", {
431
+ }, onSend: sendWords, canMove: playerToMove, name: name, problem: unsent, text: text }), _jsx(PlayerCard, { place: "you", picture: picture, name: _jsx(NameButton, { profile: profile, onOpen: () => setDialog("profiles"), text: text }), side: idle ? undefined : player, taken: material.taken[player], ahead: ahead(player), turn: playerToMove, clock: _jsxs(_Fragment, { children: [clockOf(player), startClocks, nextGame] }), text: text })] })] })) }), _jsxs("div", {
408
432
  // Between games the board goes off (D-77): a game the page saw end once its effects are over, its final position less off than the table set.
409
433
  class: between && !justEnded ? "board-wrap off" : "board-wrap", onWheel: wheel,
410
434
  // A press on the board while the player looks back brings the game as it is: the next one plays.
411
- onPointerDownCapture: viewing === undefined || studied !== undefined ? undefined : (event) => event.button === 0 && goTo(total), children: [_jsx(Board, { fen: shown.position.fen, bottom: flipped ? ai : player, lastMove: shown.lastMove, pointed: pointed, ...(helps === undefined ? {} : { helps }), ...(mapping && helps !== undefined ? { control: helps.strategy.control } : {}), bare: bare && helps !== undefined, ...(review?.badge === undefined ? {} : { badge: { badge: review.badge, name: text.badges[review.badge] } }), ...(drawn === undefined ? {} : { drawn }), nextMove: next, jumped: jumped, check: shown.position.check, end: boardEnd, player: playerToMove && pending === undefined && viewing === undefined ? player : undefined, selected: selected, targets: selected === undefined || viewing !== undefined ? [] : live.legalMoves(selected), dots: settings.dots, names: settings.squareNames, notice: viewing === undefined ? refusal : undefined, promotion: promoting === undefined || viewing !== undefined || !playerToMove ? undefined : { ...promoting, side: player }, onPromote: (piece) => piece === undefined ? setPromoting(undefined) : play(promoting.from, promoting.to, piece), onSelect: select, onMove: play, onRefuse: refuse, text: text }), folds.talk !== "open" && (asleep || opener !== undefined || clockWaits)
412
- ? (_jsxs("div", { class: "over-board", children: [opener ?? (asleep ? _jsx("p", { class: "asleep", role: "status", children: text.asleep(name) }) : null), startClocks] }))
413
- : null] }), studied === undefined ? null : (_jsx("div", { class: "board-steps", children: _jsx(Steps, { shown: at, total: total, onStep: step, text: text }) })), helps === undefined ? null : (_jsxs("div", { class: "board-map", children: [legendAt(true), _jsx(BareButton, { bare: bare, onBare: setBare, text: text }), _jsx(MapButton, { mapping: mapping, onMap: setMapping, text: text }), hint === undefined || viewing !== undefined ? null : _jsx(HintLine, { hint: hint, open: hintOpen, onOpen: openTheHint, text: text })] })), _jsx(GameColumn, { plies: idle ? [] : plies, result: ended ? result : undefined, how: how, lit: justEnded, started: idle ? undefined : played?.started, move: live.position.moveNumber, toMove: live.position.turn, opening: opening, helps: helps === undefined ? undefined : (_jsx(HelpsCard, { helps: helps, judged: judgments, hint: viewing === undefined ? hint : undefined, hintOpen: hintOpen, onHint: openTheHint, onPoint: setPointed, onMap: setMapping, mapping: mapping, onBare: setBare, bare: bare, legend: legend, text: text })), ...(helps === undefined && legendRows.length > 0 ? { legend } : {}), ...(standings === undefined ? {} : { standings }), ...(studied === undefined ? {} : { analysis: summary === undefined ? { by: analyst } : { by: analyst, summary } }), shownPlies: at, onShow: goTo, onStep: step, ...(studied === undefined ? {} : { onDownload: () => download(studied.pgn, pgnName(live.record.tags, "analysis")) }), ...(total === 0 ? {} : { onSaveMoves: () => download(writePgn(movesOnly(live.record)), pgnName(live.record.tags)) }), ...(between && game !== null && replayed.record.moves.length > 0 ? { onAnalyse: () => void studyGame({ last: true }) } : {}), text: text })] })] }) }));
435
+ onPointerDownCapture: viewing === undefined || studied !== undefined ? undefined : (event) => event.button === 0 && goTo(total), children: [_jsx(Board, { fen: shown.position.fen, bottom: flipped ? ai : player, lastMove: shown.lastMove, pointed: pointed, ...(helps === undefined ? {} : { helps }), ...(mapping && helps !== undefined ? { control: helps.strategy.control } : {}), bare: bare && helps !== undefined, ...(review?.badge === undefined ? {} : { badge: { badge: review.badge, name: text.badges[review.badge] } }), ...(drawn === undefined ? {} : { drawn }), nextMove: next, jumped: jumped, check: shown.position.check, end: boardEnd, player: playerToMove && pending === undefined && viewing === undefined ? player : undefined, selected: selected, targets: selected === undefined || viewing !== undefined ? [] : live.legalMoves(selected), dots: settings.dots, names: settings.squareNames, notice: viewing === undefined ? refusal : undefined, promotion: promoting === undefined || viewing !== undefined || !playerToMove ? undefined : { ...promoting, side: player }, onPromote: (piece) => piece === undefined ? setPromoting(undefined) : play(promoting.from, promoting.to, piece), onSelect: select, onMove: play, onRefuse: refuse, text: text }), folds.talk !== "open" && (asleepNote !== null || clockWaits)
436
+ ? (_jsxs("div", { class: "over-board", children: [asleepNote, startClocks] }))
437
+ : null] }), studied === undefined ? null : (_jsx("div", { class: "board-steps", children: _jsx(Steps, { shown: at, total: total, onStep: step, text: text }) })), helps === undefined ? null : (_jsxs("div", { class: "board-map", children: [legendAt(true), _jsx(BareButton, { bare: bare, onBare: setBare, text: text }), _jsx(MapButton, { mapping: mapping, onMap: setMapping, text: text }), hint === undefined || viewing !== undefined ? null : _jsx(HintLine, { hint: hint, open: hintOpen, onOpen: openTheHint, text: text })] })), _jsx(GameColumn, { plies: idle ? [] : plies, result: ended ? result : undefined, how: how, lit: justEnded, started: idle ? undefined : played?.started, move: live.position.moveNumber, toMove: live.position.turn, opening: opening, helps: helps === undefined ? undefined : (_jsx(HelpsCard, { helps: helps, judged: judgments, hint: viewing === undefined ? hint : undefined, hintOpen: hintOpen, onHint: openTheHint, onPoint: setPointed, onMap: setMapping, mapping: mapping, onBare: setBare, bare: bare, legend: legend, text: text })), ...(helps === undefined && legendRows.length > 0 ? { legend } : {}), ...(standings === undefined ? {} : { standings }), ...(studied === undefined ? {} : { analysis: { by: analyst, ...(summary === undefined ? {} : { summary }), ...(reviewing ? { reviewing: true } : {}) } }), shownPlies: at, onShow: goTo, onStep: step, ...(studied === undefined ? {} : { onDownload: () => download(studied.pgn, pgnName(live.record.tags, "analysis")) }), ...(total === 0 ? {} : { onSaveMoves: () => download(writePgn(movesOnly(live.record)), pgnName(live.record.tags)) }), ...(between && game !== null && replayed.record.moves.length > 0 ? { onAnalyse: () => void studyGame({ last: true }) } : {}), text: text }), _jsx(PageFoot, { version: served.version, text: text })] })] }) }));
414
438
  }
415
439
  /** The picture of a player of the game in analysis: the player's own, the AI's Torretta with its mood, or the plain face of someone else. */
416
440
  function pictureOf({ who }, profile, size, mood) {
417
- return who === "ai" ? _jsx(Torretta, { size: size, mood: mood }) : _jsx(PlayerPicture, { profile: who === "player" ? profile : undefined, size: size });
441
+ return who === "ai" ? _jsx(Torretta, { size: size, mood: mood, bust: true }) : _jsx(PlayerPicture, { profile: who === "player" ? profile : undefined, size: size });
418
442
  }
419
443
  /**
420
444
  * Plays the sound of each move the game gains while the page watches it, the AI's and the player's, of its end without a move and of a draw offered:
@@ -13,6 +13,8 @@ export function useServed(onNewGame) {
13
13
  const reload = useRef(() => { });
14
14
  useEffect(() => {
15
15
  let latest = 0;
16
+ // The Chesspal version of the first answer: a server of another, after an update, sends data this page may not read.
17
+ let pageVersion;
16
18
  const load = () => {
17
19
  // Two reads in flight may answer out of order: only the last one asked counts.
18
20
  const asked = ++latest;
@@ -22,6 +24,11 @@ export function useServed(onNewGame) {
22
24
  if (asked !== latest) {
23
25
  return;
24
26
  }
27
+ pageVersion ??= body.version;
28
+ if (body.version !== undefined && body.version !== pageVersion) {
29
+ window.location.reload();
30
+ return;
31
+ }
25
32
  setProblem(body.error);
26
33
  if (body.error === undefined) {
27
34
  setData(body);
@@ -1,12 +1,19 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "preact/jsx-runtime";
2
2
  // The Torretta (D-45, D-46): the opponent and the logo, a rook that is a robot. Its screen shows the AI's mood (D-14, D-70): only the eyes, the mouth and the light change.
3
3
  import { useRef } from "preact/hooks";
4
- /** The Torretta at a size in pixels, with the face of a mood (neutral by default); with a label it is an image a screen reader names, else a decoration. */
5
- export function Torretta({ size, label, mood = "neutral" }) {
4
+ /**
5
+ * The Torretta at a size in pixels, with the face of a mood (neutral by default); with a label it is an image a screen reader names, else a decoration.
6
+ * As a bust it is its own profile picture, on a player's card: a disc that frames it from the antenna to the screen, so that its face reads (Stefano).
7
+ */
8
+ export function Torretta({ size, label, mood = "neutral", bust = false }) {
6
9
  // The face pops in when the mood changes, never when the Torretta first shows (the logo, a card, a dialog).
7
10
  const changed = useChanged(mood);
8
- return (_jsxs("svg", { class: `torretta mood-${mood}`, viewBox: "0 0 128 128", width: size, height: size, role: label === undefined ? undefined : "img", "aria-label": label, "aria-hidden": label === undefined ? "true" : undefined, children: [_jsx("path", { class: "antenna", d: "M64 31V19" }), _jsx("circle", { class: "light", cx: "64", cy: "16", r: "5" }), _jsx("rect", { class: "shade", x: "29", y: "64", width: "12", height: "26", rx: "2" }), _jsx("rect", { class: "shade", x: "87", y: "64", width: "12", height: "26", rx: "2" }), _jsx("path", { class: "body", d: "M34 30H47V40H57V30H71V40H81V30H94V54L89 58L92 106H36L39 58L34 54Z" }), _jsx("rect", { class: "shade", x: "30", y: "104", width: "68", height: "12", rx: "4" }), _jsx("rect", { class: "screen", x: "44", y: "63", width: "40", height: "32", rx: "7" }), _jsx("g", { class: changed ? "expression changed" : "expression", children: _jsx(Face, { mood: mood }) }, mood)] }));
11
+ return (_jsxs("svg", { class: `torretta mood-${mood}${bust ? " bust" : ""}`, viewBox: bust ? bustFrame : fullFrame, width: size, height: size, role: label === undefined ? undefined : "img", "aria-label": label, "aria-hidden": label === undefined ? "true" : undefined, children: [bust ? _jsx("circle", { class: "back", cx: "64", cy: "56", r: "48" }) : null, _jsx("path", { class: "antenna", d: "M64 31V19" }), _jsx("circle", { class: "light", cx: "64", cy: "16", r: "5" }), _jsx("rect", { class: "shade", x: "29", y: "64", width: "12", height: "26", rx: "2" }), _jsx("rect", { class: "shade", x: "87", y: "64", width: "12", height: "26", rx: "2" }), _jsx("path", { class: "body", d: "M34 30H47V40H57V30H71V40H81V30H94V54L89 58L92 106H36L39 58L34 54Z" }), _jsx("rect", { class: "shade", x: "30", y: "104", width: "68", height: "12", rx: "4" }), _jsx("rect", { class: "screen", x: "44", y: "63", width: "40", height: "32", rx: "7" }), _jsx("g", { class: changed ? "expression changed" : "expression", children: _jsx(Face, { mood: mood }) }, mood)] }));
9
12
  }
13
+ // The whole figure's frame: the square around it, from the antenna's light (y 11) to the base (y 116), two units clear of each, so that it fills its size.
14
+ const fullFrame = "9.5 9 109 109";
15
+ // The bust's frame, the square around its disc: the antenna's light three units under its top, the base left out below.
16
+ const bustFrame = "16 8 96 96";
10
17
  /** Whether the mood has changed since the Torretta first showed: true from its first change on, so a face already shown never starts its entrance again. */
11
18
  function useChanged(mood) {
12
19
  const first = useRef(mood);
@@ -291,6 +291,16 @@ h3 + .switch {
291
291
  translate: 14px 0;
292
292
  }
293
293
 
294
+ /* A switch that cannot be used here: faded, and its tooltip says why */
295
+ .switch:has(input:disabled),
296
+ .switch input:disabled {
297
+ cursor: help;
298
+ }
299
+
300
+ .switch:has(input:disabled) > * {
301
+ opacity: 0.5;
302
+ }
303
+
294
304
  @media (prefers-reduced-motion: reduce) {
295
305
  .switch input,
296
306
  .switch input::after {
@@ -11,11 +11,7 @@ import { Dialog } from "./dialog.js";
11
11
  export function Wizard({ text, hasGame, account, dataFolder, onMade }) {
12
12
  return (_jsxs("main", { class: "wizard", children: [_jsx(Torretta, { size: 56 }), _jsx("h1", { children: text.profile.welcome }), _jsxs("p", { class: "lead", children: [text.profile.lead, hasGame ? _jsx("span", { children: text.profile.gameMoves }) : null] }), _jsx(ProfileForm, { text: text, submit: text.profile.start, suggestion: account, onMade: onMade }), _jsx(DataFolder, { folders: dataFolder, text: text })] }));
13
13
  }
14
- /** The player's picture on their card: a click opens their profile; a mark on it says so where nothing hovers. */
15
- export function PictureButton({ profile, onOpen, text }) {
16
- return (_jsxs("button", { class: "me", type: "button", title: text.profile.editProfile, "aria-label": text.profile.yourPicture, "aria-haspopup": "dialog", onClick: onOpen, children: [_jsx(PlayerPicture, { profile: profile }), _jsx("span", { class: "me-cover", children: pencilIcon }), _jsx("span", { class: "me-badge", children: pencilIcon })] }));
17
- }
18
- /** The player's name on their card: a click opens the menu of the players. */
14
+ /** The player's name on their card: a click opens the menu of the players, as their picture in the header does; from there the pencil opens their profile. */
19
15
  export function NameButton({ profile, onOpen, text }) {
20
16
  return (_jsxs("button", { class: "profile-name", type: "button", title: text.profile.changePlayer, "aria-label": `${profile.name}: ${text.profile.changePlayer}`, "aria-haspopup": "dialog", onClick: onOpen, children: [_jsx("span", { children: profile.name }), _jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M7 10L12 15L17 10" }) })] }));
21
17
  }
@@ -197,8 +193,8 @@ function refusalText(refused, text) {
197
193
  }
198
194
  // Zoe's camera, on the picture to change.
199
195
  const cameraIcon = (_jsxs("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: [_jsx("path", { d: "M4 8H7L9 5H15L17 8H20V19H4Z" }), _jsx("circle", { cx: "12", cy: "13", r: "3.5" })] }));
200
- // The profile in use, in the menu, and a field saved in the profile window.
201
- const checkIcon = (_jsx("svg", { class: "icon in-use", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M5 12.5L10 17.5L19 7.5" }) }));
196
+ /** The tick on the choice in use: the profile, a name saved, the chat at the table. */
197
+ export const checkIcon = (_jsx("svg", { class: "icon in-use", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M5 12.5L10 17.5L19 7.5" }) }));
202
198
  // The room of the tick beside a field not saved yet.
203
199
  const noTick = _jsx("svg", { class: "icon in-use", viewBox: "0 0 24 24", "aria-hidden": "true" });
204
200
  // The pencil on the player's picture and beside them in the menu: their profile opens.
@@ -1,10 +1,12 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "preact/jsx-runtime";
2
- import { useEffect, useRef } from "preact/hooks";
2
+ import { useEffect, useRef, useState } from "preact/hooks";
3
3
  import { Piece } from "../pieces/pieces.js";
4
4
  import { looks, pieceSets } from "../profile/settings.js";
5
+ import { hasVoice, onVoices } from "../talk/voice.js";
5
6
  /** The panel of the settings, open while `open` is true: every change goes to `onChange` at once. */
6
7
  export function SettingsPanel({ open, settings, onChange, onClose, text }) {
7
8
  const dialog = useRef(null);
9
+ const voiced = useVoiced(text.language);
8
10
  useEffect(() => {
9
11
  const element = dialog.current;
10
12
  if (open && !element.open) {
@@ -15,7 +17,7 @@ export function SettingsPanel({ open, settings, onChange, onClose, text }) {
15
17
  }
16
18
  }, [open]);
17
19
  const set = (key, value) => onChange({ ...settings, [key]: value });
18
- return (_jsxs("dialog", { ref: dialog, class: "dialog", "aria-label": text.settings, onClose: onClose, onClick: (event) => event.target === dialog.current && onClose(), children: [_jsxs("header", { children: [_jsx("h2", { children: text.settings }), _jsx("button", { class: "icon-button", type: "button", title: text.close, "aria-label": text.close, onClick: onClose, children: _jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M6 6L18 18M18 6L6 18" }) }) })] }), _jsxs("div", { class: "setting-columns", children: [_jsxs(Section, { title: text.look, icon: lookIcon, children: [_jsxs("div", { class: "setting-group", children: [_jsx("p", { class: "setting-label", children: text.themeChoice }), _jsx(Segments, { label: text.themeChoice, children: ["system", "light", "dark"].map((theme) => _jsx(Choice, { on: settings.theme === theme, onPick: () => set("theme", theme), children: text.themes[theme] }, theme)) })] }), _jsxs("div", { class: "setting-group", children: [_jsxs("p", { class: "setting-label", children: [text.boardSkin, " ", _jsx("b", { children: nameOf(settings.board, text) })] }), _jsx(Swatches, { look: "board", label: text.boardSkin, settings: settings, onPick: (id) => set("board", id), text: text })] }), _jsxs("div", { class: "setting-group", children: [_jsx("p", { class: "setting-label", children: text.pieceSet }), _jsx("div", { class: "piece-sets", role: "radiogroup", "aria-label": text.pieceSet, children: pieceSets.map((pieceSet) => (_jsxs(Choice, { on: settings.set === pieceSet, onPick: () => set("set", pieceSet), children: [_jsxs("span", { class: "sample", children: [_jsx(Piece, { letter: "N", set: pieceSet }), _jsx(Piece, { letter: "n", set: pieceSet })] }), nameOf(pieceSet, text)] }, pieceSet))) })] }), _jsxs("div", { class: "setting-group setting-pair", children: [_jsxs("div", { children: [_jsx("p", { class: "setting-label", children: text.pieceColours }), _jsx(Swatches, { look: "pieces", label: text.pieceColours, settings: settings, onPick: (id) => set("pieces", id), text: text })] }), _jsxs("div", { children: [_jsx("p", { class: "setting-label", children: text.marksColour }), _jsx(Swatches, { look: "marks", label: text.marksColour, settings: settings, onPick: (id) => set("marks", id), text: text })] })] })] }), _jsxs("div", { class: "setting-column", children: [_jsxs(Section, { title: text.play, icon: playIcon, children: [_jsx(Switch, { on: settings.dots, onSwitch: (on) => set("dots", on), children: text.dots }), _jsx(Switch, { on: settings.squareNames, onSwitch: (on) => set("squareNames", on), children: text.squareNames }), _jsx(Switch, { on: settings.explain, onSwitch: (on) => set("explain", on), children: text.explain }), _jsx(Switch, { on: settings.book, onSwitch: (on) => set("book", on), children: text.bookBadges }), _jsx(Switch, { on: settings.helps, onSwitch: (on) => set("helps", on), children: text.helps }), _jsx(Switch, { on: settings.sound, onSwitch: (on) => set("sound", on), children: text.sounds }), _jsx(Switch, { on: settings.voice, onSwitch: (on) => set("voice", on), children: text.voice })] }), _jsx(Section, { title: text.languageChoice, icon: languageIcon, children: _jsx(Segments, { label: text.languageChoice, children: ["auto", "it", "en"].map((language) => _jsx(Choice, { on: settings.language === language, onPick: () => set("language", language), children: text.languages[language] }, language)) }) })] })] })] }));
20
+ return (_jsxs("dialog", { ref: dialog, class: "dialog", "aria-label": text.settings, onClose: onClose, onClick: (event) => event.target === dialog.current && onClose(), children: [_jsxs("header", { children: [_jsx("h2", { children: text.settings }), _jsx("button", { class: "icon-button", type: "button", title: text.close, "aria-label": text.close, onClick: onClose, children: _jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M6 6L18 18M18 6L6 18" }) }) })] }), _jsxs("div", { class: "setting-columns", children: [_jsxs(Section, { title: text.look, icon: lookIcon, children: [_jsxs("div", { class: "setting-group", children: [_jsx("p", { class: "setting-label", children: text.themeChoice }), _jsx(Segments, { label: text.themeChoice, children: ["system", "light", "dark"].map((theme) => _jsx(Choice, { on: settings.theme === theme, onPick: () => set("theme", theme), children: text.themes[theme] }, theme)) })] }), _jsxs("div", { class: "setting-group", children: [_jsxs("p", { class: "setting-label", children: [text.boardSkin, " ", _jsx("b", { children: nameOf(settings.board, text) })] }), _jsx(Swatches, { look: "board", label: text.boardSkin, settings: settings, onPick: (id) => set("board", id), text: text })] }), _jsxs("div", { class: "setting-group", children: [_jsx("p", { class: "setting-label", children: text.pieceSet }), _jsx("div", { class: "piece-sets", role: "radiogroup", "aria-label": text.pieceSet, children: pieceSets.map((pieceSet) => (_jsxs(Choice, { on: settings.set === pieceSet, onPick: () => set("set", pieceSet), children: [_jsxs("span", { class: "sample", children: [_jsx(Piece, { letter: "N", set: pieceSet }), _jsx(Piece, { letter: "n", set: pieceSet })] }), nameOf(pieceSet, text)] }, pieceSet))) })] }), _jsxs("div", { class: "setting-group setting-pair", children: [_jsxs("div", { children: [_jsx("p", { class: "setting-label", children: text.pieceColours }), _jsx(Swatches, { look: "pieces", label: text.pieceColours, settings: settings, onPick: (id) => set("pieces", id), text: text })] }), _jsxs("div", { children: [_jsx("p", { class: "setting-label", children: text.marksColour }), _jsx(Swatches, { look: "marks", label: text.marksColour, settings: settings, onPick: (id) => set("marks", id), text: text })] })] })] }), _jsxs("div", { class: "setting-column", children: [_jsxs(Section, { title: text.play, icon: playIcon, children: [_jsx(Switch, { on: settings.dots, onSwitch: (on) => set("dots", on), children: text.dots }), _jsx(Switch, { on: settings.squareNames, onSwitch: (on) => set("squareNames", on), children: text.squareNames }), _jsx(Switch, { on: settings.explain, onSwitch: (on) => set("explain", on), children: text.explain }), _jsx(Switch, { on: settings.book, onSwitch: (on) => set("book", on), children: text.bookBadges }), _jsx(Switch, { on: settings.helps, onSwitch: (on) => set("helps", on), children: text.helps }), _jsx(Switch, { on: settings.sound, onSwitch: (on) => set("sound", on), children: text.sounds }), _jsx(Switch, { on: settings.voice, onSwitch: (on) => set("voice", on), ...(voiced ? {} : { unavailable: text.noVoice }), children: text.voice })] }), _jsx(Section, { title: text.languageChoice, icon: languageIcon, children: _jsx(Segments, { label: text.languageChoice, children: ["auto", "it", "en"].map((language) => _jsx(Choice, { on: settings.language === language, onPick: () => set("language", language), children: text.languages[language] }, language)) }) })] })] })] }));
19
21
  }
20
22
  // The sections' line icons: a palette, a pawn, a globe.
21
23
  const lookIcon = (_jsxs(_Fragment, { children: [_jsx("path", { d: "M12 3a9 9 0 1 0 0 18c1.2 0 1.8-.8 1.8-1.7 0-1.2-1-1.6-1-2.6 0-1 .8-1.7 1.8-1.7H17a4 4 0 0 0 4-4C21 6.5 17 3 12 3Z" }), _jsx("circle", { cx: "7.5", cy: "11", r: "1.2" }), _jsx("circle", { cx: "10", cy: "7", r: "1.2" }), _jsx("circle", { cx: "14.5", cy: "7", r: "1.2" })] }));
@@ -34,8 +36,20 @@ function Segments({ label, children }) {
34
36
  function Choice({ on, onPick, children }) {
35
37
  return (_jsx("button", { type: "button", role: "radio", "aria-checked": on, onClick: onPick, children: children }));
36
38
  }
37
- function Switch({ on, onSwitch, children }) {
38
- return (_jsxs("label", { class: "switch", children: [_jsx("span", { children: children }), _jsx("input", { type: "checkbox", role: "switch", checked: on, onChange: (event) => onSwitch(event.currentTarget.checked) })] }));
39
+ /** A switch; one that cannot be used here is off and disabled, and its tooltip says why. */
40
+ function Switch({ on, onSwitch, unavailable, children }) {
41
+ const usable = unavailable === undefined;
42
+ return (_jsxs("label", { class: "switch", title: unavailable, children: [_jsx("span", { children: children }), _jsx("input", { type: "checkbox", role: "switch", checked: on && usable, disabled: !usable, title: unavailable, onChange: (event) => onSwitch(event.currentTarget.checked) })] }));
43
+ }
44
+ /** Whether the browser has a voice for the language, looked at again whenever its voices change. */
45
+ function useVoiced(language) {
46
+ const [voiced, setVoiced] = useState(() => hasVoice(language));
47
+ useEffect(() => {
48
+ const look = () => setVoiced(hasVoice(language));
49
+ look();
50
+ return onVoices(look);
51
+ }, [language]);
52
+ return voiced;
39
53
  }
40
54
  function nameOf(id, text) {
41
55
  return text.names[id] ?? id;
@@ -14,7 +14,7 @@ export function GameColumn({ plies, result, how, lit, started, move, toMove, ope
14
14
  const bar = standings === undefined ? undefined : _jsx(LeadBar, { standing: standingAt(standings, shownPlies), shown: shownPlies, text: text });
15
15
  return (_jsx("aside", { class: "side", id: "game", "aria-label": text.game, children: started === undefined && analysis === undefined
16
16
  ? (_jsxs(_Fragment, { children: [_jsx("p", { class: "card empty", children: text.noGame }), onAnalyse === undefined ? null : _jsx(AnalyseButton, { label: text.study.analyseLast, onAnalyse: onAnalyse })] }))
17
- : (_jsxs(_Fragment, { children: [_jsx(GameCard, { plies: plies, result: result, how: how, lit: lit, started: started, move: move, toMove: toMove, bar: bar, text: text }), analysis === undefined || standings === undefined ? null : _jsx(LeadChart, { plies: plies, standings: standings, shown: shownPlies, onShow: onShow, text: text }), opening === undefined ? null : _jsx(OpeningCard, { opening: opening, text: text }), helps, analysis === undefined || analysis.summary !== undefined ? null : (_jsxs("p", { class: "card review waiting", role: "status", children: [_jsx("span", { class: "spinner", "aria-hidden": "true" }), text.reviewing(analysis.by)] })), result !== undefined && plies.length === 0 ? null : _jsx(MoveList, { plies: plies, result: result, shown: shownPlies, onShow: onShow, tools: legend, text: text }), onDownload === undefined && onSaveMoves === undefined ? null : (_jsxs("div", { class: "card analysis-actions", children: [onDownload === undefined ? null : _jsx(SaveButton, { label: text.study.download, hint: text.study.downloadHint, onSave: onDownload }), onSaveMoves === undefined ? null : _jsx(SaveButton, { label: text.saveMoves, hint: text.saveMovesHint, onSave: onSaveMoves })] })), onAnalyse === undefined ? null : _jsx(AnalyseButton, { label: text.study.analyseIt, onAnalyse: onAnalyse }), _jsx(Steps, { shown: shownPlies, total: plies.length, onStep: onStep, text: text })] })) }));
17
+ : (_jsxs(_Fragment, { children: [_jsxs("div", { class: "side-cards", children: [_jsx(GameCard, { plies: plies, result: result, how: how, lit: lit, started: started, move: move, toMove: toMove, bar: bar, text: text }), analysis === undefined || standings === undefined ? null : _jsx(LeadChart, { plies: plies, standings: standings, shown: shownPlies, onShow: onShow, text: text }), opening === undefined ? null : _jsx(OpeningCard, { opening: opening, text: text }), helps, analysis?.reviewing !== true ? null : (_jsxs("p", { class: "card review waiting", role: "status", children: [_jsx("span", { class: "spinner", "aria-hidden": "true" }), text.reviewing(analysis.by)] }))] }), _jsxs("div", { class: "side-foot", children: [result !== undefined && plies.length === 0 ? null : _jsx(MoveList, { plies: plies, result: result, shown: shownPlies, onShow: onShow, tools: legend, text: text }), onDownload === undefined && onSaveMoves === undefined ? null : (_jsxs("div", { class: "card analysis-actions", children: [onDownload === undefined ? null : _jsx(SaveButton, { label: text.study.download, hint: text.study.downloadHint, onSave: onDownload }), onSaveMoves === undefined ? null : _jsx(SaveButton, { label: text.saveMoves, hint: text.saveMovesHint, onSave: onSaveMoves })] })), onAnalyse === undefined ? null : _jsx(AnalyseButton, { label: text.study.analyseIt, onAnalyse: onAnalyse }), _jsx(Steps, { shown: shownPlies, total: plies.length, onStep: onStep, text: text })] })] })) }));
18
18
  }
19
19
  /** A button that saves the game as a file in the player's downloads, its tooltip saying what the file holds. */
20
20
  function SaveButton({ label, hint, onSave }) {
@@ -584,6 +584,6 @@
584
584
  }
585
585
 
586
586
  /* The saves and the steps stay at the foot of the column, with the moves folded too (Stefano) */
587
- .side > .moves-card + * {
587
+ .side-foot > .moves-card + * {
588
588
  margin-top: auto;
589
589
  }