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:
@@ -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;
|
||||
|
||||
38
js/levels.js
38
js/levels.js
@@ -127,13 +127,39 @@ export const LEVELS = [
|
||||
}
|
||||
];
|
||||
|
||||
// Progress tracking
|
||||
export const progress = {
|
||||
// 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: {} // { name -> component definition }
|
||||
};
|
||||
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();
|
||||
}
|
||||
|
||||
@@ -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'));
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user