diff --git a/src/app.js b/src/app.js
index 42d5bf7..54c2877 100644
--- a/src/app.js
+++ b/src/app.js
@@ -8,7 +8,8 @@ const state = {
currentModule: null,
currentLessonIndex: 0,
modules: [],
- userProgress: {} // Format: { moduleId: { completed: [0, 2, 3], current: 4 } }
+ userProgress: {}, // Format: { moduleId: { completed: [0, 2, 3], current: 4 } }
+ userCodeBeforeValidation: "" // Track user code state before validation
};
// DOM elements
@@ -97,14 +98,14 @@ function updateModuleSelectorButtonProgress() {
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;
- `;
+ 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}%`;
@@ -158,6 +159,34 @@ function resetSuccessIndicators() {
elements.taskInstruction.classList.remove("success-instruction");
}
+// Configure editor layout based on display type
+function configureEditorLayout(lesson) {
+ // Default to block display if not specified
+ const displayType = lesson.editorDisplayType || "block";
+
+ // Reset classes
+ elements.codeEditor.classList.remove("inline-editor", "block-editor");
+ elements.editorContent.classList.remove("inline-mode", "block-mode");
+
+ // Apply appropriate layout class
+ if (displayType === "inline") {
+ elements.codeEditor.classList.add("inline-editor");
+ elements.editorContent.classList.add("inline-mode");
+
+ // Add special styling for inline mode
+ elements.codeInput.style.display = "inline-block";
+ elements.codeInput.style.width = lesson.inlineInputWidth || "auto";
+ } else {
+ // Default block mode
+ elements.codeEditor.classList.add("block-editor");
+ elements.editorContent.classList.add("block-mode");
+
+ // Reset styles for block mode
+ elements.codeInput.style.display = "block";
+ elements.codeInput.style.width = "100%";
+ }
+}
+
// Load the current lesson
function loadCurrentLesson() {
if (!state.currentModule || !state.currentModule.lessons) {
@@ -189,6 +218,9 @@ function loadCurrentLesson() {
lesson
);
+ // Configure editor layout based on lesson settings
+ configureEditorLayout(lesson);
+
// Update level indicator
renderLevelIndicator(elements.levelIndicator, state.currentLessonIndex + 1, state.currentModule.lessons.length);
@@ -204,6 +236,39 @@ function loadCurrentLesson() {
// Focus on the code editor by default
elements.codeInput.focus();
+
+ // Store current code
+ state.userCodeBeforeValidation = elements.codeInput.value;
+
+ // Track live changes and update preview when the user pauses typing
+ setupLivePreview();
+}
+
+// Setup live preview functionality
+let previewTimer = null;
+function setupLivePreview() {
+ // Clear previous event listener if any
+ elements.codeInput.removeEventListener('input', handleUserInput);
+
+ // Add new event listener
+ elements.codeInput.addEventListener('input', handleUserInput);
+}
+
+// Handle user input with debounced preview updates
+function handleUserInput() {
+ // Clear the previous timer
+ if (previewTimer) {
+ clearTimeout(previewTimer);
+ }
+
+ // Set a new timer for preview update after user stops typing
+ previewTimer = setTimeout(() => {
+ // Apply the code for preview without validation
+ lessonEngine.applyUserCode(elements.codeInput.value);
+ }, 500); // Update preview 500ms after user stops typing
+
+ // Store current code state
+ state.userCodeBeforeValidation = elements.codeInput.value;
}
// Update navigation buttons state
@@ -248,6 +313,9 @@ function runCode() {
const userCode = elements.codeInput.value;
const lesson = state.currentModule.lessons[state.currentLessonIndex];
+ // Always apply the code to the preview, regardless of validation result
+ lessonEngine.applyUserCode(userCode, true);
+
const validationResult = validateUserCode(userCode, lesson);
if (validationResult.isValid) {
@@ -268,9 +336,6 @@ function runCode() {
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;
@@ -306,17 +371,17 @@ function showModuleSelector() {
const percentComplete = Math.round((completedCount / totalLessons) * 100);
button.innerHTML = `
- ${module.title}
-
- ${module.description}
-
-
-
- ${completedCount}/${totalLessons} lessons completed
-
- `;
+ ${module.title}
+
+ ${module.description}
+
+
+
+ ${completedCount}/${totalLessons} lessons completed
+
+ `;
button.addEventListener("click", () => {
selectModule(module.id);
@@ -341,38 +406,39 @@ function showHelp() {
elements.modalTitle.textContent = "Help";
elements.modalContent.innerHTML = `
- How to Use Code Crispies
- Code Crispies is an interactive platform for learning CSS through practical exercises.
-
- Getting Started
- Select a module from the sidebar to start learning. Each module contains a series of lessons focused on specific CSS concepts.
-
- Completing Lessons
- For each lesson:
-
- - Read the instructions and objective
- - Write your CSS code in the editor
- - Click "Run" to test your solution
- - If correct, you can proceed to the next lesson
-
-
- Controls
-
- - Run - Test your CSS code
- - Previous/Next - Navigate between lessons
- - Progress - Select a different learning module
- - Reset Progress - Clear all your saved progress
-
-
- Tips
-
- - Use the preview area to see how your CSS affects the elements
- - Your progress is automatically saved in your browser storage
- - You can revisit completed lessons at any time
- - Press Tab in the code editor to indent with two spaces
- - Use Ctrl+Enter to quickly run your code
-
- `;
+ How to Use Code Crispies
+ Code Crispies is an interactive platform for learning CSS through practical exercises.
+
+ Getting Started
+ Select a module from the sidebar to start learning. Each module contains a series of lessons focused on specific CSS concepts.
+
+ Completing Lessons
+ For each lesson:
+
+ - Read the instructions and objective
+ - Write your CSS code in the editor
+ - Click "Run" to test your solution
+ - If correct, you can proceed to the next lesson
+
+
+ Controls
+
+ - Run - Test your CSS code and apply it to the preview
+ - Previous/Next - Navigate between lessons
+ - Progress - Select a different learning module
+ - Reset Progress - Clear all your saved progress
+
+
+ Tips
+
+ - Your code changes will automatically preview as you type
+ - The preview area shows how your CSS affects the elements
+ - Your progress is automatically saved in your browser storage
+ - You can revisit completed lessons at any time
+ - Press Tab in the code editor to indent with two spaces
+ - Use Ctrl+Enter to quickly run your code
+
+ `;
elements.modalContainer.classList.remove("hidden");
}
@@ -382,12 +448,12 @@ function resetProgress() {
elements.modalTitle.textContent = "Reset Progress";
elements.modalContent.innerHTML = `
- Are you sure you want to reset all your progress? This cannot be undone.
-
-
-
-
- `;
+ Are you sure you want to reset all your progress? This cannot be undone.
+
+
+
+
+ `;
document.getElementById("cancel-reset").addEventListener("click", closeModal);
document.getElementById("confirm-reset").addEventListener("click", () => {
@@ -479,4 +545,4 @@ function init() {
}
// Start the application
-init();
+init();
\ No newline at end of file
diff --git a/src/config/lessons.js b/src/config/lessons.js
index 0bde848..03578b1 100644
--- a/src/config/lessons.js
+++ b/src/config/lessons.js
@@ -16,16 +16,16 @@ import responsiveConfig from "../../lessons/08-responsive.json";
// Module store
const moduleStore = [
- basicSelectorsConfig
- // basicsConfig,
- // boxModelConfig,
- // selectorsConfig,
- // colorsConfig,
- // typographyConfig,
- // unitVariablesConfig,
- // transitionsAnimationsConfig,
- // layoutConfig,
- // responsiveConfig
+ basicSelectorsConfig,
+ basicsConfig,
+ boxModelConfig,
+ selectorsConfig,
+ colorsConfig,
+ typographyConfig,
+ unitVariablesConfig,
+ transitionsAnimationsConfig,
+ layoutConfig,
+ responsiveConfig
];
/**
@@ -83,6 +83,15 @@ function validateModuleConfig(config) {
config.lessons.forEach((lesson, index) => {
if (!lesson.title) throw new Error(`Lesson ${index} missing "title"`);
if (!lesson.previewHTML) throw new Error(`Lesson ${index} missing "previewHTML"`);
+
+ // Apply defaults for new properties if they don't exist
+ if (lesson.editorDisplayType === undefined) {
+ lesson.editorDisplayType = "block"; // Default to block display
+ }
+
+ if (lesson.editorDisplayType === "inline" && lesson.inlineInputWidth === undefined) {
+ lesson.inlineInputWidth = "auto"; // Default width for inline input
+ }
});
}
@@ -111,3 +120,36 @@ export function addCustomModule(moduleConfig) {
return false;
}
}
+
+/**
+ * Convert a module to include the enhanced schema with editorDisplayType
+ * @param {Object} moduleConfig - The module configuration to convert
+ * @returns {Object} The enhanced module configuration
+ */
+export function enhanceModuleSchema(moduleConfig) {
+ if (!moduleConfig || !moduleConfig.lessons) return moduleConfig;
+
+ const enhancedModule = {...moduleConfig};
+
+ enhancedModule.lessons = moduleConfig.lessons.map(lesson => {
+ const enhancedLesson = {...lesson};
+
+ // Apply defaults for new properties if they don't exist
+ if (enhancedLesson.editorDisplayType === undefined) {
+ enhancedLesson.editorDisplayType = "block"; // Default to block display
+ }
+
+ if (enhancedLesson.editorDisplayType === "inline" && enhancedLesson.inlineInputWidth === undefined) {
+ enhancedLesson.inlineInputWidth = "auto"; // Default width for inline input
+ }
+
+ return enhancedLesson;
+ });
+
+ return enhancedModule;
+}
+
+// Enhance all modules on load to ensure they have the new schema properties
+moduleStore.forEach((module, index) => {
+ moduleStore[index] = enhanceModuleSchema(module);
+});
\ No newline at end of file
diff --git a/src/impl/LessonEngine.js b/src/impl/LessonEngine.js
index cd9bb1f..ad39063 100644
--- a/src/impl/LessonEngine.js
+++ b/src/impl/LessonEngine.js
@@ -11,6 +11,7 @@ export class LessonEngine {
this.userCode = "";
this.currentModule = null;
this.currentLessonIndex = 0;
+ this.lastRenderedCode = ""; // Track last applied code to prevent unnecessary re-renders
}
/**
@@ -32,6 +33,7 @@ export class LessonEngine {
setLesson(lesson) {
this.currentLesson = lesson;
this.userCode = lesson.initialCode || "";
+ this.lastRenderedCode = ""; // Reset last rendered code
this.renderPreview();
}
@@ -73,12 +75,34 @@ export class LessonEngine {
/**
* Apply user-written CSS to the preview area
* @param {string} code - User CSS code
+ * @param {boolean} forceUpdate - Force update the preview even if code hasn't changed
*/
- applyUserCode(code) {
+ applyUserCode(code, forceUpdate = false) {
if (!this.currentLesson) return;
this.userCode = code;
- this.renderPreview();
+
+ // Only re-render if code changed or forced update
+ if (forceUpdate || this.lastRenderedCode !== code) {
+ this.lastRenderedCode = code;
+ this.renderPreview();
+ }
+ }
+
+ /**
+ * Get the complete CSS by combining all parts
+ * @returns {string} The complete CSS
+ */
+ getCompleteCss() {
+ if (!this.currentLesson) return "";
+
+ const { codePrefix, codeSuffix } = this.currentLesson;
+
+ return `
+ ${codePrefix || ""}
+ ${this.userCode || ""}
+ ${codeSuffix || ""}
+ `;
}
/**
@@ -103,32 +127,35 @@ export class LessonEngine {
container.innerHTML = "";
container.appendChild(iframe);
+ // Get the complete CSS by combining all parts
+ const userCssWithWrapper = this.getCompleteCss();
+
// Create the complete CSS by combining base CSS with user code and sandbox CSS
const combinedCSS = `
- /* Base CSS */
- ${previewBaseCSS || ""}
-
- /* User Code */
- ${this.userCode || ""}
-
- /* Sandbox CSS (for visualizing the exercise) */
- ${sandboxCSS || ""}
- `;
+ /* Base CSS */
+ ${previewBaseCSS || ""}
+
+ /* User Code */
+ ${userCssWithWrapper || ""}
+
+ /* Sandbox CSS (for visualizing the exercise) */
+ ${sandboxCSS || ""}
+ `;
// Write the content to the iframe
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
iframeDoc.open();
iframeDoc.write(`
-
-
-
-
-
-
- ${previewHTML || "No preview available
"}
-
-
- `);
+
+
+
+
+
+
+ ${previewHTML || "No preview available
"}
+
+
+ `);
iframeDoc.close();
}
@@ -176,7 +203,7 @@ export class LessonEngine {
timestamp: new Date().toISOString()
};
- localStorage.setItem("cssQuest_progress", JSON.stringify(progressData));
+ localStorage.setItem("codeCrispies.progress", JSON.stringify(progressData));
}
/**
@@ -185,7 +212,7 @@ export class LessonEngine {
* @returns {Object|null} Loaded progress data or null if not found
*/
loadProgress(modules) {
- const savedProgress = localStorage.getItem("cssQuest_progress");
+ const savedProgress = localStorage.getItem("codeCrispies.progress");
if (!savedProgress) return null;
try {
@@ -225,6 +252,6 @@ export class LessonEngine {
* Clear all saved progress
*/
clearProgress() {
- localStorage.removeItem("cssQuest_progress");
+ localStorage.removeItem("codeCrispies.progress");
}
-}
+}
\ No newline at end of file
diff --git a/src/main.css b/src/main.css
index 4ea214c..9d5fa67 100644
--- a/src/main.css
+++ b/src/main.css
@@ -271,6 +271,15 @@ code {
overflow: hidden;
}
+.code-editor.block-editor {
+ display: block;
+}
+
+.code-editor.inline-editor {
+ display: inline-block;
+ width: 100%;
+}
+
.editor-header {
background-color: var(--code-bg);
padding: var(--spacing-xs) var(--spacing-md);
@@ -283,11 +292,13 @@ code {
}
.editor-content {
+ display: flex;
+ flex-direction: column;
background-color: var(--editor-bg);
color: #d4d4d4;
padding: var(--spacing-md);
- margin-bottom: 4rem;
- overflow-y: auto;
+ /*margin-bottom: 4rem;*/
+ overflow-y: scroll;
height: 100%;
font-family: var(--font-code);
font-size: 14px;
@@ -314,6 +325,8 @@ code {
}
.code-input {
+ flex: 1;
+ display: block;
background-color: transparent;
color: #d4d4d4;
border: none;