Skip to content

Commit daab71b

Browse files
committed
feat: enhance lesson functionality with user code storage and progress tracking
1 parent 9e28534 commit daab71b

3 files changed

Lines changed: 43 additions & 31 deletions

File tree

lessons/01-advanced-selectors.json

Lines changed: 34 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,12 @@
1717
"initialCode": "",
1818
"codeSuffix": "",
1919
"previewContainer": "preview-area",
20-
"solution": "input[type=\"text\"] {\n background-color: lightblue;\n border: 2px solid blue;\n}",
20+
"solution": "input[type=\"text\"] {\n background-color: lightblue;\n border: 2px solid blue\n}",
2121
"validations": [
2222
{
2323
"type": "regex",
2424
"value": "^input\\[type=\"text\"\\]\\s*{",
25-
"message": "Use <kbd>input[type=\"text\"]</kbd> as your attribute selector",
25+
"message": "Use <kbd>input[type=\"text\"] { … }</kbd> as your attribute selector",
2626
"options": {
2727
"caseSensitive": true
2828
}
@@ -40,6 +40,11 @@
4040
},
4141
"message": "Set the background color to <kbd>lightblue</kbd>"
4242
},
43+
{
44+
"type": "regex",
45+
"value": "background-color:\\s*[^;]*;",
46+
"message": "Make sure to close the <kbd>background-color</kbd> declaration with a semicolon <kbd>;</kbd>"
47+
},
4348
{
4449
"type": "contains",
4550
"value": "border:",
@@ -67,7 +72,7 @@
6772
"id": "attribute-partial-matching",
6873
"title": "Attribute Partial Matching",
6974
"description": "Attribute selectors support partial matching patterns that let you target elements based on portions of attribute values. The <kbd>[attribute^=\"value\"]</kbd> selector matches elements where the attribute starts with the specified value, <kbd>[attribute$=\"value\"]</kbd> matches where it ends with the value, and <kbd>[attribute*=\"value\"]</kbd> 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 <kbd>color</kbd> to change text color and <kbd>text-decoration</kbd> to add visual emphasis like underlines.",
70-
"task": "Create a CSS rule that targets all anchor elements (<kbd>a</kbd>) with <kbd>href</kbd> attributes that start with <kbd>\"https\"</kbd>. Make the text <kbd>green</kbd> and add an <kbd>underline</kbd>.",
75+
"task": "Create a CSS rule that targets all anchor elements (<kbd>a</kbd>) with <kbd>href</kbd> attributes starting with <kbd>\"https\"</kbd>. Style them with <kbd>green</kbd> text color and <kbd>underline</kbd> text decoration.",
7176
"previewHTML": "<h2>Different Types of Links</h2>\n<ul>\n <li><a href=\"https://example.com\">External HTTPS link</a></li>\n <li><a href=\"http://oldsite.com\">External HTTP link</a></li>\n <li><a href=\"#section1\">Internal anchor link</a></li>\n <li><a href=\"/about\">Relative link</a></li>\n <li><a href=\"https://secure-site.org\">Another HTTPS link</a></li>\n</ul>",
7277
"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; }",
7378
"sandboxCSS": "",
@@ -80,37 +85,39 @@
8085
{
8186
"type": "regex",
8287
"value": "^a\\[href\\^=\"https\"\\]\\s*{",
83-
"message": "Use <kbd>a[href^=\"https\"]</kbd> to target links starting with https",
88+
"message": "Use <kbd>a[href^=\"https\"] { … }</kbd> as your attribute selector to target HTTPS links",
8489
"options": {
8590
"caseSensitive": true
8691
}
8792
},
8893
{
8994
"type": "contains",
9095
"value": "color:",
91-
"message": "Include the <kbd>color</kbd> property"
96+
"message": "Include the <kbd>color</kbd> property to set the text color"
9297
},
9398
{
9499
"type": "property_value",
95100
"value": {
96101
"property": "color",
97102
"expected": "green"
98103
},
99-
"message": "Set the color to <kbd>green</kbd>"
104+
"message": "Set the text color to <kbd>green</kbd>"
100105
},
106+
101107
{
102108
"type": "contains",
103109
"value": "text-decoration:",
104-
"message": "Include the <kbd>text-decoration</kbd> property"
110+
"message": "Include the <kbd>text-decoration</kbd> property to style the link appearance"
105111
},
106112
{
107113
"type": "property_value",
108114
"value": {
109115
"property": "text-decoration",
110116
"expected": "underline"
111117
},
112-
"message": "Set text-decoration to <kbd>underline</kbd>"
118+
"message": "Set text-decoration to <kbd>underline</kbd> to add underlines to HTTPS links"
113119
},
120+
114121
{
115122
"type": "regex",
116123
"value": "a\\[href\\^=\"https\"\\]\\s*{[^}]*}",
@@ -124,54 +131,54 @@
124131
{
125132
"id": "child-combinator",
126133
"title": "Child Combinator: Direct Children Only",
127-
"description": "The child combinator (<kbd>></kbd>) selects elements that are direct children of another element, not grandchildren or deeper descendants. For example, <kbd>ul > li</kbd> 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 <kbd>font-weight</kbd> to make text bold and <kbd>background-color</kbd> to highlight specific elements. The child combinator gives you more control than the descendant selector by limiting selection to one level deep.",
128-
"task": "Use the child combinator to target only the direct <kbd>li</kbd> children of the <kbd>ul</kbd> with class <kbd>menu</kbd>. Make them <kbd>bold</kbd> and give them a <kbd>lightgray</kbd> background.",
129-
"previewHTML": "<ul class=\"menu\">\n <li>Home (direct child)</li>\n <li>Products (direct child)\n <ul>\n <li>Laptops (nested, not direct child)</li>\n <li>Phones (nested, not direct child)</li>\n </ul>\n </li>\n <li>About (direct child)</li>\n</ul>",
130-
"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; }",
134+
"description": "The child combinator (<kbd>></kbd>) 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 (<kbd>></kbd>) gives you surgical precision - <kbd>ul > li</kbd> targets only direct list items, while <kbd>ul li</kbd> would target ALL list items including nested ones. This prevents style inheritance chaos in complex layouts.",
135+
"task": "Use the child combinator to target only the direct <kbd>li</kbd> children of <kbd>.main-nav</kbd>. Give them a <kbd>cornflowerblue</kbd> background and <kbd>white</kbd> text color. Notice how the nested submenu items remain completely unstyled!",
136+
"previewHTML": "<ul class=\"main-nav\">\n <li>🏠 Home</li>\n <li>📱 Products\n <ul>\n <li>💻 Laptops</li>\n <li>📱 Phones</li>\n <li>⌚ Watches</li>\n </ul>\n </li>\n <li>ℹ️ About\n <ul>\n <li>👥 Team</li>\n <li>📍 Location</li>\n </ul>\n </li>\n <li>📧 Contact</li>\n</ul>",
137+
"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; }",
131138
"sandboxCSS": "",
132-
"codePrefix": "/* Target only direct li children of ul.menu using the child combinator */\n",
139+
"codePrefix": "/* Target only the direct li children of .main-nav (not nested submenu items) */\n",
133140
"initialCode": "",
134141
"codeSuffix": "",
135142
"previewContainer": "preview-area",
136-
"solution": "ul.menu > li {\n font-weight: bold;\n background-color: lightgray;\n}",
143+
"solution": ".main-nav > li {\n background-color: cornflowerblue;\n color: white;\n}",
137144
"validations": [
138145
{
139146
"type": "regex",
140-
"value": "^ul\\.menu\\s*>\\s*li\\s*{",
141-
"message": "Use <kbd>ul.menu > li</kbd> with the child combinator (>)",
147+
"value": "^\\.main-nav\\s*>\\s*li\\s*{",
148+
"message": "Use <kbd>.main-nav > li { … }</kbd> with the child combinator to target only direct children",
142149
"options": {
143150
"caseSensitive": true
144151
}
145152
},
146153
{
147154
"type": "contains",
148-
"value": "font-weight:",
149-
"message": "Include the <kbd>font-weight</kbd> property"
155+
"value": "background-color:",
156+
"message": "Include the <kbd>background-color</kbd> property to highlight main menu items"
150157
},
151158
{
152159
"type": "property_value",
153160
"value": {
154-
"property": "font-weight",
155-
"expected": "bold"
161+
"property": "background-color",
162+
"expected": "cornflowerblue"
156163
},
157-
"message": "Set font-weight to <kbd>bold</kbd>"
164+
"message": "Set background-color to <kbd>cornflowerblue</kbd> for main menu styling"
158165
},
159166
{
160167
"type": "contains",
161-
"value": "background-color:",
162-
"message": "Include the <kbd>background-color</kbd> property"
168+
"value": "color:",
169+
"message": "Include the <kbd>color</kbd> property to set the text color"
163170
},
164171
{
165172
"type": "property_value",
166173
"value": {
167-
"property": "background-color",
168-
"expected": "lightgray"
174+
"property": "color",
175+
"expected": "white"
169176
},
170-
"message": "Set background-color to <kbd>lightgray</kbd>"
177+
"message": "Set text color to <kbd>white</kbd> for contrast against the blue background"
171178
},
172179
{
173180
"type": "regex",
174-
"value": "ul\\.menu\\s*>\\s*li\\s*{[^}]*}",
181+
"value": "\\.main-nav\\s*>\\s*li\\s*{[^}]*}",
175182
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
176183
"options": {
177184
"caseSensitive": true

src/app.js

Lines changed: 8 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ const state = {
88
currentModule: null,
99
currentLessonIndex: 0,
1010
modules: [],
11+
userCode: new Map(), // Store user code for each lesson
1112
userProgress: {}, // Format: { moduleId: { completed: [0, 2, 3], current: 4 } }
1213
userCodeBeforeValidation: "", // Track user code state before validation
1314
userSettings: {
@@ -48,15 +49,15 @@ const lessonEngine = new LessonEngine();
4849

4950
// Load user progress from localStorage
5051
function loadUserProgress() {
51-
const savedProgress = localStorage.getItem("codeCrispies.Progress");
52+
const savedProgress = localStorage.getItem("codeCrispies.progress");
5253
if (savedProgress) {
5354
state.userProgress = JSON.parse(savedProgress);
5455
}
5556
}
5657

5758
// Save user progress to localStorage
5859
function saveUserProgress() {
59-
localStorage.setItem("codeCrispies.Progress", JSON.stringify(state.userProgress));
60+
localStorage.setItem("codeCrispies.progress", JSON.stringify(state.userProgress));
6061
}
6162

6263
function loadUserSettings() {
@@ -392,6 +393,10 @@ function runCode() {
392393
// Always apply the code to the preview, regardless of validation result
393394
lessonEngine.applyUserCode(userCode, true);
394395

396+
// Backup code in local storage
397+
state.userCode.set(state.currentLessonIndex, userCode);
398+
localStorage.setItem("codeCrispies.userCode", JSON.stringify(Array.from(state.userCode.entries())));
399+
395400
const validationResult = validateUserCode(userCode, lesson);
396401

397402
// Add validation indicators based on validCases count if available
@@ -557,7 +562,7 @@ function resetProgress() {
557562

558563
document.getElementById("cancel-reset").addEventListener("click", closeModal);
559564
document.getElementById("confirm-reset").addEventListener("click", () => {
560-
localStorage.removeItem("codeCrispies.Progress");
565+
localStorage.removeItem("codeCrispies.progress");
561566
localStorage.removeItem("codeCrispies.lastModuleId");
562567
state.userProgress = {};
563568
closeModal();

src/helpers/renderer.js

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ export function renderModuleList(container, modules, onSelectModule, onSelectLes
1818
container.innerHTML = "<h3>CSS Lessons</h3>";
1919

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

0 commit comments

Comments
 (0)