diff --git a/public_html/README.md b/public_html/README.md index 75fa9f6..945d273 100644 --- a/public_html/README.md +++ b/public_html/README.md @@ -60,6 +60,13 @@ except installation and the service worker. it paused — everything in there resets the clocks anyway — while the score sheet picks the turn back up when you close it, so glancing at the score doesn't cost you a tap on play. +- **Settings are only reachable before a match starts.** Every control in that + sheet resets both clocks, so the gear disappears the moment the first tap + starts the clock and comes back after a reset. Its space in the bar is kept + empty, so nothing else shifts. That means a mistake in the time or delay can + only be fixed by resetting and starting the match again — deliberately, since + the alternative is wiping two live clocks by accident. Correcting a clock + mid-match is what the score sheet is for. Backgammon clocks pause between games, and after a cocked die the delay is normally restarted — use pause and reset for those. diff --git a/public_html/index.html b/public_html/index.html index b231531..0244774 100644 --- a/public_html/index.html +++ b/public_html/index.html @@ -165,6 +165,10 @@ .btn svg{width:clamp(23px,6.4vw,32px);height:auto;display:block} .btn svg[hidden]{display:none} .btn[hidden]{display:none} /* .btn's own display would otherwise win */ + /* not [hidden]: that would collapse the slot and shift the whole bar. This + keeps the space, and still drops the button out of the tab order, the + accessibility tree and the path of a stray tap. */ + .btn--vacant{visibility:hidden} .btn:focus-visible{outline:2px solid var(--icon-hot);outline-offset:-6px;border-radius:8px} /* reset armed for a second tap — the ring unwinds over the same 2s as the timeout */ @@ -591,6 +595,7 @@ var btnSound = document.getElementById("sound"); var btnReset = document.getElementById("reset"); var btnScore = document.getElementById("score"); + var btnSettings = document.getElementById("settings"); var sheet = document.getElementById("sheet"); var scoreSheet = document.getElementById("scoresheet"); var scrim = document.getElementById("scrim"); @@ -681,6 +686,18 @@ return Math.max(0, st.reserve[i] - consumed(i)); } + /* Every timer control in the settings resets both clocks, so the sheet is only + safe to reach in exactly the state resetAll() leaves behind. Checking the + phase alone would not do: a score change runs newGame(), which returns to + IDLE waiting for the next game's first tap, with the match still very much + on. The score and reserve checks are what cover that. */ + function matchPristine(){ + return st.phase === IDLE && st.active === -1 && + st.moves[0] === 0 && st.moves[1] === 0 && + st.score[0] === 0 && st.score[1] === 0 && + st.reserve[0] === matchTime() && st.reserve[1] === matchTime(); + } + /* ---- write only on change ---- render() runs 60 times a second but the digits turn over once a second, and .time is a ~190px glyph run: rewriting its text node every frame forces a @@ -735,6 +752,10 @@ (st.phase === FLAG && i === st.active) ? "flagged" : (i === st.active && st.phase !== IDLE) ? "active" : "idle"); } + + // here rather than in the mutators, so it can never go stale; the + // two-argument toggle leaves the DOM alone when the state already matches + btnSettings.classList.toggle("btn--vacant", !matchPristine()); } function loop(){ render(); requestAnimationFrame(loop); } @@ -1026,7 +1047,10 @@ } function closeSheet(){ openSheet(null); } - document.getElementById("settings").addEventListener("click", function(){ + btnSettings.addEventListener("click", function(){ + // the CSS only hides the button; the rule itself lives here, so no route in + // can reach the timer controls once the match is under way + if(!matchPristine()) return; pauseNow(); // nobody's clock runs while a sheet is open disarm(); paintSettings(); diff --git a/public_html/sw.js b/public_html/sw.js index 61214cc..ddf8fef 100644 --- a/public_html/sw.js +++ b/public_html/sw.js @@ -1,5 +1,5 @@ /* Cache-first: once installed the clock never touches the network again. */ -const CACHE = "bgclock-v7"; +const CACHE = "bgclock-v8"; const FILES = [ "./", "./index.html",