chesspal 0.3.1 → 0.3.3

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 (185) 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} +4 -4
  14. package/dist/commands/{agents.js → answers/agents.js} +13 -9
  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 +53 -21
  92. package/dist/page/app/served.js +15 -3
  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 +26 -3
  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 +201 -88
  116. package/dist/page/helps/helps.js +50 -31
  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/table/window.js +6 -5
  128. package/dist/page/talk/chat.css +6 -6
  129. package/dist/page/talk/talk.css +17 -67
  130. package/dist/page/talk/talk.js +6 -6
  131. package/dist/page/talk/voice.js +12 -0
  132. package/dist/page/text.js +78 -20
  133. package/dist/page/theme.css +2 -0
  134. package/dist/server/{bell.js → bell/bell.js} +17 -4
  135. package/dist/server/bell/index.d.ts +1 -0
  136. package/dist/server/bell/index.js +2 -0
  137. package/dist/server/{caller.d.ts → mcp/caller.d.ts} +1 -1
  138. package/dist/server/{caller.js → mcp/caller.js} +1 -1
  139. package/dist/server/mcp/index.d.ts +1 -0
  140. package/dist/server/mcp/index.js +2 -0
  141. package/dist/server/{mcp.d.ts → mcp/mcp.d.ts} +1 -1
  142. package/dist/server/{mcp.js → mcp/mcp.js} +9 -9
  143. package/dist/server/page/attach.js +8 -3
  144. package/dist/server/page/game.js +19 -3
  145. package/dist/server/page/http.d.ts +1 -1
  146. package/dist/server/page/index.d.ts +3 -0
  147. package/dist/server/page/index.js +3 -0
  148. package/dist/server/page/page.d.ts +12 -3
  149. package/dist/server/page/page.js +26 -5
  150. package/dist/server/page/table.d.ts +7 -1
  151. package/dist/server/page/table.js +79 -3
  152. package/dist/server/page/update.d.ts +18 -0
  153. package/dist/server/page/update.js +40 -0
  154. package/dist/server/table/browser.d.ts +1 -1
  155. package/dist/server/table/browser.js +1 -1
  156. package/dist/server/table/hosts/chats.d.ts +1 -1
  157. package/dist/server/table/hosts/chats.js +2 -2
  158. package/dist/server/table/hosts/index.d.ts +1 -1
  159. package/dist/server/table/hosts/index.js +1 -1
  160. package/dist/server/table/hosts/play.d.ts +8 -1
  161. package/dist/server/table/hosts/play.js +40 -4
  162. package/dist/server/table/hosts/seat.d.ts +5 -1
  163. package/dist/server/table/hosts/seat.js +1 -1
  164. package/dist/server/table/index.d.ts +3 -2
  165. package/dist/server/table/index.js +3 -2
  166. package/dist/server/table/launch.d.ts +5 -2
  167. package/dist/server/table/launch.js +6 -3
  168. package/dist/server/table/link.d.ts +1 -1
  169. package/dist/server/table/link.js +3 -3
  170. package/dist/server/table/table.d.ts +4 -5
  171. package/dist/server/table/table.js +15 -12
  172. package/dist/server/table/update.d.ts +17 -0
  173. package/dist/server/table/update.js +77 -0
  174. package/dist/sites/index.d.ts +1 -1
  175. package/dist/sites/index.js +2 -2
  176. package/dist/version.d.ts +4 -0
  177. package/dist/version.js +12 -1
  178. package/package.json +1 -1
  179. /package/dist/commands/{speech.d.ts → answers/speech.d.ts} +0 -0
  180. /package/dist/commands/{view.js → answers/view.js} +0 -0
  181. /package/dist/commands/{page-link.js → game/page-link.js} +0 -0
  182. /package/dist/commands/{context.d.ts → registry/context.d.ts} +0 -0
  183. /package/dist/commands/{registry.d.ts → registry/registry.d.ts} +0 -0
  184. /package/dist/commands/{registry.js → registry/registry.js} +0 -0
  185. /package/dist/server/{bell.d.ts → bell/bell.d.ts} +0 -0
@@ -1,7 +1,4 @@
1
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
2
  import { useState } from "preact/hooks";
6
3
  import { Notch, Pushpin, Skewer } from "../board/marks.js";
7
4
  import { BadgeIcon, NoteIcon } from "../game/badges.js";
@@ -11,7 +8,7 @@ import { Figurine } from "../pieces/pieces.js";
11
8
  import { Legend, Marked } from "../talk/talk.js";
12
9
  import { groupOf, pinSquares, riskSquares, squaresOf, strategyOf } from "./view.js";
13
10
  /**
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
11
+ * The helps' card, between the opening and the moves: the coach's words, then the tactics in lines, the strategy in rows and the control map, seen while
15
12
  * 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
13
  * folds it to the title and opens it again (Stefano).
17
14
  */
@@ -44,12 +41,12 @@ export function BareButton({ bare, onBare, text }) {
44
41
  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
42
  }
46
43
  /**
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).
44
+ * The coach's hint, a line that opens on its words and the legend of what it draws (D-88): in the helps' card a tinted row behind a bulb, as the tactics',
45
+ * and on a phone under the board, beside the map, where its drawings are in sight (Zoe).
49
46
  */
50
47
  export function HintLine({ hint, open, onOpen, text }) {
51
48
  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 })] }));
49
+ return (_jsxs("details", { class: "coach-hint", open: open, onToggle: (event) => onOpen(event.currentTarget.open), children: [_jsxs("summary", { children: [_jsxs(RowIcon, { children: [_jsx("path", { d: "M5.5 10.5C4.3 9.7 3.5 8.4 3.5 7a4.5 4.5 0 0 1 9 0c0 1.4-.8 2.7-2 3.5V12h-5Z" }), _jsx("path", { d: "M6.5 14.2h3" })] }), 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
50
  }
54
51
  /** An icon the mouse holds, while what it shows lasts; a finger, or the keyboard, turns it on and off (D-78). */
55
52
  function HoldButton({ on, onHold, title, label, kind, children }) {
@@ -57,11 +54,11 @@ function HoldButton({ on, onHold, title, label, kind, children }) {
57
54
  const mouse = (event) => event instanceof PointerEvent && event.pointerType === "mouse";
58
55
  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
56
  }
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). */
57
+ /** The coach's judgment of the player's move: its badge and the move in a row tinted in the badge's colour, which opens on its note (D-78, D-88). */
61
58
  function Judged({ ply, text }) {
62
59
  const { badge, note } = ply.review ?? {};
63
60
  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 }) })] })) }));
61
+ return (_jsx("div", { class: `move-note judged ${badge ?? ""}`, style: badge === undefined ? undefined : { "--row": `var(--badge-${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
62
  }
66
63
  /**
67
64
  * 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
@@ -146,8 +143,8 @@ function Squares({ squares }) {
146
143
  return _jsx("b", { class: "square-name", children: squares.join(", ") });
147
144
  }
148
145
  /**
149
- * 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
150
- * 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
+ * The strategy (Stefano): the phases with the current one in a pill, the bar of the squares each side controls, then the lines in groups, each a name and a
147
+ * tile of a half per side, and the notes as the tactics'. The player's side comes first, with no colour of good or bad (D-91).
151
148
  */
152
149
  function Strategy({ helps, onPoint, text }) {
153
150
  const words = text.helpsCard;
@@ -166,34 +163,56 @@ function Strategy({ helps, onPoint, text }) {
166
163
  ...(pawnEnding?.squares ?? []).map((square) => words.pawnSquare(square.pawn, square.caught)),
167
164
  pawnEnding?.opposition === undefined ? undefined : words.opposition(pawnEnding.opposition),
168
165
  ].filter((note) => note !== undefined);
169
- 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 }) => {
170
- const shown = lines.filter((line) => groupOf[line.row] === group);
171
- 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) => {
172
- const [name, meaning] = words.rows[line.row];
173
- const open = explained === line.row;
174
- const listed = helps.sides.filter((side) => typeof line[side] === "object");
175
- 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
176
- ? (_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)))] }) }))
177
- : null] }, line.row));
178
- })] }, group));
179
- })] }), notes.length === 0 ? null : (_jsx("ul", { class: "help-rows", children: notes.map((note) => (_jsxs("li", { class: "info", children: [_jsx(InfoIcon, {}), note] }, note))) }))] }));
180
- }
181
- // The groups of the strategy's table in their order, each with its figure.
182
- const groups = [{ group: "pawns", letter: "P" }, { group: "pieces", letter: "N" }, { group: "king", letter: "K" }];
166
+ return (_jsxs("div", { class: "strategy", children: [_jsx(Phases, { current: profile.phase, words: words }), _jsx(ControlBar, { controlled: profile.controlled, sides: helps.sides, you: helps.you, words: words }), groups.map((group) => {
167
+ const shown = lines.filter((line) => groupOf[line.row] === group);
168
+ return shown.length === 0 ? null : (_jsx("ul", { class: "strategy-rows", children: shown.map((line) => {
169
+ const [name, meaning] = words.rows[line.row];
170
+ const open = explained === line.row;
171
+ return (_jsxs("li", { class: open ? "open" : undefined, ...point(squaresIn(line)), children: [_jsxs("div", { class: "row-head", children: [_jsx("button", { type: "button", class: "term", title: meaning, "aria-expanded": open, onClick: () => setExplained(open ? undefined : line.row), children: name }), _jsx(Tile, { line: line, sides: helps.sides, sideName: sideName, words: words })] }), open ? _jsx(Meaning, { line: line, meaning: meaning, sides: helps.sides, sideName: sideName }) : null] }, line.row));
172
+ }) }, group));
173
+ }), notes.length === 0 ? null : (_jsx("ul", { class: "help-rows", children: notes.map((note) => (_jsxs("li", { class: "info", children: [_jsx(InfoIcon, {}), note] }, note))) }))] }));
174
+ }
175
+ // The strategy's groups in their order, apart only by a gap.
176
+ const groups = ["pawns", "pieces", "king"];
177
+ const phases = ["opening", "middlegame", "endgame"];
183
178
  // The fifty-move count shows only when the draw is near: ten moves each, or less.
184
179
  const fiftyMovesShown = 20;
185
180
  /** The squares a side's cells of a strategy's line name, to light on the board; files and islands light none. */
186
181
  function squaresIn(line) {
182
+ if (line.row === "openFiles") {
183
+ return [];
184
+ }
187
185
  return [line.white, line.black].flatMap((cell) => typeof cell === "object" ? cell.filter((item) => /^[a-h][1-8]$/.test(item)) : []);
188
186
  }
189
- /**
190
- * 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.
191
- */
192
- function ControlBar({ controlled, sides, sideName, words }) {
187
+ /** The game's three phases in a row, the current one in a pill. */
188
+ function Phases({ current, words }) {
189
+ return (_jsx("ol", { class: "phases", children: phases.map((phase) => (_jsx("li", { "aria-current": phase === current ? "step" : undefined, children: _jsx("span", { children: words.phases[phase] }) }, phase))) }));
190
+ }
191
+ /** 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; each part's title says it in words. */
192
+ function ControlBar({ controlled, sides, you, words }) {
193
193
  const { white, black, contested } = controlled;
194
194
  const [left, right] = sides;
195
- const parts = [{ side: left, count: controlled[left] }, { side: "both", count: contested }, { side: right, count: controlled[right] }].filter(({ count }) => count > 0);
196
- 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) })] })] }));
195
+ const parts = [{ who: left, count: controlled[left] }, { who: "both", count: contested }, { who: right, count: controlled[right] }];
196
+ return (_jsx("span", { class: "control-bar", role: "img", "aria-label": words.controlled(white, black, contested), children: parts.filter(({ count }) => count > 0).map(({ who, count }) => _jsx("span", { class: who, style: { flexGrow: count }, title: words.controlledBy(who, count, who === you), children: count }, who)) }));
197
+ }
198
+ /** A line's tile: a half for each side, the player's first, in the bar's colours; the open files, both sides', in one half as wide as the two. */
199
+ function Tile({ line, sides, sideName, words }) {
200
+ if (line.row === "openFiles") {
201
+ return (_jsx("span", { class: "row-tile", children: _jsxs("span", { class: "both", title: words.bothSides, children: [_jsx("span", { class: "visually-hidden", children: `${words.bothSides} ` }), _jsx("b", { class: "count", children: line.both.length })] }) }));
202
+ }
203
+ return (_jsx("span", { class: "row-tile", children: sides.map((side) => (_jsxs("span", { class: side, title: sideName(side), children: [_jsx("span", { class: "visually-hidden", children: `${sideName(side)} ` }), _jsx(Cell, { cell: line[side], words: words })] }, side))) }));
204
+ }
205
+ /** A line opened: its meaning, then each listed side's squares or files in chips; the open files with no side's name. */
206
+ function Meaning({ line, meaning, sides, sideName }) {
207
+ if (line.row === "openFiles") {
208
+ return (_jsxs("div", { class: "meaning", children: [meaning, _jsx("span", { class: "listed", children: _jsx(Chips, { items: line.both }) })] }));
209
+ }
210
+ return (_jsxs("div", { class: "meaning", children: [meaning, sides.map((side) => {
211
+ const cell = line[side];
212
+ return typeof cell === "object"
213
+ ? (_jsxs("span", { class: "listed", children: [sideName(side), ": ", _jsx(Chips, { items: cell })] }, side))
214
+ : null;
215
+ })] }));
197
216
  }
198
217
  /** Squares, files or islands in chips; none as a faint dash. */
199
218
  function Chips({ items }) {
@@ -62,13 +62,14 @@ export function riskSquares(risk) {
62
62
  export function pinSquares(pin) {
63
63
  return [pin.piece.square, pin.by.square, pin.to.square];
64
64
  }
65
- /** Which group each line of the strategy's table stands in. */
65
+ /** Which group each line of the strategy stands in. */
66
66
  export const groupOf = {
67
67
  isolated: "pawns",
68
68
  doubled: "pawns",
69
69
  backward: "pawns",
70
70
  passed: "pawns",
71
71
  islands: "pawns",
72
+ openFiles: "pieces",
72
73
  halfOpen: "pieces",
73
74
  heavyOnFiles: "pieces",
74
75
  onSeventh: "pieces",
@@ -79,11 +80,20 @@ export const groupOf = {
79
80
  castled: "king",
80
81
  kingAttacks: "king",
81
82
  };
82
- /** The strategy's table: only the lines where a side has something; the pawn islands only when a side has more than one. */
83
+ /**
84
+ * The strategy's lines: only those where a side has something, the pawn islands only when a side has more than one; the open files, when there are any,
85
+ * first among the pieces' lines.
86
+ */
83
87
  export function strategyOf(profile) {
84
88
  const lines = Object.entries(cells).map(([row, cell]) => ({ row: row, white: cell(profile.white), black: cell(profile.black) }));
85
89
  const split = profile.white.pawns.islands.length > 1 || profile.black.pawns.islands.length > 1;
86
- return lines.filter((line) => line.row === "islands" ? split : something(line.white) || something(line.black));
90
+ const shown = lines.filter((line) => line.row === "islands" ? split : something(line.white) || something(line.black));
91
+ if (profile.openFiles.length === 0) {
92
+ return shown;
93
+ }
94
+ const pawnLines = shown.filter((line) => groupOf[line.row] === "pawns");
95
+ const otherLines = shown.filter((line) => groupOf[line.row] !== "pawns");
96
+ return [...pawnLines, { row: "openFiles", both: profile.openFiles }, ...otherLines];
87
97
  }
88
98
  const cells = {
89
99
  isolated: (side) => side.pawns.isolated,
@@ -1,17 +1,16 @@
1
1
  import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
2
- import { Inline } from "./markdown.js";
3
- /** Where Chesspal lives, and how to support it. */
4
- export const footLinks = {
2
+ /** The foot under the board, with the version of Chesspal that serves the page. */
3
+ export function PageFoot({ version, text }) {
4
+ const words = text.foot;
5
+ return (_jsxs("footer", { class: "page-foot", children: [_jsxs("span", { class: "foot-own", children: ["© 2026 ", _jsx("a", { href: footLinks.author, target: "_blank", rel: "noreferrer", children: "Stefano Caronia" }), version === undefined ? null : _jsx("span", { class: "foot-version", children: ` · Chesspal ${version}` })] }), _jsxs("span", { class: "foot-links", children: [_jsx("a", { href: footLinks.github, target: "_blank", rel: "noreferrer", title: words.onGitHub, "aria-label": words.onGitHub, children: _jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: githubPath }) }) }), _jsx("a", { href: footLinks.npm, target: "_blank", rel: "noreferrer", title: words.onNpm, "aria-label": words.onNpm, children: _jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: npmPath }) }) }), _jsxs("a", { class: "foot-support", href: footLinks.support, target: "_blank", rel: "noreferrer", children: [_jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: heartPath }) }), words.support] })] })] }));
6
+ }
7
+ // Where Chesspal lives, and how to support it.
8
+ const footLinks = {
5
9
  author: "https://stefanocaronia.it",
6
10
  github: "https://github.com/stefanocaronia/chesspal",
7
11
  npm: "https://www.npmjs.com/package/chesspal",
8
12
  support: "https://www.paypal.com/paypalme/stefanocaronia",
9
13
  };
10
- /** The foot of the guide. */
11
- export function GuideFoot({ text }) {
12
- const words = text.guide;
13
- return (_jsxs("footer", { class: "guide-foot", children: [_jsxs("p", { class: "foot-own", children: [_jsxs("span", { children: ["© 2026 ", _jsx("a", { href: footLinks.author, target: "_blank", rel: "noreferrer", children: "Stefano Caronia" }), " · Chesspal"] }), _jsx("a", { href: footLinks.github, target: "_blank", rel: "noreferrer", title: "GitHub", "aria-label": words.onGitHub, children: _jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: githubPath }) }) }), _jsx("a", { href: footLinks.npm, target: "_blank", rel: "noreferrer", title: "npm", "aria-label": words.onNpm, children: _jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: npmPath }) }) }), _jsxs("a", { class: "foot-support", href: footLinks.support, target: "_blank", rel: "noreferrer", children: [_jsx("svg", { class: "icon", viewBox: "0 0 24 24", "aria-hidden": "true", children: _jsx("path", { d: heartPath }) }), words.support] })] }), _jsxs("details", { class: "credits", children: [_jsx("summary", { children: words.credits }), _jsx("ul", { children: words.creditLines.map((line) => (_jsx("li", { children: _jsx(Inline, { text: line, go: () => undefined }) }, line))) })] })] }));
14
- }
15
14
  // The icons of rpg-engine's foot.
16
15
  const githubPath = "M15 22v-4a4.8 4.8 0 0 0-1-3.5c3 0 6-2 6-5.5.08-1.25-.27-2.48-1-3.5.28-1.15.28-2.35 0-3.5 0 0-1 0-3 1.5-2.64-.5-5.36-.5-8 0C6 2 5 2 5 2c-.3 1.15-.3 2.35 0 3.5A5.4 5.4 0 0 0 4 9c0 3.5 3 5.5 6 5.5-.39.49-.68 1.05-.85 1.65S8.93 17.38 9 18v4 M9 18c-4.51 2-5-2-7-2";
17
16
  const npmPath = "M11 21.73a2 2 0 0 0 2 0l7-4A2 2 0 0 0 21 16V8a2 2 0 0 0-1-1.73l-7-4a2 2 0 0 0-2 0l-7 4A2 2 0 0 0 3 8v8a2 2 0 0 0 1 1.73Z M12 22V12 M3.29 7 12 12l8.71-5 M7.5 4.27l9 5.15";
@@ -1,13 +1,15 @@
1
1
  import { jsx as _jsx, Fragment as _Fragment, jsxs as _jsxs } from "preact/jsx-runtime";
2
2
  import { PlayerPicture } from "../avatars/avatars.js";
3
3
  import { Torretta } from "../avatars/torretta.js";
4
+ import { UpdateMark } from "../table/update.js";
4
5
  /**
5
6
  * 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
6
7
  * 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
7
- * turned, the switches of the sounds and the theme, the gear of the settings (D-56) and the book of the guide.
8
+ * turned, on the table the mark of a newer Chesspal (D-108), the switches of the sounds and the theme, the gear of the settings (D-56) and the book of
9
+ * the guide.
8
10
  */
9
- export function Header({ text, verdict, kind, who, actions, onNewGame, standOut = false, onStudy, flipped, onFlip, sound, onSound, onTheme, onSettings, onGuide, 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 sound-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 theme-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" })] }) }), _jsx("button", { class: "icon-button guide-button", type: "button", title: text.guide.title, "aria-label": text.guide.title, "aria-haspopup": "dialog", onClick: onGuide, children: _jsx(Icon, { class: "book", children: _jsx("path", { d: "M12 6.5C10 5 7 4.5 3.5 5V18.5C7 18 10 18.5 12 20C14 18.5 17 18 20.5 18.5V5C17 4.5 14 5 12 6.5ZM12 6.5V20" }) }) }), 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 }) }))] }));
11
+ export function Header({ text, verdict, kind, who, actions, onNewGame, standOut = false, onStudy, flipped, onFlip, sound, onSound, onTheme, onSettings, onGuide, profile, onProfile, update, onUpdate }) {
12
+ 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 : (_jsx("button", { class: "icon-button", type: "button", title: text.study.title, "aria-label": text.study.title, "aria-haspopup": "dialog", 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("button", { class: "icon-button flip-button", type: "button", title: text.flip, "aria-label": 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" }) }) }), update === undefined || onUpdate === undefined ? null : _jsx(UpdateMark, { version: update.version, installing: update.installing, onOpen: onUpdate, text: text }), _jsx("button", { class: "icon-button sound-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 theme-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" })] }) }), _jsx("button", { class: "icon-button guide-button", type: "button", title: text.guide.title, "aria-label": text.guide.title, "aria-haspopup": "dialog", onClick: onGuide, children: _jsx(Icon, { class: "book", children: _jsx("path", { d: "M12 6.5C10 5 7 4.5 3.5 5V18.5C7 18 10 18.5 12 20C14 18.5 17 18 20.5 18.5V5C17 4.5 14 5 12 6.5ZM12 6.5V20" }) }) }), 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 }) }))] }));
11
13
  }
12
14
  /** The answer to the AI's draw offer while it stands, else the player's own offer; then the resignation. */
13
15
  function Actions({ actions, text }) {
@@ -81,8 +81,9 @@
81
81
  display: none;
82
82
  }
83
83
 
84
- /* Beside a narrow board the offer and its answer take the room of the other commands, which come back once it is answered */
85
- .top:has(.answer) .command:not(.answer) {
84
+ /* Beside a narrow board the offer and its answer take the room of the other commands and the flip, which come back once it is answered */
85
+ .top:has(.answer) .command:not(.answer),
86
+ .top:has(.answer) .flip-button {
86
87
  display: none;
87
88
  }
88
89
  }
@@ -114,6 +115,13 @@
114
115
  white-space: nowrap;
115
116
  }
116
117
 
118
+ /* The board turned round: the flip stands pressed, its edge in the soft ink that reads on both themes (Zoe) */
119
+ .top .flip-button[aria-pressed="true"] {
120
+ color: var(--ink);
121
+ border-color: var(--soft);
122
+ background: var(--card);
123
+ }
124
+
117
125
  /* The player's picture, round and borderless, at the end of the header: it opens the menu of the players */
118
126
  .top .player {
119
127
  border-color: transparent;
@@ -152,10 +160,6 @@
152
160
  }
153
161
  }
154
162
 
155
- .top:has(.verdict.idle, .verdict.over) .command:has(.icon.flip) span {
156
- display: none;
157
- }
158
-
159
163
  /* On a phone the player's card is under the board, below the fold: between games the header's new game stands out too, on the first screen */
160
164
  @media (max-width: 840px) {
161
165
  .top:has(.verdict.idle, .verdict.over) .command:has(.icon.new) {
@@ -205,10 +209,54 @@
205
209
  position: relative;
206
210
  /* 12.5cqw is a square, for the lifted piece too */
207
211
  container-type: inline-size;
208
- width: min(calc(100vw - var(--left-now) - var(--right-now) - 2 * var(--gap) - 2 * var(--between)), max(var(--least), calc(100vh - var(--bar) - 3 * var(--between))));
212
+ width: min(calc(100vw - var(--left-now) - var(--right-now) - 2 * var(--gap) - 2 * var(--between)), max(var(--least), calc(100vh - var(--bar) - var(--foot) - 4 * var(--between))));
209
213
  transition: width 200ms cubic-bezier(0.2, 0, 0, 1);
210
214
  }
211
215
 
216
+ /* The page's foot, a thin line under the board and as wide as it, in a row of its own under the others at every width (Stefano); it never widens the board's
217
+ column */
218
+ .page-foot {
219
+ grid-column: board;
220
+ grid-row: -1 / span 1;
221
+ display: flex;
222
+ flex-wrap: wrap;
223
+ align-items: center;
224
+ justify-content: space-between;
225
+ gap: 4px 14px;
226
+ min-height: var(--foot);
227
+ contain: inline-size;
228
+ color: var(--soft);
229
+ font-size: 12px;
230
+ }
231
+
232
+ .page-foot a {
233
+ display: inline-flex;
234
+ align-items: center;
235
+ gap: 5px;
236
+ color: inherit;
237
+ text-decoration: none;
238
+ }
239
+
240
+ .page-foot a:hover,
241
+ .page-foot a:focus-visible {
242
+ color: var(--ink);
243
+ }
244
+
245
+ .foot-links {
246
+ display: inline-flex;
247
+ align-items: center;
248
+ gap: 14px;
249
+ }
250
+
251
+ .page-foot .icon {
252
+ width: 15px;
253
+ height: 15px;
254
+ }
255
+
256
+ .foot-support .icon {
257
+ color: var(--low-time);
258
+ }
259
+
212
260
  .talk {
213
261
  grid-area: talk;
214
262
  display: flex;
@@ -422,6 +470,13 @@
422
470
  box-shadow: 0 0 0 2px var(--page);
423
471
  }
424
472
 
473
+ /* On the Torretta's disc, clear of its battlement, ringed in the card's colour */
474
+ .face.unread::after {
475
+ top: -2px;
476
+ right: -2px;
477
+ box-shadow: 0 0 0 2px var(--card);
478
+ }
479
+
425
480
  .fold-tab button.unread::after {
426
481
  top: -4px;
427
482
  right: auto;
@@ -436,11 +491,11 @@
436
491
  flex: none;
437
492
  }
438
493
 
439
- /* Its trail clear of the battlements' corner, which the halo would bite */
494
+ /* The trail's dot on the disc's edge, clear of the battlement */
440
495
  .thought {
441
496
  position: absolute;
442
- top: -8px;
443
- left: -10px;
497
+ top: -9px;
498
+ left: -12px;
444
499
  width: 24px;
445
500
  height: 20px;
446
501
  overflow: visible;
@@ -614,6 +669,14 @@
614
669
  min-height: 0;
615
670
  }
616
671
 
672
+ /* The cards, then the foot: the moves, the saves and the steps */
673
+ .side-cards,
674
+ .side-foot {
675
+ display: flex;
676
+ flex-direction: column;
677
+ gap: var(--between);
678
+ }
679
+
617
680
  .board-steps,
618
681
  .board-map {
619
682
  display: none;
@@ -664,6 +727,38 @@
664
727
  .side > * {
665
728
  width: var(--right);
666
729
  }
730
+
731
+ /* Beside the board the moves, the saves and the steps take the room the cards leave, by the board's lower edge, and the moves keep four rows at least; a
732
+ column too short for both scrolls the cards under the wheel, with no scrollbar, and the cards never give way themselves (Stefano, Zoe). A halo round
733
+ them keeps their shadows, which the scroll would cut, and they fade at the board's top edge and over the moves */
734
+ .side > .side-cards {
735
+ --halo: 16px;
736
+ flex: 0 1 auto;
737
+ min-height: 0;
738
+ width: calc(var(--right) + 2 * var(--halo));
739
+ margin: calc(-1 * var(--halo)) calc(-1 * var(--halo)) calc(-1 * var(--between));
740
+ padding: var(--halo) var(--halo) var(--between);
741
+ overflow: hidden auto;
742
+ scrollbar-width: none;
743
+ mask-image: linear-gradient(transparent, #000 var(--halo), #000 calc(100% - var(--between)), transparent);
744
+ }
745
+
746
+ .side > .side-cards > * {
747
+ flex-shrink: 0;
748
+ }
749
+
750
+ .side > .side-foot {
751
+ flex: 1 1 auto;
752
+ }
753
+
754
+ .side-foot > .moves-card:not(.folded) {
755
+ min-height: 150px;
756
+ }
757
+
758
+ /* A long game asks no room of its own: its moves scroll in what the cards leave */
759
+ .side-foot .moves {
760
+ contain: size;
761
+ }
667
762
  }
668
763
 
669
764
  /* Where three columns would leave the board small, the game's column goes under the board, always open */
@@ -909,6 +1004,15 @@
909
1004
  box-shadow: 0 8px 24px rgb(0 0 0 / 16%);
910
1005
  }
911
1006
 
1007
+ /* On a phone it would cover the chat's line at the foot: it stands at the top of the column instead (Zoe) */
1008
+ @media (max-width: 840px) {
1009
+ .outdated {
1010
+ position: static;
1011
+ margin: var(--between) var(--gap) 0;
1012
+ box-shadow: none;
1013
+ }
1014
+ }
1015
+
912
1016
  /* On a phone the header's first line holds the Torretta, which stands for the name, and every button down to the player's picture, a little closer; the
913
1017
  sound and the theme stay in the settings */
914
1018
  @media (max-width: 420px) {
@@ -1,10 +1,9 @@
1
- // The table's window the first time it opens (D-104): Chrome and Edge open an application window about 1000 px wide, where the page has two columns. The
2
- // page asks once for 16:9, 1600×900 or what the screen has, centred; after that the browser keeps the size the player gives it (Zoe).
1
+ // The table's window as it opens (D-104): 16:9, 1600×900 or what the screen has, centred, every time the table opens it. Chrome and Edge keep one size for
2
+ // every application window on 127.0.0.1, whatever its port, so the size they would give is any page's, often half a screen (Stefano).
3
3
  // The mark the table puts on the address of the window it opens (server/table/browser.ts).
4
4
  const mark = "opened";
5
- const sizedKey = "chesspal-window-sized";
6
5
  const wanted = { width: 1600, height: 900 };
7
- /** Sizes the window the table opened, once in this browser, and takes the mark off the address. */
6
+ /** Sizes the window the table opened and takes the mark off the address, so that a reload keeps the size the player gave it. */
8
7
  export function fitOpenedWindow() {
9
8
  const address = new URL(location.href);
10
9
  if (!address.searchParams.has(mark)) {
@@ -12,13 +11,11 @@ export function fitOpenedWindow() {
12
11
  }
13
12
  address.searchParams.delete(mark);
14
13
  history.replaceState(history.state, "", address.pathname + address.search + address.hash);
15
- if (sizedBefore()) {
16
- return;
17
- }
18
14
  const place = windowPlace(screen.availWidth, screen.availHeight);
15
+ // Chrome and Edge say where the window's screen starts: on a second screen the window stays there (Zoe).
16
+ const { availLeft = 0, availTop = 0 } = screen;
19
17
  window.resizeTo(place.width, place.height);
20
- window.moveTo(place.left, place.top);
21
- noteSized();
18
+ window.moveTo(availLeft + place.left, availTop + place.top);
22
19
  }
23
20
  /** Where the window goes on a screen with this much room: 1600×900, or less on a smaller screen, centred. */
24
21
  export function windowPlace(roomWidth, roomHeight) {
@@ -26,20 +23,3 @@ export function windowPlace(roomWidth, roomHeight) {
26
23
  const height = Math.min(wanted.height, roomHeight);
27
24
  return { width, height, left: Math.round((roomWidth - width) / 2), top: Math.round((roomHeight - height) / 2) };
28
25
  }
29
- function sizedBefore() {
30
- try {
31
- return localStorage.getItem(sizedKey) !== null;
32
- }
33
- catch {
34
- // A browser that keeps nothing for the page: sizing it at every opening is the lesser harm.
35
- return false;
36
- }
37
- }
38
- function noteSized() {
39
- try {
40
- localStorage.setItem(sizedKey, "1");
41
- }
42
- catch {
43
- // Nothing kept: the next opening sizes the window again.
44
- }
45
- }
package/dist/page/main.js CHANGED
@@ -1,5 +1,5 @@
1
1
  import { jsx as _jsx } from "preact/jsx-runtime";
2
- // The page's start: the window the table opened sized the first time, the settings this browser remembers, shown before the first frame, then the app
2
+ // The page's start: the window the table opened sized as it opens, the settings this browser remembers, shown before the first frame, then the app
3
3
  // drawn into its place.
4
4
  import { render } from "preact";
5
5
  import { App } from "./app/app.js";
@@ -12,8 +12,11 @@ const folder = "/page/pieces/";
12
12
  */
13
13
  export function Piece({ letter, set, small = false }) {
14
14
  const chosen = useContext(ChosenSet);
15
- const white = letter === letter.toUpperCase();
16
- return _jsx(Drawing, { href: `${folder}${set ?? chosen}${small ? "-small" : ""}.svg#${white ? "w" : "b"}${letter.toUpperCase()}`, class: `piece ${white ? "white" : "black"}` });
15
+ return _jsx(Drawing, { href: pieceSymbol(letter, set ?? chosen, small), class: `piece ${letter === letter.toUpperCase() ? "white" : "black"}` });
16
+ }
17
+ /** Where a piece is drawn, by its FEN letter: its symbol in the set's map, or in the map of the set's small pieces. */
18
+ export function pieceSymbol(letter, set, small = false) {
19
+ return `${folder}${set}${small ? "-small" : ""}.svg#${letter === letter.toUpperCase() ? "w" : "b"}${letter.toUpperCase()}`;
17
20
  }
18
21
  /** A piece as the notation shows it (D-50): the chosen set's small piece, White's white and Black's black. */
19
22
  export function Figurine({ letter, side }) {
@@ -11,3 +11,4 @@
11
11
  @import "legend/legend.css";
12
12
  @import "dialogs/dialogs.css";
13
13
  @import "guide/guide.css";
14
+ @import "guide/drawings/drawings.css";
@@ -60,6 +60,75 @@
60
60
  font-size: 13px;
61
61
  }
62
62
 
63
+ /* The header's mark of a newer Chesspal (D-108): its border as the other buttons', not the accent, which reads as focus; inside, a full accent disc with a
64
+ white arrow (Zoe) */
65
+ .top .update-button .icon circle {
66
+ fill: var(--accent);
67
+ stroke: none;
68
+ }
69
+
70
+ .top .update-button .icon path {
71
+ stroke: var(--on-accent);
72
+ }
73
+
74
+ /* One button more: under 360px the header's buttons come a little closer, or the last one goes past the edge */
75
+ @media (max-width: 359px) {
76
+ .top {
77
+ column-gap: 2px;
78
+ }
79
+ }
80
+
81
+ /* Its window, as narrow as the chat's; the spinner beside what npm does; npm's failure with a cross, and each command on its line */
82
+ .dialog:has(> .update-offer) {
83
+ width: min(460px, calc(100vw - 32px));
84
+ }
85
+
86
+ .update-state {
87
+ display: flex;
88
+ align-items: center;
89
+ gap: 8px;
90
+ }
91
+
92
+ .update-failed p {
93
+ margin: 0 0 6px;
94
+ color: var(--ink);
95
+ font-size: 14px;
96
+ }
97
+
98
+ .update-cross {
99
+ color: var(--low-time);
100
+ font-weight: 700;
101
+ }
102
+
103
+ .update-failed code,
104
+ .update-notice code {
105
+ display: block;
106
+ margin-top: 4px;
107
+ font-family: ui-monospace, monospace;
108
+ white-space: nowrap;
109
+ user-select: all;
110
+ }
111
+
112
+ /* On a chat's own page, news and no warning: the card's ground and an accent border, the words beside the × that hides it */
113
+ .outdated.update-notice {
114
+ display: flex;
115
+ align-items: flex-start;
116
+ gap: 8px;
117
+ border-color: var(--accent);
118
+ background: var(--card);
119
+ }
120
+
121
+ .update-notice > span {
122
+ flex: 1;
123
+ min-width: 0;
124
+ }
125
+
126
+ .update-notice .icon-button {
127
+ flex: none;
128
+ width: 30px;
129
+ height: 30px;
130
+ }
131
+
63
132
  /* Empty, the state takes no room */
64
133
  .chat-state:empty {
65
134
  display: none;