From e17667d034ed3c8c411f2e4ba8bd317753ffc895 Mon Sep 17 00:00:00 2001 From: funman300 Date: Fri, 15 May 2026 17:32:13 -0700 Subject: [PATCH] feat(web): add undo button directly on the game board MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Places a floating "↩ Undo" button at the bottom-right of the green felt surface so it is visible without looking in the header. Both the board button and the header button share the same handler; both track undo_stack_len and disable when nothing can be undone. Co-Authored-By: Claude Sonnet 4.6 --- solitaire_server/web/game.css | 11 +++++++++++ solitaire_server/web/game.html | 1 + solitaire_server/web/game.js | 15 ++++++++------- 3 files changed, 20 insertions(+), 7 deletions(-) diff --git a/solitaire_server/web/game.css b/solitaire_server/web/game.css index 239846a..2a028e9 100644 --- a/solitaire_server/web/game.css +++ b/solitaire_server/web/game.css @@ -98,6 +98,16 @@ button:disabled { opacity: 0.4; cursor: default; } /* ── Board ───────────────────────────────────────────────────────────── */ +.board-undo { + position: absolute; + bottom: 24px; + right: 24px; + z-index: 50; + font-size: 15px; + padding: 8px 20px; + pointer-events: auto; +} + main { flex: 1; display: flex; @@ -108,6 +118,7 @@ main { /* Full-bleed felt surface — flex:1 reliably fills main's remaining height. */ #board { + position: relative; flex: 1; background: var(--felt); display: flex; diff --git a/solitaire_server/web/game.html b/solitaire_server/web/game.html index 8de5265..e09c9e1 100644 --- a/solitaire_server/web/game.html +++ b/solitaire_server/web/game.html @@ -46,6 +46,7 @@
+
diff --git a/solitaire_server/web/game.js b/solitaire_server/web/game.js index 994c4ec..9a41095 100644 --- a/solitaire_server/web/game.js +++ b/solitaire_server/web/game.js @@ -103,7 +103,8 @@ const hudMoves = document.getElementById("hud-moves"); const hudTimer = document.getElementById("hud-timer"); const hudStock = document.getElementById("hud-stock"); const hudSeed = document.getElementById("hud-seed"); -const btnUndo = document.getElementById("btn-undo"); +const btnUndo = document.getElementById("btn-undo"); +const btnBoardUndo = document.getElementById("btn-board-undo"); const btnNew = document.getElementById("btn-new"); const chkDraw3 = document.getElementById("chk-draw3"); const btnTheme = document.getElementById("btn-theme"); @@ -243,7 +244,8 @@ function render(s) { hudScore.textContent = `Score: ${s.score}`; hudMoves.textContent = `Moves: ${s.move_count}`; if (hudStock) hudStock.textContent = `Stock: ${s.stock.length}`; - btnUndo.disabled = s.undo_stack_len === 0; + btnUndo.disabled = s.undo_stack_len === 0; + btnBoardUndo.disabled = s.undo_stack_len === 0; const visible = new Map(); const addPile = (name, cards) => @@ -385,10 +387,9 @@ function flashIllegal(cardIds) { // ── Input ───────────────────────────────────────────────────────────────────── function attachHandlers() { - btnUndo.addEventListener("click", () => { - const r = game.undo(); - if (r.ok) render(r.snapshot); - }); + const doUndo = () => { const r = game.undo(); if (r.ok) render(r.snapshot); }; + btnUndo.addEventListener("click", doUndo); + btnBoardUndo.addEventListener("click", doUndo); btnNew.addEventListener("click", () => startGame(randomSeed())); btnWinNew.addEventListener("click", () => startGame(randomSeed())); chkDraw3.addEventListener("change", () => { @@ -404,7 +405,7 @@ function attachHandlers() { document.addEventListener("keydown", (e) => { if (e.target.tagName === "INPUT") return; - if (e.key === "z" || e.key === "Z") { const r = game.undo(); if (r.ok) render(r.snapshot); } + if (e.key === "z" || e.key === "Z") doUndo(); if (e.key === "n" || e.key === "N") startGame(randomSeed()); });