/* global Phaser, NetworkManager */ class MainMenuScene extends Phaser.Scene { constructor() { super({ key: 'MainMenuScene' }); } create() { const { width, height } = this.sys.game.config; // Connect to server on scene create NetworkManager.connect(); // Background const bg = this.add.graphics(); bg.fillStyle(0x1a1a2e, 1); bg.fillRect(0, 0, width, height); bg.fillStyle(0x16213e, 1); bg.fillRect(0, height * 0.5, width, height * 0.5); // Title this.add.text(width / 2, height * 0.22, 'iPuzzle', { fontFamily: 'Georgia, serif', fontSize: Math.round(height * 0.083) + 'px', color: '#e0e0ff', stroke: '#4444aa', strokeThickness: 6 }).setOrigin(0.5); this.add.text(width / 2, height * 0.36, 'Put the pieces together', { fontFamily: 'Georgia, serif', fontSize: Math.round(height * 0.026) + 'px', color: '#8888bb' }).setOrigin(0.5); this._buildDomUI(); } // ─── DOM UI ────────────────────────────────────────────────────────── _buildDomUI() { this._uiLayer = document.createElement('div'); Object.assign(this._uiLayer.style, { position: 'fixed', top: '50%', left: '50%', transform: 'translate(-50%, -50%)', width: '100vw', height: '56.25vw', maxHeight: '100vh', maxWidth: '177.78vh', pointerEvents: 'none', zIndex: '10', }); document.body.appendChild(this._uiLayer); const newBtn = this._makeDomBtn('New Puzzle', '#1a3366', '#2255aa', '#66aaff', () => { this.scene.start('NewPuzzleScene'); }); Object.assign(newBtn.style, { top: '55%', left: '50%', transform: 'translateX(-50%)', width: '18%', }); this._uiLayer.appendChild(newBtn); const joinBtn = this._makeDomBtn('Join Puzzle', '#1a3322', '#225533', '#44aa66', () => { this._showJoinDialog(); }); Object.assign(joinBtn.style, { top: '70%', left: '50%', transform: 'translateX(-50%)', width: '18%', }); this._uiLayer.appendChild(joinBtn); // Version credit — bottom right const credit = document.createElement('div'); Object.assign(credit.style, { position: 'absolute', bottom: '1%', right: '0.6%', color: '#333355', fontSize: '1.2vmin', fontFamily: 'Arial, sans-serif', }); credit.textContent = 'iPuzzle'; this._uiLayer.appendChild(credit); this.events.once('shutdown', () => this._destroyDomUI()); } _showJoinDialog() { if (this._joinDialogEl) return; const overlay = document.createElement('div'); Object.assign(overlay.style, { position: 'absolute', inset: '0', background: 'rgba(0,0,0,0.6)', pointerEvents: 'auto', display: 'flex', alignItems: 'center', justifyContent: 'center', }); const panel = document.createElement('div'); Object.assign(panel.style, { background: '#1a1a3e', border: '2px solid #4444aa', borderRadius: '6px', padding: '3vmin 4vmin', textAlign: 'center', display: 'flex', flexDirection: 'column', gap: '1.5vmin', }); const title = document.createElement('div'); Object.assign(title.style, { color: '#aaccff', fontSize: '2.4vmin', fontFamily: 'Arial, sans-serif' }); title.textContent = 'Enter Room Code'; const input = document.createElement('input'); Object.assign(input.style, { background: '#111133', color: '#eeeeff', border: '2px solid #4466aa', borderRadius: '4px', padding: '1vmin 1.5vmin', fontSize: '2.8vmin', fontFamily: 'monospace', textAlign: 'center', letterSpacing:'0.3em', outline: 'none', width: '14vmin', }); input.maxLength = 4; input.placeholder = 'ABCD'; input.autocomplete = 'off'; input.addEventListener('input', () => { input.value = input.value.toUpperCase().replace(/[^A-Z0-9]/g, ''); joinBtn.disabled = input.value.length !== 4; joinBtn.style.opacity = input.value.length === 4 ? '1' : '0.4'; }); const errorMsg = document.createElement('div'); Object.assign(errorMsg.style, { color: '#ff6666', fontSize: '1.5vmin', fontFamily: 'Arial, sans-serif', display: 'none', }); const btnRow = document.createElement('div'); Object.assign(btnRow.style, { display: 'flex', gap: '1.5vmin', justifyContent: 'center' }); const joinBtn = document.createElement('button'); joinBtn.textContent = 'Join'; Object.assign(joinBtn.style, { padding: '0.8vmin 3vmin', background: '#1a3322', color: '#ddeeff', border: '2px solid #44aa66', borderRadius: '4px', fontSize: '1.8vmin', fontFamily: 'Arial, sans-serif', cursor: 'pointer', opacity: '0.4', }); joinBtn.disabled = true; const cancelBtn = document.createElement('button'); cancelBtn.textContent = 'Cancel'; Object.assign(cancelBtn.style, { padding: '0.8vmin 3vmin', background: '#332222', color: '#ddeeff', border: '2px solid #aa4444', borderRadius: '4px', fontSize: '1.8vmin', fontFamily: 'Arial, sans-serif', cursor: 'pointer', }); btnRow.appendChild(joinBtn); btnRow.appendChild(cancelBtn); panel.appendChild(title); panel.appendChild(input); panel.appendChild(errorMsg); panel.appendChild(btnRow); overlay.appendChild(panel); this._uiLayer.appendChild(overlay); this._joinDialogEl = overlay; // Focus the input setTimeout(() => input.focus(), 50); // Enter key submits input.addEventListener('keydown', (e) => { if (e.key === 'Enter' && input.value.length === 4) { joinBtn.click(); } if (e.key === 'Escape') { cancelBtn.click(); } }); cancelBtn.addEventListener('click', () => { overlay.remove(); this._joinDialogEl = null; }); // Clicking overlay background dismisses overlay.addEventListener('click', (e) => { if (e.target === overlay) { overlay.remove(); this._joinDialogEl = null; } }); // Join logic const onJoinedOk = (msg) => { NetworkManager.off('room_joined', onJoinedOk); NetworkManager.off('error', onJoinError); // Start PuzzleScene with the state received from server this.scene.start('PuzzleScene', { imageKey: msg.state.imageKey, imagePath: msg.state.imagePath, pieceCount: msg.state.pieceCount, roomCode: msg.state.roomCode, _networkJoin: true, _networkState: msg.state, }); }; const onJoinError = (msg) => { NetworkManager.off('room_joined', onJoinedOk); NetworkManager.off('error', onJoinError); errorMsg.textContent = msg.message || 'Room not found'; errorMsg.style.display = 'block'; joinBtn.disabled = false; joinBtn.style.opacity = '1'; }; joinBtn.addEventListener('click', () => { const code = input.value.trim().toUpperCase(); if (code.length !== 4) return; errorMsg.style.display = 'none'; joinBtn.disabled = true; joinBtn.style.opacity = '0.4'; NetworkManager.on('room_joined', onJoinedOk); NetworkManager.on('error', onJoinError); NetworkManager.joinRoom(code); }); } _makeDomBtn(label, bgNormal, bgHover, borderColor, onClick) { const btn = document.createElement('button'); btn.textContent = label; Object.assign(btn.style, { position: 'absolute', padding: '1.2vmin 2vmin', background: bgNormal, color: '#ddeeff', border: `2px solid ${borderColor}`, borderRadius: '4px', fontSize: '2vmin', fontFamily: 'Arial, sans-serif', cursor: 'pointer', pointerEvents: 'auto', whiteSpace: 'nowrap', textAlign: 'center', }); btn.addEventListener('mouseenter', () => { btn.style.background = bgHover; }); btn.addEventListener('mouseleave', () => { btn.style.background = bgNormal; }); btn.addEventListener('mousedown', () => { btn.style.transform = (btn.style.transform || '') + ' scale(0.97)'; }); btn.addEventListener('mouseup', () => { btn.style.transform = btn.style.transform.replace(' scale(0.97)', ''); }); btn.addEventListener('click', onClick); return btn; } _destroyDomUI() { if (this._uiLayer && this._uiLayer.parentNode) { this._uiLayer.parentNode.removeChild(this._uiLayer); } this._uiLayer = null; } }