|
272 | 272 | line-height: 1; |
273 | 273 | } |
274 | 274 |
|
| 275 | + .caps span.recent { |
| 276 | + outline: 2px solid var(--accent); |
| 277 | + outline-offset: 2px; |
| 278 | + border-radius: 4px; |
| 279 | + } |
| 280 | + |
275 | 281 | .reactions { |
276 | 282 | display: flex; |
277 | 283 | gap: 6px; |
|
486 | 492 | let playerColor = "white"; |
487 | 493 | let playerColorSet = false; |
488 | 494 | let isSpectator = false; |
| 495 | + let gameOver = false; |
| 496 | + let prevCaptured = { byWhite: [], byBlack: [] }; |
489 | 497 |
|
490 | 498 | function normalizeColor(c) { |
491 | 499 | if (!c) return "white"; |
|
799 | 807 |
|
800 | 808 | // Board-level click handler |
801 | 809 | boardEl.addEventListener("click", (e) => { |
802 | | - if (isSpectator) return; |
| 810 | + if (isSpectator || gameOver) return; |
803 | 811 | const rect = boardEl.getBoundingClientRect(); |
804 | 812 | const x = Math.min( |
805 | 813 | Math.max(0, e.clientX - rect.left), |
|
839 | 847 | statusEl.style.color = isErr ? "var(--err)" : "inherit"; |
840 | 848 | } |
841 | 849 |
|
| 850 | + function updateTurn(st) { |
| 851 | + if (!turnEl) return; |
| 852 | + if (st.status) { |
| 853 | + turnEl.textContent = "Game over"; |
| 854 | + return; |
| 855 | + } |
| 856 | + const t = normalizeColor(st.turn); |
| 857 | + if (isSpectator) { |
| 858 | + turnEl.textContent = t || ""; |
| 859 | + } else if (t === playerColor) { |
| 860 | + turnEl.textContent = "Your turn"; |
| 861 | + } else { |
| 862 | + turnEl.textContent = "Their turn"; |
| 863 | + } |
| 864 | + } |
| 865 | + |
842 | 866 | // ---- Captured pieces (derived from FEN) + persisted per game ---- |
843 | 867 | var startCounts = { |
844 | 868 | P: 8, |
|
947 | 971 | for (var i = 0; i < byWhite.length; i++) { |
948 | 972 | var s1 = document.createElement("span"); |
949 | 973 | s1.textContent = byWhite[i]; |
| 974 | + s1.classList.add("black-piece"); |
| 975 | + if (i >= prevCaptured.byWhite.length) s1.classList.add("recent"); |
950 | 976 | capWhiteEl.appendChild(s1); |
951 | 977 | } |
952 | 978 | for (var j = 0; j < byBlack.length; j++) { |
953 | 979 | var s2 = document.createElement("span"); |
954 | 980 | s2.textContent = byBlack[j]; |
| 981 | + s2.classList.add("white-piece"); |
| 982 | + if (j >= prevCaptured.byBlack.length) s2.classList.add("recent"); |
955 | 983 | capBlackEl.appendChild(s2); |
956 | 984 | } |
| 985 | + prevCaptured.byWhite = byWhite.slice(); |
| 986 | + prevCaptured.byBlack = byBlack.slice(); |
957 | 987 | } |
958 | 988 |
|
959 | 989 | function capKey(id) { |
|
1058 | 1088 |
|
1059 | 1089 | // Render start position immediately (prevents blank board) |
1060 | 1090 | renderFEN(START_FEN); |
1061 | | - turnEl.textContent = "white"; |
| 1091 | + turnEl.textContent = ""; |
1062 | 1092 | status(""); |
1063 | 1093 |
|
1064 | 1094 | if (gameId) { |
|
1095 | 1125 | releaseBtn.style.display = isSpectator ? "none" : ""; |
1096 | 1126 | lastMoveSquares = deriveLastMoveSquares(st.uci || []); |
1097 | 1127 | renderFEN(st.fen); |
1098 | | - turnEl.textContent = st.turn || ""; |
| 1128 | + updateTurn(st); |
1099 | 1129 | pgnEl.textContent = formatPGNLines(st.pgn || ""); |
1100 | 1130 | lanEl.textContent = formatUCIMoves(st.uci || []); |
1101 | 1131 | status(st.status || ""); |
| 1132 | + gameOver = !!st.status; |
1102 | 1133 | const caps = capturedFromFEN(st.fen); |
1103 | 1134 | renderCaptured(caps.byWhite, caps.byBlack); |
1104 | 1135 | try { |
|
1130 | 1161 | }); |
1131 | 1162 | } |
1132 | 1163 | }; |
| 1164 | + es.onopen = () => { |
| 1165 | + status(""); |
| 1166 | + }; |
1133 | 1167 | es.onerror = () => { |
1134 | 1168 | status("Disconnected. Reconnecting…", true); |
1135 | 1169 | }; |
|
0 commit comments