diff --git a/index.html b/index.html index cd8ca7b..dfc1e46 100644 --- a/index.html +++ b/index.html @@ -82,6 +82,25 @@ font-size: 13px; color: var(--muted); } + .pause-btn { + background: rgba(34, 211, 238, 0.14); + color: #67e8f9; + border: 1px solid rgba(34, 211, 238, 0.4); + border-radius: 10px; + padding: 8px 12px; + font-weight: 700; + cursor: pointer; + transition: + transform 120ms ease, + filter 120ms ease; + } + .pause-btn:hover { + filter: brightness(1.05); + transform: translateY(-1px); + } + .pause-btn:active { + transform: translateY(0); + } header .pill { padding: 6px 10px; border-radius: 8px; @@ -159,6 +178,25 @@ border: 1px solid rgba(255, 255, 255, 0.12); display: inline-block; } + .pause-overlay { + position: absolute; + top: 12px; + left: 50%; + transform: translateX(-50%); + background: rgba(255, 255, 255, 0.08); + border: 1px solid rgba(148, 163, 184, 0.3); + color: #e2e8f0; + padding: 8px 14px; + border-radius: 12px; + font-weight: 800; + letter-spacing: 0.5px; + opacity: 0; + pointer-events: none; + transition: opacity 160ms ease; + } + .pause-overlay.visible { + opacity: 1; + } .game-over { position: absolute; inset: 0; @@ -256,9 +294,11 @@ Physics Link same-colored balls to clear them. +
+
Paused
Game Over
@@ -295,7 +335,7 @@ than the minimum vanish; chains with enough balls clear all linked balls (score: 10 × length²). If the entry gets blocked and a new ball cannot drop in, the run ends—restart to try - again. + again. Pause/resume with the Pause button or Escape key.
diff --git a/main.js b/main.js index 67e796b..a3e33f3 100644 --- a/main.js +++ b/main.js @@ -29,6 +29,8 @@ const gameOverEl = document.getElementById("game-over"); const finalScoreEl = document.getElementById("final-score"); const restartBtn = document.getElementById("restart-btn"); + const pauseBtn = document.getElementById("pause-btn"); + const pauseOverlay = document.getElementById("pause-overlay"); let width = sceneEl.clientWidth; let height = sceneEl.clientHeight; @@ -86,6 +88,7 @@ let spawnTimer = null; let score = 0; let gameOver = false; + let isPaused = false; const chain = { active: false, @@ -131,6 +134,13 @@ spawnTimer = setInterval(spawnBall, config.spawnIntervalMs); }; + const stopSpawner = () => { + if (spawnTimer) { + clearInterval(spawnTimer); + spawnTimer = null; + } + }; + const cleanupBall = (ball) => { if (ball.plugin && ball.plugin.entryCheckId) { clearTimeout(ball.plugin.entryCheckId); @@ -141,14 +151,19 @@ const triggerGameOver = () => { if (gameOver) return; gameOver = true; + isPaused = false; resetChainVisuals(); - if (spawnTimer) clearInterval(spawnTimer); + stopSpawner(); + Runner.stop(runner); + pauseOverlay.classList.remove("visible"); + pauseBtn.textContent = "Pause"; finalScoreEl.textContent = score; gameOverEl.classList.add("visible"); }; const restartGame = () => { gameOver = false; + isPaused = false; score = 0; resetChainVisuals(); balls.forEach((ball) => { @@ -157,7 +172,10 @@ }); balls.length = 0; gameOverEl.classList.remove("visible"); + pauseOverlay.classList.remove("visible"); + pauseBtn.textContent = "Pause"; updateHud(); + Runner.run(runner, engine); startSpawner(); }; @@ -166,6 +184,22 @@ body.render.strokeStyle = on ? "#f8fafc" : "#0b1222"; }; + const setPaused = (state) => { + if (gameOver) return; + if (state === isPaused) return; + isPaused = state; + pauseBtn.textContent = isPaused ? "Resume" : "Pause"; + pauseOverlay.classList.toggle("visible", isPaused); + if (isPaused) { + resetChainVisuals(); + stopSpawner(); + Runner.stop(runner); + } else { + startSpawner(); + Runner.run(runner, engine); + } + }; + const resetChainVisuals = () => { chain.bodies.forEach((b) => setHighlight(b, false)); chain.constraints.forEach((c) => World.remove(world, c)); @@ -220,7 +254,7 @@ }; const finishChain = (releasePoint) => { - if (!chain.active || gameOver) return; + if (!chain.active || gameOver || isPaused) return; if (chain.bodies.length >= config.minChain) { const gain = 10 * Math.pow(chain.bodies.length, 2); score += gain; @@ -264,7 +298,7 @@ }; const handlePointerDown = (evt) => { - if (gameOver) return; + if (gameOver || isPaused) return; const point = getPointerPosition(evt); const body = pickBody(point); if (!body) return; @@ -279,7 +313,7 @@ const handlePointerMove = (evt) => { if (!chain.active) return; - if (gameOver) return; + if (gameOver || isPaused) return; const point = getPointerPosition(evt); chain.pointer = point; const body = pickBody(point); @@ -407,6 +441,12 @@ window.addEventListener("resize", handleResize); restartBtn.addEventListener("click", restartGame); + pauseBtn.addEventListener("click", () => setPaused(!isPaused)); + window.addEventListener("keydown", (e) => { + if (e.key === "Escape") { + setPaused(!isPaused); + } + }); buildLegend(); updateHud(); startSpawner();