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 <noreply@anthropic.com>
This commit is contained in:
Jose Luis
2026-03-20 02:32:35 +01:00
parent b2e367817c
commit 920a30ffa8
4 changed files with 72 additions and 17 deletions

View File

@@ -73,6 +73,7 @@ body {
position: fixed; position: fixed;
top: 48px; left: 0; right: 0; bottom: 0; top: 48px; left: 0; right: 0; bottom: 0;
cursor: default; cursor: default;
transition: left 0.2s ease;
} }
/* ==================== Waveform Panel ==================== */ /* ==================== Waveform Panel ==================== */
@@ -85,6 +86,7 @@ body {
border-top: 2px solid #00e599; border-top: 2px solid #00e599;
z-index: 90; z-index: 90;
flex-direction: column; flex-direction: column;
transition: left 0.2s ease;
} }
#waveform-panel.visible { display: flex; } #waveform-panel.visible { display: flex; }
@@ -248,14 +250,13 @@ body {
position: fixed; position: fixed;
top: 48px; top: 48px;
left: 0; left: 0;
width: 380px; width: 340px;
height: calc(100vh - 48px); height: calc(100vh - 48px);
background: #12121a; background: #12121a;
border-right: 1px solid #2a2a3a; border-right: 1px solid #2a2a3a;
z-index: 95; z-index: 95;
flex-direction: column; flex-direction: column;
overflow-y: auto; overflow-y: auto;
box-shadow: 2px 0 10px rgba(0,0,0,0.5);
} }
.puzzle-panel.visible { .puzzle-panel.visible {
@@ -267,6 +268,16 @@ body {
width: 340px; 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 { .puzzle-header {
padding: 16px; padding: 16px;
background: #0a0a0f; background: #0a0a0f;

View File

@@ -127,13 +127,39 @@ export const LEVELS = [
} }
]; ];
// Progress tracking // Progress tracking — load from localStorage if available
export const progress = { function loadProgress() {
unlockedLevels: ['buffer'], try {
completedLevels: [], const saved = localStorage.getItem('logiclab_progress');
currentLevel: null, if (saved) {
customComponents: {} // { name -> component definition } 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 * Get all available levels for display
@@ -259,6 +285,8 @@ export function completeLevel(levelId) {
progress.unlockedLevels.push(nextId); progress.unlockedLevels.push(nextId);
} }
} }
saveProgress();
} }
/** /**
@@ -270,6 +298,7 @@ export function registerComponent(name, gateTypes, connections) {
gateTypes, gateTypes,
connections connections
}; };
saveProgress();
} }
/** /**
@@ -294,4 +323,5 @@ export function resetProgress() {
progress.completedLevels = []; progress.completedLevels = [];
progress.currentLevel = null; progress.currentLevel = null;
progress.customComponents = {}; progress.customComponents = {};
saveProgress();
} }

View File

@@ -31,10 +31,21 @@ function createLevelSelectionPanel() {
`; `;
document.body.appendChild(panel); document.body.appendChild(panel);
// Create level cards document.getElementById('close-levels').addEventListener('click', () => {
const container = document.getElementById('levels-container'); panel.classList.remove('visible');
const categories = new Map(); 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 => { LEVELS.forEach(level => {
if (!categories.has(level.category)) { if (!categories.has(level.category)) {
categories.set(level.category, []); categories.set(level.category, []);
@@ -72,10 +83,6 @@ function createLevelSelectionPanel() {
container.appendChild(categoryEl); 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('level-panel').classList.remove('visible');
document.getElementById('puzzle-panel').classList.remove('visible'); document.getElementById('puzzle-panel').classList.remove('visible');
document.getElementById('toolbar').classList.remove('puzzle-mode'); 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; puzzleMode = true;
document.getElementById('mode-puzzle').classList.add('active'); document.getElementById('mode-puzzle').classList.add('active');
document.getElementById('mode-sandbox').classList.remove('active'); document.getElementById('mode-sandbox').classList.remove('active');
refreshLevelCards();
document.getElementById('level-panel').classList.add('visible'); document.getElementById('level-panel').classList.add('visible');
document.body.classList.add('puzzle-sidebar-open');
window.dispatchEvent(new Event('resize'));
} }
/** /**

View File

@@ -15,7 +15,9 @@ export function initRenderer() {
} }
export function resize() { 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; const waveH = state.waveformVisible ? state.waveformHeight : 0;
canvas.height = window.innerHeight - 48 - waveH; canvas.height = window.innerHeight - 48 - waveH;
} }