import { Scene } from 'phaser'; import { sound } from '../managers/SoundManager.js'; import { AchievementsManager } from '../managers/AchievementsManager.js'; import { StatsManager } from '../managers/StatsManager.js'; import { ParticleManager } from '../managers/ParticleManager.js'; import { createButton } from '../utils/ui.js'; import { storage, KEYS } from '../utils/storage.js'; import { todaySeed } from '../utils/random.js'; export class MenuScene extends Scene { constructor() { super({ key: 'MenuScene' }); } create() { const { width, height } = this.scale; this.add.tileSprite(width / 2, height / 2, width, height, 'gridBg'); this.add.text(width / 2, height * 0.16, 'NADDIE JUMP', { fontFamily: '"Press Start 2P", monospace', fontSize: '38px', color: '#d8b4fe', align: 'center', }).setOrigin(0.5).setShadow(4, 4, '#581c87', 0, false, true); this.add.text(width / 2, height * 0.25, 'MONAD EDITION', { fontFamily: '"Press Start 2P", monospace', fontSize: '14px', color: '#a855f7', align: 'center', }).setOrigin(0.5); const preview = this.add.image(width / 2, height * 0.44, 'player_idle').setScale(0.55); this.tweens.add({ targets: preview, y: height * 0.44 - 15, duration: 1400, yoyo: true, repeat: -1, ease: 'Sine.easeInOut' }); this.tweens.add({ targets: preview, angle: 5, duration: 2000, yoyo: true, repeat: -1, ease: 'Sine.easeInOut' }); createButton(this, width / 2, height * 0.585, 'START GAME', () => this.startGame(), { width: 280, height: 54, fontSize: '15px', }); this.add.text(width / 2, height * 0.635, 'ENTER / SPACE / TAP', { fontFamily: '"Press Start 2P", monospace', fontSize: '9px', color: '#888', align: 'center', }).setOrigin(0.5); this.input.keyboard.on('keydown-ENTER', () => this.startGame()); this.input.keyboard.on('keydown-SPACE', () => this.startGame()); createButton(this, width / 2, height * 0.69, 'DAILY CHALLENGE', () => this.startDaily(), { width: 280, height: 46, fontSize: '12px', bgColor: 0x854d0e, hoverColor: 0xa16207, strokeColor: 0xffd700, }); createButton(this, width / 2 - 75, height * 0.775, 'BOARD', () => this.showLeaderboard(), { width: 140, height: 42, fontSize: '11px', }); createButton(this, width / 2 + 75, height * 0.775, 'BADGES', () => this.showAchievements(), { width: 140, height: 42, fontSize: '11px', }); createButton(this, width / 2 - 75, height * 0.85, 'STATS', () => this.showStats(), { width: 140, height: 42, fontSize: '11px', }); createButton(this, width / 2 + 75, height * 0.85, 'SETTINGS', () => this.showSettings(), { width: 140, height: 42, fontSize: '11px', }); this.createMuteButton(); this.add.text(width / 2, height * 0.95, 'Web3 integration coming soon', { fontFamily: '"Press Start 2P", monospace', fontSize: '9px', color: '#444', align: 'center', }).setOrigin(0.5); this.createAmbientParticles(); this.input.once('pointerdown', () => { sound.init(); sound.resume(); }); this.input.keyboard.once('keydown', () => { sound.init(); sound.resume(); }); } startGame() { sound.init(); sound.resume(); sound.click(); this.scene.start('GameScene', { mode: 'normal' }); } startDaily() { sound.init(); sound.resume(); sound.click(); this.scene.start('GameScene', { mode: 'daily', seed: todaySeed() }); } createMuteButton() { const { width } = this.scale; const icon = this.add.text(width - 30, 30, sound.isMuted() ? '🔇' : '🔊', { fontSize: '24px', }).setOrigin(0.5).setInteractive({ useHandCursor: true }); icon.on('pointerdown', () => { sound.init(); const next = !sound.isMuted(); sound.setMuted(next); icon.setText(next ? '🔇' : '🔊'); if (!next) sound.click(); }); } showLeaderboard() { const { width, height } = this.scale; const overlay = this.add.rectangle(width / 2, height / 2, width, height, 0x000000, 0.7).setDepth(100); const panel = this.add.rectangle(width / 2, height / 2, 400, 440, 0x1a0533).setStrokeStyle(3, 0xa855f7).setDepth(101); const title = this.add.text(width / 2, height * 0.22, 'LEADERBOARD', { fontFamily: '"Press Start 2P", monospace', fontSize: '18px', color: '#d8b4fe', }).setOrigin(0.5).setDepth(102); const close = this.add.text(width / 2 + 170, height * 0.22 - 80, 'X', { fontFamily: '"Press Start 2P", monospace', fontSize: '18px', color: '#fff', }).setOrigin(0.5).setDepth(102).setInteractive({ useHandCursor: true }); const rows = []; const mock = [ { rank: 1, addr: '0xMonad...Dev', score: 99999 }, { rank: 2, addr: '0xAlice...xyz', score: 87500 }, { rank: 3, addr: '0xBob...abc', score: 74200 }, { rank: 4, addr: '0xYou', score: storage.getInt(KEYS.best, 0) }, ]; mock.forEach((entry, i) => { const y = height * 0.32 + i * 55; rows.push({ rank: this.add.text(width / 2 - 160, y, `#${entry.rank}`, { fontFamily: '"Press Start 2P", monospace', fontSize: '12px', color: '#a855f7' }).setOrigin(0.5).setDepth(102), addr: this.add.text(width / 2, y, entry.addr, { fontFamily: '"Press Start 2P", monospace', fontSize: '10px', color: '#fff' }).setOrigin(0.5).setDepth(102), score: this.add.text(width / 2 + 150, y, String(entry.score), { fontFamily: '"Press Start 2P", monospace', fontSize: '12px', color: '#d8b4fe' }).setOrigin(0.5).setDepth(102), }); }); close.on('pointerdown', () => { sound.click(); overlay.destroy(); panel.destroy(); title.destroy(); close.destroy(); rows.forEach(r => { r.rank.destroy(); r.addr.destroy(); r.score.destroy(); }); }); } showAchievements() { const { width, height } = this.scale; const all = AchievementsManager.getAll(); const unlocked = storage.getJSON(KEYS.achievements, {}) || {}; const elements = []; const overlay = this.add.rectangle(width / 2, height / 2, width, height, 0x000000, 0.75).setDepth(100); const panel = this.add.rectangle(width / 2, height / 2, 420, 700, 0x1a0533).setStrokeStyle(3, 0xa855f7).setDepth(101); const total = all.length; const got = Object.keys(unlocked).length; const title = this.add.text(width / 2, height * 0.12, `ACHIEVEMENTS ${got}/${total}`, { fontFamily: '"Press Start 2P", monospace', fontSize: '14px', color: '#d8b4fe', }).setOrigin(0.5).setDepth(102); elements.push(title); const close = this.add.text(width / 2 + 190, height * 0.12 - 5, 'X', { fontFamily: '"Press Start 2P", monospace', fontSize: '18px', color: '#fff', }).setOrigin(0.5).setDepth(102).setInteractive({ useHandCursor: true }); elements.push(close); all.forEach((def, i) => { const y = height * 0.18 + i * 55; const isGot = !!unlocked[def.id]; const icon = this.add.text(width / 2 - 180, y, isGot ? '★' : '☆', { fontFamily: '"Press Start 2P", monospace', fontSize: '18px', color: isGot ? '#ffd700' : '#555', }).setOrigin(0.5).setDepth(102); const nameText = this.add.text(width / 2 - 150, y - 8, def.title, { fontFamily: '"Press Start 2P", monospace', fontSize: '10px', color: isGot ? '#ffd700' : '#888', }).setOrigin(0, 0.5).setDepth(102); const descText = this.add.text(width / 2 - 150, y + 10, def.desc, { fontFamily: '"Press Start 2P", monospace', fontSize: '8px', color: '#d8b4fe', }).setOrigin(0, 0.5).setDepth(102); elements.push(icon, nameText, descText); }); close.on('pointerdown', () => { sound.click(); overlay.destroy(); panel.destroy(); elements.forEach((e) => e.destroy()); }); } _openModal(titleText, panelH = 480) { const { width, height } = this.scale; const elements = []; const overlay = this.add.rectangle(width / 2, height / 2, width, height, 0x000000, 0.75).setDepth(100) .setInteractive(); const panel = this.add.rectangle(width / 2, height / 2, 420, panelH, 0x1a0533).setStrokeStyle(3, 0xa855f7).setDepth(101); const title = this.add.text(width / 2, height / 2 - panelH / 2 + 34, titleText, { fontFamily: '"Press Start 2P", monospace', fontSize: '15px', color: '#d8b4fe', }).setOrigin(0.5).setDepth(102); const close = this.add.text(width / 2 + 185, height / 2 - panelH / 2 + 22, 'X', { fontFamily: '"Press Start 2P", monospace', fontSize: '18px', color: '#fff', }).setOrigin(0.5).setDepth(102).setInteractive({ useHandCursor: true }); elements.push(overlay, panel, title, close); const api = { elements, add: (obj) => { obj.setDepth(102); elements.push(obj); return obj; }, close: () => { sound.click(); elements.forEach((e) => e.destroy()); }, }; close.on('pointerdown', api.close); return api; } showStats() { const { width, height } = this.scale; const s = StatsManager.load(); const m = this._openModal('STATS', 420); const rows = [ ['Games played', s.gamesPlayed], ['Total jumps', s.totalJumps], ['Enemies stomped', s.totalStomps], ['Blocks climbed', s.totalBlocks], ['Best combo', `x${(s.bestCombo || 1).toFixed(1)}`], ['Best score', storage.getInt(KEYS.best, 0)], ]; rows.forEach((row, i) => { const y = height / 2 - 100 + i * 42; m.add(this.add.text(width / 2 - 170, y, row[0], { fontFamily: '"Press Start 2P", monospace', fontSize: '10px', color: '#d8b4fe', }).setOrigin(0, 0.5)); m.add(this.add.text(width / 2 + 170, y, String(row[1]), { fontFamily: '"Press Start 2P", monospace', fontSize: '11px', color: '#ffd700', }).setOrigin(1, 0.5)); }); } showSettings() { const { width, height } = this.scale; const m = this._openModal('SETTINGS', 460); const cx = width / 2; let baseY = height / 2 - 130; // Volume stepper m.add(this.add.text(cx, baseY, 'VOLUME', { fontFamily: '"Press Start 2P", monospace', fontSize: '11px', color: '#d8b4fe', }).setOrigin(0.5)); const volText = this.add.text(cx, baseY + 34, `${Math.round(sound.getVolume() * 100)}%`, { fontFamily: '"Press Start 2P", monospace', fontSize: '13px', color: '#ffd700', }).setOrigin(0.5); m.add(volText); const stepVol = (delta) => { sound.init(); sound.setVolume(Math.round((sound.getVolume() + delta) * 10) / 10); if (sound.isMuted() && sound.getVolume() > 0) sound.setMuted(false); volText.setText(`${Math.round(sound.getVolume() * 100)}%`); sound.click(); }; m.add(createButton(this, cx - 90, baseY + 34, '-', () => stepVol(-0.1), { width: 44, height: 36, fontSize: '14px' }).bg); m.add(createButton(this, cx + 90, baseY + 34, '+', () => stepVol(0.1), { width: 44, height: 36, fontSize: '14px' }).bg); // Particle quality toggle baseY += 96; m.add(this.add.text(cx, baseY, 'PARTICLE QUALITY', { fontFamily: '"Press Start 2P", monospace', fontSize: '11px', color: '#d8b4fe', }).setOrigin(0.5)); const currentQ = () => storage.getItem(KEYS.particleQuality, ParticleManager.resolveQuality(this)); const qBtn = createButton(this, cx, baseY + 34, currentQ().toUpperCase(), () => { const next = currentQ() === 'low' ? 'high' : 'low'; storage.setItem(KEYS.particleQuality, next); qBtn.label.setText(next.toUpperCase()); sound.click(); }, { width: 160, height: 38, fontSize: '12px' }); m.add(qBtn.bg); m.add(qBtn.label); // Reset progress (two-step confirm) baseY += 96; let armed = false; const resetBtn = createButton(this, cx, baseY + 10, 'RESET PROGRESS', () => { if (!armed) { armed = true; resetBtn.label.setText('TAP AGAIN!'); resetBtn.bg.setFillStyle(0x7f1d1d); return; } StatsManager.reset(); storage.removeItem(KEYS.best); storage.removeItem(KEYS.achievements); resetBtn.label.setText('DONE'); sound.click(); }, { width: 240, height: 42, fontSize: '12px', bgColor: 0x581c87, hoverColor: 0x7e22ce }); m.add(resetBtn.bg); m.add(resetBtn.label); } createAmbientParticles() { const { width, height } = this.scale; for (let i = 0; i < 40; i++) { const s = this.add.image(Phaser.Math.Between(0, width), Phaser.Math.Between(0, height), 'star'); s.setAlpha(Phaser.Math.FloatBetween(0.15, 0.7)); this.tweens.add({ targets: s, y: s.y - Phaser.Math.Between(50, 250), alpha: 0, duration: Phaser.Math.Between(2000, 6000), repeat: -1, delay: Phaser.Math.Between(0, 3000), }); } } }