From 920a30ffa8ef4d12e5480a67d6390e8458d01c8c Mon Sep 17 00:00:00 2001 From: Jose Luis Date: Fri, 20 Mar 2026 02:32:35 +0100 Subject: [PATCH] fix: puzzle sidebar integrates into layout instead of overlapping waveform - Puzzle panel now shifts canvas and waveform viewer right (340px) instead of overlapping them, using body class toggle and CSS transitions - Canvas resize accounts for sidebar width - Progress (completed/unlocked levels, custom components) persists in localStorage - Level cards refresh on each panel open to reflect current progress Co-Authored-By: Claude Opus 4.6 --- css/style.css | 15 +++++++++++++-- js/levels.js | 44 +++++++++++++++++++++++++++++++++++++------- js/puzzleUI.js | 26 +++++++++++++++++++------- js/renderer.js | 4 +++- 4 files changed, 72 insertions(+), 17 deletions(-) diff --git a/css/style.css b/css/style.css index 636d36f..d41d6b9 100644 --- a/css/style.css +++ b/css/style.css @@ -73,6 +73,7 @@ body { position: fixed; top: 48px; left: 0; right: 0; bottom: 0; cursor: default; + transition: left 0.2s ease; } /* ==================== Waveform Panel ==================== */ @@ -85,6 +86,7 @@ body { border-top: 2px solid #00e599; z-index: 90; flex-direction: column; + transition: left 0.2s ease; } #waveform-panel.visible { display: flex; } @@ -248,14 +250,13 @@ body { position: fixed; top: 48px; left: 0; - width: 380px; + width: 340px; height: calc(100vh - 48px); background: #12121a; border-right: 1px solid #2a2a3a; z-index: 95; flex-direction: column; overflow-y: auto; - box-shadow: 2px 0 10px rgba(0,0,0,0.5); } .puzzle-panel.visible { @@ -267,6 +268,16 @@ body { width: 340px; } +/* When puzzle panel is visible, shift canvas and waveform */ +.puzzle-panel.visible ~ #canvas, +body.puzzle-sidebar-open #canvas { + left: 340px; +} + +body.puzzle-sidebar-open #waveform-panel { + left: 340px; +} + .puzzle-header { padding: 16px; background: #0a0a0f; diff --git a/js/levels.js b/js/levels.js index 3c66b1b..7ea1044 100644 --- a/js/levels.js +++ b/js/levels.js @@ -127,13 +127,39 @@ export const LEVELS = [ } ]; -// Progress tracking -export const progress = { - unlockedLevels: ['buffer'], - completedLevels: [], - currentLevel: null, - customComponents: {} // { name -> component definition } -}; +// Progress tracking — load from localStorage if available +function loadProgress() { + try { + const saved = localStorage.getItem('logiclab_progress'); + if (saved) { + const parsed = JSON.parse(saved); + return { + unlockedLevels: parsed.unlockedLevels || ['buffer'], + completedLevels: parsed.completedLevels || [], + currentLevel: null, + customComponents: parsed.customComponents || {} + }; + } + } catch (e) { /* ignore parse errors */ } + return { + unlockedLevels: ['buffer'], + completedLevels: [], + currentLevel: null, + customComponents: {} + }; +} + +function saveProgress() { + try { + localStorage.setItem('logiclab_progress', JSON.stringify({ + unlockedLevels: progress.unlockedLevels, + completedLevels: progress.completedLevels, + customComponents: progress.customComponents + })); + } catch (e) { /* ignore storage errors */ } +} + +export const progress = loadProgress(); /** * Get all available levels for display @@ -259,6 +285,8 @@ export function completeLevel(levelId) { progress.unlockedLevels.push(nextId); } } + + saveProgress(); } /** @@ -270,6 +298,7 @@ export function registerComponent(name, gateTypes, connections) { gateTypes, connections }; + saveProgress(); } /** @@ -294,4 +323,5 @@ export function resetProgress() { progress.completedLevels = []; progress.currentLevel = null; progress.customComponents = {}; + saveProgress(); } diff --git a/js/puzzleUI.js b/js/puzzleUI.js index ba65179..af098ee 100644 --- a/js/puzzleUI.js +++ b/js/puzzleUI.js @@ -31,10 +31,21 @@ function createLevelSelectionPanel() { `; document.body.appendChild(panel); - // Create level cards - const container = document.getElementById('levels-container'); - const categories = new Map(); + document.getElementById('close-levels').addEventListener('click', () => { + panel.classList.remove('visible'); + document.body.classList.remove('puzzle-sidebar-open'); + window.dispatchEvent(new Event('resize')); + }); +} +/** + * Refresh level cards to reflect current progress + */ +function refreshLevelCards() { + const container = document.getElementById('levels-container'); + container.innerHTML = ''; + + const categories = new Map(); LEVELS.forEach(level => { if (!categories.has(level.category)) { categories.set(level.category, []); @@ -72,10 +83,6 @@ function createLevelSelectionPanel() { container.appendChild(categoryEl); }); - - document.getElementById('close-levels').addEventListener('click', () => { - panel.classList.remove('visible'); - }); } /** @@ -168,6 +175,8 @@ export function setSandboxMode() { document.getElementById('level-panel').classList.remove('visible'); document.getElementById('puzzle-panel').classList.remove('visible'); document.getElementById('toolbar').classList.remove('puzzle-mode'); + document.body.classList.remove('puzzle-sidebar-open'); + window.dispatchEvent(new Event('resize')); } /** @@ -177,7 +186,10 @@ export function setPuzzleMode() { puzzleMode = true; document.getElementById('mode-puzzle').classList.add('active'); document.getElementById('mode-sandbox').classList.remove('active'); + refreshLevelCards(); document.getElementById('level-panel').classList.add('visible'); + document.body.classList.add('puzzle-sidebar-open'); + window.dispatchEvent(new Event('resize')); } /** diff --git a/js/renderer.js b/js/renderer.js index 2a778ce..9b9310d 100644 --- a/js/renderer.js +++ b/js/renderer.js @@ -15,7 +15,9 @@ export function initRenderer() { } export function resize() { - canvas.width = window.innerWidth; + const sidebarOpen = document.body.classList.contains('puzzle-sidebar-open'); + const sidebarW = sidebarOpen ? 340 : 0; + canvas.width = window.innerWidth - sidebarW; const waveH = state.waveformVisible ? state.waveformHeight : 0; canvas.height = window.innerHeight - 48 - waveH; }