chesspal 0.3.1 → 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 (183) 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.js → registry/context.js} +2 -2
  77. package/dist/commands/registry/index.d.ts +2 -0
  78. package/dist/commands/registry/index.js +3 -0
  79. package/dist/commands/setup/index.d.ts +2 -0
  80. package/dist/commands/setup/index.js +3 -0
  81. package/dist/commands/{init.d.ts → setup/init.d.ts} +1 -1
  82. package/dist/commands/{init.js → setup/init.js} +2 -3
  83. package/dist/commands/{instructions.d.ts → setup/instructions.d.ts} +2 -2
  84. package/dist/commands/{instructions.js → setup/instructions.js} +3 -3
  85. package/dist/commands/{ai-say.d.ts → talk/ai-say.d.ts} +2 -3
  86. package/dist/commands/{ai-say.js → talk/ai-say.js} +5 -9
  87. package/dist/commands/talk/index.d.ts +2 -0
  88. package/dist/commands/talk/index.js +3 -0
  89. package/dist/commands/{player-say.d.ts → talk/player-say.d.ts} +1 -1
  90. package/dist/commands/{player-say.js → talk/player-say.js} +4 -4
  91. package/dist/page/app/app.js +19 -11
  92. package/dist/page/app/served.js +7 -0
  93. package/dist/page/avatars/torretta.js +10 -3
  94. package/dist/page/dialogs/dialogs.css +10 -0
  95. package/dist/page/dialogs/profiles.js +1 -5
  96. package/dist/page/dialogs/settings.js +18 -4
  97. package/dist/page/game/column.js +1 -1
  98. package/dist/page/game/game.css +1 -1
  99. package/dist/page/guide/blocks.js +13 -2
  100. package/dist/page/guide/credits.js +6 -0
  101. package/dist/page/guide/drawings/analysis.js +13 -0
  102. package/dist/page/guide/drawings/drawings.css +329 -0
  103. package/dist/page/guide/drawings/drawings.js +15 -0
  104. package/dist/page/guide/drawings/game.js +15 -0
  105. package/dist/page/guide/drawings/helps.js +13 -0
  106. package/dist/page/guide/drawings/how-it-works.js +13 -0
  107. package/dist/page/guide/drawings/parts.js +103 -0
  108. package/dist/page/guide/drawings/players.js +19 -0
  109. package/dist/page/guide/drawings/talk.js +9 -0
  110. package/dist/page/guide/en.md +39 -25
  111. package/dist/page/guide/guide.css +123 -91
  112. package/dist/page/guide/guide.js +47 -25
  113. package/dist/page/guide/it.md +39 -25
  114. package/dist/page/guide/markdown.js +5 -0
  115. package/dist/page/helps/helps.css +127 -83
  116. package/dist/page/helps/helps.js +45 -26
  117. package/dist/page/helps/view.js +13 -3
  118. package/dist/page/{guide/foot.js → layout/footer.js} +7 -8
  119. package/dist/page/layout/header.js +5 -3
  120. package/dist/page/layout/layout.css +114 -10
  121. package/dist/page/layout/window.js +6 -26
  122. package/dist/page/main.js +1 -1
  123. package/dist/page/pieces/pieces.js +5 -2
  124. package/dist/page/style.css +1 -0
  125. package/dist/page/table/table.css +69 -0
  126. package/dist/page/table/update.js +82 -0
  127. package/dist/page/talk/chat.css +6 -6
  128. package/dist/page/talk/talk.css +17 -67
  129. package/dist/page/talk/talk.js +5 -5
  130. package/dist/page/talk/voice.js +12 -0
  131. package/dist/page/text.js +66 -18
  132. package/dist/page/theme.css +2 -0
  133. package/dist/server/{bell.js → bell/bell.js} +3 -3
  134. package/dist/server/bell/index.d.ts +1 -0
  135. package/dist/server/bell/index.js +2 -0
  136. package/dist/server/{caller.d.ts → mcp/caller.d.ts} +1 -1
  137. package/dist/server/{caller.js → mcp/caller.js} +1 -1
  138. package/dist/server/mcp/index.d.ts +1 -0
  139. package/dist/server/mcp/index.js +2 -0
  140. package/dist/server/{mcp.d.ts → mcp/mcp.d.ts} +1 -1
  141. package/dist/server/{mcp.js → mcp/mcp.js} +9 -9
  142. package/dist/server/page/game.js +16 -3
  143. package/dist/server/page/http.d.ts +1 -1
  144. package/dist/server/page/index.d.ts +3 -0
  145. package/dist/server/page/index.js +3 -0
  146. package/dist/server/page/page.d.ts +12 -3
  147. package/dist/server/page/page.js +13 -4
  148. package/dist/server/page/table.d.ts +7 -1
  149. package/dist/server/page/table.js +79 -3
  150. package/dist/server/page/update.d.ts +18 -0
  151. package/dist/server/page/update.js +40 -0
  152. package/dist/server/table/browser.d.ts +1 -1
  153. package/dist/server/table/browser.js +1 -1
  154. package/dist/server/table/hosts/chats.d.ts +1 -1
  155. package/dist/server/table/hosts/chats.js +2 -2
  156. package/dist/server/table/hosts/index.d.ts +1 -1
  157. package/dist/server/table/hosts/index.js +1 -1
  158. package/dist/server/table/hosts/play.d.ts +8 -1
  159. package/dist/server/table/hosts/play.js +40 -4
  160. package/dist/server/table/hosts/seat.d.ts +5 -1
  161. package/dist/server/table/hosts/seat.js +1 -1
  162. package/dist/server/table/index.d.ts +3 -2
  163. package/dist/server/table/index.js +3 -2
  164. package/dist/server/table/launch.d.ts +5 -2
  165. package/dist/server/table/launch.js +6 -3
  166. package/dist/server/table/link.d.ts +1 -1
  167. package/dist/server/table/link.js +3 -3
  168. package/dist/server/table/table.d.ts +4 -5
  169. package/dist/server/table/table.js +15 -12
  170. package/dist/server/table/update.d.ts +17 -0
  171. package/dist/server/table/update.js +77 -0
  172. package/dist/sites/index.d.ts +1 -1
  173. package/dist/sites/index.js +2 -2
  174. package/dist/version.d.ts +4 -0
  175. package/dist/version.js +12 -1
  176. package/package.json +1 -1
  177. /package/dist/commands/{speech.d.ts → answers/speech.d.ts} +0 -0
  178. /package/dist/commands/{view.js → answers/view.js} +0 -0
  179. /package/dist/commands/{page-link.js → game/page-link.js} +0 -0
  180. /package/dist/commands/{context.d.ts → registry/context.d.ts} +0 -0
  181. /package/dist/commands/{registry.d.ts → registry/registry.d.ts} +0 -0
  182. /package/dist/commands/{registry.js → registry/registry.js} +0 -0
  183. /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
  /**
@@ -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,6 +36,7 @@ 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";
39
+ import { UpdateDialog, UpdateNotice } from "../table/update.js";
38
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";
@@ -251,7 +253,7 @@ export function App({ start }) {
251
253
  const { kind, verdict } = aborted && phase === "over" ? { kind: "over", verdict: text.aborted } : verdictOf(phase, how, winner, offer, declined, text);
252
254
  const header = (_jsx(Header, { text: text, kind: kind, verdict: verdict, ...(!ended && (offer === "ai" || declined) ? { who: name } : {}), actions: actions, onNewGame: served === undefined ? undefined : askNewGame,
253
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.
254
- 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") }) }));
255
257
  const panel = _jsx(SettingsPanel, { open: setting, settings: settings, onChange: change, onClose: () => setSetting(false), text: text });
256
258
  // The guide opens before the page has its data too.
257
259
  const guide = _jsx(GuideDialog, { open: dialog === "guide", onClose: () => setDialog(undefined), text: text });
@@ -298,6 +300,8 @@ export function App({ start }) {
298
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 }));
299
301
  const legend = legendAt(false);
300
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;
301
305
  const closing = studied === undefined ? undefined : closingOf(live.record);
302
306
  // In analysis the AI's card says who played that game, as its PGN keeps it (D-82), not who plays now.
303
307
  const playedBy = studied === undefined ? undefined : aiModelIn(live.record);
@@ -309,7 +313,7 @@ export function App({ start }) {
309
313
  const asleep = served.listening === false && !offline;
310
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.
311
315
  const thinking = going && !asleep && !offline && studied === undefined ? served.thinking : undefined;
312
- 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 }) }));
313
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).
314
318
  const clockWaits = clock?.waiting === true && !ended && studied === undefined && viewing === undefined && live.position.turn === ai;
315
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 }));
@@ -397,9 +401,7 @@ export function App({ start }) {
397
401
  act("/api/new", { ai_colour: colour === "random" ? "random" : colour === "white" ? "black" : "white", level: chosen, ...(control === undefined ? {} : { clock: control }) });
398
402
  };
399
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).
400
- const studiedCard = (side, place) => sides === undefined ? null : (_jsx(PlayerCard, { place: place,
401
- // The player opens their profile in analysis too, from their own side's card.
402
- 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 }));
403
405
  // A game brought to analyse: the page shows it once the server has it in analysis; a refusal stays in the dialog.
404
406
  const studyGame = async (studied) => {
405
407
  const refused = await post("/api/study", studied, text, name);
@@ -408,10 +410,16 @@ export function App({ start }) {
408
410
  }
409
411
  return refused?.text;
410
412
  };
411
- 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 }), 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
412
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 }))
413
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
414
- ? (_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 } : {}), 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) => {
415
423
  setDraft(words);
416
424
  setUnsent(undefined);
417
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")] })] }))
@@ -420,17 +428,17 @@ export function App({ start }) {
420
428
  spent: going ? playerSpent(plies, ai, talk) : 0, draft: draft, onDraft: (words) => {
421
429
  setDraft(words);
422
430
  setUnsent(undefined);
423
- }, 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", {
424
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.
425
433
  class: between && !justEnded ? "board-wrap off" : "board-wrap", onWheel: wheel,
426
434
  // A press on the board while the player looks back brings the game as it is: the next one plays.
427
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)
428
436
  ? (_jsxs("div", { class: "over-board", children: [asleepNote, startClocks] }))
429
- : 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 })] })] }) }));
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 })] })] }) }));
430
438
  }
431
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. */
432
440
  function pictureOf({ who }, profile, size, mood) {
433
- 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 });
434
442
  }
435
443
  /**
436
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
  }
@@ -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
  }
@@ -1,4 +1,5 @@
1
- // The markdown the guide is written in, read into blocks: headings, paragraphs, lists, tables, quotes, code; its comments left out. markdown.tsx draws them.
1
+ // The markdown the guide is written in, read into blocks: headings, paragraphs, lists, tables, quotes, code and drawings; its comments left out. markdown.tsx
2
+ // draws them.
2
3
  // Copied from rpg-engine's guide (src/dashboard/help/blocks.ts), without the headings' icons and the cards' previews.
3
4
  /** The blocks of a markdown text. */
4
5
  export function markdownBlocks(text) {
@@ -16,9 +17,14 @@ export function markdownBlocks(text) {
16
17
  };
17
18
  for (let line = lines[index]; line !== undefined; line = lines[index]) {
18
19
  const heading = /^(#{1,6})\s+(.*?)\s*$/.exec(line);
20
+ const drawing = drawingOf(line);
19
21
  if (line.trim() === "") {
20
22
  index += 1;
21
23
  }
24
+ else if (drawing !== undefined) {
25
+ blocks.push(drawing);
26
+ index += 1;
27
+ }
22
28
  else if (heading !== null) {
23
29
  blocks.push({ kind: "heading", level: heading[1]?.length ?? 1, text: heading[2] ?? "" });
24
30
  index += 1;
@@ -46,7 +52,7 @@ export function markdownBlocks(text) {
46
52
  blocks.push({ kind: "list", ordered, items: items.reduce((all, one) => (listItem(one) === undefined ? [...all.slice(0, -1), `${all.at(-1) ?? ""} ${one.trim()}`] : [...all, listItem(one)?.text ?? ""]), []) });
47
53
  }
48
54
  else {
49
- const paragraph = run((one) => one.trim() !== "" && !/^(#{1,6}\s|```|>|\|)/.test(one) && listItem(one) === undefined);
55
+ const paragraph = run((one) => one.trim() !== "" && !/^(#{1,6}\s|```|>|\|)/.test(one) && listItem(one) === undefined && drawingOf(one) === undefined);
50
56
  blocks.push({ kind: "paragraph", text: paragraph.map((one) => one.trim()).join(" ") });
51
57
  }
52
58
  }
@@ -56,6 +62,11 @@ export function markdownBlocks(text) {
56
62
  export function anchorOf(heading) {
57
63
  return heading.trim().toLowerCase().replace(/[^\p{L}\p{N}\s_-]/gu, "").replace(/\s/g, "-");
58
64
  }
65
+ /** A drawing on a line of its own: ![what it shows](its name); undefined for another line. */
66
+ function drawingOf(line) {
67
+ const drawing = /^!\[([^\]]*)\]\(([\w-]+)\)$/.exec(line.trim());
68
+ return drawing === null ? undefined : { kind: "drawing", name: drawing[2] ?? "", alt: drawing[1] ?? "" };
69
+ }
59
70
  /** A list item's mark and text: - text, or 1. text; undefined for another line. */
60
71
  function listItem(line) {
61
72
  const item = /^([-*]|\d+\.)\s+(.*)$/.exec(line);
@@ -0,0 +1,6 @@
1
+ import { jsx as _jsx } from "preact/jsx-runtime";
2
+ import { Inline } from "./markdown.js";
3
+ /** The credits' page of the guide, under the title the guide gives every page. */
4
+ export function GuideCredits({ text }) {
5
+ return (_jsx("ul", { children: text.guide.creditLines.map((line) => (_jsx("li", { children: _jsx(Inline, { text: line, go: () => undefined }) }, line))) }));
6
+ }
@@ -0,0 +1,13 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
+ // Looking back at a game: the moves with their judgements, the one shown on the board in gold, and the bar of steps to the start, back, on and to the end
3
+ import { Badge, Bar, Board, Card, Chevron, Pieces, Square } from "./parts.js";
4
+ const grid = { x: 16, y: 10, cell: 25 };
5
+ const position = ["r...r.k.", "pp...ppp", "..n.....", "...p....", "...P.B..", "..N..Q..", "PP...PPP", "R....RK."];
6
+ /** The board at a move judged a mistake, the Moves card and the steps under it. */
7
+ export function Analysis() {
8
+ return (_jsxs("svg", { class: "guide-art", viewBox: "0 0 480 220", "aria-hidden": "true", children: [_jsxs(Board, { grid: grid, children: [_jsx(Square, { grid: grid, file: 3, rank: 1, class: "last" }), _jsx(Square, { grid: grid, file: 5, rank: 3, class: "last" }), _jsx(Pieces, { grid: grid, ranks: position })] }), _jsx(Badge, { x: 163, y: 138, r: 6.5, kind: "mistake" }), _jsx(Card, { x: 236, y: 10, width: 228, height: 134 }), _jsx(Bar, { x: 248, y: 20.5, width: 48, strong: true }), _jsx("rect", { class: "quote", x: "242", y: "34", width: "216", height: "20", rx: "4" }), _jsx(Move, { y: 34, white: "best" }), _jsx(Move, { y: 58, black: "mistake" }), _jsx("rect", { class: "quote", x: "242", y: "82", width: "216", height: "20", rx: "4" }), _jsx(Move, { y: 82, white: "blunder", black: "best" }), _jsx("rect", { class: "last", x: "258", y: "108", width: "66", height: "16", rx: "4" }), _jsx(Move, { y: 106, white: "mistake", shown: true }), _jsx(Card, { x: 236, y: 156, width: 228, height: 54 }), _jsxs("g", { class: "accent-line", children: [_jsx("path", { d: "M259 177V189" }), _jsx(Chevron, { x: 268, y: 183, size: 12, left: true }), _jsx(Chevron, { x: 321.5, y: 183, size: 12, left: true }), _jsx(Chevron, { x: 378.5, y: 183, size: 12 }), _jsx(Chevron, { x: 432, y: 183, size: 12 }), _jsx("path", { d: "M441 177V189" })] })] }));
9
+ }
10
+ /** A row of the moves: its number, White's move and Black's, each with its badge if it has one; `shown` when White's is the one on the board. */
11
+ function Move({ y, white, black, shown = false }) {
12
+ return (_jsxs("g", { children: [_jsx(Bar, { x: 246, y: y + 7.5, width: 8 }), _jsx(Bar, { x: 264, y: y + 7.5, width: 36, strong: true, class: shown ? "bar on-last" : undefined }), white !== undefined && _jsx(Badge, { x: 312, y: y + 10, r: 6, kind: white }), _jsx(Bar, { x: 350, y: y + 7.5, width: 36, strong: true }), black !== undefined && _jsx(Badge, { x: 398, y: y + 10, r: 6, kind: black })] }));
13
+ }