From 1605b194afcd8a78923d5c9a0bbe5d709c8f0872 Mon Sep 17 00:00:00 2001 From: Daddy32 Date: Fri, 12 Dec 2025 22:21:46 +0100 Subject: [PATCH] Extract UI handling to dedicated module --- index.html | 1 + main.js | 107 +++++++++--------------------------- ui.js | 156 +++++++++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 183 insertions(+), 81 deletions(-) create mode 100644 ui.js diff --git a/index.html b/index.html index 21902be..6db5ab3 100644 --- a/index.html +++ b/index.html @@ -76,6 +76,7 @@ + diff --git a/main.js b/main.js index bc181f9..eb06589 100644 --- a/main.js +++ b/main.js @@ -31,20 +31,8 @@ }, }; - const sceneEl = document.getElementById("scene-wrapper"); - const activeColorEl = document.getElementById("active-color"); - const chainLenEl = document.getElementById("chain-length"); - const spawnRateEl = document.getElementById("spawn-rate"); - const minLinkEl = document.getElementById("min-link"); - const paletteLegendEl = document.getElementById("palette-legend"); - const scoreEl = document.getElementById("score"); - const highScoreEl = document.getElementById("high-score"); - const sceneSelectEl = document.getElementById("scene-select"); - 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"); + const ui = window.PhysilinksUI.create(); + const { sceneEl } = ui; let width = sceneEl.clientWidth; let height = sceneEl.clientHeight; @@ -130,21 +118,10 @@ } }; - const populateSceneSelect = () => { - if (!sceneSelectEl) return; - sceneSelectEl.innerHTML = ""; - scenes.forEach((scene) => { - const opt = document.createElement("option"); - opt.value = scene.id; - opt.textContent = scene.name; - sceneSelectEl.appendChild(opt); - }); - }; - const applyScene = (sceneId) => { const next = scenes.find((s) => s.id === sceneId) || scenes[0]; currentScene = next; - if (sceneSelectEl) sceneSelectEl.value = next.id; + ui.setSceneSelection(next.id); Object.assign(config, next.config); config.link = { ...next.config.link }; engine.gravity.y = config.gravity; @@ -230,10 +207,8 @@ stopSpawner(); stopRunner(); engine.timing.timeScale = 0; - pauseOverlay.classList.remove("visible"); - pauseBtn.textContent = "Pause"; - finalScoreEl.textContent = score; - gameOverEl.classList.add("visible"); + ui.setPauseState(false); + ui.showGameOver(score); }; const restartGame = () => { @@ -246,9 +221,8 @@ World.remove(world, ball); }); balls.length = 0; - gameOverEl.classList.remove("visible"); - pauseOverlay.classList.remove("visible"); - pauseBtn.textContent = "Pause"; + ui.hideGameOver(); + ui.setPauseState(false); engine.timing.timeScale = 1; startRunner(); updateHud(); @@ -264,8 +238,7 @@ if (gameOver) return; if (state === isPaused) return; isPaused = state; - pauseBtn.textContent = isPaused ? "Resume" : "Pause"; - pauseOverlay.classList.toggle("visible", isPaused); + ui.setPauseState(isPaused); if (isPaused) { resetChainVisuals(); stopSpawner(); @@ -328,18 +301,6 @@ updateHud(); }; - const spawnScorePopup = (point, amount, color) => { - if (!point) return; - const el = document.createElement("div"); - el.className = "floating-score"; - el.textContent = `+${amount}`; - el.style.left = `${point.x}px`; - el.style.top = `${point.y}px`; - el.style.color = color || "#e0f2fe"; - sceneEl.appendChild(el); - setTimeout(() => el.remove(), 950); - }; - const finishChain = (releasePoint) => { if (!chain.active || gameOver || isPaused) return; if (chain.bodies.length >= config.minChain) { @@ -349,7 +310,7 @@ highScore = score; saveHighScore(); } - spawnScorePopup(releasePoint || chain.pointer, gain, chain.color); + ui.spawnScorePopup(releasePoint || chain.pointer, gain, chain.color); chain.constraints.forEach((c) => World.remove(world, c)); chain.bodies.forEach((body) => { cleanupBall(body); @@ -457,32 +418,18 @@ ); const updateHud = () => { - spawnRateEl.textContent = `${config.spawnIntervalMs} ms`; - minLinkEl.textContent = config.minChain; - chainLenEl.textContent = chain.bodies.length; - scoreEl.textContent = score; - highScoreEl.textContent = highScore; - if (chain.color) { - activeColorEl.textContent = ""; - activeColorEl.style.display = "inline-block"; - activeColorEl.style.width = "14px"; - activeColorEl.style.height = "14px"; - activeColorEl.style.borderRadius = "50%"; - activeColorEl.style.background = chain.color; - activeColorEl.style.border = "1px solid rgba(255,255,255,0.3)"; - } else { - activeColorEl.removeAttribute("style"); - activeColorEl.textContent = "—"; - } + ui.updateHud({ + spawnIntervalMs: config.spawnIntervalMs, + minChain: config.minChain, + chainLength: chain.bodies.length, + score, + highScore, + activeColor: chain.color, + }); }; const buildLegend = () => { - paletteLegendEl.innerHTML = ""; - config.palette.forEach((color) => { - const swatch = document.createElement("span"); - swatch.style.background = color; - paletteLegendEl.appendChild(swatch); - }); + ui.buildLegend(config.palette); }; const handleResize = () => { @@ -581,16 +528,14 @@ }); window.addEventListener("resize", handleResize); - restartBtn.addEventListener("click", restartGame); - pauseBtn.addEventListener("click", () => setPaused(!isPaused)); - window.addEventListener("keydown", (e) => { - if (e.key === "Escape") { - setPaused(!isPaused); - } + ui.setHandlers({ + onPauseToggle: () => setPaused(!isPaused), + onRestart: restartGame, + onSceneChange: (id) => applyScene(id), }); - if (sceneSelectEl) { - sceneSelectEl.addEventListener("change", (e) => applyScene(e.target.value)); - } - populateSceneSelect(); + ui.setSceneOptions( + scenes, + (currentScene && currentScene.id) || defaultSceneId, + ); applyScene((currentScene && currentScene.id) || defaultSceneId); })(); diff --git a/ui.js b/ui.js new file mode 100644 index 0000000..90bfcd2 --- /dev/null +++ b/ui.js @@ -0,0 +1,156 @@ +(() => { + const create = () => { + const sceneEl = document.getElementById("scene-wrapper"); + const activeColorEl = document.getElementById("active-color"); + const chainLenEl = document.getElementById("chain-length"); + const spawnRateEl = document.getElementById("spawn-rate"); + const minLinkEl = document.getElementById("min-link"); + const paletteLegendEl = document.getElementById("palette-legend"); + const scoreEl = document.getElementById("score"); + const highScoreEl = document.getElementById("high-score"); + const sceneSelectEl = document.getElementById("scene-select"); + 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"); + + const handlers = { + onPauseToggle: null, + onRestart: null, + onSceneChange: null, + }; + + if (pauseBtn) { + pauseBtn.addEventListener("click", () => { + if (handlers.onPauseToggle) handlers.onPauseToggle(); + }); + } + + if (restartBtn) { + restartBtn.addEventListener("click", () => { + if (handlers.onRestart) handlers.onRestart(); + }); + } + + if (sceneSelectEl) { + sceneSelectEl.addEventListener("change", (e) => { + if (handlers.onSceneChange) handlers.onSceneChange(e.target.value); + }); + } + + window.addEventListener("keydown", (e) => { + if (e.key === "Escape" && handlers.onPauseToggle) { + handlers.onPauseToggle(); + } + }); + + const setHandlers = (nextHandlers = {}) => { + Object.assign(handlers, nextHandlers); + }; + + const setSceneOptions = (scenes, activeId) => { + if (!sceneSelectEl) return; + sceneSelectEl.innerHTML = ""; + scenes.forEach((scene) => { + const opt = document.createElement("option"); + opt.value = scene.id; + opt.textContent = scene.name; + sceneSelectEl.appendChild(opt); + }); + if (activeId) { + sceneSelectEl.value = activeId; + } + }; + + const setSceneSelection = (sceneId) => { + if (sceneSelectEl) { + sceneSelectEl.value = sceneId; + } + }; + + const setPauseState = (paused) => { + if (pauseOverlay) { + pauseOverlay.classList.toggle("visible", paused); + } + if (pauseBtn) { + pauseBtn.textContent = paused ? "Resume" : "Pause"; + } + }; + + const showGameOver = (score) => { + if (finalScoreEl) finalScoreEl.textContent = score; + if (gameOverEl) gameOverEl.classList.add("visible"); + }; + + const hideGameOver = () => { + if (gameOverEl) gameOverEl.classList.remove("visible"); + }; + + const spawnScorePopup = (point, amount, color) => { + if (!point || !sceneEl) return; + const el = document.createElement("div"); + el.className = "floating-score"; + el.textContent = `+${amount}`; + el.style.left = `${point.x}px`; + el.style.top = `${point.y}px`; + el.style.color = color || "#e0f2fe"; + sceneEl.appendChild(el); + setTimeout(() => el.remove(), 950); + }; + + const updateHud = ({ + spawnIntervalMs, + minChain, + chainLength, + score, + highScore, + activeColor, + }) => { + if (spawnRateEl) spawnRateEl.textContent = `${spawnIntervalMs} ms`; + if (minLinkEl) minLinkEl.textContent = minChain; + if (chainLenEl) chainLenEl.textContent = chainLength; + if (scoreEl) scoreEl.textContent = score; + if (highScoreEl) highScoreEl.textContent = highScore; + if (activeColorEl) { + if (activeColor) { + activeColorEl.textContent = ""; + activeColorEl.style.display = "inline-block"; + activeColorEl.style.width = "14px"; + activeColorEl.style.height = "14px"; + activeColorEl.style.borderRadius = "50%"; + activeColorEl.style.background = activeColor; + activeColorEl.style.border = "1px solid rgba(255,255,255,0.3)"; + } else { + activeColorEl.removeAttribute("style"); + activeColorEl.textContent = "—"; + } + } + }; + + const buildLegend = (palette) => { + if (!paletteLegendEl) return; + paletteLegendEl.innerHTML = ""; + palette.forEach((color) => { + const swatch = document.createElement("span"); + swatch.style.background = color; + paletteLegendEl.appendChild(swatch); + }); + }; + + return { + sceneEl, + updateHud, + buildLegend, + spawnScorePopup, + setPauseState, + showGameOver, + hideGameOver, + setSceneOptions, + setSceneSelection, + setHandlers, + }; + }; + + window.PhysilinksUI = { create }; +})();