chesspal 0.1.0 → 0.2.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (211) hide show
  1. package/README.md +45 -82
  2. package/dist/cli/chat-hook.js +1 -1
  3. package/dist/cli/command-line.d.ts +1 -2
  4. package/dist/cli/command-line.js +2 -3
  5. package/dist/commands/agents.d.ts +26 -0
  6. package/dist/commands/agents.js +72 -0
  7. package/dist/commands/ai-move.d.ts +5 -14
  8. package/dist/commands/ai-move.js +28 -35
  9. package/dist/commands/ai-say.d.ts +3 -1
  10. package/dist/commands/ai-say.js +24 -6
  11. package/dist/commands/analyse-game.d.ts +4 -0
  12. package/dist/commands/analyse-game.js +43 -16
  13. package/dist/commands/annotate-game.d.ts +4 -0
  14. package/dist/commands/annotate-game.js +65 -12
  15. package/dist/commands/answer-lines.d.ts +28 -0
  16. package/dist/commands/answer-lines.js +144 -0
  17. package/dist/commands/coach.d.ts +45 -0
  18. package/dist/commands/coach.js +344 -0
  19. package/dist/commands/context.d.ts +16 -5
  20. package/dist/commands/context.js +1 -0
  21. package/dist/commands/delete-game.d.ts +7 -0
  22. package/dist/commands/delete-game.js +30 -0
  23. package/dist/commands/delete-review.d.ts +8 -0
  24. package/dist/commands/delete-review.js +34 -0
  25. package/dist/commands/drawings.d.ts +0 -2
  26. package/dist/commands/drawings.js +5 -3
  27. package/dist/commands/end-game.js +5 -2
  28. package/dist/commands/export-game.d.ts +1 -1
  29. package/dist/commands/export-game.js +4 -3
  30. package/dist/commands/game-summary.d.ts +21 -38
  31. package/dist/commands/game-summary.js +30 -167
  32. package/dist/commands/index.d.ts +15 -0
  33. package/dist/commands/index.js +47 -2
  34. package/dist/commands/init.d.ts +3 -3
  35. package/dist/commands/init.js +4 -4
  36. package/dist/commands/lead-input.d.ts +8 -0
  37. package/dist/commands/lead-input.js +21 -0
  38. package/dist/commands/look.js +4 -1
  39. package/dist/commands/new-game.d.ts +2 -3
  40. package/dist/commands/new-game.js +7 -4
  41. package/dist/commands/play-move.d.ts +8 -15
  42. package/dist/commands/play-move.js +5 -13
  43. package/dist/commands/player-move.d.ts +2 -6
  44. package/dist/commands/player-move.js +13 -6
  45. package/dist/commands/player-say.d.ts +1 -1
  46. package/dist/commands/player-say.js +1 -1
  47. package/dist/commands/ponder.d.ts +10 -0
  48. package/dist/commands/ponder.js +33 -21
  49. package/dist/commands/position.d.ts +1 -1
  50. package/dist/commands/position.js +3 -1
  51. package/dist/commands/profiles.d.ts +13 -4
  52. package/dist/commands/profiles.js +49 -15
  53. package/dist/commands/registry.d.ts +8 -3
  54. package/dist/commands/registry.js +12 -11
  55. package/dist/commands/say.d.ts +1 -1
  56. package/dist/commands/say.js +16 -16
  57. package/dist/commands/speech.d.ts +8 -0
  58. package/dist/commands/speech.js +20 -0
  59. package/dist/commands/status.d.ts +9 -3
  60. package/dist/commands/status.js +40 -15
  61. package/dist/commands/study-games.js +1 -1
  62. package/dist/commands/study.js +3 -19
  63. package/dist/core/facts.js +15 -15
  64. package/dist/core/game.d.ts +3 -1
  65. package/dist/core/game.js +8 -4
  66. package/dist/core/illegal.js +1 -1
  67. package/dist/core/index.d.ts +5 -4
  68. package/dist/core/index.js +5 -4
  69. package/dist/core/lead.d.ts +15 -0
  70. package/dist/core/lead.js +40 -0
  71. package/dist/core/levels.d.ts +1 -1
  72. package/dist/core/levels.js +1 -1
  73. package/dist/core/moods.d.ts +0 -2
  74. package/dist/core/moods.js +2 -2
  75. package/dist/core/openings.d.ts +0 -2
  76. package/dist/core/openings.js +1 -1
  77. package/dist/core/profile.js +1 -1
  78. package/dist/core/record.d.ts +1 -1
  79. package/dist/core/record.js +1 -1
  80. package/dist/core/review.d.ts +18 -3
  81. package/dist/core/review.js +52 -9
  82. package/dist/games/ai-model.d.ts +4 -0
  83. package/dist/games/ai-model.js +9 -0
  84. package/dist/games/analysis.d.ts +1 -1
  85. package/dist/games/archive.d.ts +25 -4
  86. package/dist/games/archive.js +99 -25
  87. package/dist/games/coach.d.ts +57 -0
  88. package/dist/games/coach.js +144 -0
  89. package/dist/games/current-game.d.ts +2 -0
  90. package/dist/games/current-game.js +7 -1
  91. package/dist/games/files.js +1 -0
  92. package/dist/games/index.d.ts +5 -4
  93. package/dist/games/index.js +5 -4
  94. package/dist/games/talk.d.ts +1 -5
  95. package/dist/games/talk.js +14 -14
  96. package/dist/home/open-folder.js +1 -1
  97. package/dist/host-files/{ponder-agents.d.ts → agents.d.ts} +10 -2
  98. package/dist/host-files/agents.js +53 -0
  99. package/dist/host-files/host-files.d.ts +7 -2
  100. package/dist/host-files/host-files.js +27 -12
  101. package/dist/host-files/index.d.ts +2 -2
  102. package/dist/host-files/index.js +3 -3
  103. package/dist/index.d.ts +1 -3
  104. package/dist/index.js +1 -0
  105. package/dist/page/app/anchor.js +88 -0
  106. package/dist/page/{app.js → app/app.js} +116 -231
  107. package/dist/page/app/keys.js +94 -0
  108. package/dist/page/app/phase.js +30 -0
  109. package/dist/page/app/served.js +73 -0
  110. package/dist/page/avatars/avatars.js +19 -0
  111. package/dist/page/avatars/thought.js +10 -0
  112. package/dist/page/base.css +160 -0
  113. package/dist/page/board/board.css +959 -0
  114. package/dist/page/board/board.js +125 -0
  115. package/dist/page/{explain.js → board/explain.js} +1 -1
  116. package/dist/page/board/marks.js +160 -0
  117. package/dist/page/board/motion.js +71 -0
  118. package/dist/page/board/pointer.js +239 -0
  119. package/dist/page/board/squares.js +26 -0
  120. package/dist/page/dialogs/analyses.js +195 -0
  121. package/dist/page/{dialog.js → dialogs/dialog.js} +1 -1
  122. package/dist/page/dialogs/dialogs.css +1228 -0
  123. package/dist/page/{new-game-dialog.js → dialogs/new-game.js} +3 -3
  124. package/dist/page/{profile-dialogs.js → dialogs/profiles.js} +40 -11
  125. package/dist/page/dialogs/search.js +31 -0
  126. package/dist/page/dialogs/settings.js +42 -0
  127. package/dist/page/dialogs/studied.js +39 -0
  128. package/dist/page/dialogs/study.js +51 -0
  129. package/dist/page/{badges.js → game/badges.js} +1 -1
  130. package/dist/page/{browse.js → game/browse.js} +1 -1
  131. package/dist/page/game/card-fold.js +40 -0
  132. package/dist/page/game/column.js +55 -0
  133. package/dist/page/{ending.js → game/ending.js} +2 -2
  134. package/dist/page/game/game.css +577 -0
  135. package/dist/page/{move-list.js → game/move-list.js} +10 -6
  136. package/dist/page/{plies.js → game/plies.js} +30 -10
  137. package/dist/page/game/result-tile.js +14 -0
  138. package/dist/page/helps/helps.css +689 -0
  139. package/dist/page/helps/helps.js +220 -0
  140. package/dist/page/helps/view.js +127 -0
  141. package/dist/page/layout/fold-tab.js +11 -0
  142. package/dist/page/{folds.js → layout/folds.js} +1 -1
  143. package/dist/page/{header.js → layout/header.js} +5 -4
  144. package/dist/page/layout/layout.css +834 -0
  145. package/dist/page/lead/lead.js +82 -0
  146. package/dist/page/lead/view.js +38 -0
  147. package/dist/page/main.js +2 -2
  148. package/dist/page/pieces/CREDITS.md +12 -29
  149. package/dist/page/pieces/capablanca-small.svg +20 -0
  150. package/dist/page/pieces/capablanca.svg +22 -0
  151. package/dist/page/pieces/nitida-small.svg +3 -2
  152. package/dist/page/pieces/nitida.svg +3 -2
  153. package/dist/page/{pieces.js → pieces/pieces.js} +2 -2
  154. package/dist/page/pieces/torretta-small.svg +15 -14
  155. package/dist/page/pieces/torretta.svg +17 -15
  156. package/dist/page/{player-card.js → players/card.js} +2 -6
  157. package/dist/page/{clock-time.js → players/clock-time.js} +2 -2
  158. package/dist/page/{profile.js → profile/profile.js} +9 -5
  159. package/dist/page/{settings.js → profile/settings.js} +5 -4
  160. package/dist/page/style.css +9 -4398
  161. package/dist/page/talk/talk.css +958 -0
  162. package/dist/page/{talk.js → talk/talk.js} +39 -46
  163. package/dist/page/text.js +137 -19
  164. package/dist/page/theme.css +292 -0
  165. package/dist/profiles/index.d.ts +1 -1
  166. package/dist/profiles/profiles.d.ts +15 -4
  167. package/dist/profiles/profiles.js +24 -7
  168. package/dist/server/bell.d.ts +10 -2
  169. package/dist/server/bell.js +28 -12
  170. package/dist/server/caller.d.ts +14 -3
  171. package/dist/server/caller.js +44 -11
  172. package/dist/server/mcp.d.ts +2 -3
  173. package/dist/server/mcp.js +53 -19
  174. package/dist/server/page/analyses.d.ts +23 -0
  175. package/dist/server/page/analyses.js +65 -0
  176. package/dist/server/page/files.d.ts +5 -0
  177. package/dist/server/page/files.js +92 -0
  178. package/dist/server/page/game.d.ts +6 -0
  179. package/dist/server/page/game.js +381 -0
  180. package/dist/server/{http.d.ts → page/http.d.ts} +13 -0
  181. package/dist/server/{http.js → page/http.js} +11 -0
  182. package/dist/server/{page.d.ts → page/page.d.ts} +8 -13
  183. package/dist/server/page/page.js +229 -0
  184. package/dist/server/{profile-routes.d.ts → page/profiles.d.ts} +2 -2
  185. package/dist/server/{profile-routes.js → page/profiles.js} +47 -18
  186. package/instructions/analysis.md +4 -0
  187. package/instructions/analyst-agent.md +8 -0
  188. package/instructions/analyst.md +3 -7
  189. package/instructions/coach.md +11 -0
  190. package/instructions/opponent.md +4 -4
  191. package/package.json +1 -1
  192. package/dist/host-files/ponder-agents.js +0 -40
  193. package/dist/page/avatars.js +0 -16
  194. package/dist/page/board.js +0 -566
  195. package/dist/page/fold-tab.js +0 -9
  196. package/dist/page/game-column.js +0 -44
  197. package/dist/page/helps-view.js +0 -51
  198. package/dist/page/helps.js +0 -90
  199. package/dist/page/pieces/cburnett-small.svg +0 -20
  200. package/dist/page/pieces/cburnett.svg +0 -20
  201. package/dist/page/settings-panel.js +0 -32
  202. package/dist/page/studied.js +0 -14
  203. package/dist/page/study-dialog.js +0 -58
  204. package/dist/server/analyses.d.ts +0 -17
  205. package/dist/server/analyses.js +0 -46
  206. package/dist/server/page.js +0 -657
  207. /package/dist/page/{torretta.js → avatars/torretta.js} +0 -0
  208. /package/dist/page/{opening-book.js → game/opening-book.js} +0 -0
  209. /package/dist/page/{model-name.js → players/model-name.js} +0 -0
  210. /package/dist/page/{sounds.js → sounds/sounds.js} +0 -0
  211. /package/dist/page/{voice.js → talk/voice.js} +0 -0
@@ -0,0 +1,220 @@
1
+ import { jsx as _jsx, jsxs as _jsxs, Fragment as _Fragment } from "preact/jsx-runtime";
2
+ // The helps on the page (D-78, Zoe's sketch B): the board says where, the card says what, the facts the AI sees at the game's level and only those; and
3
+ // the coach's words (D-88).
4
+ import { Fragment } from "preact";
5
+ import { useState } from "preact/hooks";
6
+ import { Notch } from "../board/marks.js";
7
+ import { BadgeIcon, NoteIcon } from "../game/badges.js";
8
+ import { FoldButton, useCardFold } from "../game/card-fold.js";
9
+ import { Figurines } from "../game/move-list.js";
10
+ import { Figurine } from "../pieces/pieces.js";
11
+ import { Legend, Marked } from "../talk/talk.js";
12
+ import { groupOf, pinSquares, riskSquares, squaresOf, strategyOf } from "./view.js";
13
+ /**
14
+ * The helps' card, between the opening and the moves: the coach's words, then the tactics in lines, the strategy in a table and the control map, seen while
15
+ * its button or M is held, and the board without its drawings while their button is. A line under the pointer lights its squares on the board. Its title
16
+ * folds it to the title and opens it again (Stefano).
17
+ */
18
+ export function HelpsCard({ helps, judged, hint, hintOpen, onHint, onPoint, onMap, mapping, onBare, bare, text }) {
19
+ const [tab, setTab] = useState("tactics");
20
+ // The tactics' lines the player opened stay open from move to move, so that a pin is followed as the game goes on.
21
+ const [opened, setOpened] = useState(new Set());
22
+ const openLine = (key, open) => setOpened((lines) => new Set(open ? [...lines, key] : [...lines].filter((line) => line !== key)));
23
+ const [folded, toggleFold] = useCardFold("helps");
24
+ const words = text.helpsCard;
25
+ // The hint closes with the card, so that its drawings never stay on the board without its words (Zoe).
26
+ const fold = () => {
27
+ if (!folded) {
28
+ onHint(false);
29
+ }
30
+ toggleFold();
31
+ };
32
+ return (_jsxs("section", { class: `card helps-card${folded ? " folded" : ""}`, "aria-label": words.title, children: [_jsxs("div", { class: "helps-head", children: [_jsx("h3", { class: "card-title", children: _jsxs(FoldButton, { folded: folded, onFold: fold, text: text, children: [_jsx(HelpsIcon, {}), words.title] }) }), _jsxs("span", { class: "helps-tools", children: [_jsx(MapButton, { mapping: mapping, onMap: onMap, text: text }), _jsx(BareButton, { bare: bare, onBare: onBare, text: text })] })] }), folded ? null : (_jsxs(_Fragment, { children: [judged.length === 0 && hint === undefined ? null : (_jsxs("section", { class: `coach${judged.length === 0 ? " hint-only" : ""}`, "aria-label": words.coach, children: [judged.map((ply) => _jsx(Judged, { ply: ply, text: text }, `${ply.number} ${ply.side}`)), hint === undefined ? null : _jsx(HintLine, { hint: hint, open: hintOpen, onOpen: onHint, text: text })] })), _jsxs("div", { class: "tabs", role: "tablist", children: [_jsx("button", { type: "button", role: "tab", "aria-selected": tab === "tactics", onClick: () => setTab("tactics"), children: words.tactics }), _jsx("button", { type: "button", role: "tab", "aria-selected": tab === "strategy", onClick: () => setTab("strategy"), children: words.strategy })] }), tab === "tactics"
33
+ ? _jsx(Tactics, { helps: helps, opened: opened, onOpen: openLine, onPoint: onPoint, text: text })
34
+ : _jsx(Strategy, { helps: helps, onPoint: onPoint, text: text })] }))] }));
35
+ }
36
+ /** The map's button (D-78), held to see the map: an icon, its tooltip saying how (Stefano); in the card's title, and on a phone under the board. */
37
+ export function MapButton({ mapping, onMap, text }) {
38
+ return (_jsx(HoldButton, { on: mapping, onHold: onMap, title: text.helpsCard.mapHint, label: text.helpsCard.map, children: _jsxs("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", children: [_jsx("path", { d: "M2 2h5v5H2zM9 9h5v5H9z" }), _jsx("path", { d: "M9 2h5v5H9zM2 9h5v5H2z", opacity: ".35" })] }) }));
39
+ }
40
+ /**
41
+ * The button held to see the board bare, without the arrows and the squares the AI, the coach and the player drew on it, nor the helps' signs (Stefano): an eye shut.
42
+ */
43
+ export function BareButton({ bare, onBare, text }) {
44
+ return (_jsx(HoldButton, { on: bare, onHold: onBare, title: text.helpsCard.bareHint, label: text.helpsCard.bare, kind: "bare-button", children: _jsxs("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", children: [_jsx("path", { d: "M1.5 8S4 3.5 8 3.5 14.5 8 14.5 8 12 12.5 8 12.5 1.5 8 1.5 8Z" }), _jsx("circle", { cx: "8", cy: "8", r: "2" }), _jsx("path", { d: "M2.5 2.5l11 11" })] }) }));
45
+ }
46
+ /**
47
+ * The coach's hint, a line that opens on its words and the legend of what it draws (D-88): in the helps' card, and on a phone under the board, beside the
48
+ * map, where its drawings are in sight (Zoe).
49
+ */
50
+ export function HintLine({ hint, open, onOpen, text }) {
51
+ const words = text.helpsCard;
52
+ return (_jsxs("details", { class: "coach-hint", open: open, onToggle: (event) => onOpen(event.currentTarget.open), children: [_jsx("summary", { children: open ? words.hideHint : words.showHint }), _jsx("span", { class: "note", children: _jsx(Marked, { text: hint.text }) }), hint.arrows === undefined && hint.squares === undefined ? null : _jsx(Legend, { review: hint, words: words.drawn })] }));
53
+ }
54
+ /** An icon the mouse holds, while what it shows lasts; a finger, or the keyboard, turns it on and off (D-78). */
55
+ function HoldButton({ on, onHold, title, label, kind, children }) {
56
+ // A click carries its pointer's type: none from the keyboard.
57
+ const mouse = (event) => event instanceof PointerEvent && event.pointerType === "mouse";
58
+ return (_jsx("button", { type: "button", class: kind === undefined ? "hold-button" : `hold-button ${kind}`, "aria-pressed": on, "aria-label": label, title: title, onPointerDown: (event) => mouse(event) && onHold(true), onPointerUp: (event) => mouse(event) && onHold(false), onPointerLeave: (event) => mouse(event) && onHold(false), onClick: (event) => !mouse(event) && onHold(!on), children: children }));
59
+ }
60
+ /** The coach's judgment of the player's move: its badge and the move in a line, which opens on its note (D-78, D-88). */
61
+ function Judged({ ply, text }) {
62
+ const { badge, note } = ply.review ?? {};
63
+ 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]] }));
64
+ return (_jsx("div", { class: `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 }) })] })) }));
65
+ }
66
+ /**
67
+ * The tactics in lines (D-91): one a kind of fact and a side, its count, red when it threatens the player and green when it is their chance; a click opens
68
+ * its pieces, each with its squares (Stefano).
69
+ */
70
+ function Tactics({ helps, opened, onOpen, onPoint, text }) {
71
+ const words = text.helpsCard;
72
+ const point = pointing(onPoint);
73
+ const calm = !helps.lines.some((line) => line.kind === "risk" || line.kind === "pin");
74
+ return (_jsxs("ul", { class: "help-rows", children: [calm
75
+ ? (_jsxs("li", { class: "safe", children: [_jsxs(RowIcon, { children: [_jsx("circle", { cx: "8", cy: "8", r: "6.5" }), _jsx("path", { d: "M5.2 8.2l2 2 3.8-4" })] }), words.nothingAtRisk] }))
76
+ : null, helps.lines.map((line) => {
77
+ const key = line.kind === "loose" ? "loose" : `${line.kind} ${line.side}`;
78
+ const open = opened.has(key);
79
+ return (_jsxs("li", { class: `help-line ${line.kind} ${line.tone}${open ? " open" : ""}`, children: [_jsxs("button", { type: "button", class: "line-head", "aria-expanded": open, onClick: () => onOpen(key, !open), ...point(squaresOf(line)), children: [_jsx(LineIcon, { kind: line.kind }), _jsx("span", { class: "line-name", children: line.kind === "loose" ? words.loose : words.line(line.kind, line.side, helps.you, line.kind !== "check" || line.toMove) }), _jsx(LineCount, { line: line, helps: helps, words: words })] }), open ? _jsx(LineDetails, { line: line, helps: helps, point: point, text: text }) : null] }, key));
80
+ })] }));
81
+ }
82
+ /** What the pointer on a line or a cell does: it lights the line's squares; a touch keeps them lit until the position changes, as a square in the AI's words. */
83
+ function pointing(onPoint) {
84
+ return (squares) => ({
85
+ onPointerEnter: () => onPoint(squares),
86
+ onPointerLeave: (event) => event.pointerType === "mouse" && onPoint([]),
87
+ });
88
+ }
89
+ /** A line's sign: the corner of a piece at risk, a pin's link, a check's plus, the loose pieces' i; its colour is the line's. */
90
+ function LineIcon({ kind }) {
91
+ if (kind === "risk") {
92
+ return _jsx(Notch, {});
93
+ }
94
+ if (kind === "loose") {
95
+ return _jsx(InfoIcon, {});
96
+ }
97
+ return (_jsx(RowIcon, { children: kind === "pin"
98
+ ? _jsx("path", { d: "M6.5 9.5l3-3M7.5 4.5l1-1a2.5 2.5 0 0 1 3.5 3.5l-1 1M8.5 11.5l-1 1a2.5 2.5 0 0 1-3.5-3.5l1-1" })
99
+ : (_jsxs(_Fragment, { children: [_jsx("circle", { cx: "8", cy: "8", r: "6.5" }), _jsx("path", { d: "M8 5v6M5 8h6" })] })) }));
100
+ }
101
+ /** How many: the loose pieces by side, each after its pawn, the player's first (Zoe); the others in one number. */
102
+ function LineCount({ line, helps, words }) {
103
+ if (line.kind !== "loose") {
104
+ return _jsx("b", { class: "count", children: line.items.length });
105
+ }
106
+ const name = (side) => helps.you === undefined ? words[side] : side === helps.you ? words.yours : words.theirs;
107
+ const counts = helps.sides.map((side) => ({ side, count: line.items.filter((piece) => piece.side === side).length })).filter(({ count }) => count > 0);
108
+ return (_jsx("span", { class: "counts", children: counts.map(({ side, count }) => (_jsxs("span", { title: name(side), "aria-label": `${name(side)} ${count}`, children: [_jsx(Figurine, { letter: "P", side: side }), _jsx("b", { class: "count", children: count })] }, side))) }));
109
+ }
110
+ /** A line opened: its pieces, each lighting its own squares; the checks in figures; the loose pieces by side. */
111
+ function LineDetails({ line, helps, point, text }) {
112
+ const words = text.helpsCard;
113
+ switch (line.kind) {
114
+ case "risk":
115
+ return (_jsx("ul", { class: "line-details", children: line.items.map((risk) => (_jsxs("li", { ...point(riskSquares(risk)), children: [text.piece(risk.piece.piece, risk.piece.side), " ", _jsx(Squares, { squares: [risk.piece.square] }), " \u00B7 ", words.attackers, ": ", _jsx(Squares, { squares: risk.piece.attackers }), risk.why === "undefended" ? null : (_jsxs(_Fragment, { children: [" ", "\u00B7 ", words.defenders, ": ", _jsx(Squares, { squares: risk.piece.defenders })] })), " \u00B7 ", words[risk.why]] }, risk.piece.square))) }));
116
+ case "pin":
117
+ return (_jsx("ul", { class: "line-details", children: line.items.map((pin) => (_jsxs("li", { ...point(pinSquares(pin)), children: [text.piece(pin.piece.piece, pin.piece.side), " ", _jsx(Squares, { squares: [pin.piece.square] }), " ", words.pinnedTo(pin.to.piece), " ", _jsx(Squares, { squares: [pin.to.square] }), ", ", words.pinnedBy(pin.by.piece), " ", _jsx(Squares, { squares: [pin.by.square] })] }, pin.piece.square))) }));
118
+ case "check":
119
+ return (_jsx("p", { class: "line-details", children: line.items.map((check) => (_jsx("span", { class: "help-move", children: _jsx(Figurines, { san: check, side: line.side }) }, check))) }));
120
+ case "loose":
121
+ return (_jsx("p", { class: "line-details", children: helps.sides.map((side) => _jsx(Pieces, { pieces: line.items.filter((piece) => piece.side === side) }, side)) }));
122
+ }
123
+ }
124
+ /** The helps' sign before the card's title: a lifebuoy (Stefano). */
125
+ function HelpsIcon() {
126
+ return (_jsxs("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: [_jsx("circle", { cx: "12", cy: "12", r: "8.5" }), _jsx("circle", { cx: "12", cy: "12", r: "3.5" }), _jsx("path", { d: "M6 6L9.5 9.5M18 6L14.5 9.5M6 18L9.5 14.5M18 18L14.5 14.5" })] }));
127
+ }
128
+ /** The icon before a line of facts, in its kind's colour (Stefano): a pin's link, nothing at risk's tick, a check's plus, the loose pieces' i. */
129
+ function RowIcon({ children }) {
130
+ return (_jsx("span", { class: "row-icon", "aria-hidden": "true", children: _jsx("svg", { viewBox: "0 0 16 16", children: children }) }));
131
+ }
132
+ /** An i in a ring, before the loose pieces and the strategy's notes. */
133
+ function InfoIcon() {
134
+ return (_jsxs(RowIcon, { children: [_jsx("circle", { cx: "8", cy: "8", r: "6.5" }), _jsx("path", { d: "M8 7.3V11M8 4.9v.1" })] }));
135
+ }
136
+ /** Pieces of one side, each its figure and its square. */
137
+ function Pieces({ pieces }) {
138
+ return pieces.length === 0 ? null : (_jsx("span", { class: "help-pieces", children: pieces.map((piece) => (_jsxs("span", { class: "help-move", children: [_jsx(Figurine, { letter: piece.piece, side: piece.side }), _jsx("b", { class: "square-name", children: piece.square })] }, piece.square))) }));
139
+ }
140
+ function Squares({ squares }) {
141
+ return _jsx("b", { class: "square-name", children: squares.join(", ") });
142
+ }
143
+ /**
144
+ * The strategy (Stefano): the phase and the open files on top, the squares each side controls in a bar, the table in groups each under its figure, a count
145
+ * or a tick in each cell, and the notes in lines as the tactics'. The player's side comes first, with no colour of good or bad (D-91).
146
+ */
147
+ function Strategy({ helps, onPoint, text }) {
148
+ const words = text.helpsCard;
149
+ const profile = helps.strategy;
150
+ const { draw, pawnEnding } = profile;
151
+ const point = pointing(onPoint);
152
+ // A line opens under it at a click or a tap, on its name's meaning and each side's squares or files, since a phone has no pointer for a title (Rosa).
153
+ const [explained, setExplained] = useState(undefined);
154
+ const lines = strategyOf(profile);
155
+ const sideName = (side) => side === helps.you ? `${words[side]} ${words.you}` : words[side];
156
+ const notes = [
157
+ profile.oppositeBishops ? words.oppositeBishops : undefined,
158
+ draw.repetitions > 1 ? words.repetitions(draw.repetitions) : undefined,
159
+ draw.fiftyMovesLeft <= fiftyMovesShown ? words.fiftyMoves(draw.fiftyMovesLeft) : undefined,
160
+ draw.insufficient ? words.insufficient : undefined,
161
+ ...(pawnEnding?.squares ?? []).map((square) => words.pawnSquare(square.pawn, square.caught)),
162
+ pawnEnding?.opposition === undefined ? undefined : words.opposition(pawnEnding.opposition),
163
+ ].filter((note) => note !== undefined);
164
+ return (_jsxs("div", { class: "strategy", children: [_jsxs("p", { class: "strategy-head", children: [_jsx("span", { class: "phase", children: words.phases[profile.phase] }), _jsxs("span", { children: [words.openFiles, ": ", _jsx(Chips, { items: profile.openFiles })] })] }), _jsx(ControlBar, { controlled: profile.controlled, sides: helps.sides, sideName: sideName, words: words }), _jsxs("table", { class: "strategy-table", children: [_jsx("thead", { children: _jsxs("tr", { children: [_jsx("th", {}), helps.sides.map((side) => (_jsxs("th", { children: [_jsx(Figurine, { letter: "P", side: side }), sideName(side)] }, side)))] }) }), groups.map(({ group, letter }) => {
165
+ const shown = lines.filter((line) => groupOf[line.row] === group);
166
+ return shown.length === 0 ? null : (_jsxs("tbody", { children: [_jsx("tr", { class: "group", children: _jsxs("th", { colSpan: 3, children: [_jsx(Figurine, { letter: letter, side: "white" }), words.groups[group]] }) }), shown.map((line) => {
167
+ const [name, meaning] = words.rows[line.row];
168
+ const open = explained === line.row;
169
+ const listed = helps.sides.filter((side) => typeof line[side] === "object");
170
+ return (_jsxs(Fragment, { children: [_jsxs("tr", { class: open ? "open" : undefined, ...point(squaresIn(line)), children: [_jsx("th", { children: _jsx("button", { type: "button", class: "term", title: meaning, "aria-expanded": open, onClick: () => setExplained(open ? undefined : line.row), children: name }) }), helps.sides.map((side) => (_jsx("td", { children: _jsx(Cell, { cell: line[side], words: words }) }, side)))] }), open
171
+ ? (_jsx("tr", { class: "meaning", children: _jsxs("td", { colSpan: 3, children: [meaning, listed.map((side) => (_jsxs("span", { class: "listed", children: [sideName(side), ": ", _jsx(Chips, { items: line[side] })] }, side)))] }) }))
172
+ : null] }, line.row));
173
+ })] }, group));
174
+ })] }), notes.length === 0 ? null : (_jsx("ul", { class: "help-rows", children: notes.map((note) => (_jsxs("li", { class: "info", children: [_jsx(InfoIcon, {}), note] }, note))) }))] }));
175
+ }
176
+ // The groups of the strategy's table in their order, each with its figure.
177
+ const groups = [{ group: "pawns", letter: "P" }, { group: "pieces", letter: "N" }, { group: "king", letter: "K" }];
178
+ // The fifty-move count shows only when the draw is near: ten moves each, or less.
179
+ const fiftyMovesShown = 20;
180
+ /** The squares a side's cells of a strategy's line name, to light on the board; files and islands light none. */
181
+ function squaresIn(line) {
182
+ return [line.white, line.black].flatMap((cell) => typeof cell === "object" ? cell.filter((item) => /^[a-h][1-8]$/.test(item)) : []);
183
+ }
184
+ /**
185
+ * The squares only one side controls and those both do, in a bar as long as their counts, the player's side on the left; its title says it in words.
186
+ */
187
+ function ControlBar({ controlled, sides, sideName, words }) {
188
+ const { white, black, contested } = controlled;
189
+ const [left, right] = sides;
190
+ const parts = [{ side: left, count: controlled[left] }, { side: "both", count: contested }, { side: right, count: controlled[right] }].filter(({ count }) => count > 0);
191
+ return (_jsxs("div", { class: "control", title: words.controlled(white, black, contested), children: [_jsx("span", { class: "control-title", children: words.control }), _jsx("span", { class: "control-bar", role: "img", "aria-label": words.controlled(white, black, contested), children: parts.map(({ side, count }) => _jsx("span", { class: side, style: { flexGrow: count }, children: count }, side)) }), _jsxs("span", { class: "control-legend", "aria-hidden": "true", children: [_jsx("span", { children: sideName(left) }), _jsx("span", { children: words.both }), _jsx("span", { children: sideName(right) })] })] }));
192
+ }
193
+ /** Squares, files or islands in chips; none as a faint dash. */
194
+ function Chips({ items }) {
195
+ return items.length === 0 ? _jsx("span", { class: "none", children: "\u2013" }) : _jsx(_Fragment, { children: items.map((item) => _jsx("b", { class: "chip", children: item }, item)) });
196
+ }
197
+ /** A cell of the strategy (Stefano): a count in bold, a yes as a tick, nothing or not yet as a faint dash, no more as a faint cross: marks, not words. */
198
+ function Cell({ cell, words }) {
199
+ const none = _jsx("span", { class: "none", children: "\u2013" });
200
+ if (typeof cell === "object") {
201
+ return cell.length === 0 ? none : _jsx("b", { class: "count", children: cell.length });
202
+ }
203
+ if (typeof cell === "number") {
204
+ return cell === 0 ? none : _jsx("b", { class: "count", children: cell });
205
+ }
206
+ if (typeof cell === "boolean") {
207
+ return cell
208
+ ? (_jsx("span", { class: "yes", role: "img", "aria-label": words.yes, title: words.yes, children: _jsx("svg", { viewBox: "0 0 16 16", children: _jsx("path", { d: "M3.5 8.5l3 3 6-7" }) }) }))
209
+ : none;
210
+ }
211
+ if (cell === "unknown") {
212
+ return none;
213
+ }
214
+ if (cell === "not-yet") {
215
+ return _jsx("span", { class: "none", role: "img", "aria-label": words.notYet, title: words.notYet, children: "\u2013" });
216
+ }
217
+ return cell === "no-more"
218
+ ? (_jsx("span", { class: "no", role: "img", "aria-label": words.noMore, title: words.noMore, children: _jsx("svg", { viewBox: "0 0 16 16", children: _jsx("path", { d: "M4.5 4.5l7 7M11.5 4.5l-7 7" }) }) }))
219
+ : _jsx("b", { class: "chip", children: cell });
220
+ }
@@ -0,0 +1,127 @@
1
+ /** The helps of a game's position, whatever the AI's level (D-79), seen from the player's side when the page knows it (D-91). */
2
+ export function helpsOf(game, you) {
3
+ const facts = game.facts;
4
+ const pieces = new Map(facts.pieces.map((piece) => [piece.square, piece]));
5
+ const on = (square) => pieces.get(square);
6
+ const risks = (squares, why) => squares.map((square) => ({ piece: on(square), why }));
7
+ const toMove = game.position.turn;
8
+ const atRisk = [...risks(facts.undefended, "undefended"), ...risks(facts.attackedByCheaper, "cheaper"), ...risks(facts.outnumbered, "outnumbered")];
9
+ const pins = facts.pins.map((pin) => ({ piece: on(pin.square), by: on(pin.by), to: on(pin.to) }));
10
+ const checks = facts.checks;
11
+ const otherChecks = facts.otherChecks ?? [];
12
+ const loose = facts.loose.map(on);
13
+ return {
14
+ toMove,
15
+ you,
16
+ atRisk,
17
+ pins,
18
+ checks,
19
+ otherChecks,
20
+ loose,
21
+ lines: tacticLines({ toMove, atRisk, pins, checks, otherChecks, loose }, you),
22
+ strategy: game.profile,
23
+ sides: you === "black" ? ["black", "white"] : ["white", "black"],
24
+ };
25
+ }
26
+ /** How the helps mark a square's piece at risk (D-91): red when it is the player's, green when it is the other side's, in ink when no side is theirs. */
27
+ export function riskOn(helps, square) {
28
+ const risk = helps.atRisk.find((risk) => risk.piece.square === square);
29
+ return risk === undefined ? undefined : toneOf(risk.piece.side, helps.you);
30
+ }
31
+ /** The squares a line of the tactics lights on the board: its pieces, and those that attack or pin them; none for the checks. */
32
+ export function squaresOf(line) {
33
+ switch (line.kind) {
34
+ case "risk":
35
+ return line.items.flatMap(riskSquares);
36
+ case "pin":
37
+ return line.items.flatMap(pinSquares);
38
+ case "check":
39
+ return [];
40
+ case "loose":
41
+ return line.items.map((piece) => piece.square);
42
+ }
43
+ }
44
+ /** A piece at risk and its attackers. */
45
+ export function riskSquares(risk) {
46
+ return [risk.piece.square, ...risk.piece.attackers];
47
+ }
48
+ /** A pinned piece, the one that pins it and the one behind it. */
49
+ export function pinSquares(pin) {
50
+ return [pin.piece.square, pin.by.square, pin.to.square];
51
+ }
52
+ /** Which group each line of the strategy's table stands in. */
53
+ export const groupOf = {
54
+ isolated: "pawns",
55
+ doubled: "pawns",
56
+ backward: "pawns",
57
+ passed: "pawns",
58
+ islands: "pawns",
59
+ halfOpen: "pieces",
60
+ heavyOnFiles: "pieces",
61
+ onSeventh: "pieces",
62
+ outposts: "pieces",
63
+ bishopPair: "pieces",
64
+ space: "pieces",
65
+ undeveloped: "pieces",
66
+ castled: "king",
67
+ kingAttacks: "king",
68
+ };
69
+ /** The strategy's table: only the lines where a side has something; the pawn islands only when a side has more than one. */
70
+ export function strategyOf(profile) {
71
+ const lines = Object.entries(cells).map(([row, cell]) => ({ row: row, white: cell(profile.white), black: cell(profile.black) }));
72
+ const split = profile.white.pawns.islands.length > 1 || profile.black.pawns.islands.length > 1;
73
+ return lines.filter((line) => line.row === "islands" ? split : something(line.white) || something(line.black));
74
+ }
75
+ const cells = {
76
+ isolated: (side) => side.pawns.isolated,
77
+ doubled: (side) => side.pawns.doubled,
78
+ backward: (side) => side.pawns.backward,
79
+ passed: (side) => side.pawns.passed,
80
+ islands: (side) => side.pawns.islands,
81
+ halfOpen: (side) => side.halfOpenFiles,
82
+ heavyOnFiles: (side) => side.heavyOnFiles,
83
+ onSeventh: (side) => side.onSeventh,
84
+ outposts: (side) => side.outposts.map((outpost) => outpost.square),
85
+ bishopPair: (side) => side.bishopPair,
86
+ space: (side) => side.space,
87
+ undeveloped: (side) => side.undeveloped,
88
+ castled: castlingOf,
89
+ kingAttacks: (side) => side.king?.attacksNear ?? 0,
90
+ };
91
+ // The tactics' lines that have something: with the player's side, their pieces at risk and pinned and the other side's checks, then the same the other way
92
+ // round; without it, White's then Black's; the loose pieces last.
93
+ function tacticLines(facts, you) {
94
+ const sideLines = (side) => [
95
+ { kind: "risk", side, tone: toneOf(side, you), items: facts.atRisk.filter((risk) => risk.piece.side === side) },
96
+ { kind: "pin", side, tone: toneOf(side, you), items: facts.pins.filter((pin) => pin.piece.side === side) },
97
+ ];
98
+ const checkLine = (side) => ({
99
+ kind: "check",
100
+ side,
101
+ tone: you === undefined ? "neutral" : side === you ? "chance" : "threat",
102
+ toMove: side === facts.toMove,
103
+ items: side === facts.toMove ? facts.checks : facts.otherChecks,
104
+ });
105
+ // The player's threats are their pieces' troubles and the other side's checks; their chances the other way round.
106
+ const lines = you === undefined
107
+ ? [...sideLines("white"), checkLine("white"), ...sideLines("black"), checkLine("black")]
108
+ : [...sideLines(you), checkLine(otherSide(you)), ...sideLines(otherSide(you)), checkLine(you)];
109
+ const loose = { kind: "loose", tone: "neutral", items: facts.loose };
110
+ return [...lines, loose].filter((line) => line.items.length > 0);
111
+ }
112
+ function otherSide(side) {
113
+ return side === "white" ? "black" : "white";
114
+ }
115
+ // A piece's trouble is a threat to its side's player and a chance for the other.
116
+ function toneOf(side, you) {
117
+ return you === undefined ? "neutral" : side === you ? "threat" : "chance";
118
+ }
119
+ function castlingOf(side) {
120
+ if (side.castled === "O-O" || side.castled === "O-O-O") {
121
+ return side.castled;
122
+ }
123
+ return side.castlingRights.length > 0 ? "not-yet" : side.castled === "no" ? "no-more" : "unknown";
124
+ }
125
+ function something(cell) {
126
+ return typeof cell === "object" ? cell.length > 0 : typeof cell === "number" ? cell > 0 : typeof cell === "boolean" ? cell : cell !== "unknown";
127
+ }
@@ -0,0 +1,11 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
+ import { foldKeys } from "./folds.js";
3
+ /**
4
+ * The tab of a column: open, on the column's outer edge; folded, peeking out from under the board, with a dot when the AI spoke meanwhile, and a mark in the
5
+ * other corner, as the AI's thought cloud.
6
+ */
7
+ export function FoldTab({ column, folded, label, unread = false, mark, onFold }) {
8
+ // The chevron points where a click moves the handle: in under the board, or out again.
9
+ const left = (column === "talk") === folded;
10
+ return (_jsx("div", { class: `fold-tab of-${column}${folded ? " folded" : ""}`, children: _jsxs("button", { id: `fold-${column}`, class: unread ? "unread" : undefined, type: "button", title: `${label} (${foldKeys[column]})`, "aria-label": label, "aria-controls": column, "aria-expanded": !folded, "aria-keyshortcuts": foldKeys[column], onClick: () => onFold(column), children: [_jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: left ? "M14 7L9 12L14 17" : "M10 7L15 12L10 17" }) }), mark] }) }));
11
+ }
@@ -1,4 +1,4 @@
1
- // The side columns the player folds (T-84): the talk on the left, which narrows to its two faces before it folds, the game on the right; each as this browser
1
+ // The side columns the player folds: the talk on the left, which narrows to its two faces before it folds, the game on the right; each as this browser
2
2
  // remembers it.
3
3
  /** The key that folds each column, as in editors with two side bars. */
4
4
  export const foldKeys = { talk: "[", game: "]" };
@@ -1,12 +1,13 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "preact/jsx-runtime";
2
- import { Torretta } from "./torretta.js";
2
+ import { PlayerPicture } from "../avatars/avatars.js";
3
+ import { Torretta } from "../avatars/torretta.js";
3
4
  /**
4
- * The header, with how a game over ended (T-77), or that none goes on (D-77), or the AI's draw offer with the player's answer, the player's draw
5
+ * The header, with how a game over ended, or that none goes on (D-77), or the AI's draw offer with the player's answer, the player's draw
5
6
  * and resignation in a game going on (D-65), a new game once the page has the folder's, the command that turns the board round and says whether it is
6
7
  * turned, the switches of the sounds and the theme, and the gear of the settings (D-56).
7
8
  */
8
- export function Header({ text, verdict, kind, who, actions, onNewGame, standOut = false, onStudy, flipped, onFlip, sound, onSound, onTheme, onSettings }) {
9
- return (_jsxs("header", { class: "top", children: [_jsx(Torretta, { size: 34 }), _jsx("b", { class: "title", children: "Chesspal" }), _jsxs("p", { class: `verdict${kind === undefined ? "" : ` ${kind}`}`, role: "status", children: [verdict === undefined || kind === undefined ? null : _jsx(Icon, { class: "kind", children: kindIcons[kind] }), who === undefined || verdict === undefined ? verdict : (_jsxs(_Fragment, { children: [_jsx("span", { class: "ai-name", children: who }), _jsx("span", { children: verdict })] }))] }), actions === undefined ? null : _jsx(Actions, { actions: actions, text: text }), onNewGame === undefined ? null : (_jsxs("button", { class: standOut ? "command primary" : "command", type: "button", title: text.newGame, onClick: onNewGame, children: [_jsx(Icon, { class: "new", children: _jsx("path", { d: "M12 5V19M5 12H19" }) }), _jsx("span", { children: text.newGame })] })), onStudy === undefined ? null : (_jsxs("button", { class: "command", type: "button", title: text.study.title, onClick: onStudy, children: [_jsxs(Icon, { class: "study", children: [_jsx("circle", { cx: "10.5", cy: "10.5", r: "5.5" }), _jsx("path", { d: "M14.5 14.5L20 20" })] }), _jsx("span", { children: text.study.title })] })), _jsxs("button", { class: "command", type: "button", title: text.flip, "aria-pressed": flipped, onClick: onFlip, children: [_jsx(Icon, { class: "flip", children: _jsx("path", { d: "M7 4V17M7 17L4 14M7 17L10 14M17 20V7M17 7L14 10M17 7L20 10" }) }), _jsx("span", { children: text.flip })] }), _jsx("button", { class: "theme", type: "button", title: text.sounds, "aria-label": text.sounds, "aria-pressed": sound, onClick: onSound, children: _jsxs(Icon, { class: "speaker", children: [_jsx("path", { d: "M5 9.5H8.5L13 5.5V18.5L8.5 14.5H5Z" }), sound ? _jsx("path", { d: "M16.5 9A4 4 0 0 1 16.5 15M19 6.5A7.5 7.5 0 0 1 19 17.5" }) : _jsx("path", { d: "M15.5 9L21.5 15M21.5 9L15.5 15" })] }) }), _jsxs("button", { class: "theme", type: "button", title: text.theme, "aria-label": text.theme, onClick: onTheme, children: [_jsxs(Icon, { class: "sun", children: [_jsx("circle", { cx: "12", cy: "12", r: "4" }), _jsx("path", { d: "M12 2V4M12 20V22M2 12H4M20 12H22M4.9 4.9L6.3 6.3M17.7 17.7L19.1 19.1M4.9 19.1L6.3 17.7M17.7 6.3L19.1 4.9" })] }), _jsx(Icon, { class: "moon", children: _jsx("path", { d: "M20 14.5A8 8 0 1 1 9.5 4A6.5 6.5 0 0 0 20 14.5Z" }) })] }), _jsx("button", { class: "theme", type: "button", title: text.settings, "aria-label": text.settings, onClick: onSettings, children: _jsxs(Icon, { class: "gear", children: [_jsx("circle", { cx: "12", cy: "12", r: "3" }), _jsx("path", { d: "M9.72 5.38L10.19 2.67L13.81 2.67L14.28 5.38L15.07 5.71L17.31 4.12L19.88 6.69L18.29 8.93L18.62 9.72L21.33 10.19L21.33 13.81L18.62 14.28L18.29 15.07L19.88 17.31L17.31 19.88L15.07 18.29L14.28 18.62L13.81 21.33L10.19 21.33L9.72 18.62L8.93 18.29L6.69 19.88L4.12 17.31L5.71 15.07L5.38 14.28L2.67 13.81L2.67 10.19L5.38 9.72L5.71 8.93L4.12 6.69L6.69 4.12L8.93 5.71Z" })] }) })] }));
9
+ export function Header({ text, verdict, kind, who, actions, onNewGame, standOut = false, onStudy, flipped, onFlip, sound, onSound, onTheme, onSettings, profile, onProfile }) {
10
+ return (_jsxs("header", { class: "top", children: [_jsx(Torretta, { size: 34 }), _jsx("b", { class: "title", children: "Chesspal" }), _jsxs("p", { class: `verdict${kind === undefined ? "" : ` ${kind}`}`, role: "status", children: [verdict === undefined || kind === undefined ? null : _jsx(Icon, { class: "kind", children: kindIcons[kind] }), who === undefined || verdict === undefined ? verdict : (_jsxs(_Fragment, { children: [_jsx("span", { class: "ai-name", children: who }), _jsx("span", { children: verdict })] }))] }), actions === undefined ? null : _jsx(Actions, { actions: actions, text: text }), onNewGame === undefined ? null : (_jsxs("button", { class: standOut ? "command primary" : "command", type: "button", title: text.newGame, onClick: onNewGame, children: [_jsx(Icon, { class: "new", children: _jsx("path", { d: "M12 5V19M5 12H19" }) }), _jsx("span", { children: text.newGame })] })), onStudy === undefined ? null : (_jsxs("button", { class: "command", type: "button", title: text.study.title, onClick: onStudy, children: [_jsxs(Icon, { class: "study", children: [_jsx("circle", { cx: "10.5", cy: "10.5", r: "5.5" }), _jsx("path", { d: "M14.5 14.5L20 20" })] }), _jsx("span", { children: text.study.title })] })), _jsxs("button", { class: "command", type: "button", title: text.flip, "aria-pressed": flipped, onClick: onFlip, children: [_jsx(Icon, { class: "flip", children: _jsx("path", { d: "M7 4V17M7 17L4 14M7 17L10 14M17 20V7M17 7L14 10M17 7L20 10" }) }), _jsx("span", { children: text.flip })] }), _jsx("button", { class: "icon-button", type: "button", title: text.sounds, "aria-label": text.sounds, "aria-pressed": sound, onClick: onSound, children: _jsxs(Icon, { class: "speaker", children: [_jsx("path", { d: "M5 9.5H8.5L13 5.5V18.5L8.5 14.5H5Z" }), sound ? _jsx("path", { d: "M16.5 9A4 4 0 0 1 16.5 15M19 6.5A7.5 7.5 0 0 1 19 17.5" }) : _jsx("path", { d: "M15.5 9L21.5 15M21.5 9L15.5 15" })] }) }), _jsxs("button", { class: "icon-button", type: "button", title: text.theme, "aria-label": text.theme, onClick: onTheme, children: [_jsxs(Icon, { class: "sun", children: [_jsx("circle", { cx: "12", cy: "12", r: "4" }), _jsx("path", { d: "M12 2V4M12 20V22M2 12H4M20 12H22M4.9 4.9L6.3 6.3M17.7 17.7L19.1 19.1M4.9 19.1L6.3 17.7M17.7 6.3L19.1 4.9" })] }), _jsx(Icon, { class: "moon", children: _jsx("path", { d: "M20 14.5A8 8 0 1 1 9.5 4A6.5 6.5 0 0 0 20 14.5Z" }) })] }), _jsx("button", { class: "icon-button", type: "button", title: text.settings, "aria-label": text.settings, onClick: onSettings, children: _jsxs(Icon, { class: "gear", children: [_jsx("circle", { cx: "12", cy: "12", r: "3" }), _jsx("path", { d: "M9.72 5.38L10.19 2.67L13.81 2.67L14.28 5.38L15.07 5.71L17.31 4.12L19.88 6.69L18.29 8.93L18.62 9.72L21.33 10.19L21.33 13.81L18.62 14.28L18.29 15.07L19.88 17.31L17.31 19.88L15.07 18.29L14.28 18.62L13.81 21.33L10.19 21.33L9.72 18.62L8.93 18.29L6.69 19.88L4.12 17.31L5.71 15.07L5.38 14.28L2.67 13.81L2.67 10.19L5.38 9.72L5.71 8.93L4.12 6.69L6.69 4.12L8.93 5.71Z" })] }) }), profile === undefined || onProfile === undefined ? null : (_jsx("button", { class: "icon-button player", type: "button", title: `${profile.name}: ${text.profile.profiles}`, "aria-label": `${profile.name}: ${text.profile.profiles}`, "aria-haspopup": "dialog", onClick: onProfile, children: _jsx(PlayerPicture, { profile: profile, size: 28 }) }))] }));
10
11
  }
11
12
  /** The answer to the AI's draw offer while it stands, else the player's own offer; then the resignation. */
12
13
  function Actions({ actions, text }) {