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

@@ -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();
}

View File

@@ -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'));
}
/**

View File

@@ -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;
}