import { init as initScene, setPaintings } from '/static/scene.js'; // ── State ───────────────────────────────────────────────────────────────────── const state = { userId: localStorage.getItem('dm_user') || '', lastSketch: null, lastDream: null, activePanel: null, }; // ── API helper ──────────────────────────────────────────────────────────────── async function api(path, body) { const r = await fetch(path, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(body), }); return r.json(); } // ── Boot ────────────────────────────────────────────────────────────────────── document.addEventListener('DOMContentLoaded', async () => { initScene(document.getElementById('museum-canvas'), openDreamModal); if (state.userId) _applyAuth(state.userId); await _refreshMuseum(); _initHUD(); _initAuth(); _initDrawCanvas(); _initControls(); _initModal(); _initSaveBtns(); }); // ── Museum ──────────────────────────────────────────────────────────────────── async function _refreshMuseum() { const data = await api('/gallery/public', { limit: 12, offset: 0 }); if (data.ok) setPaintings(data.dreams); } // ── HUD / panels ────────────────────────────────────────────────────────────── function _initHUD() { document.getElementById('btn-create').addEventListener('click', () => _openPanel('create')); document.getElementById('btn-my-gallery').addEventListener('click', () => _openPanel('gallery')); document.getElementById('close-create').addEventListener('click', () => _openPanel(null)); document.getElementById('close-gallery').addEventListener('click', () => _openPanel(null)); } function _openPanel(name) { if (state.activePanel) { document.getElementById(`panel-${state.activePanel}`).classList.remove('open'); } state.activePanel = name; const canvas = document.getElementById('museum-canvas'); if (name) { const panel = document.getElementById(`panel-${name}`); panel.classList.remove('hidden'); requestAnimationFrame(() => panel.classList.add('open')); canvas.classList.add('dimmed'); document.getElementById('room-hint').classList.add('hidden'); if (name === 'gallery') _loadUserGallery(); } else { canvas.classList.remove('dimmed'); document.getElementById('room-hint').classList.remove('hidden'); } } // Hide panel after slide-out transition ends document.addEventListener('transitionend', e => { const el = e.target; if (el.classList.contains('side-panel') && !el.classList.contains('open')) { el.classList.add('hidden'); } }); // ── Auth ────────────────────────────────────────────────────────────────────── function _initAuth() { const input = document.getElementById('auth-input'); const btn = document.getElementById('auth-btn'); btn.addEventListener('click', () => { const v = input.value.trim(); if (v) _applyAuth(v); }); input.addEventListener('keydown', e => { if (e.key === 'Enter') btn.click(); }); } function _applyAuth(name) { state.userId = name; localStorage.setItem('dm_user', name); document.getElementById('auth-input').classList.add('hidden'); document.getElementById('auth-btn').classList.add('hidden'); const nameEl = document.getElementById('auth-name'); nameEl.textContent = `✦ ${name}`; nameEl.classList.remove('hidden'); _updateSaveArea(); } // ── Drawing canvas ───────────────────────────────────────────────────────────── function _initDrawCanvas() { const canvas = document.getElementById('draw-canvas'); const ctx = canvas.getContext('2d'); ctx.fillStyle = '#0f0d1e'; ctx.fillRect(0, 0, canvas.width, canvas.height); let drawing = false, tool = 'pen', color = '#ffffff', size = 6; // Brush preview dot const preview = document.getElementById('brush-preview'); function _updatePreview() { preview.style.width = Math.min(size, 24) + 'px'; preview.style.height = Math.min(size, 24) + 'px'; preview.style.background = tool === 'eraser' ? 'rgba(255,255,255,.3)' : color; } _updatePreview(); function pos(e) { const r = canvas.getBoundingClientRect(); const sx = canvas.width / r.width; const sy = canvas.height / r.height; const s = e.touches ? e.touches[0] : e; return { x: (s.clientX - r.left) * sx, y: (s.clientY - r.top) * sy }; } function down(e) { e.preventDefault(); drawing = true; const { x, y } = pos(e); ctx.beginPath(); ctx.moveTo(x, y); } function move(e) { e.preventDefault(); if (!drawing) return; const { x, y } = pos(e); ctx.lineWidth = tool === 'eraser' ? size * 3 : size; ctx.strokeStyle = tool === 'eraser' ? '#0f0d1e' : color; ctx.lineCap = ctx.lineJoin = 'round'; ctx.globalAlpha = 1; ctx.lineTo(x, y); ctx.stroke(); ctx.beginPath(); ctx.moveTo(x, y); } function up() { drawing = false; ctx.beginPath(); } canvas.addEventListener('mousedown', down); canvas.addEventListener('mousemove', move); canvas.addEventListener('mouseup', up); canvas.addEventListener('mouseleave', up); canvas.addEventListener('touchstart', down, { passive: false }); canvas.addEventListener('touchmove', move, { passive: false }); canvas.addEventListener('touchend', up); // Tool buttons document.getElementById('t-pen').addEventListener('click', () => { tool = 'pen'; _setActiveTool('t-pen'); _updatePreview(); }); document.getElementById('t-eraser').addEventListener('click', () => { tool = 'eraser'; _setActiveTool('t-eraser'); _updatePreview(); }); // Color picker document.getElementById('t-color').addEventListener('input', e => { color = e.target.value; tool = 'pen'; _setActiveTool('t-pen'); _updatePreview(); }); // Color swatches document.querySelectorAll('.swatch').forEach(s => { s.addEventListener('click', () => { color = s.dataset.color; document.getElementById('t-color').value = color; tool = 'pen'; _setActiveTool('t-pen'); _updatePreview(); }); }); // Size slider document.getElementById('t-size').addEventListener('input', e => { size = +e.target.value; _updatePreview(); }); // Clear document.getElementById('t-clear').addEventListener('click', () => { ctx.fillStyle = '#0f0d1e'; ctx.fillRect(0, 0, canvas.width, canvas.height); }); } function _setActiveTool(id) { document.querySelectorAll('.tool').forEach(t => t.classList.remove('active')); document.getElementById(id).classList.add('active'); } // ── Generation ───────────────────────────────────────────────────────────────── function _initControls() { const slider = document.getElementById('strength'); const valEl = document.getElementById('strength-val'); slider.addEventListener('input', () => { valEl.textContent = slider.value; }); document.getElementById('generate-btn').addEventListener('click', _generate); } async function _generate() { const desc = document.getElementById('dream-desc').value.trim(); if (!desc) { alert('Please describe your dream first.'); return; } const canvas = document.getElementById('draw-canvas'); const sketch = canvas.toDataURL('image/png'); const strength = parseFloat(document.getElementById('strength').value); const btn = document.getElementById('generate-btn'); const label = document.getElementById('gen-label'); const spin = document.getElementById('gen-spin'); btn.disabled = true; label.textContent = '✦ Materializing…'; spin.classList.remove('hidden'); try { const data = await api('/generate', { sketch_b64: sketch, description: desc, strength }); if (!data.ok) { alert('Error: ' + data.error); return; } state.lastSketch = sketch; state.lastDream = 'data:image/png;base64,' + data.image; document.getElementById('res-sketch').src = state.lastSketch; document.getElementById('res-dream').src = state.lastDream; document.getElementById('result-area').classList.remove('hidden'); _updateSaveArea(); } finally { btn.disabled = false; label.textContent = '✦ Materialize Dream'; spin.classList.add('hidden'); } } // ── Save buttons ─────────────────────────────────────────────────────────────── function _initSaveBtns() { document.getElementById('btn-publish').addEventListener('click', () => _save('public')); document.getElementById('btn-private').addEventListener('click', () => _save('private')); document.getElementById('btn-download').addEventListener('click', _download); } function _updateSaveArea() { if (!state.lastDream) return; document.getElementById('need-auth').classList.toggle('hidden', !!state.userId); document.getElementById('save-btns').classList.toggle('hidden', !state.userId); } async function _save(visibility) { const desc = document.getElementById('dream-desc').value.trim(); const data = await api('/gallery/save', { user_id: state.userId, sketch_b64: state.lastSketch, image_b64: state.lastDream, description: desc, visibility, }); if (data.ok) { alert(visibility === 'public' ? '◈ Dream exhibited in the Museum.' : 'Dream saved to your private gallery.'); if (visibility === 'public') _refreshMuseum(); } else { alert('Could not save: ' + data.error); } } function _download() { if (!state.lastDream) return; const a = document.createElement('a'); a.href = state.lastDream; a.download = 'sueno.png'; a.click(); } // ── My Gallery ───────────────────────────────────────────────────────────────── async function _loadUserGallery() { const grid = document.getElementById('my-grid'); if (!state.userId) { grid.innerHTML = '