diff --git a/lessons/01-advanced-selectors.json b/lessons/01-advanced-selectors.json index f856f06..3ca7892 100644 --- a/lessons/01-advanced-selectors.json +++ b/lessons/01-advanced-selectors.json @@ -17,12 +17,12 @@ "initialCode": "", "codeSuffix": "", "previewContainer": "preview-area", - "solution": "input[type=\"text\"] {\n background-color: lightblue;\n border: 2px solid blue;\n}", + "solution": "input[type=\"text\"] {\n background-color: lightblue;\n border: 2px solid blue\n}", "validations": [ { "type": "regex", "value": "^input\\[type=\"text\"\\]\\s*{", - "message": "Use input[type=\"text\"] as your attribute selector", + "message": "Use input[type=\"text\"] { â€Ļ } as your attribute selector", "options": { "caseSensitive": true } @@ -40,6 +40,11 @@ }, "message": "Set the background color to lightblue" }, + { + "type": "regex", + "value": "background-color:\\s*[^;]*;", + "message": "Make sure to close the background-color declaration with a semicolon ;" + }, { "type": "contains", "value": "border:", @@ -67,7 +72,7 @@ "id": "attribute-partial-matching", "title": "Attribute Partial Matching", "description": "Attribute selectors support partial matching patterns that let you target elements based on portions of attribute values. The [attribute^=\"value\"] selector matches elements where the attribute starts with the specified value, [attribute$=\"value\"] matches where it ends with the value, and [attribute*=\"value\"] matches where the value appears anywhere within the attribute. These patterns are particularly useful for styling external links, file types, or elements with class names that follow naming conventions. When styling these matched elements, you can use properties like color to change text color and text-decoration to add visual emphasis like underlines.", - "task": "Create a CSS rule that targets all anchor elements (a) with href attributes that start with \"https\". Make the text green and add an underline.", + "task": "Create a CSS rule that targets all anchor elements (a) with href attributes starting with \"https\". Style them with green text color and underline text decoration.", "previewHTML": "

Different Types of Links

\n", "previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } ul { list-style-type: none; padding: 0; } li { margin-bottom: 8px; } a { text-decoration: none; }", "sandboxCSS": "", @@ -80,7 +85,7 @@ { "type": "regex", "value": "^a\\[href\\^=\"https\"\\]\\s*{", - "message": "Use a[href^=\"https\"] to target links starting with https", + "message": "Use a[href^=\"https\"] { â€Ļ } as your attribute selector to target HTTPS links", "options": { "caseSensitive": true } @@ -88,7 +93,7 @@ { "type": "contains", "value": "color:", - "message": "Include the color property" + "message": "Include the color property to set the text color" }, { "type": "property_value", @@ -96,12 +101,13 @@ "property": "color", "expected": "green" }, - "message": "Set the color to green" + "message": "Set the text color to green" }, + { "type": "contains", "value": "text-decoration:", - "message": "Include the text-decoration property" + "message": "Include the text-decoration property to style the link appearance" }, { "type": "property_value", @@ -109,8 +115,9 @@ "property": "text-decoration", "expected": "underline" }, - "message": "Set text-decoration to underline" + "message": "Set text-decoration to underline to add underlines to HTTPS links" }, + { "type": "regex", "value": "a\\[href\\^=\"https\"\\]\\s*{[^}]*}", @@ -124,54 +131,54 @@ { "id": "child-combinator", "title": "Child Combinator: Direct Children Only", - "description": "The child combinator (>) selects elements that are direct children of another element, not grandchildren or deeper descendants. For example, ul > li selects list items that are immediate children of unordered lists, but not list items nested inside other list items. This precise targeting is useful when you want to style only the top level of nested structures like navigation menus or nested lists. When styling direct children, you can use properties like font-weight to make text bold and background-color to highlight specific elements. The child combinator gives you more control than the descendant selector by limiting selection to one level deep.", - "task": "Use the child combinator to target only the direct li children of the ul with class menu. Make them bold and give them a lightgray background.", - "previewHTML": "", - "previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } ul { margin-bottom: 10px; } li { padding: 5px; margin-bottom: 3px; border: 1px dashed #ccc; }", + "description": "The child combinator (>) selects elements that are direct children of another element, not grandchildren or deeper descendants. This is crucial when you have nested structures where you want to style only the outer level. For example, in a navigation menu with dropdowns, you might want main menu items to have different styling than submenu items. The child combinator (>) gives you surgical precision - ul > li targets only direct list items, while ul li would target ALL list items including nested ones. This prevents style inheritance chaos in complex layouts.", + "task": "Use the child combinator to target only the direct li children of .main-nav. Give them a cornflowerblue background and white text color. Notice how the nested submenu items remain completely unstyled!", + "previewHTML": "", + "previewBaseCSS": "body { font-family: sans-serif; padding: 20px; background: #f5f5f5; } .main-nav { background: white; border-radius: 8px; padding: 0; margin: 0; box-shadow: 0 2px 8px rgba(0,0,0,0.1); list-style: none; } .main-nav li { padding: 12px 16px; margin: 2px 0; cursor: pointer; transition: all 0.2s; } .main-nav ul { margin: 8px 0 0 20px; padding: 0; list-style: none; }", "sandboxCSS": "", - "codePrefix": "/* Target only direct li children of ul.menu using the child combinator */\n", + "codePrefix": "/* Target only the direct li children of .main-nav (not nested submenu items) */\n", "initialCode": "", "codeSuffix": "", "previewContainer": "preview-area", - "solution": "ul.menu > li {\n font-weight: bold;\n background-color: lightgray;\n}", + "solution": ".main-nav > li {\n background-color: cornflowerblue;\n color: white;\n}", "validations": [ { "type": "regex", - "value": "^ul\\.menu\\s*>\\s*li\\s*{", - "message": "Use ul.menu > li with the child combinator (>)", + "value": "^\\.main-nav\\s*>\\s*li\\s*{", + "message": "Use .main-nav > li { â€Ļ } with the child combinator to target only direct children", "options": { "caseSensitive": true } }, - { - "type": "contains", - "value": "font-weight:", - "message": "Include the font-weight property" - }, - { - "type": "property_value", - "value": { - "property": "font-weight", - "expected": "bold" - }, - "message": "Set font-weight to bold" - }, { "type": "contains", "value": "background-color:", - "message": "Include the background-color property" + "message": "Include the background-color property to highlight main menu items" }, { "type": "property_value", "value": { "property": "background-color", - "expected": "lightgray" + "expected": "cornflowerblue" }, - "message": "Set background-color to lightgray" + "message": "Set background-color to cornflowerblue for main menu styling" + }, + { + "type": "contains", + "value": "color:", + "message": "Include the color property to set the text color" + }, + { + "type": "property_value", + "value": { + "property": "color", + "expected": "white" + }, + "message": "Set text color to white for contrast against the blue background" }, { "type": "regex", - "value": "ul\\.menu\\s*>\\s*li\\s*{[^}]*}", + "value": "\\.main-nav\\s*>\\s*li\\s*{[^}]*}", "message": "Make sure to close your CSS rule with a closing brace }", "options": { "caseSensitive": true diff --git a/src/app.js b/src/app.js index 3d4c255..ca70dc6 100644 --- a/src/app.js +++ b/src/app.js @@ -8,6 +8,7 @@ const state = { currentModule: null, currentLessonIndex: 0, modules: [], + userCode: new Map(), // Store user code for each lesson userProgress: {}, // Format: { moduleId: { completed: [0, 2, 3], current: 4 } } userCodeBeforeValidation: "", // Track user code state before validation userSettings: { @@ -48,7 +49,7 @@ const lessonEngine = new LessonEngine(); // Load user progress from localStorage function loadUserProgress() { - const savedProgress = localStorage.getItem("codeCrispies.Progress"); + const savedProgress = localStorage.getItem("codeCrispies.progress"); if (savedProgress) { state.userProgress = JSON.parse(savedProgress); } @@ -56,7 +57,7 @@ function loadUserProgress() { // Save user progress to localStorage function saveUserProgress() { - localStorage.setItem("codeCrispies.Progress", JSON.stringify(state.userProgress)); + localStorage.setItem("codeCrispies.progress", JSON.stringify(state.userProgress)); } function loadUserSettings() { @@ -392,6 +393,10 @@ function runCode() { // Always apply the code to the preview, regardless of validation result lessonEngine.applyUserCode(userCode, true); + // Backup code in local storage + state.userCode.set(state.currentLessonIndex, userCode); + localStorage.setItem("codeCrispies.userCode", JSON.stringify(Array.from(state.userCode.entries()))); + const validationResult = validateUserCode(userCode, lesson); // Add validation indicators based on validCases count if available @@ -557,7 +562,7 @@ function resetProgress() { document.getElementById("cancel-reset").addEventListener("click", closeModal); document.getElementById("confirm-reset").addEventListener("click", () => { - localStorage.removeItem("codeCrispies.Progress"); + localStorage.removeItem("codeCrispies.progress"); localStorage.removeItem("codeCrispies.lastModuleId"); state.userProgress = {}; closeModal(); diff --git a/src/helpers/renderer.js b/src/helpers/renderer.js index f8d40a0..e9b9011 100644 --- a/src/helpers/renderer.js +++ b/src/helpers/renderer.js @@ -18,7 +18,7 @@ export function renderModuleList(container, modules, onSelectModule, onSelectLes container.innerHTML = "

CSS Lessons

"; // Get user progress from localStorage - const progressData = localStorage.getItem("codeCrispies.Progress"); + const progressData = localStorage.getItem("codeCrispies.progress"); let progress = {}; if (progressData) { try {