chesspal 0.3.5 → 0.4.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/cli/cli.js +17 -4
- package/dist/cli/command-line.js +22 -12
- package/dist/cli/icon.d.ts +2 -0
- package/dist/cli/icon.js +11 -1
- package/dist/cli/prompt-hook.d.ts +17 -0
- package/dist/cli/prompt-hook.js +100 -0
- package/dist/commands/analysis/analyse.d.ts +2 -2
- package/dist/commands/analysis/analyse.js +10 -16
- package/dist/commands/analysis/annotate.d.ts +8 -0
- package/dist/commands/analysis/annotate.js +19 -3
- package/dist/commands/answers/agents.d.ts +17 -7
- package/dist/commands/answers/agents.js +46 -16
- package/dist/commands/answers/clock.js +2 -1
- package/dist/commands/answers/drawings.js +6 -2
- package/dist/commands/answers/index.d.ts +4 -3
- package/dist/commands/answers/index.js +4 -3
- package/dist/commands/answers/lines.d.ts +5 -3
- package/dist/commands/answers/lines.js +17 -9
- package/dist/commands/answers/opening-book.d.ts +13 -7
- package/dist/commands/answers/opening-book.js +34 -38
- package/dist/commands/answers/opening-tags.d.ts +4 -0
- package/dist/commands/answers/opening-tags.js +11 -0
- package/dist/commands/answers/play-move.d.ts +3 -1
- package/dist/commands/answers/play-move.js +9 -6
- package/dist/commands/answers/say.js +2 -1
- package/dist/commands/answers/summary.d.ts +7 -3
- package/dist/commands/answers/summary.js +16 -5
- package/dist/commands/answers/view.d.ts +1 -1
- package/dist/commands/answers/view.js +2 -2
- package/dist/commands/game/ai-move.js +4 -4
- package/dist/commands/game/coach.js +7 -6
- package/dist/commands/game/end.js +2 -2
- package/dist/commands/game/export.d.ts +1 -1
- package/dist/commands/game/index.d.ts +1 -1
- package/dist/commands/game/index.js +1 -1
- package/dist/commands/game/look.d.ts +7 -3
- package/dist/commands/game/look.js +9 -4
- package/dist/commands/game/new.js +42 -12
- package/dist/commands/game/player-move.d.ts +16 -0
- package/dist/commands/game/player-move.js +69 -19
- package/dist/commands/game/ponder.js +7 -4
- package/dist/commands/game/position.d.ts +2 -1
- package/dist/commands/game/position.js +9 -4
- package/dist/commands/game/status.d.ts +4 -2
- package/dist/commands/game/status.js +24 -14
- package/dist/commands/index.d.ts +3 -3
- package/dist/commands/index.js +3 -3
- package/dist/commands/profiles/past-games.d.ts +7 -2
- package/dist/commands/profiles/past-games.js +45 -18
- package/dist/commands/registry/context.d.ts +2 -0
- package/dist/commands/registry/context.js +5 -0
- package/dist/commands/registry/index.d.ts +1 -1
- package/dist/commands/registry/index.js +1 -1
- package/dist/commands/talk/ai-say.d.ts +1 -1
- package/dist/commands/talk/ai-say.js +7 -6
- package/dist/core/game.d.ts +13 -0
- package/dist/core/game.js +29 -3
- package/dist/core/index.d.ts +4 -4
- package/dist/core/index.js +3 -3
- package/dist/core/openings.d.ts +20 -6
- package/dist/core/openings.js +45 -22
- package/dist/core/pgn.d.ts +4 -0
- package/dist/core/pgn.js +35 -6
- package/dist/games/analysis.d.ts +7 -4
- package/dist/games/analysis.js +5 -3
- package/dist/games/archive.d.ts +25 -5
- package/dist/games/archive.js +52 -19
- package/dist/games/chat-start.d.ts +2 -2
- package/dist/games/chat-start.js +6 -1
- package/dist/games/current-game.d.ts +2 -2
- package/dist/games/current-game.js +3 -2
- package/dist/games/index.d.ts +3 -2
- package/dist/games/index.js +3 -2
- package/dist/games/position-shown.d.ts +24 -0
- package/dist/games/position-shown.js +51 -0
- package/dist/games/thoughts.d.ts +12 -4
- package/dist/games/thoughts.js +27 -10
- package/dist/home/index.d.ts +1 -0
- package/dist/home/index.js +1 -0
- package/dist/home/same-path.d.ts +7 -0
- package/dist/home/same-path.js +24 -0
- package/dist/host-files/host-files.d.ts +5 -0
- package/dist/host-files/host-files.js +21 -7
- package/dist/host-files/index.d.ts +1 -1
- package/dist/host-files/index.js +1 -1
- package/dist/page/app/anchor.js +15 -11
- package/dist/page/app/app.js +49 -14
- package/dist/page/app/served.js +30 -38
- package/dist/page/app/watch.js +97 -0
- package/dist/page/avatars/torretta.css +108 -0
- package/dist/page/base.css +1 -1
- package/dist/page/board/board.css +11 -8
- package/dist/page/board/marks.js +2 -2
- package/dist/page/board/pointer.js +2 -2
- package/dist/page/dialogs/analyses.js +5 -3
- package/dist/page/dialogs/dialogs.css +3 -1
- package/dist/page/dialogs/rows.js +72 -0
- package/dist/page/dialogs/site-games.js +3 -1
- package/dist/page/game/column.js +10 -8
- package/dist/page/game/game.css +137 -16
- package/dist/page/game/move-list.js +7 -5
- package/dist/page/game/plies.js +4 -0
- package/dist/page/game/review-progress.js +11 -0
- package/dist/page/guide/drawings/drawings.css +0 -1
- package/dist/page/guide/en.md +2 -2
- package/dist/page/guide/it.md +2 -2
- package/dist/page/helps/helps.css +5 -5
- package/dist/page/helps/helps.js +3 -181
- package/dist/page/helps/rows.js +16 -0
- package/dist/page/helps/strategy.js +105 -0
- package/dist/page/helps/tactics.js +69 -0
- package/dist/page/layout/header.js +49 -6
- package/dist/page/layout/icon.ico +0 -0
- package/dist/page/layout/layout.css +49 -17
- package/dist/page/layout/window.js +45 -6
- package/dist/page/lead/lead.js +41 -4
- package/dist/page/legend/legend.css +4 -16
- package/dist/page/legend/legend.js +28 -15
- package/dist/page/profile/settings.js +1 -0
- package/dist/page/sounds/sounds.js +1 -1
- package/dist/page/style.css +1 -0
- package/dist/page/table/action.js +42 -0
- package/dist/page/table/table.css +57 -39
- package/dist/page/table/window.js +67 -67
- package/dist/page/talk/talk.css +52 -118
- package/dist/page/talk/talk.js +65 -20
- package/dist/page/talk/voice.js +92 -21
- package/dist/page/text.js +34 -8
- package/dist/page/theme.css +13 -0
- package/dist/profiles/profiles.js +5 -1
- package/dist/server/bell/bell.d.ts +24 -29
- package/dist/server/bell/bell.js +79 -133
- package/dist/server/bell/index.d.ts +1 -1
- package/dist/server/bell/index.js +2 -2
- package/dist/server/mcp/mcp.js +49 -7
- package/dist/server/page/analyses.js +15 -4
- package/dist/server/page/chat.d.ts +5 -1
- package/dist/server/page/chat.js +5 -1
- package/dist/server/page/files.js +3 -1
- package/dist/server/page/game.d.ts +5 -0
- package/dist/server/page/game.js +95 -15
- package/dist/server/page/http.d.ts +1 -1
- package/dist/server/page/page.d.ts +14 -9
- package/dist/server/page/page.js +111 -26
- package/dist/server/page/table.d.ts +40 -5
- package/dist/server/page/table.js +65 -25
- package/dist/server/page/update.d.ts +1 -1
- package/dist/server/page/update.js +4 -4
- package/dist/server/page/windows.d.ts +22 -0
- package/dist/server/page/windows.js +45 -0
- package/dist/server/ports/index.d.ts +1 -0
- package/dist/server/ports/index.js +2 -0
- package/dist/server/ports/ports.d.ts +78 -0
- package/dist/server/ports/ports.js +222 -0
- package/dist/server/table/browser.d.ts +22 -6
- package/dist/server/table/browser.js +98 -20
- package/dist/server/table/hosts/chats.d.ts +5 -0
- package/dist/server/table/hosts/chats.js +11 -4
- package/dist/server/table/hosts/hooks.d.ts +6 -0
- package/dist/server/table/hosts/hooks.js +38 -0
- package/dist/server/table/hosts/hosts.d.ts +13 -5
- package/dist/server/table/hosts/hosts.js +16 -10
- package/dist/server/table/hosts/index.d.ts +1 -0
- package/dist/server/table/hosts/index.js +1 -0
- package/dist/server/table/hosts/play.d.ts +7 -3
- package/dist/server/table/hosts/play.js +63 -9
- package/dist/server/table/icon/index.d.ts +2 -0
- package/dist/server/table/icon/index.js +16 -2
- package/dist/server/table/index.d.ts +2 -2
- package/dist/server/table/index.js +1 -1
- package/dist/server/table/launch.d.ts +5 -4
- package/dist/server/table/launch.js +18 -5
- package/dist/server/table/link.js +4 -1
- package/dist/server/table/table.d.ts +7 -0
- package/dist/server/table/table.js +40 -12
- package/dist/sites/index.d.ts +1 -1
- package/dist/sites/index.js +1 -1
- package/dist/sites/{chesscom.d.ts → ratings/chesscom.d.ts} +2 -2
- package/dist/sites/{chesscom.js → ratings/chesscom.js} +1 -1
- package/dist/sites/{choice.d.ts → ratings/choice.d.ts} +1 -1
- package/dist/sites/{choice.js → ratings/choice.js} +1 -1
- package/dist/sites/ratings/index.d.ts +1 -0
- package/dist/sites/ratings/index.js +2 -0
- package/dist/sites/{lichess.d.ts → ratings/lichess.d.ts} +2 -2
- package/dist/sites/{lichess.js → ratings/lichess.js} +1 -1
- package/dist/sites/{sites.d.ts → ratings/ratings.d.ts} +2 -2
- package/dist/sites/{sites.js → ratings/ratings.js} +1 -1
- package/instructions/opponent.md +1 -1
- package/instructions/start.md +1 -1
- package/package.json +1 -1
|
@@ -272,7 +272,7 @@
|
|
|
272
272
|
.app.talk-folded .talk {
|
|
273
273
|
visibility: hidden;
|
|
274
274
|
opacity: 0;
|
|
275
|
-
transition: opacity 120ms 80ms, visibility
|
|
275
|
+
transition: opacity 120ms 80ms, visibility 0ms 200ms;
|
|
276
276
|
}
|
|
277
277
|
|
|
278
278
|
/* A column's tab, a drawer's handle at the top: open, on the outer edge of the column's first card; folded, the column slides under the board and the
|
|
@@ -381,7 +381,7 @@
|
|
|
381
381
|
.app.talk-folded .faces {
|
|
382
382
|
display: flex;
|
|
383
383
|
visibility: hidden;
|
|
384
|
-
transition: visibility
|
|
384
|
+
transition: visibility 0ms 200ms;
|
|
385
385
|
}
|
|
386
386
|
|
|
387
387
|
.app.flipped .faces {
|
|
@@ -536,16 +536,16 @@
|
|
|
536
536
|
|
|
537
537
|
.thought.move .dot {
|
|
538
538
|
fill: var(--on-accent);
|
|
539
|
-
animation: thought-dot
|
|
539
|
+
animation: thought-dot 1200ms ease-in-out infinite;
|
|
540
540
|
}
|
|
541
541
|
|
|
542
542
|
/* In order from the left: nth-of-type would count the trail, a circle too */
|
|
543
543
|
.thought.move .dot + .dot {
|
|
544
|
-
animation-delay:
|
|
544
|
+
animation-delay: 200ms;
|
|
545
545
|
}
|
|
546
546
|
|
|
547
547
|
.thought.move .dot + .dot + .dot {
|
|
548
|
-
animation-delay:
|
|
548
|
+
animation-delay: 400ms;
|
|
549
549
|
}
|
|
550
550
|
|
|
551
551
|
.thought.ponder .cloud {
|
|
@@ -556,7 +556,7 @@
|
|
|
556
556
|
|
|
557
557
|
.thought.ponder .dot {
|
|
558
558
|
fill: var(--soft);
|
|
559
|
-
animation: thought-breath
|
|
559
|
+
animation: thought-breath 3000ms ease-in-out infinite;
|
|
560
560
|
}
|
|
561
561
|
|
|
562
562
|
@keyframes thought-in {
|
|
@@ -610,7 +610,7 @@
|
|
|
610
610
|
|
|
611
611
|
/* Beside the board the talk is a row a side, each half the column, or what its card, note and box need when that is more: only the words give way, and
|
|
612
612
|
never spill over the other side (Stefano, Zoe). Turned round, the player's row comes first. In analysis the AI's note keeps its height while the thread
|
|
613
|
-
gives way, and scrolls by itself
|
|
613
|
+
gives way, and never scrolls by itself */
|
|
614
614
|
@media (min-width: 841px) {
|
|
615
615
|
.talk {
|
|
616
616
|
display: grid;
|
|
@@ -643,19 +643,36 @@
|
|
|
643
643
|
contain: size;
|
|
644
644
|
}
|
|
645
645
|
|
|
646
|
+
/* The note is always whole: when the column cannot hold it with the box and the cards, the column scrolls under the wheel or the finger, with no
|
|
647
|
+
scrollbar, fading at its edges as the game's column does; a halo keeps the cards' shadows. Positioned, so the words hidden for screen readers
|
|
648
|
+
scroll with it instead of stretching the page */
|
|
646
649
|
.talk.studied {
|
|
647
|
-
|
|
648
|
-
|
|
650
|
+
--halo: 16px;
|
|
651
|
+
position: relative;
|
|
652
|
+
grid-template-rows: max-content minmax(min-content, 1fr);
|
|
653
|
+
margin: calc(-1 * var(--halo));
|
|
654
|
+
padding: var(--halo);
|
|
655
|
+
overflow: hidden auto;
|
|
656
|
+
scrollbar-width: none;
|
|
657
|
+
mask-image: linear-gradient(transparent, #000 var(--halo), #000 calc(100% - var(--halo)), transparent);
|
|
649
658
|
}
|
|
650
659
|
|
|
660
|
+
/* Turned round the note is at the foot: a column laid from its foot opens scrolled to it */
|
|
651
661
|
.flipped .talk.studied {
|
|
652
|
-
|
|
662
|
+
display: flex;
|
|
663
|
+
}
|
|
664
|
+
|
|
665
|
+
.flipped .talk.studied > .you-block {
|
|
666
|
+
flex: 1 1 0;
|
|
667
|
+
min-height: auto;
|
|
668
|
+
}
|
|
669
|
+
|
|
670
|
+
.flipped .talk.studied > .ai-block {
|
|
671
|
+
flex: none;
|
|
653
672
|
}
|
|
654
673
|
|
|
655
674
|
.talk.studied > .ai-block > .ai-note {
|
|
656
|
-
flex:
|
|
657
|
-
min-height: 0;
|
|
658
|
-
overflow-y: auto;
|
|
675
|
+
flex: none;
|
|
659
676
|
}
|
|
660
677
|
}
|
|
661
678
|
|
|
@@ -684,7 +701,7 @@
|
|
|
684
701
|
|
|
685
702
|
.app.folded .side {
|
|
686
703
|
visibility: hidden;
|
|
687
|
-
transition: visibility
|
|
704
|
+
transition: visibility 0ms 200ms;
|
|
688
705
|
}
|
|
689
706
|
|
|
690
707
|
/* With no game, the game's column opens with this note, as tall as the game's card, so the column's tab sits in its middle */
|
|
@@ -975,12 +992,14 @@
|
|
|
975
992
|
margin: 0;
|
|
976
993
|
}
|
|
977
994
|
|
|
995
|
+
/* The steps, and under them the chart of the game */
|
|
978
996
|
.board-steps {
|
|
979
|
-
display:
|
|
997
|
+
display: grid;
|
|
998
|
+
gap: var(--between);
|
|
980
999
|
grid-area: steps;
|
|
981
1000
|
}
|
|
982
1001
|
|
|
983
|
-
.app:has(.talk.studied) .side .steps {
|
|
1002
|
+
.app:has(.talk.studied) .side :is(.steps, .lead-card) {
|
|
984
1003
|
display: none;
|
|
985
1004
|
}
|
|
986
1005
|
}
|
|
@@ -1001,7 +1020,7 @@
|
|
|
1001
1020
|
color: var(--ink);
|
|
1002
1021
|
font-size: 14px;
|
|
1003
1022
|
line-height: 1.45;
|
|
1004
|
-
box-shadow: 0 8px 24px
|
|
1023
|
+
box-shadow: 0 8px 24px rgba(0, 0, 0, 0.16);
|
|
1005
1024
|
}
|
|
1006
1025
|
|
|
1007
1026
|
/* On a phone it would cover the chat's line at the foot: it stands at the top of the column instead (Zoe) */
|
|
@@ -1041,3 +1060,16 @@
|
|
|
1041
1060
|
justify-content: center;
|
|
1042
1061
|
}
|
|
1043
1062
|
}
|
|
1063
|
+
|
|
1064
|
+
/* On the narrowest phones a table's header holds one more button, the newer Chesspal's: the buttons draw a little closer and smaller, the picture stays on the line */
|
|
1065
|
+
@media (max-width: 359px) {
|
|
1066
|
+
.top {
|
|
1067
|
+
column-gap: 2px;
|
|
1068
|
+
}
|
|
1069
|
+
|
|
1070
|
+
.top .command,
|
|
1071
|
+
.top .icon-button {
|
|
1072
|
+
width: 30px;
|
|
1073
|
+
height: 30px;
|
|
1074
|
+
}
|
|
1075
|
+
}
|
|
@@ -1,19 +1,22 @@
|
|
|
1
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
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
|
+
// One window of the table alone: the newest it opened stays, the older ones close themselves (server/page/windows.ts).
|
|
4
|
+
// The mark the table puts on the address of the window it opens, with its opening (server/table/browser.ts), kept by the window for its reloads.
|
|
4
5
|
const mark = "opened";
|
|
6
|
+
const kept = "chesspal-opening";
|
|
5
7
|
const wanted = { width: 1600, height: 900 };
|
|
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
|
+
/** Sizes the window the table opened and takes the mark off the address, so that a reload keeps the size the player gave it; the window keeps its opening. */
|
|
7
9
|
export function fitOpenedWindow() {
|
|
8
|
-
const address = new URL(location.href);
|
|
10
|
+
const address = new URL(window.location.href);
|
|
9
11
|
if (!address.searchParams.has(mark)) {
|
|
10
12
|
return;
|
|
11
13
|
}
|
|
14
|
+
keepOpening(Number(address.searchParams.get(mark)));
|
|
12
15
|
address.searchParams.delete(mark);
|
|
13
|
-
history.replaceState(history.state, "", address.pathname + address.search + address.hash);
|
|
14
|
-
const place = windowPlace(screen.availWidth, screen.availHeight);
|
|
16
|
+
window.history.replaceState(window.history.state, "", address.pathname + address.search + address.hash);
|
|
17
|
+
const place = windowPlace(window.screen.availWidth, window.screen.availHeight);
|
|
15
18
|
// 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
|
+
const { availLeft = 0, availTop = 0 } = window.screen;
|
|
17
20
|
window.resizeTo(place.width, place.height);
|
|
18
21
|
window.moveTo(availLeft + place.left, availTop + place.top);
|
|
19
22
|
}
|
|
@@ -23,3 +26,39 @@ export function windowPlace(roomWidth, roomHeight) {
|
|
|
23
26
|
const height = Math.min(wanted.height, roomHeight);
|
|
24
27
|
return { width, height, left: Math.round((roomWidth - width) / 2), top: Math.round((roomHeight - height) / 2) };
|
|
25
28
|
}
|
|
29
|
+
/** The events of the server, for this window: with its opening when the table opened it, so that the table tells the older windows to close. */
|
|
30
|
+
export function eventsAddress() {
|
|
31
|
+
const opening = openingOf();
|
|
32
|
+
return opening === undefined ? "/api/events" : `/api/events?window=${opening}`;
|
|
33
|
+
}
|
|
34
|
+
/** Whether an event of the server says the table opened a newer window than this one, which then closes; an event of anything else is false. */
|
|
35
|
+
export function closesForNewer(data) {
|
|
36
|
+
const newest = /^window (\d+)$/.exec(data);
|
|
37
|
+
if (newest === null) {
|
|
38
|
+
return false;
|
|
39
|
+
}
|
|
40
|
+
const opening = openingOf();
|
|
41
|
+
if (opening !== undefined && Number(newest[1]) > opening) {
|
|
42
|
+
window.close();
|
|
43
|
+
}
|
|
44
|
+
return true;
|
|
45
|
+
}
|
|
46
|
+
function keepOpening(opening) {
|
|
47
|
+
try {
|
|
48
|
+
if (Number.isSafeInteger(opening) && opening > 0) {
|
|
49
|
+
window.sessionStorage.setItem(kept, String(opening));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
catch {
|
|
53
|
+
// A browser that keeps nothing for the window: it never closes for a newer one.
|
|
54
|
+
}
|
|
55
|
+
}
|
|
56
|
+
function openingOf() {
|
|
57
|
+
try {
|
|
58
|
+
const opening = Number(window.sessionStorage.getItem(kept));
|
|
59
|
+
return Number.isSafeInteger(opening) && opening > 0 ? opening : undefined;
|
|
60
|
+
}
|
|
61
|
+
catch {
|
|
62
|
+
return undefined;
|
|
63
|
+
}
|
|
64
|
+
}
|
package/dist/page/lead/lead.js
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "preact/jsx-runtime";
|
|
2
2
|
// Who is ahead as the AI estimates it (D-90): a two-colour bar in the game's card, with the helps on and in analysis, and in analysis a chart of the game,
|
|
3
3
|
// as chess.com draws them, that goes to the move clicked. The page draws the AI's figures, never computes them (D-6, D-19).
|
|
4
|
+
import { useRef, useState } from "preact/hooks";
|
|
4
5
|
import { FoldButton, useCardFold } from "../game/card-fold.js";
|
|
5
6
|
import { chartBadges, sideAhead } from "./view.js";
|
|
6
7
|
/**
|
|
@@ -15,10 +16,14 @@ export function LeadBar({ standing, shown, text }) {
|
|
|
15
16
|
}
|
|
16
17
|
/**
|
|
17
18
|
* The chart of the game in analysis, in a card that folds: White's share as the white area, the moves marked by a badge as dots, kept whole inside its
|
|
18
|
-
* edges, the move shown as a line.
|
|
19
|
+
* edges, the move shown as a line, and under the mouse a fainter line on the move a click would show. A finger or the mouse drawn along it runs through the
|
|
20
|
+
* game.
|
|
19
21
|
*/
|
|
20
22
|
export function LeadChart({ plies, standings, shown, onShow, text }) {
|
|
21
23
|
const [folded, fold] = useCardFold("chart");
|
|
24
|
+
const [pointed, setPointed] = useState(undefined);
|
|
25
|
+
// A finger or the pressed mouse on the chart: where it went down, whether it runs through the game yet, and the move it showed last.
|
|
26
|
+
const pressed = useRef(undefined);
|
|
22
27
|
const width = Math.max(plies.length, 1);
|
|
23
28
|
const heightOf = (share) => 1 - share;
|
|
24
29
|
const first = standings[0];
|
|
@@ -34,11 +39,43 @@ export function LeadChart({ plies, standings, shown, onShow, text }) {
|
|
|
34
39
|
const at = standings.find((standing) => standing.plies === index + 1);
|
|
35
40
|
return badge === undefined || at === undefined || !chartBadges.has(badge) ? [] : [{ badge, at }];
|
|
36
41
|
});
|
|
37
|
-
|
|
42
|
+
// The move at the pointer: how many moves the board would show.
|
|
43
|
+
const plyAt = (event) => {
|
|
38
44
|
const box = event.currentTarget.getBoundingClientRect();
|
|
39
|
-
|
|
45
|
+
return Math.min(width, Math.max(0, Math.round(((event.clientX - box.left) / box.width) * width)));
|
|
40
46
|
};
|
|
41
|
-
|
|
47
|
+
const showAt = (at) => {
|
|
48
|
+
if (pressed.current !== undefined && pressed.current.showed !== at) {
|
|
49
|
+
pressed.current.showed = at;
|
|
50
|
+
onShow(at);
|
|
51
|
+
}
|
|
52
|
+
};
|
|
53
|
+
// The mouse shows its move as it goes down; a finger once it runs across, as one that runs up or down scrolls the page, or as it lifts, a tap.
|
|
54
|
+
const press = (event) => {
|
|
55
|
+
event.currentTarget.setPointerCapture(event.pointerId);
|
|
56
|
+
pressed.current = { x: event.clientX, y: event.clientY, along: event.pointerType === "mouse" };
|
|
57
|
+
setPointed(undefined);
|
|
58
|
+
if (pressed.current.along) {
|
|
59
|
+
showAt(plyAt(event));
|
|
60
|
+
}
|
|
61
|
+
};
|
|
62
|
+
const point = (event) => {
|
|
63
|
+
const press = pressed.current;
|
|
64
|
+
if (press === undefined) {
|
|
65
|
+
setPointed(event.pointerType === "mouse" ? plyAt(event) : undefined);
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
const across = Math.abs(event.clientX - press.x);
|
|
69
|
+
press.along ||= across > 8 && across > Math.abs(event.clientY - press.y);
|
|
70
|
+
if (press.along) {
|
|
71
|
+
showAt(plyAt(event));
|
|
72
|
+
}
|
|
73
|
+
};
|
|
74
|
+
const lift = (event) => {
|
|
75
|
+
showAt(plyAt(event));
|
|
76
|
+
pressed.current = undefined;
|
|
77
|
+
};
|
|
78
|
+
return (_jsxs("section", { class: `card lead-card${folded ? " folded" : ""}`, children: [_jsx("h3", { class: "card-title", children: _jsxs(FoldButton, { folded: folded, onFold: fold, text: text, children: [_jsx(ChartIcon, {}), text.lead.chart] }) }), folded ? null : (_jsxs("div", { class: "lead-chart", role: "img", "aria-label": text.lead.chartHint, title: text.lead.chartHint, onPointerDown: press, onPointerMove: point, onPointerUp: lift, onPointerCancel: () => void (pressed.current = undefined), onPointerLeave: () => setPointed(undefined), children: [_jsxs("svg", { viewBox: `0 0 ${width} 2`, preserveAspectRatio: "none", "aria-hidden": "true", children: [_jsx("path", { class: "white-area", d: area }), last.plies < width ? _jsx("rect", { class: "pending", x: last.plies, y: "0", width: width - last.plies, height: "2" }) : null, _jsx("path", { class: "even", d: `M0 1 L${width} 1`, "vector-effect": "non-scaling-stroke" })] }), pointed === undefined || pointed === shown ? null : _jsx("span", { class: "shown ghost", style: { left: inside((pointed / width) * 100, 2) }, "aria-hidden": "true" }), _jsx("span", { class: "shown", style: { left: inside((shown / width) * 100, 2) }, "aria-hidden": "true" }), dots.map(({ badge, at }) => (_jsx("span", { class: `dot ${badge}`, style: { left: inside((at.plies / width) * 100, 6), top: inside((heightOf(at.share) / 2) * 100, 6) }, "aria-hidden": "true" }, at.plies)))] }))] }));
|
|
42
79
|
}
|
|
43
80
|
/** A place along the chart, in percent, kept whole inside its edges by so many pixels. */
|
|
44
81
|
function inside(percent, pixels) {
|
|
@@ -21,10 +21,11 @@
|
|
|
21
21
|
border-color: var(--accent);
|
|
22
22
|
}
|
|
23
23
|
|
|
24
|
-
/* Over the
|
|
24
|
+
/* Over the whole page, in the browser's top layer as a popover, the board in sight beside it; placed by its button by the page */
|
|
25
25
|
.legend-panel {
|
|
26
26
|
position: fixed;
|
|
27
|
-
|
|
27
|
+
inset: auto;
|
|
28
|
+
margin: 0;
|
|
28
29
|
width: 300px;
|
|
29
30
|
max-width: calc(100vw - 32px);
|
|
30
31
|
overflow-y: auto;
|
|
@@ -171,7 +172,7 @@
|
|
|
171
172
|
font-size: 12px;
|
|
172
173
|
}
|
|
173
174
|
|
|
174
|
-
/* On a phone the ? is the last on the right under the board, and the legend opens below the board's row, as wide as it, never over the board */
|
|
175
|
+
/* On a phone the ? is the last on the right under the board, and the legend opens below the board's row, as wide as it, never over the board (legend.tsx) */
|
|
175
176
|
.board-map .marks-legend {
|
|
176
177
|
position: static;
|
|
177
178
|
float: right;
|
|
@@ -183,20 +184,7 @@
|
|
|
183
184
|
margin-left: 0;
|
|
184
185
|
}
|
|
185
186
|
|
|
186
|
-
.legend-panel.under {
|
|
187
|
-
position: absolute;
|
|
188
|
-
top: calc(100% + 6px);
|
|
189
|
-
right: 0;
|
|
190
|
-
left: 0;
|
|
191
|
-
width: auto;
|
|
192
|
-
max-width: none;
|
|
193
|
-
}
|
|
194
|
-
|
|
195
187
|
@media (max-width: 840px) {
|
|
196
|
-
.board-map {
|
|
197
|
-
position: relative;
|
|
198
|
-
}
|
|
199
|
-
|
|
200
188
|
.helps-card .marks-legend {
|
|
201
189
|
display: none;
|
|
202
190
|
}
|
|
@@ -4,19 +4,25 @@ import { Badge, Notch, Pushpin, Skewer } from "../board/marks.js";
|
|
|
4
4
|
import { BadgeIcon } from "../game/badges.js";
|
|
5
5
|
/**
|
|
6
6
|
* The ? beside the map's button, and the legend it opens: a click, the ? key, Esc or a click elsewhere closes it, and puts out the squares it lit; while
|
|
7
|
-
* open it follows the board. The pointer on a line lights the squares that bear its mark, as a square in the AI's words.
|
|
8
|
-
* `under` opens it below the board's row, never over the board.
|
|
7
|
+
* open it follows the board. The pointer on a line lights the squares that bear its mark, as a square in the AI's words. It stands over the whole page, in
|
|
8
|
+
* the browser's top layer, so that no card clips it; under the board on a phone, `under` opens it below the board's row, never over the board.
|
|
9
9
|
*/
|
|
10
10
|
export function MarksLegend({ rows, open, onOpen, drawn, onPoint, under = false, text }) {
|
|
11
11
|
const button = useRef(null);
|
|
12
|
+
const section = useRef(null);
|
|
12
13
|
const panel = useId();
|
|
13
14
|
useClosing(open, () => onOpen(false));
|
|
14
15
|
usePutOut(open, onPoint);
|
|
15
|
-
const place = usePlace(button, open
|
|
16
|
+
const place = usePlace(button, open, under);
|
|
16
17
|
const words = text.legend;
|
|
17
|
-
const shown = open &&
|
|
18
|
+
const shown = open && place !== undefined;
|
|
19
|
+
useLayoutEffect(() => {
|
|
20
|
+
if (shown) {
|
|
21
|
+
section.current?.showPopover();
|
|
22
|
+
}
|
|
23
|
+
}, [shown]);
|
|
18
24
|
return (_jsxs("span", { class: "marks-legend", children: [_jsx("button", { ref: button, type: "button", class: "hold-button legend-button", "aria-expanded": open, "aria-controls": shown ? panel : undefined, "aria-label": words.title, title: words.hint, onClick: () => onOpen(!open), children: _jsxs("svg", { viewBox: "0 0 16 16", "aria-hidden": "true", children: [_jsx("circle", { cx: "8", cy: "8", r: "6.5" }), _jsx("path", { d: "M6.1 6.3a1.95 1.95 0 1 1 2.7 1.8c-.5.2-.8.6-.8 1.1v.5M8 11.4v.1" })] }) }), shown
|
|
19
|
-
? (_jsxs("section", { id: panel, class:
|
|
25
|
+
? (_jsxs("section", { ref: section, id: panel, class: "legend-panel", popover: "manual", "aria-label": words.title, style: place, children: [_jsx("p", { class: "legend-title", children: words.title }), rows.length === 0 ? _jsx("p", { class: "legend-none", children: words.none }) : (_jsx("ul", { children: rows.map((row) => (_jsxs("li", { onPointerEnter: () => onPoint(row.squares), onPointerLeave: (event) => event.pointerType === "mouse" && onPoint([]), children: [_jsx("span", { class: "legend-mark", children: _jsx(Mark, { row: row }) }), _jsx(Name, { row: row, drawn: drawn, text: text })] }, keyOf(row)))) }))] }))
|
|
20
26
|
: null] }));
|
|
21
27
|
}
|
|
22
28
|
/** A mark as the board draws it, on a square of the board's colour where the board draws it on one. */
|
|
@@ -110,10 +116,10 @@ function useClosing(open, close) {
|
|
|
110
116
|
}
|
|
111
117
|
/**
|
|
112
118
|
* Where the legend opens: under its button, or over it when the window has more room there, its right edge on the button's and no wider than the card it
|
|
113
|
-
* sits in, so that it leaves the board in sight;
|
|
114
|
-
* scrolls.
|
|
119
|
+
* sits in, so that it leaves the board in sight, inside the window; `under`, below the row under the board and as wide as it. None while the button is not
|
|
120
|
+
* shown, as the one of the layout the window does not use. It follows the button as the window changes or scrolls, kept inside the window.
|
|
115
121
|
*/
|
|
116
|
-
function usePlace(button, open) {
|
|
122
|
+
function usePlace(button, open, under) {
|
|
117
123
|
const [place, setPlace] = useState(undefined);
|
|
118
124
|
useLayoutEffect(() => {
|
|
119
125
|
if (!open) {
|
|
@@ -121,16 +127,21 @@ function usePlace(button, open) {
|
|
|
121
127
|
return;
|
|
122
128
|
}
|
|
123
129
|
const follow = () => {
|
|
124
|
-
const
|
|
130
|
+
const shown = button.current !== null && button.current.getClientRects().length > 0;
|
|
131
|
+
const box = (under ? button.current?.closest(".board-map") ?? button.current : button.current)?.getBoundingClientRect();
|
|
125
132
|
const card = button.current?.closest(".card")?.getBoundingClientRect();
|
|
126
|
-
if (box
|
|
133
|
+
if (!shown || box === undefined) {
|
|
134
|
+
setPlace(undefined);
|
|
135
|
+
}
|
|
136
|
+
else {
|
|
127
137
|
const { clientWidth: width, clientHeight: height } = document.documentElement;
|
|
128
138
|
const below = height - box.bottom - panelGap - edge;
|
|
129
139
|
const above = box.top - panelGap - edge;
|
|
130
|
-
const
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
140
|
+
const across = under ? box.width : Math.min(panelWidth, box.right - (card?.left ?? box.right - panelWidth));
|
|
141
|
+
const side = { right: `${Math.max(edge, width - box.right)}px`, width: `${Math.min(across, width - 2 * edge)}px` };
|
|
142
|
+
setPlace(under || below >= roomEnough || below >= above
|
|
143
|
+
? { top: `${Math.max(edge, box.bottom + panelGap)}px`, ...side, maxHeight: `${under ? Math.max(roomLeast, below) : below}px` }
|
|
144
|
+
: { bottom: `${Math.max(edge, height - box.top + panelGap)}px`, ...side, maxHeight: `${above}px` });
|
|
134
145
|
}
|
|
135
146
|
};
|
|
136
147
|
follow();
|
|
@@ -140,7 +151,7 @@ function usePlace(button, open) {
|
|
|
140
151
|
window.removeEventListener("resize", follow);
|
|
141
152
|
window.removeEventListener("scroll", follow, true);
|
|
142
153
|
};
|
|
143
|
-
}, [open]);
|
|
154
|
+
}, [open, under]);
|
|
144
155
|
return place;
|
|
145
156
|
}
|
|
146
157
|
// How far the legend stands from its button, and from the window's edges; its width at most; the room under the button that is enough for it.
|
|
@@ -148,6 +159,8 @@ const panelGap = 6;
|
|
|
148
159
|
const edge = 16;
|
|
149
160
|
const panelWidth = 300;
|
|
150
161
|
const roomEnough = 320;
|
|
162
|
+
// The least room the legend takes under the board's row on a phone, never over the board: the page scrolls to the rest, and the legend follows.
|
|
163
|
+
const roomLeast = 120;
|
|
151
164
|
function keyOf(row) {
|
|
152
165
|
switch (row.kind) {
|
|
153
166
|
case "badge":
|
|
@@ -31,6 +31,7 @@ export function settingsFrom(value, defaults = { theme: "system", sound: true })
|
|
|
31
31
|
voice: typeof stored.voice === "boolean" ? stored.voice : false,
|
|
32
32
|
language: oneOf(stored.language, ["auto", "it", "en"], "auto"),
|
|
33
33
|
// Checked against what the host offers when the chat opens, which this page does not know.
|
|
34
|
+
chatHost: word(stored.chatHost),
|
|
34
35
|
claudeModel: word(stored.claudeModel),
|
|
35
36
|
claudeEffort: word(stored.claudeEffort),
|
|
36
37
|
codexModel: word(stored.codexModel),
|
|
@@ -18,7 +18,7 @@ export function endSoundOf(how) {
|
|
|
18
18
|
/** Lets the page play sounds: loads the recordings, and since a browser plays none before the player has touched the page, the first touch wakes them. */
|
|
19
19
|
export function wakeSounds() {
|
|
20
20
|
loadRecordings();
|
|
21
|
-
addEventListener("pointerdown", () => void audio()?.resume(), { once: true });
|
|
21
|
+
window.addEventListener("pointerdown", () => void audio()?.resume(), { once: true });
|
|
22
22
|
}
|
|
23
23
|
/** Plays a sound: its recordings together, else its tone; a browser without Web Audio plays none. */
|
|
24
24
|
export function playSound(sound) {
|
package/dist/page/style.css
CHANGED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/** The settings that keep each host's choice. */
|
|
2
|
+
export const choiceKeys = {
|
|
3
|
+
claude: { model: "claudeModel", effort: "claudeEffort" },
|
|
4
|
+
codex: { model: "codexModel", effort: "codexEffort" },
|
|
5
|
+
};
|
|
6
|
+
/** The hosts the window offers: those installed whose choices the settings keep. */
|
|
7
|
+
export function offeredHosts(table) {
|
|
8
|
+
return table.hosts.filter((host) => host.installed && host.name in choiceKeys);
|
|
9
|
+
}
|
|
10
|
+
/** The host selected when the window opens: the one at the table, else the last the button used, else the first offered. */
|
|
11
|
+
export function firstHost(table, settings) {
|
|
12
|
+
const offered = offeredHosts(table);
|
|
13
|
+
const atTable = offered.find((host) => table.chat && host.name === table.host);
|
|
14
|
+
return (atTable ?? offered.find((host) => host.name === settings.chatHost) ?? offered[0])?.name;
|
|
15
|
+
}
|
|
16
|
+
/** A host's model or effort as the settings keep it: the host's middle one for none, and for a choice it no longer offers. */
|
|
17
|
+
export function chosen(host, kind, settings) {
|
|
18
|
+
const key = choiceKeys[host.name][kind];
|
|
19
|
+
return offeredOr(host, kind, settings[key]);
|
|
20
|
+
}
|
|
21
|
+
/** What the chat held started with: the host's middle one where the table does not say, or says one it no longer offers. */
|
|
22
|
+
export function startedWith(table, host, kind) {
|
|
23
|
+
return offeredOr(host, kind, table.started?.[kind]);
|
|
24
|
+
}
|
|
25
|
+
/** What the button does with the host selected, none without one: with no chat, open one, to review the game in `review`; else start the chat again, in another host when it is not the one at the table. */
|
|
26
|
+
export function chatAction(table, host, review) {
|
|
27
|
+
if (host === undefined) {
|
|
28
|
+
return undefined;
|
|
29
|
+
}
|
|
30
|
+
if (!table.chat) {
|
|
31
|
+
return review ? "review" : "open";
|
|
32
|
+
}
|
|
33
|
+
return table.host === host.name ? "restart" : "switch";
|
|
34
|
+
}
|
|
35
|
+
/** Whether the button can do its action now: not while a chat opens or waits, nor on a chat opened by hand, which is the player's. */
|
|
36
|
+
export function canAct(table) {
|
|
37
|
+
return table.opening === undefined && table.waiting === undefined && (!table.chat || table.held);
|
|
38
|
+
}
|
|
39
|
+
function offeredOr(host, kind, value) {
|
|
40
|
+
const offered = kind === "model" ? host.models : host.efforts;
|
|
41
|
+
return value !== undefined && offered.includes(value) ? value : host[kind];
|
|
42
|
+
}
|
|
@@ -1,63 +1,80 @@
|
|
|
1
|
-
/* The chat's window (D-107): narrow, a
|
|
2
|
-
|
|
3
|
-
.dialog:has(> .chat-
|
|
1
|
+
/* The chat's window (D-107), as the new game's: narrow, a card for each host with the tick of the one at the table, the selected one's model and effort
|
|
2
|
+
as rows of small choices, then the one button and the command to type instead */
|
|
3
|
+
.dialog:has(> .chat-window) {
|
|
4
4
|
width: min(460px, calc(100vw - 32px));
|
|
5
5
|
}
|
|
6
6
|
|
|
7
|
-
.chat-
|
|
8
|
-
|
|
9
|
-
grid-template-columns: minmax(0, 1fr) auto auto;
|
|
10
|
-
gap: 6px;
|
|
11
|
-
margin: 8px 0 12px;
|
|
7
|
+
.profile-form.chat-window fieldset {
|
|
8
|
+
margin-bottom: 16px;
|
|
12
9
|
}
|
|
13
10
|
|
|
14
|
-
.chat-
|
|
15
|
-
.chat-host {
|
|
11
|
+
.chat-hosts {
|
|
16
12
|
display: grid;
|
|
17
|
-
grid-
|
|
18
|
-
|
|
13
|
+
grid-template-columns: repeat(auto-fit, minmax(140px, 1fr));
|
|
14
|
+
gap: 8px;
|
|
19
15
|
}
|
|
20
16
|
|
|
21
|
-
.chat-
|
|
22
|
-
|
|
23
|
-
|
|
17
|
+
.chat-hosts .level > span {
|
|
18
|
+
flex-direction: row;
|
|
19
|
+
align-items: center;
|
|
20
|
+
justify-content: space-between;
|
|
21
|
+
gap: 8px;
|
|
22
|
+
min-height: 48px;
|
|
23
|
+
padding: 10px 14px;
|
|
24
|
+
font-size: 16px;
|
|
24
25
|
}
|
|
25
26
|
|
|
26
|
-
|
|
27
|
+
/* A host's whole name, on two lines rather than cut on a phone */
|
|
28
|
+
.chat-hosts b {
|
|
27
29
|
min-width: 0;
|
|
28
|
-
|
|
29
|
-
text-align: left;
|
|
30
|
+
line-height: 1.2;
|
|
30
31
|
}
|
|
31
32
|
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
white-space: nowrap;
|
|
37
|
-
text-overflow: ellipsis;
|
|
33
|
+
@media (max-width: 420px) {
|
|
34
|
+
.chat-hosts .level > span {
|
|
35
|
+
padding: 8px 12px;
|
|
36
|
+
}
|
|
38
37
|
}
|
|
39
38
|
|
|
40
|
-
.chat-
|
|
39
|
+
.chat-hosts .at-table {
|
|
40
|
+
display: flex;
|
|
41
41
|
flex: none;
|
|
42
|
-
|
|
43
|
-
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
.chat-hosts .in-use {
|
|
45
|
+
width: 20px;
|
|
46
|
+
height: 20px;
|
|
44
47
|
color: var(--accent);
|
|
45
48
|
}
|
|
46
49
|
|
|
47
|
-
.chat-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
50
|
+
.chat-choices {
|
|
51
|
+
display: flex;
|
|
52
|
+
flex-wrap: wrap;
|
|
53
|
+
gap: 6px;
|
|
51
54
|
}
|
|
52
55
|
|
|
53
|
-
.chat-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
56
|
+
.chat-choices .level > span {
|
|
57
|
+
flex-direction: row;
|
|
58
|
+
min-width: 40px;
|
|
59
|
+
justify-content: center;
|
|
60
|
+
padding: 6px 10px;
|
|
61
|
+
font-size: 14px;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/* The choice the chat at the table started with: a dot in the corner, so that one changed shows beside the one kept */
|
|
65
|
+
.chat-choices .started::after {
|
|
66
|
+
position: absolute;
|
|
67
|
+
top: 4px;
|
|
68
|
+
right: 4px;
|
|
69
|
+
width: 5px;
|
|
70
|
+
height: 5px;
|
|
71
|
+
border-radius: 50%;
|
|
72
|
+
background: var(--accent);
|
|
73
|
+
content: "";
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
.chat-window .problem.chat-state {
|
|
77
|
+
margin-top: -4px;
|
|
61
78
|
}
|
|
62
79
|
|
|
63
80
|
/* 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
|
|
@@ -135,6 +152,7 @@
|
|
|
135
152
|
}
|
|
136
153
|
|
|
137
154
|
.chat-command {
|
|
155
|
+
margin: 12px 0 0;
|
|
138
156
|
color: var(--soft);
|
|
139
157
|
font-size: 12.5px;
|
|
140
158
|
}
|