From a9285ccb41df04cf30081f66f9cd48dc372fb0bc Mon Sep 17 00:00:00 2001 From: funman300 Date: Fri, 15 May 2026 17:21:32 -0700 Subject: [PATCH] feat(web): add step-back to replay viewer MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The "⏮ Restart" button now steps back one move at a time instead of resetting to the beginning. Re-creates the ReplayPlayer and fast-forwards to (step_idx - 1) without rendering intermediate frames; the CSS transform transition then animates each card back to its previous position. Co-Authored-By: Claude Sonnet 4.6 --- solitaire_server/web/index.html | 2 +- solitaire_server/web/replay.js | 32 +++++++++++++++++++++++--------- 2 files changed, 24 insertions(+), 10 deletions(-) diff --git a/solitaire_server/web/index.html b/solitaire_server/web/index.html index 6896d5c..7338013 100644 --- a/solitaire_server/web/index.html +++ b/solitaire_server/web/index.html @@ -30,7 +30,7 @@
- + step 0 / 0 diff --git a/solitaire_server/web/replay.js b/solitaire_server/web/replay.js index a95de43..23c8c5b 100644 --- a/solitaire_server/web/replay.js +++ b/solitaire_server/web/replay.js @@ -301,16 +301,30 @@ btnPlay.addEventListener("click", () => { }, STEP_INTERVAL_MS); }); +/// Step the player back one move. Re-creates the ReplayPlayer and fast- +/// forwards to (step_idx - 1) without rendering intermediate frames, then +/// renders once so the CSS transition animates each card to its previous +/// position. +function stepBack() { + if (!player || player.step_idx() === 0) return; + if (playInterval) { + clearInterval(playInterval); + playInterval = null; + btnPlay.textContent = "▶ Play"; + } + const target = player.step_idx() - 1; + player = new ReplayPlayer(replayJson); + for (let i = 0; i < target; i++) { + player.step(); + } + render(player.state()); + btnPrev.disabled = player.step_idx() === 0; + btnStep.disabled = false; + btnPlay.disabled = false; +} + btnPrev.addEventListener("click", () => { - if (!replayJson) return; - // Drop every existing card so the next render fades them all in - // at the freshly-dealt positions. Without this, cards from the - // current state would slide to wherever the new deal puts them - // — confusing since the deal is supposed to look like a fresh - // start, not a continuation. - cardEls.forEach((el) => el.remove()); - cardEls.clear(); - resetPlayer(); + if (player) stepBack(); }); bootstrap();