diff --git a/css/style.css b/css/style.css
index 6a05b72..a677bf5 100644
--- a/css/style.css
+++ b/css/style.css
@@ -102,6 +102,48 @@ canvas {
font-weight: 600;
}
+.game-over-overlay {
+ position: absolute;
+ inset: 0;
+ z-index: 3;
+ display: flex;
+ flex-direction: column;
+ align-items: center;
+ justify-content: center;
+ gap: 10px;
+ background: rgba(0, 0, 0, 0.78);
+ text-align: center;
+ padding: 20px;
+}
+
+.game-over-overlay[hidden] {
+ display: none;
+}
+
+.game-over-image {
+ width: min(70%, 460px);
+ max-height: 52vh;
+ object-fit: contain;
+ border-radius: 12px;
+ border: 1px solid rgba(255, 255, 255, 0.22);
+ box-shadow: 0 14px 32px rgba(0, 0, 0, 0.45);
+}
+
+.game-over-text {
+ margin-top: 4px;
+ font-size: clamp(24px, 4vw, 40px);
+ font-weight: 700;
+ color: #f3f7ff;
+ text-transform: uppercase;
+ letter-spacing: 0.08em;
+}
+
+.game-over-subtext {
+ font-size: 14px;
+ color: #c9d8ea;
+ letter-spacing: 0.05em;
+}
+
.control-panel {
padding: 16px;
overflow-y: auto;
diff --git a/img/img_door.png b/img/img_door.png
new file mode 100644
index 0000000..6146e59
Binary files /dev/null and b/img/img_door.png differ
diff --git a/img/img_jobapplication.png b/img/img_jobapplication.png
new file mode 100644
index 0000000..f85f41c
Binary files /dev/null and b/img/img_jobapplication.png differ
diff --git a/index.html b/index.html
index 03659c7..2b0fd5b 100644
--- a/index.html
+++ b/index.html
@@ -19,6 +19,11 @@
Has keyno
Rounds0
+
+
![Game over]()
+
Game Over
+
Press R to play again
+
diff --git a/src/babylon_setup.js b/src/babylon_setup.js
index 69fdab7..b832441 100644
--- a/src/babylon_setup.js
+++ b/src/babylon_setup.js
@@ -6,6 +6,8 @@ import { playSfx, primeSfx } from "./sfx.js";
import chestTextureUrl from "../img/img_chest.png";
import wallTextureUrl from "../img/img_wall.png";
import groundTextureUrl from "../img/img_ground.png";
+import doorTextureUrl from "../img/img_door.png";
+import gameOverImageUrl from "../img/img_jobapplication.png";
// Initialize Babylon.js engine and scene
const canvas = document.getElementById("renderCanvas");
@@ -13,6 +15,11 @@ const engine = new BABYLON.Engine(canvas, true);
const canvasTime = document.getElementById("canvas-time");
const canvasKey = document.getElementById("canvas-key");
const canvasRounds = document.getElementById("canvas-rounds");
+const gameOverOverlay = document.getElementById("game-over-overlay");
+const gameOverImage = document.getElementById("game-over-image");
+if (gameOverImage) {
+ gameOverImage.src = gameOverImageUrl;
+}
const scene = new BABYLON.Scene(engine);
scene.clearColor = new BABYLON.Color4(0.05, 0.07, 0.1, 1);
@@ -49,6 +56,7 @@ scene.activeCamera = camera;
let lastFootstepPosition = null;
let footstepAccumulator = 0;
let footstepElapsed = 0;
+let gameOverActive = false;
scene.gravity = new BABYLON.Vector3(0, -0.2, 0);
scene.collisionsEnabled = true;
@@ -66,6 +74,34 @@ function updateOverviewCameraForMaze(w, h) {
overviewCamera.target = new BABYLON.Vector3(0, 0, 0);
}
+function showGameOverScreen() {
+ gameOverActive = true;
+ if (gameOverOverlay) {
+ gameOverOverlay.hidden = false;
+ }
+ if (document.pointerLockElement === canvas && document.exitPointerLock) {
+ document.exitPointerLock();
+ }
+}
+
+function hideGameOverScreen() {
+ gameOverActive = false;
+ if (gameOverOverlay) {
+ gameOverOverlay.hidden = true;
+ }
+}
+
+function restartRunFromGameOver() {
+ sharedState.runtime.runActive = true;
+ sharedState.runtime.hasKey = false;
+ sharedState.runtime.roundsCompleted = 0;
+ sharedState.runtime.elapsedSeconds = 0;
+ sharedState.runtime.message = "Run restarted.";
+ sharedState.config.level = 1;
+ hideGameOverScreen();
+ generateLevel();
+}
+
function switchCameraMode() {
if (cameraMode === "fp") {
if (document.pointerLockElement === canvas && document.exitPointerLock) {
@@ -87,9 +123,13 @@ function switchCameraMode() {
}
window.addEventListener("keydown", (event) => {
- if (event.code === "KeyW" || event.code === "KeyA" || event.code === "KeyS" || event.code === "KeyD" || event.code === "KeyV") {
+ if (event.code === "KeyW" || event.code === "KeyA" || event.code === "KeyS" || event.code === "KeyD" || event.code === "KeyV" || event.code === "KeyR") {
primeSfx();
}
+ if (event.code === "KeyR" && gameOverActive) {
+ restartRunFromGameOver();
+ return;
+ }
if (event.code === "KeyV") {
switchCameraMode();
}
@@ -280,39 +320,21 @@ function placeExit(grid, seed) {
const exitWorld = gridCellToWorld(grid, x, y, cellSize);
const ex = exitWorld.x;
const ez = exitWorld.z;
- const worldSpan = Math.max(grid[0].length, grid.length) * cellSize;
-
- // Ground plane indicator (larger + slightly raised to avoid z-fighting)
- const plane = BABYLON.MeshBuilder.CreateGround('exitZone', { width: cellSize*1.4, height: cellSize*1.4 }, scene);
+
+ const plane = BABYLON.MeshBuilder.CreatePlane('exitDoor', {
+ width: cellSize * 1.35,
+ height: cellSize * 1.85,
+ sideOrientation: BABYLON.Mesh.DOUBLESIDE,
+ }, scene);
const exitMat = new BABYLON.StandardMaterial('exitMat', scene);
- exitMat.diffuseColor = new BABYLON.Color3(0.25, 0.2, 0.03);
- exitMat.emissiveColor = new BABYLON.Color3(1.0, 0.85, 0.15);
- exitMat.disableLighting = true;
+ exitMat.diffuseTexture = new BABYLON.Texture(doorTextureUrl, scene);
+ exitMat.diffuseColor = new BABYLON.Color3(0.95, 0.95, 0.95);
+ exitMat.emissiveColor = new BABYLON.Color3(0.07, 0.07, 0.07);
plane.material = exitMat;
- plane.position = new BABYLON.Vector3(ex, 0.08, ez);
+ plane.position = new BABYLON.Vector3(ex, cellSize * 0.92, ez);
+ plane.billboardMode = BABYLON.Mesh.BILLBOARDMODE_Y;
exitBox = plane;
levelMeshes.push(plane);
-
- // Very tall beacon for visibility over maze walls
- const beaconHeight = Math.max(18, worldSpan * 0.8);
- const pillar = BABYLON.MeshBuilder.CreateCylinder('exitPillar', { diameter: cellSize*0.55, height: beaconHeight }, scene);
- const pillarMat = new BABYLON.StandardMaterial('exitPillarMat', scene);
- pillarMat.diffuseColor = new BABYLON.Color3(0.95, 0.8, 0.1);
- pillarMat.emissiveColor = new BABYLON.Color3(1.0, 0.7, 0.1);
- pillarMat.disableLighting = true;
- pillar.material = pillarMat;
- pillar.position = new BABYLON.Vector3(ex, beaconHeight * 0.5, ez);
- levelMeshes.push(pillar);
-
- // Bright orb at the top of beacon
- const orb = BABYLON.MeshBuilder.CreateSphere('exitOrb', { diameter: cellSize * 1.0 }, scene);
- const orbMat = new BABYLON.StandardMaterial('exitOrbMat', scene);
- orbMat.diffuseColor = new BABYLON.Color3(1.0, 0.9, 0.3);
- orbMat.emissiveColor = new BABYLON.Color3(1.0, 0.9, 0.35);
- orbMat.disableLighting = true;
- orb.material = orbMat;
- orb.position = new BABYLON.Vector3(ex, beaconHeight + cellSize * 0.7, ez);
- levelMeshes.push(orb);
}
function spawnCameraAt(grid) {
@@ -368,6 +390,7 @@ function spawnCameraAt(grid) {
}
function generateLevel() {
+ hideGameOverScreen();
const cfg = sharedState.config;
const seed = cfg.seed;
const w = Math.max(9, cfg.mazeWidth);
@@ -415,8 +438,10 @@ scene.onPointerObservable.add((pi) => {
if (entry.opened) {
primeSfx();
playSfx("chestClose", 0.8);
+ playSfx("lose", 0.85);
sharedState.runtime.runActive = false;
sharedState.runtime.message = 'Opened chest again — game over.';
+ showGameOverScreen();
return;
}
primeSfx();
diff --git a/src/sfx.js b/src/sfx.js
index 447564e..7fc9c18 100644
--- a/src/sfx.js
+++ b/src/sfx.js
@@ -3,6 +3,7 @@ const soundFiles = {
chestOpen: "/sfx/sfx_chest_open.wav",
click: "/sfx/sfx_click.wav",
key: "/sfx/sfx_key.wav",
+ lose: "/sfx/sfx_lose.wav",
step: "/sfx/sfx_step.wav",
win: "/sfx/sfx_win.wav",
};