From 78f75a866b9a8253a95a7bf842fa8c41eefd3ada Mon Sep 17 00:00:00 2001 From: michael <1+michael@noreply.git.casaderoll.de> Date: Thu, 24 Sep 2026 10:08:48 +0200 Subject: [PATCH] Add shared UI overlay (load, title, HUD, pause, gameover, highscore) --- public/js/core/ui.js | 141 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 141 insertions(+) create mode 100644 public/js/core/ui.js diff --git a/public/js/core/ui.js b/public/js/core/ui.js new file mode 100644 index 0000000..783409a --- /dev/null +++ b/public/js/core/ui.js @@ -0,0 +1,141 @@ +/* ============================================================ + DSP ARCADE — shared UI: load screen, title, HUD, pause, + game over, high-score entry. Draws on top of the game canvas. + ============================================================ */ +(function () { + 'use strict'; + + const { STATE } = window.DSP; + + function text(ctx, str, x, y, size, color, align = 'center') { + ctx.font = `${size}px "Press Start 2P", monospace`; + ctx.textAlign = align; + ctx.textBaseline = 'middle'; + ctx.fillStyle = color; + ctx.fillText(str, x, y); + } + + function glowText(ctx, str, x, y, size, color, glow) { + ctx.save(); + ctx.shadowColor = glow || color; + ctx.shadowBlur = 12; + text(ctx, str, x, y, size, color); + ctx.restore(); + } + + function blink(on) { return Math.floor(performance.now() / 400) % 2 === on; } + + function draw(game) { + const ctx = game.ctx; + const W = game.W, H = game.H; + const cx = W / 2; + + switch (game.state) { + case STATE.LOADING: { + ctx.fillStyle = '#05010d'; + ctx.fillRect(0, 0, W, H); + for (let i = 0; i < 40; i++) { + const sx = (i * 97 + game.time * 0.3) % W; + const sy = (i * 53) % H; + ctx.fillStyle = i % 3 === 0 ? '#ff2d95' : '#7df9ff'; + ctx.globalAlpha = 0.3 + (i % 5) * 0.12; + ctx.fillRect(sx, sy, 2, 2); + } + ctx.globalAlpha = 1; + glowText(ctx, game.title, cx, H * 0.3, 20, '#ff2d95'); + const step = game.loadSteps[Math.min(game.loadStep, game.loadSteps.length - 1)]; + text(ctx, step + '...', cx, H * 0.5, 8, '#7df9ff'); + const bw = W * 0.5, bh = 14; + const bx = cx - bw / 2, by = H * 0.6; + ctx.strokeStyle = '#7df9ff'; + ctx.lineWidth = 2; + ctx.strokeRect(bx, by, bw, bh); + ctx.fillStyle = '#ff2d95'; + ctx.fillRect(bx + 2, by + 2, (bw - 4) * Math.min(1, game.loadProgress), bh - 4); + text(ctx, Math.floor(Math.min(1, game.loadProgress) * 100) + '%', cx, by + bh + 16, 8, '#ffffff'); + break; + } + + case STATE.TITLE: { + ctx.fillStyle = 'rgba(5,1,13,0.75)'; + ctx.fillRect(0, 0, W, H); + glowText(ctx, game.title, cx, H * 0.28, 22, '#ff2d95'); + text(ctx, 'DIGITAL SPACEPORT ARCADE', cx, H * 0.28 + 30, 7, '#7df9ff'); + if (blink(0)) text(ctx, 'PRESS SPACE TO START', cx, H * 0.55, 10, '#ffe14d'); + text(ctx, 'HI-SCORE: ' + String(game.best).padStart(6, '0'), cx, H * 0.68, 8, '#7dff5e'); + text(ctx, 'M: MUTE P: PAUSE', cx, H * 0.85, 7, '#8a7fb5'); + break; + } + + case STATE.PLAYING: + case STATE.PAUSED: { + ctx.fillStyle = 'rgba(5,1,13,0.55)'; + ctx.fillRect(0, 0, W, 26); + text(ctx, 'SCORE ' + String(game.score).padStart(6, '0'), 8, 13, 8, '#ffffff', 'left'); + text(ctx, 'HI ' + String(game.best).padStart(6, '0'), W / 2, 13, 8, '#7dff5e'); + text(ctx, 'LV ' + game.level, W - 8, 13, 8, '#ffe14d', 'right'); + const hearts = Math.min(game.lives, 5); + for (let i = 0; i < hearts; i++) { + const img = window.DSP_SPRITES.get('heart', 2); + ctx.drawImage(img, 8 + i * 18, 32); + } + if (game.mouseCaptured !== undefined) { + if (game.mouseCaptured) { + text(ctx, 'MOUS FANGEN - ESC FREIGIBT', W - 8, 44, 7, '#ff7de9', 'right'); + } else if (game.state === STATE.PLAYING) { + text(ctx, 'KLICK IN CANVAS = MAUS FANGEN', W - 8, 44, 7, '#8a7fb5', 'right'); + } + } + if (game.state === STATE.PAUSED) { + ctx.fillStyle = 'rgba(5,1,13,0.7)'; + ctx.fillRect(0, 0, W, H); + glowText(ctx, 'PAUSED', cx, H * 0.45, 20, '#7df9ff'); + text(ctx, 'PRESS P TO RESUME', cx, H * 0.58, 8, '#ffffff'); + } + break; + } + + case STATE.GAMEOVER: { + ctx.fillStyle = 'rgba(5,1,13,0.8)'; + ctx.fillRect(0, 0, W, H); + glowText(ctx, 'GAME OVER', cx, H * 0.3, 22, '#ff4d6d'); + text(ctx, 'SCORE ' + String(game.score).padStart(6, '0'), cx, H * 0.45, 10, '#ffffff'); + if (game.gameOverRank > 0) { + text(ctx, 'RANK #' + game.gameOverRank + ' ON THE BOARD', cx, H * 0.55, 8, '#ffe14d'); + } + if (blink(0)) text(ctx, 'PRESS SPACE', cx, H * 0.7, 9, '#7df9ff'); + break; + } + + case STATE.HIGHSCORE: { + ctx.fillStyle = 'rgba(5,1,13,0.85)'; + ctx.fillRect(0, 0, W, H); + glowText(ctx, 'NEW HIGH SCORE!', cx, H * 0.25, 14, '#ffe14d'); + text(ctx, 'SCORE ' + String(game.score).padStart(6, '0'), cx, H * 0.38, 10, '#ffffff'); + text(ctx, 'ENTER PILOT NAME', cx, H * 0.5, 8, '#7df9ff'); + const name = (game._hsName || '').toUpperCase(); + const cursor = blink(0) ? '_' : ' '; + text(ctx, (name + cursor).padStart(9, ' '), cx, H * 0.6, 14, '#7dff5e'); + text(ctx, 'TYPE NAME + ENTER', cx, H * 0.72, 7, '#8a7fb5'); + break; + } + } + } + + window.addEventListener('keydown', (e) => { + const g = window.DSP_ACTIVE_GAME; + if (!g || g.state !== STATE.HIGHSCORE) return; + if (e.code === 'Enter') return; + if (e.code === 'Backspace') { + g._hsName = (g._hsName || '').slice(0, -1); + e.preventDefault(); + return; + } + if (e.key && e.key.length === 1 && /[a-zA-Z]/.test(e.key)) { + g._hsName = ((g._hsName || '') + e.key).slice(0, 8); + e.preventDefault(); + } + }); + + window.DSP_UI = { draw }; +})(); \ No newline at end of file