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;
|
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;
|
||||||
|
|||||||
44
js/levels.js
44
js/levels.js
@@ -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();
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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'));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|||||||
@@ -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;
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user