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();