import { LessonEngine } from "./impl/LessonEngine.js"; import { renderLesson, renderModuleList, renderLevelIndicator, showFeedback } from "./helpers/renderer.js"; import { validateUserCode } from "./helpers/validator.js"; import { loadModules } from "./config/lessons.js"; // Main Application state const state = { currentModule: null, currentLessonIndex: 0, modules: [], userProgress: {} // Format: { moduleId: { completed: [0, 2, 3], current: 4 } } }; // DOM elements const elements = { moduleList: document.querySelector(".module-list"), lessonTitle: document.getElementById("lesson-title"), lessonDescription: document.getElementById("lesson-description"), taskInstruction: document.getElementById("task-instruction"), previewArea: document.getElementById("preview-area"), editorPrefix: document.getElementById("editor-prefix"), codeInput: document.getElementById("code-input"), editorSuffix: document.getElementById("editor-suffix"), prevBtn: document.getElementById("prev-btn"), nextBtn: document.getElementById("next-btn"), runBtn: document.getElementById("run-btn"), levelIndicator: document.getElementById("level-indicator"), modalContainer: document.getElementById("modal-container"), modalTitle: document.getElementById("modal-title"), modalContent: document.getElementById("modal-content"), modalClose: document.getElementById("modal-close"), moduleSelectorBtn: document.getElementById("module-selector-btn"), resetBtn: document.getElementById("reset-btn"), helpBtn: document.getElementById("help-btn"), lessonContainer: document.querySelector(".lesson-container"), editorContent: document.querySelector(".editor-content"), codeEditor: document.querySelector(".code-editor") }; // Initialize the lesson engine const lessonEngine = new LessonEngine(); // Load user progress from localStorage function loadUserProgress() { const savedProgress = localStorage.getItem("codeCrispiesProgress"); if (savedProgress) { state.userProgress = JSON.parse(savedProgress); } } // Save user progress to localStorage function saveUserProgress() { localStorage.setItem("codeCrispiesProgress", JSON.stringify(state.userProgress)); } // Initialize the module list async function initializeModules() { try { state.modules = await loadModules(); renderModuleList(elements.moduleList, state.modules, selectModule); // Select the first module or the last one user was on const lastModuleId = localStorage.getItem("lastModuleId"); if (lastModuleId && state.modules.find((m) => m.id === lastModuleId)) { selectModule(lastModuleId); } else if (state.modules.length > 0) { selectModule(state.modules[0].id); } // Update progress indicator on module selector button updateModuleSelectorButtonProgress(); } catch (error) { console.error("Failed to load modules:", error); elements.lessonDescription.textContent = "Failed to load modules. Please refresh the page."; } } // Update progress indicator on module selector button function updateModuleSelectorButtonProgress() { if (!state.modules.length) return; // Calculate overall progress across all modules let totalLessons = 0; let totalCompleted = 0; state.modules.forEach((module) => { totalLessons += module.lessons.length; const progress = state.userProgress[module.id]; if (progress && progress.completed) { totalCompleted += progress.completed.length; } }); const percentComplete = totalLessons > 0 ? Math.round((totalCompleted / totalLessons) * 100) : 0; // Create progress indicator const progressBar = document.createElement("div"); progressBar.className = "progress-indicator"; progressBar.style.cssText = ` position: absolute; bottom: 0; left: 0; height: 3px; width: ${percentComplete}%; background-color: var(--success-color); border-radius: 0 3px 3px 0; `; // Add progress percentage text elements.moduleSelectorBtn.innerHTML = `Progress ${percentComplete}%`; elements.moduleSelectorBtn.style.position = "relative"; // Remove any existing progress bar before adding new one const existingBar = elements.moduleSelectorBtn.querySelector(".progress-indicator"); if (existingBar) { existingBar.remove(); } elements.moduleSelectorBtn.appendChild(progressBar); } // Select a module function selectModule(moduleId) { const selectedModule = state.modules.find((module) => module.id === moduleId); if (!selectedModule) return; state.currentModule = selectedModule; // Update module list UI const moduleItems = elements.moduleList.querySelectorAll(".module-list-item"); moduleItems.forEach((item) => { item.classList.remove("active"); if (item.dataset.moduleId === moduleId) { item.classList.add("active"); } }); // Load user progress for this module if (!state.userProgress[moduleId]) { state.userProgress[moduleId] = { completed: [], current: 0 }; } state.currentLessonIndex = state.userProgress[moduleId].current || 0; loadCurrentLesson(); // Save the last selected module localStorage.setItem("lastModuleId", moduleId); // Reset any success indicators resetSuccessIndicators(); } // Reset success indicators function resetSuccessIndicators() { elements.codeEditor.classList.remove("success-highlight"); elements.lessonTitle.classList.remove("success-text"); elements.nextBtn.classList.remove("success"); elements.taskInstruction.classList.remove("success-instruction"); } // Load the current lesson function loadCurrentLesson() { if (!state.currentModule || !state.currentModule.lessons) { return; } // Make sure lesson index is in bounds if (state.currentLessonIndex >= state.currentModule.lessons.length) { state.currentLessonIndex = state.currentModule.lessons.length - 1; } else if (state.currentLessonIndex < 0) { state.currentLessonIndex = 0; } const lesson = state.currentModule.lessons[state.currentLessonIndex]; lessonEngine.setLesson(lesson); // Reset any success indicators resetSuccessIndicators(); // Update UI renderLesson( elements.lessonTitle, elements.lessonDescription, elements.taskInstruction, elements.previewArea, elements.editorPrefix, elements.codeInput, elements.editorSuffix, lesson ); // Update level indicator renderLevelIndicator(elements.levelIndicator, state.currentLessonIndex + 1, state.currentModule.lessons.length); // Update navigation buttons updateNavigationButtons(); // Save current progress state.userProgress[state.currentModule.id].current = state.currentLessonIndex; saveUserProgress(); // Update progress indicator on module selector button updateModuleSelectorButtonProgress(); // Focus on the code editor by default elements.codeInput.focus(); } // Update navigation buttons state function updateNavigationButtons() { elements.prevBtn.disabled = state.currentLessonIndex === 0; elements.nextBtn.disabled = !state.currentModule || state.currentLessonIndex === state.currentModule.lessons.length - 1; // Style changes for disabled buttons if (elements.prevBtn.disabled) { elements.prevBtn.classList.add("btn-disabled"); } else { elements.prevBtn.classList.remove("btn-disabled"); } if (elements.nextBtn.disabled) { elements.nextBtn.classList.add("btn-disabled"); } else { elements.nextBtn.classList.remove("btn-disabled"); } } // Go to the next lesson function nextLesson() { if (!state.currentModule) return; if (state.currentLessonIndex < state.currentModule.lessons.length - 1) { state.currentLessonIndex++; loadCurrentLesson(); } } // Go to the previous lesson function prevLesson() { if (state.currentLessonIndex > 0) { state.currentLessonIndex--; loadCurrentLesson(); } } // Run the user code function runCode() { const userCode = elements.codeInput.value; const lesson = state.currentModule.lessons[state.currentLessonIndex]; const validationResult = validateUserCode(userCode, lesson); if (validationResult.isValid) { // Mark lesson as completed const moduleProgress = state.userProgress[state.currentModule.id]; if (!moduleProgress.completed.includes(state.currentLessonIndex)) { moduleProgress.completed.push(state.currentLessonIndex); saveUserProgress(); updateModuleSelectorButtonProgress(); } // Show success feedback with visual indicators showFeedback(true, validationResult.message || "Great job! Your code works correctly."); // Add success visual indicators elements.codeEditor.classList.add("success-highlight"); elements.lessonTitle.classList.add("success-text"); elements.nextBtn.classList.add("success"); elements.taskInstruction.classList.add("success-instruction"); // Apply the code to see the result lessonEngine.applyUserCode(userCode); // Enable the next button if not already on the last lesson if (state.currentLessonIndex < state.currentModule.lessons.length - 1) { elements.nextBtn.disabled = false; elements.nextBtn.classList.remove("btn-disabled"); } } else { // Reset any success indicators resetSuccessIndicators(); // Show error feedback (with friendly message) showFeedback(false, validationResult.message || "Not quite there yet! Let's try again."); } } // Show the module selector modal function showModuleSelector() { elements.modalTitle.textContent = "Select a Module"; // Create module buttons const moduleButtons = state.modules.map((module) => { const button = document.createElement("button"); button.classList.add("btn", "module-button"); button.style.display = "block"; button.style.width = "100%"; button.style.marginBottom = "10px"; button.style.padding = "15px"; button.style.textAlign = "left"; // Add completion status const progress = state.userProgress[module.id]; const completedCount = progress ? progress.completed.length : 0; const totalLessons = module.lessons.length; const percentComplete = Math.round((completedCount / totalLessons) * 100); button.innerHTML = ` ${module.title}
Code Crispies is an interactive platform for learning CSS through practical exercises.
Select a module from the sidebar to start learning. Each module contains a series of lessons focused on specific CSS concepts.
For each lesson:
Are you sure you want to reset all your progress? This cannot be undone.