/* global Phaser, generateRoomCode */ const DIFFICULTIES = [ { pieces: 20, label: '20 Pieces' }, { pieces: 40, label: '40 Pieces' }, { pieces: 60, label: '60 Pieces' }, { pieces: 100, label: '100 Pieces' } ]; const BACKGROUNDS = [ { key: 'bg_dark_wood', path: 'assets/images/ui/dark_wood.jpg', label: 'Dark Wood' }, { key: 'bg_green_felt', path: 'assets/images/ui/green_felt.jpg', label: 'Green Felt' }, ]; class NewPuzzleScene extends Phaser.Scene { constructor() { super({ key: 'NewPuzzleScene' }); } preload() { // Load the puzzle list JSON; images are loaded dynamically in create() this.load.json('puzzle_list', 'assets/puzzles.json'); } create() { this.selectedImageIdx = null; this.selectedPieces = null; this.selectedBg = BACKGROUNDS[0]; // default to dark wood const W = this.sys.game.config.width; const H = this.sys.game.config.height; // Background this.add.graphics().fillStyle(0x1a1a2e, 1).fillRect(0, 0, W, H); // Title — fixed position (canvas is always 1920×1080) this.add.text(960, -30, 'Choose Your Puzzle', { fontFamily: 'Georgia, serif', fontSize: '58px', color: '#e8f4ff', stroke: '#1a4a88', strokeThickness: 6, shadow: { offsetX: 0, offsetY: 0, color: '#3388ff', blur: 28, fill: true, stroke: true } }).setOrigin(0.5, 0.5); // Decorative separator below the title const sep = this.add.graphics(); // Faint full-width rule sep.lineStyle(1, 0x223355, 0.7); sep.lineBetween(77, -81, 1843, -81); // Bright centre segment sep.lineStyle(2, 0x3377bb, 1); sep.lineBetween(653, -81, 1267, -81); // Diamond accent at the midpoint sep.fillStyle(0x66aaff, 1); sep.fillTriangle(960, 100, 966, 105, 960, 110); sep.fillTriangle(960, 100, 954, 105, 960, 110); // Read puzzle list from cache, then load any images not yet in the texture cache this._puzzleImages = this.cache.json.get('puzzle_list'); const toLoad = this._puzzleImages.filter(img => !this.textures.exists(img.key)); if (toLoad.length > 0) { toLoad.forEach(img => this.load.image(img.key, img.path)); this.load.once('complete', () => this._buildDomUI()); this.load.start(); } else { 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); this._buildPuzzleGrid(this._uiLayer); this._buildControlsBar(this._uiLayer); this.events.once('shutdown', () => this._destroyDomUI()); } _buildPuzzleGrid(uiLayer) { // Scrollable area: sits below the Phaser title (top 10%) and above the controls bar (bottom 28%) const scrollArea = document.createElement('div'); Object.assign(scrollArea.style, { position: 'absolute', top: '10%', left: '5%', width: '90%', height: '62%', overflowY: 'auto', overflowX: 'hidden', pointerEvents: 'auto', }); const grid = document.createElement('div'); Object.assign(grid.style, { display: 'grid', gridTemplateColumns: 'repeat(3, 1fr)', gap: '1.5vmin', padding: '1vmin', }); this._cardEls = this._puzzleImages.map((img, i) => { const card = document.createElement('div'); Object.assign(card.style, { cursor: 'pointer', border: '2px solid transparent', borderRadius: '6px', overflow: 'hidden', transition: 'border-color 0.15s, box-shadow 0.15s', background: '#111128', }); const thumb = document.createElement('img'); // Use thumbnail version: insert "thumb_" before the filename const lastSlash = img.path.lastIndexOf('/'); thumb.src = img.path.substring(0, lastSlash + 1) + 'thumb_' + img.path.substring(lastSlash + 1); Object.assign(thumb.style, { display: 'block', width: '100%', aspectRatio: '16 / 9', objectFit: 'cover', }); const label = document.createElement('div'); Object.assign(label.style, { padding: '0.5vmin 0.8vmin', color: '#8899cc', fontSize: '1.5vmin', fontFamily: 'Arial, sans-serif', textAlign: 'center', }); label.textContent = img.label; card.appendChild(thumb); card.appendChild(label); card.addEventListener('mouseenter', () => { if (this.selectedImageIdx !== i) { card.style.borderColor = '#334466'; } }); card.addEventListener('mouseleave', () => { if (this.selectedImageIdx !== i) { card.style.borderColor = 'transparent'; card.style.boxShadow = ''; } }); card.addEventListener('click', () => this._selectImage(i)); grid.appendChild(card); return card; }); scrollArea.appendChild(grid); uiLayer.appendChild(scrollArea); } _buildControlsBar(uiLayer) { const bar = document.createElement('div'); Object.assign(bar.style, { position: 'absolute', bottom: '0', left: '0', width: '100%', height: '28%', display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', gap: '1.5vmin', borderTop: '1px solid #222244', background: 'rgba(10, 10, 25, 0.7)', pointerEvents: 'auto', }); // "Difficulty" label const diffLabel = document.createElement('div'); Object.assign(diffLabel.style, { color: '#8899cc', fontSize: '1.8vmin', fontFamily: 'Arial, sans-serif', }); diffLabel.textContent = 'Difficulty'; // Difficulty buttons row const diffRow = document.createElement('div'); Object.assign(diffRow.style, { display: 'flex', gap: '2vmin', }); this._diffBtnEls = DIFFICULTIES.map((diff, i) => { const btn = document.createElement('button'); btn.textContent = diff.label; Object.assign(btn.style, { padding: '0.7vmin 2.5vmin', background: '#111133', color: '#ccddff', border: '1px solid #222255', borderRadius: '4px', fontSize: '1.6vmin', fontFamily: 'Arial, sans-serif', cursor: 'pointer', transition: 'background 0.1s, border-color 0.1s', }); btn.addEventListener('mouseenter', () => { if (this.selectedPieces !== diff.pieces) btn.style.background = '#1a3366'; }); btn.addEventListener('mouseleave', () => { if (this.selectedPieces !== diff.pieces) btn.style.background = '#111133'; }); btn.addEventListener('click', () => this._selectDifficulty(i, diff.pieces)); diffRow.appendChild(btn); return { el: btn, pieces: diff.pieces }; }); // "Background" label const bgLabel = document.createElement('div'); Object.assign(bgLabel.style, { color: '#8899cc', fontSize: '1.8vmin', fontFamily: 'Arial, sans-serif', }); bgLabel.textContent = 'Background'; // Background buttons row const bgRow = document.createElement('div'); Object.assign(bgRow.style, { display: 'flex', gap: '2vmin', }); this._bgBtnEls = BACKGROUNDS.map((bg, i) => { const btn = document.createElement('button'); btn.textContent = bg.label; const isDefault = bg.key === this.selectedBg.key; Object.assign(btn.style, { padding: '0.7vmin 2.5vmin', background: isDefault ? '#224488' : '#111133', color: '#ccddff', border: '1px solid ' + (isDefault ? '#44aaff' : '#222255'), borderRadius: '4px', fontSize: '1.6vmin', fontFamily: 'Arial, sans-serif', cursor: 'pointer', transition: 'background 0.1s, border-color 0.1s', }); btn.addEventListener('mouseenter', () => { if (this.selectedBg.key !== bg.key) btn.style.background = '#1a3366'; }); btn.addEventListener('mouseleave', () => { if (this.selectedBg.key !== bg.key) btn.style.background = '#111133'; }); btn.addEventListener('click', () => this._selectBackground(i)); bgRow.appendChild(btn); return { el: btn, bg }; }); // Action buttons row (Back left, Start right) const actionRow = document.createElement('div'); Object.assign(actionRow.style, { display: 'flex', justifyContent: 'center', gap: '4vmin', width: '55%', }); const backBtn = this._makeDomBtn('← Back', '#221133', '#332244', '#443366'); backBtn.style.color = '#8899cc'; backBtn.style.flex = '1'; backBtn.addEventListener('click', () => this.scene.start('MainMenuScene')); this._startBtnEl = this._makeDomBtn('Start Puzzle →', '#111111', '#111111', '#222222'); Object.assign(this._startBtnEl.style, { flex: '1', color: '#445544', cursor: 'not-allowed', }); this._startBtnEl.disabled = true; this._startBtnEl._bgNormal = '#111111'; this._startBtnEl._bgHover = '#111111'; this._startBtnEl.addEventListener('click', () => this._startPuzzle()); actionRow.appendChild(backBtn); actionRow.appendChild(this._startBtnEl); bar.appendChild(diffLabel); bar.appendChild(diffRow); bar.appendChild(bgLabel); bar.appendChild(bgRow); bar.appendChild(actionRow); uiLayer.appendChild(bar); } // ─── Selection ─────────────────────────────────────────────────────── _selectImage(idx) { this.selectedImageIdx = idx; this._cardEls.forEach((card, i) => { const sel = i === idx; card.style.borderColor = sel ? '#44aaff' : 'transparent'; card.style.boxShadow = sel ? '0 0 10px #44aaff88' : ''; }); this._refreshStartButton(); } _selectDifficulty(idx, pieces) { this.selectedPieces = pieces; this._diffBtnEls.forEach(({ el, pieces: p }) => { const sel = p === pieces; el.style.background = sel ? '#224488' : '#111133'; el.style.borderColor = sel ? '#44aaff' : '#222255'; }); this._refreshStartButton(); } _selectBackground(idx) { this.selectedBg = BACKGROUNDS[idx]; this._bgBtnEls.forEach(({ el, bg }) => { const sel = bg.key === this.selectedBg.key; el.style.background = sel ? '#224488' : '#111133'; el.style.borderColor = sel ? '#44aaff' : '#222255'; }); } _refreshStartButton() { if (!this._startBtnEl) return; const ready = this.selectedImageIdx !== null && this.selectedPieces !== null; this._startBtnEl.disabled = !ready; this._startBtnEl._bgNormal = ready ? '#115511' : '#111111'; this._startBtnEl._bgHover = ready ? '#227722' : '#111111'; Object.assign(this._startBtnEl.style, { background: this._startBtnEl._bgNormal, borderColor: ready ? '#44cc44' : '#222222', color: ready ? '#aaffaa' : '#445544', cursor: ready ? 'pointer' : 'not-allowed', }); } // ─── Helpers ───────────────────────────────────────────────────────── _makeDomBtn(label, bgNormal, bgHover, borderColor) { const btn = document.createElement('button'); btn.textContent = label; btn._bgNormal = bgNormal; btn._bgHover = bgHover; Object.assign(btn.style, { padding: '0.8vmin 0', background: bgNormal, color: '#ddeeff', border: `1px solid ${borderColor}`, borderRadius: '4px', fontSize: '1.7vmin', fontFamily: 'Arial, sans-serif', cursor: 'pointer', whiteSpace: 'nowrap', textAlign: 'center', }); btn.addEventListener('mouseenter', () => { if (!btn.disabled) btn.style.background = btn._bgHover; }); btn.addEventListener('mouseleave', () => { if (!btn.disabled) btn.style.background = btn._bgNormal; }); return btn; } _destroyDomUI() { if (this._uiLayer && this._uiLayer.parentNode) { this._uiLayer.parentNode.removeChild(this._uiLayer); } this._uiLayer = null; } // ─── Start puzzle ──────────────────────────────────────────────────── _startPuzzle() { if (this.selectedImageIdx === null || this.selectedPieces === null) return; const img = this._puzzleImages[this.selectedImageIdx]; this.scene.start('PuzzleScene', { imageKey: img.key, imagePath: img.path, pieceCount: this.selectedPieces, roomCode: generateRoomCode(), bgKey: this.selectedBg.key, bgPath: this.selectedBg.path, }); } }