chesspal 0.0.1 → 0.1.0

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 (225) hide show
  1. package/README.md +136 -2
  2. package/dist/cli/chat-hook.d.ts +2 -0
  3. package/dist/cli/chat-hook.js +33 -0
  4. package/dist/cli/cli.d.ts +2 -0
  5. package/dist/cli/cli.js +28 -0
  6. package/dist/cli/command-line.d.ts +8 -0
  7. package/dist/cli/command-line.js +178 -0
  8. package/dist/commands/ai-move.d.ts +37 -0
  9. package/dist/commands/ai-move.js +92 -0
  10. package/dist/commands/ai-say.d.ts +20 -0
  11. package/dist/commands/ai-say.js +40 -0
  12. package/dist/commands/analyse-game.d.ts +61 -0
  13. package/dist/commands/analyse-game.js +238 -0
  14. package/dist/commands/annotate-game.d.ts +35 -0
  15. package/dist/commands/annotate-game.js +78 -0
  16. package/dist/commands/clock.d.ts +15 -0
  17. package/dist/commands/clock.js +45 -0
  18. package/dist/commands/context.d.ts +56 -0
  19. package/dist/commands/context.js +36 -0
  20. package/dist/commands/draw-offer.d.ts +10 -0
  21. package/dist/commands/draw-offer.js +43 -0
  22. package/dist/commands/drawings.d.ts +15 -0
  23. package/dist/commands/drawings.js +23 -0
  24. package/dist/commands/end-game.d.ts +23 -0
  25. package/dist/commands/end-game.js +64 -0
  26. package/dist/commands/export-game.d.ts +14 -0
  27. package/dist/commands/export-game.js +26 -0
  28. package/dist/commands/game-summary.d.ts +95 -0
  29. package/dist/commands/game-summary.js +239 -0
  30. package/dist/commands/index.d.ts +3 -0
  31. package/dist/commands/index.js +19 -0
  32. package/dist/commands/init.d.ts +15 -0
  33. package/dist/commands/init.js +73 -0
  34. package/dist/commands/levels.d.ts +9 -0
  35. package/dist/commands/levels.js +25 -0
  36. package/dist/commands/look.d.ts +31 -0
  37. package/dist/commands/look.js +61 -0
  38. package/dist/commands/new-game.d.ts +69 -0
  39. package/dist/commands/new-game.js +215 -0
  40. package/dist/commands/opening-book.d.ts +13 -0
  41. package/dist/commands/opening-book.js +84 -0
  42. package/dist/commands/page-link.d.ts +11 -0
  43. package/dist/commands/page-link.js +28 -0
  44. package/dist/commands/past-games.d.ts +11 -0
  45. package/dist/commands/past-games.js +47 -0
  46. package/dist/commands/play-move.d.ts +44 -0
  47. package/dist/commands/play-move.js +102 -0
  48. package/dist/commands/player-move.d.ts +17 -0
  49. package/dist/commands/player-move.js +44 -0
  50. package/dist/commands/player-say.d.ts +5 -0
  51. package/dist/commands/player-say.js +16 -0
  52. package/dist/commands/ponder.d.ts +42 -0
  53. package/dist/commands/ponder.js +167 -0
  54. package/dist/commands/position.d.ts +14 -0
  55. package/dist/commands/position.js +128 -0
  56. package/dist/commands/profiles.d.ts +60 -0
  57. package/dist/commands/profiles.js +129 -0
  58. package/dist/commands/registry.d.ts +54 -0
  59. package/dist/commands/registry.js +61 -0
  60. package/dist/commands/say.d.ts +16 -0
  61. package/dist/commands/say.js +84 -0
  62. package/dist/commands/status.d.ts +29 -0
  63. package/dist/commands/status.js +103 -0
  64. package/dist/commands/study-games.d.ts +5 -0
  65. package/dist/commands/study-games.js +40 -0
  66. package/dist/commands/study.d.ts +38 -0
  67. package/dist/commands/study.js +122 -0
  68. package/dist/commands/view.d.ts +14 -0
  69. package/dist/commands/view.js +125 -0
  70. package/dist/core/board-text.d.ts +7 -0
  71. package/dist/core/board-text.js +18 -0
  72. package/dist/core/book.json +1 -0
  73. package/dist/core/clock.d.ts +45 -0
  74. package/dist/core/clock.js +109 -0
  75. package/dist/core/facts.d.ts +55 -0
  76. package/dist/core/facts.js +175 -0
  77. package/dist/core/game.d.ts +93 -0
  78. package/dist/core/game.js +348 -0
  79. package/dist/core/illegal.d.ts +19 -0
  80. package/dist/core/illegal.js +141 -0
  81. package/dist/core/index.d.ts +19 -0
  82. package/dist/core/index.js +11 -0
  83. package/dist/core/levels.d.ts +20 -0
  84. package/dist/core/levels.js +33 -0
  85. package/dist/core/material.d.ts +9 -0
  86. package/dist/core/material.js +12 -0
  87. package/dist/core/moods.d.ts +23 -0
  88. package/dist/core/moods.js +45 -0
  89. package/dist/core/openings.d.ts +37 -0
  90. package/dist/core/openings.js +98 -0
  91. package/dist/core/openings.json +1 -0
  92. package/dist/core/pgn.d.ts +10 -0
  93. package/dist/core/pgn.js +213 -0
  94. package/dist/core/profile.d.ts +117 -0
  95. package/dist/core/profile.js +232 -0
  96. package/dist/core/record.d.ts +21 -0
  97. package/dist/core/record.js +25 -0
  98. package/dist/core/review.d.ts +74 -0
  99. package/dist/core/review.js +126 -0
  100. package/dist/games/ai-model.d.ts +5 -0
  101. package/dist/games/ai-model.js +15 -0
  102. package/dist/games/analysis.d.ts +12 -0
  103. package/dist/games/analysis.js +26 -0
  104. package/dist/games/archive.d.ts +42 -0
  105. package/dist/games/archive.js +168 -0
  106. package/dist/games/chat-start.d.ts +16 -0
  107. package/dist/games/chat-start.js +26 -0
  108. package/dist/games/current-game.d.ts +54 -0
  109. package/dist/games/current-game.js +128 -0
  110. package/dist/games/files.d.ts +9 -0
  111. package/dist/games/files.js +52 -0
  112. package/dist/games/index.d.ts +10 -0
  113. package/dist/games/index.js +11 -0
  114. package/dist/games/plans.d.ts +9 -0
  115. package/dist/games/plans.js +23 -0
  116. package/dist/games/table-talk.d.ts +12 -0
  117. package/dist/games/table-talk.js +25 -0
  118. package/dist/games/talk.d.ts +40 -0
  119. package/dist/games/talk.js +131 -0
  120. package/dist/games/thoughts.d.ts +15 -0
  121. package/dist/games/thoughts.js +60 -0
  122. package/dist/home/data-folder.d.ts +12 -0
  123. package/dist/home/data-folder.js +55 -0
  124. package/dist/home/index.d.ts +2 -0
  125. package/dist/home/index.js +3 -0
  126. package/dist/home/open-folder.d.ts +2 -0
  127. package/dist/home/open-folder.js +14 -0
  128. package/dist/host-files/host-files.d.ts +10 -0
  129. package/dist/host-files/host-files.js +167 -0
  130. package/dist/host-files/index.d.ts +2 -0
  131. package/dist/host-files/index.js +3 -0
  132. package/dist/host-files/ponder-agents.d.ts +12 -0
  133. package/dist/host-files/ponder-agents.js +40 -0
  134. package/dist/index.d.ts +4 -0
  135. package/dist/index.js +2 -0
  136. package/dist/page/app.js +576 -0
  137. package/dist/page/avatars/beanie.webp +0 -0
  138. package/dist/page/avatars/beard.webp +0 -0
  139. package/dist/page/avatars/bun.webp +0 -0
  140. package/dist/page/avatars/cat.webp +0 -0
  141. package/dist/page/avatars/curls.webp +0 -0
  142. package/dist/page/avatars/glasses.webp +0 -0
  143. package/dist/page/avatars/moustache.webp +0 -0
  144. package/dist/page/avatars/owl.webp +0 -0
  145. package/dist/page/avatars/short-hair.webp +0 -0
  146. package/dist/page/avatars/silver-bob.webp +0 -0
  147. package/dist/page/avatars.js +16 -0
  148. package/dist/page/badges.js +50 -0
  149. package/dist/page/board.js +566 -0
  150. package/dist/page/browse.js +17 -0
  151. package/dist/page/clock-time.js +12 -0
  152. package/dist/page/dialog.js +26 -0
  153. package/dist/page/ending.js +73 -0
  154. package/dist/page/explain.js +58 -0
  155. package/dist/page/fold-tab.js +9 -0
  156. package/dist/page/folds.js +43 -0
  157. package/dist/page/game-column.js +44 -0
  158. package/dist/page/header.js +27 -0
  159. package/dist/page/helps-view.js +51 -0
  160. package/dist/page/helps.js +90 -0
  161. package/dist/page/main.js +8 -0
  162. package/dist/page/model-name.js +20 -0
  163. package/dist/page/move-list.js +68 -0
  164. package/dist/page/new-game-dialog.js +42 -0
  165. package/dist/page/opening-book.js +14 -0
  166. package/dist/page/pieces/CREDITS.md +36 -0
  167. package/dist/page/pieces/cburnett-small.svg +20 -0
  168. package/dist/page/pieces/cburnett.svg +20 -0
  169. package/dist/page/pieces/nitida-small.svg +19 -0
  170. package/dist/page/pieces/nitida.svg +20 -0
  171. package/dist/page/pieces/torretta-small.svg +19 -0
  172. package/dist/page/pieces/torretta.svg +19 -0
  173. package/dist/page/pieces.js +28 -0
  174. package/dist/page/player-card.js +60 -0
  175. package/dist/page/plies.js +62 -0
  176. package/dist/page/profile-dialogs.js +123 -0
  177. package/dist/page/profile.js +95 -0
  178. package/dist/page/settings-panel.js +32 -0
  179. package/dist/page/settings.js +76 -0
  180. package/dist/page/sounds/capture.ogg +0 -0
  181. package/dist/page/sounds/check.ogg +0 -0
  182. package/dist/page/sounds/draw.ogg +0 -0
  183. package/dist/page/sounds/flag.ogg +0 -0
  184. package/dist/page/sounds/mate.ogg +0 -0
  185. package/dist/page/sounds/move.ogg +0 -0
  186. package/dist/page/sounds/offer.ogg +0 -0
  187. package/dist/page/sounds/resign.ogg +0 -0
  188. package/dist/page/sounds.js +105 -0
  189. package/dist/page/studied.js +14 -0
  190. package/dist/page/study-dialog.js +58 -0
  191. package/dist/page/style.css +4398 -0
  192. package/dist/page/talk.js +172 -0
  193. package/dist/page/text.js +593 -0
  194. package/dist/page/torretta.js +38 -0
  195. package/dist/page/voice.js +38 -0
  196. package/dist/profiles/avatar.d.ts +23 -0
  197. package/dist/profiles/avatar.js +84 -0
  198. package/dist/profiles/front-matter.d.ts +9 -0
  199. package/dist/profiles/front-matter.js +33 -0
  200. package/dist/profiles/index.d.ts +3 -0
  201. package/dist/profiles/index.js +5 -0
  202. package/dist/profiles/profiles.d.ts +58 -0
  203. package/dist/profiles/profiles.js +237 -0
  204. package/dist/profiles/settings.d.ts +12 -0
  205. package/dist/profiles/settings.js +46 -0
  206. package/dist/server/analyses.d.ts +17 -0
  207. package/dist/server/analyses.js +46 -0
  208. package/dist/server/bell.d.ts +60 -0
  209. package/dist/server/bell.js +209 -0
  210. package/dist/server/caller.d.ts +10 -0
  211. package/dist/server/caller.js +99 -0
  212. package/dist/server/http.d.ts +16 -0
  213. package/dist/server/http.js +57 -0
  214. package/dist/server/mcp.d.ts +14 -0
  215. package/dist/server/mcp.js +179 -0
  216. package/dist/server/page.d.ts +46 -0
  217. package/dist/server/page.js +657 -0
  218. package/dist/server/profile-routes.d.ts +19 -0
  219. package/dist/server/profile-routes.js +186 -0
  220. package/dist/version.d.ts +2 -0
  221. package/dist/version.js +3 -0
  222. package/instructions/analyst.md +8 -0
  223. package/instructions/opponent.md +15 -0
  224. package/instructions/ponder.md +9 -0
  225. package/package.json +66 -5
@@ -0,0 +1,172 @@
1
+ import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "preact/jsx-runtime";
2
+ // The talk on the board page (D-51, D-53): the AI's words in bubbles that point at the Torretta, the player's above the box they write in.
3
+ import { createContext } from "preact";
4
+ import { useContext, useEffect, useLayoutEffect, useRef } from "preact/hooks";
5
+ import { BadgeIcon, NoteIcon } from "./badges.js";
6
+ import { signature } from "./model-name.js";
7
+ import { Figurines } from "./move-list.js";
8
+ import { talkLimits } from "./plies.js";
9
+ import { Torretta } from "./torretta.js";
10
+ import { hush, speak } from "./voice.js";
11
+ /**
12
+ * The AI's words of the game, a bubble each, the newest by the Torretta: below it, or above it on a board turned round; the first points at it. With the helps
13
+ * on, its judgment of the player's last move comes newest, in a line that opens on its note (D-78).
14
+ */
15
+ export function AiWords({ words, newestFirst, voice, judged, text }) {
16
+ const list = useRef(null);
17
+ useOnlyWhole(list, words, newestFirst);
18
+ useSpokenNews(words, voice);
19
+ const says = (_jsx("ol", { ref: list, class: "says", "aria-live": "polite", children: inOrder(words.map((word, index) => (_jsx("li", { class: "say", style: aged(words.length - 1 - index), children: _jsx(Marked, { text: word }) }, index))), newestFirst) }));
20
+ // The judgment sits by the Torretta, out of the bubbles' order: it never pushes the AI's words away.
21
+ if (judged === undefined) {
22
+ return says;
23
+ }
24
+ const line = _jsx(Judged, { ply: judged, text: text });
25
+ return newestFirst ? _jsxs(_Fragment, { children: [line, says] }) : _jsxs(_Fragment, { children: [says, line] });
26
+ }
27
+ /**
28
+ * The AI's word on the move shown of a game in analysis (D-69), in a bubble that points down at the Torretta beside the AI's name: the move, its badge by name,
29
+ * and its note when it wrote one; read out as the player steps through the game. The Torretta and the name stay when the move has no word.
30
+ */
31
+ export function AiNote({ ply, opening, closing, summary, name, by, mood, voice, text }) {
32
+ // On a move of the book without the AI's note, the AI reports what the book says: the opening in the title, the variation under it.
33
+ const book = ply?.review?.note === undefined ? opening : undefined;
34
+ const shown = ply?.review ?? (book === undefined ? undefined : { badge: "book" });
35
+ const title = [shown?.badge === undefined || (book !== undefined && shown.badge === "book") ? undefined : text.badges[shown.badge], book?.family].filter((part) => part !== undefined).join(" · ");
36
+ const note = shown?.note ?? book?.variation;
37
+ const spoken = [ply === undefined || shown === undefined ? undefined : title, ply === undefined ? undefined : note, summary?.replace(/\s*•\s+/g, ". "), closing].filter((part) => part !== undefined && part !== "").join(". ");
38
+ useEffect(() => {
39
+ if (voice !== undefined) {
40
+ speak(spoken, voice);
41
+ }
42
+ return hush;
43
+ }, [spoken, voice]);
44
+ // The Torretta stays in sight through the whole analysis, under its bubbles, which show only where it wrote something.
45
+ return (_jsxs("div", { class: "ai-note", "aria-live": "polite", children: [ply === undefined || shown === undefined ? null : (_jsxs("p", { class: `say move-note ${shown.badge ?? ""}`, children: [_jsxs("span", { class: "head", children: [shown.badge === undefined ? _jsx(NoteIcon, {}) : _jsx(BadgeIcon, { badge: shown.badge }), _jsxs("b", { children: [_jsxs("span", { class: "visually-hidden", children: [ply.number, ply.side === "white" ? "." : "...", ply.san] }), _jsxs("span", { "aria-hidden": "true", children: [_jsxs("span", { class: "move-number", children: [ply.number, ply.side === "white" ? "." : "..."] }), _jsx(Figurines, { san: ply.san, side: ply.side })] })] }), " ", title] }), note === undefined ? null : (_jsx("span", { class: "note", children: _jsx(Marked, { text: note }) })), shown.arrows === undefined && shown.squares === undefined ? null : _jsx(Legend, { review: shown, text: text })] })), summary === undefined ? null : _jsx(GameReview, { by: name, summary: summary, text: text }), closing === undefined ? null : (_jsx("p", { class: "say closing", children: _jsx(Marked, { text: closing }) })), _jsxs("p", { class: "who", children: [_jsx(Torretta, { size: 36, mood: mood }), _jsxs("span", { class: "signed", children: [name, by === undefined ? null : _jsx("small", { children: signature(by.annotator, by.effort) })] })] })] }));
46
+ }
47
+ /** The AI's judgment of the player's move, with the helps on: its badge and the move in a line, which opens on its note (D-78). */
48
+ function Judged({ ply, text }) {
49
+ const { badge, note } = ply.review ?? {};
50
+ const head = (_jsxs(_Fragment, { children: [badge === undefined ? _jsx(NoteIcon, {}) : _jsx(BadgeIcon, { badge: badge }), _jsxs("b", { children: [_jsxs("span", { class: "move-number", children: [ply.number, ply.side === "white" ? "." : "..."] }), _jsx(Figurines, { san: ply.san, side: ply.side })] }), " ", badge === undefined ? null : text.badges[badge]] }));
51
+ return (_jsx("div", { class: `say move-note judged ${badge ?? ""}`, children: note === undefined ? _jsx("span", { class: "head", children: head }) : (_jsxs("details", { children: [_jsx("summary", { class: "head", children: head }), _jsx("span", { class: "note", children: _jsx(Marked, { text: note }) })] })) }));
52
+ }
53
+ /** The squares and moves in the AI's words, such as e3, Cxe4, 7.Nd4 or O-O: in bold, as the player looks for them on the board. */
54
+ export function Marked({ text }) {
55
+ // The words the AI wrote between two asterisks first, in bold as it chose; the squares and moves in the rest, and in them, in the accent.
56
+ return (_jsx(_Fragment, { children: text.split(boldWords).map((part, index) => index % 2 === 1
57
+ ? (_jsx("b", { children: _jsx(OnBoard, { text: part }) }, index))
58
+ : _jsx(OnBoard, { text: part }, index)) }));
59
+ }
60
+ /** Where the pointer is among the squares and moves in the AI's words (T-111): the board lights that square up. */
61
+ export const PointSquare = createContext(() => undefined);
62
+ function OnBoard({ text }) {
63
+ const point = useContext(PointSquare);
64
+ return (_jsx(_Fragment, { children: text.split(boardWords).map((part, index) => index % 2 === 1
65
+ ? _jsx("b", { class: "on-board", onPointerEnter: () => point(squareIn(part)), onPointerLeave: (event) => event.pointerType === "mouse" && point(undefined), children: part }, index)
66
+ : part) }));
67
+ }
68
+ /** The square a word names: a square, or where a move goes, the last square in it; none for a castling. */
69
+ function squareIn(word) {
70
+ return /[a-h][1-8](?!.*[a-h][1-8])/.exec(word)?.[0];
71
+ }
72
+ // **a few words** the AI put in bold, as Markdown writes them.
73
+ const boldWords = /\*\*(.+?)\*\*/;
74
+ // A square or a move in SAN or with the pieces' figurines (D-86, D-87), with its number if it has one; the capture group keeps it in split's parts.
75
+ const boardWords = /(?<![\p{L}\d])((?:\d+\.(?:\.\.)?\s?)?(?:O-O(?:-O)?|[KQRBN♔-♟]?[a-h]?[1-8]?x?[a-h][1-8](?:=[QRBN♔-♟])?)[+#]?[!?]{0,2})(?![\p{L}\d])/u;
76
+ /** The AI's word on the whole game (D-69), in the Torretta's bubble at the start of the game: a paragraph, and its points as a list. */
77
+ function GameReview({ by, summary, text }) {
78
+ const [lead = "", ...points] = summary.split(/\s*•\s+/);
79
+ return (_jsxs("section", { class: "say review", "aria-label": text.reviewBy(by), children: [_jsxs("p", { class: "head", children: [_jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M12 4V20M8 20H16M5 7H19M5 7L2.5 13A2.5 2 0 0 0 7.5 13ZM19 7L16.5 13A2.5 2 0 0 0 21.5 13Z" }) }), text.reviewBy(by)] }), lead === "" ? null : (_jsx("p", { children: _jsx(Marked, { text: lead }) })), points.length === 0 ? null : (_jsx("ul", { children: points.map((point, index) => (_jsx("li", { children: _jsx(Marked, { text: point }) }, index))) }))] }));
80
+ }
81
+ /** What the colours drawn with a note mean, the ones it uses: the AI's legend, for the player (D-69). */
82
+ function Legend({ review, text }) {
83
+ const used = [...new Set([...(review.arrows ?? []), ...(review.squares ?? [])].map((drawn) => drawn.colour))];
84
+ return (_jsx("span", { class: "legend", children: used.map((colour) => (_jsxs("span", { class: colour, children: [_jsx("i", { "aria-hidden": "true" }), text.drawn[colour]] }, colour))) }));
85
+ }
86
+ /**
87
+ * The player's words of the game, the newest by the box they write in with how far it got, and the box: Enter or the button sends the words alone, the next
88
+ * move takes them with it. 200 characters from one move of the AI's to the next: once they are written, the box goes until its move (D-54).
89
+ */
90
+ export function Compose({ lines, newestFirst, spent, draft, onDraft, onSend, canMove, name, problem, text }) {
91
+ const list = useRef(null);
92
+ useOnlyWhole(list, lines.map((line) => line.text));
93
+ const left = talkLimits.line - spent;
94
+ const blank = draft.trim() === "";
95
+ const submit = () => {
96
+ if (!blank) {
97
+ onSend();
98
+ }
99
+ };
100
+ return (_jsxs(_Fragment, { children: [_jsx("ol", { ref: list, class: "sent", "aria-live": "polite", children: inOrder(lines.map((line, index) => (_jsxs("li", { style: aged(lines.length - 1 - index), children: [_jsx("p", { class: "said", children: line.text }), index === lines.length - 1 ? _jsx(Status, { heard: line.heard, name: name, text: text }) : null] }, index))), newestFirst) }), left <= 0 ? null : (_jsxs("form", { class: "compose", onSubmit: (event) => {
101
+ event.preventDefault();
102
+ submit();
103
+ }, children: [_jsx("p", { class: "limit", role: "status", children: problem ?? "" }), _jsx("textarea", { rows: 2, maxLength: left, placeholder: text.writeTo(name), "aria-label": text.writeTo(name), value: draft, onInput: (event) => onDraft(event.currentTarget.value), onKeyDown: (event) => {
104
+ if (event.key === "Enter" && !event.shiftKey && !event.isComposing) {
105
+ event.preventDefault();
106
+ submit();
107
+ }
108
+ } }), _jsxs("div", { class: "foot", children: [spent + draft.length >= talkLimits.line - 50
109
+ ? _jsxs("span", { class: "count", children: [spent + draft.length, "/", talkLimits.line] })
110
+ : _jsx("span", { class: "hint", children: !blank && canMove ? text.withMove : "" }), _jsxs("button", { class: "send", type: "submit", disabled: blank, children: [text.send, _jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: "M5 12H18M13 7L18 12L13 17" }) })] })] })] }))] }));
111
+ }
112
+ /**
113
+ * The talk narrowed to a card beside the board (T-84): the Torretta above, the player below, each with their name and side written down the card, a ring on
114
+ * whoever moves and a dot on the Torretta when the AI spoke meanwhile.
115
+ */
116
+ export function TalkFaces({ name, aiPicture, you, picture, ai, player, aiMoves, playerMoves, unread, text }) {
117
+ return (_jsxs("div", { class: "faces", children: [_jsx(RailPlayer, { place: "ai", picture: aiPicture ?? _jsx(Torretta, { size: 40 }), name: name, ...(ai === undefined ? {} : { side: text.sides[ai] }), moves: aiMoves, unread: unread }), _jsx(RailPlayer, { place: "you", picture: picture, name: you, ...(player === undefined ? {} : { side: text.sides[player] }), moves: playerMoves })] }));
118
+ }
119
+ function RailPlayer({ place, picture, name, side, moves, unread = false }) {
120
+ return (_jsxs("div", { class: `rail-player ${place}${moves ? " moving" : ""}`, children: [_jsx("span", { class: `face${unread ? " unread" : ""}`, children: picture }), _jsxs("span", { class: "rail-name", children: [name, " ", side === undefined ? null : _jsxs("span", { class: "note", children: ["\u00B7 ", side] })] })] }));
121
+ }
122
+ /** Reads aloud each word the AI adds while the page is open, not those already there. */
123
+ function useSpokenNews(words, voice) {
124
+ const heard = useRef(words.length);
125
+ useEffect(() => {
126
+ if (voice !== undefined && words.length > heard.current) {
127
+ speak(words.slice(heard.current).join(" "), voice);
128
+ }
129
+ heard.current = words.length;
130
+ }, [words.length, voice]);
131
+ }
132
+ /** A list's items, the newest first when it asks. */
133
+ function inOrder(items, newestFirst) {
134
+ return newestFirst ? items.reverse() : items;
135
+ }
136
+ /** A word fades with age: the newest whole, the older ones far back, so they never pass for the newest (Stefano), each fainter down to a floor. */
137
+ function aged(age) {
138
+ return { opacity: age === 0 ? 1 : Math.max(0.2, 0.4 - (age - 1) * 0.1) };
139
+ }
140
+ /**
141
+ * A list that never scrolls: a bubble that does not fit whole in it is hidden, the oldest first since the newest sit by the card, and shows again when the
142
+ * window grows.
143
+ */
144
+ function useOnlyWhole(list, lines, newestFirst = false) {
145
+ useLayoutEffect(() => {
146
+ const element = list.current;
147
+ if (element === null) {
148
+ return;
149
+ }
150
+ const fit = () => {
151
+ const box = element.getBoundingClientRect();
152
+ const newest = newestFirst ? element.firstElementChild : element.lastElementChild;
153
+ for (const item of element.children) {
154
+ const place = item.getBoundingClientRect();
155
+ const style = item.style;
156
+ // The newest never hides: one longer than the column, as a note with the helps on (D-78), scrolls in itself.
157
+ style.maxHeight = item === newest ? box.height + "px" : "";
158
+ style.overflowY = item === newest && item.scrollHeight > box.height ? "auto" : "";
159
+ style.visibility = item === newest || (place.top >= box.top - 1 && place.bottom <= box.bottom + 1) ? "" : "hidden";
160
+ }
161
+ };
162
+ fit();
163
+ const observer = new ResizeObserver(fit);
164
+ observer.observe(element);
165
+ return () => observer.disconnect();
166
+ }, [list, lines.length, lines.at(-1)]);
167
+ }
168
+ /** Under the player's last line: one check once written, two once the AI has read it. */
169
+ function Status({ heard, name, text }) {
170
+ const read = heard === "read" || heard === "answered";
171
+ return (_jsxs("span", { class: read ? "status read" : "status", children: [_jsx("svg", { class: "icon", viewBox: "0 0 21 24", "aria-hidden": "true", children: read ? _jsx("path", { d: "M2 12.5L6 16.5L13.5 8M10 15.5L11 16.5L18.5 8" }) : _jsx("path", { d: "M3.5 12.5L7.5 16.5L15 8" }) }), read ? text.readBy(name) : text.sent] }));
172
+ }