Extract UI handling to dedicated module

This commit is contained in:
Daddy32
2025-12-12 22:21:46 +01:00
parent c1b40448ee
commit 1605b194af
3 changed files with 183 additions and 81 deletions

107
main.js
View File

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