feat: reorder learning path for design students and improve animations

Learning path changes:
- Reorder modules: CSS visual (selectors, colors, typography) first,
  then layout (flexbox, grid), then HTML structure last
- Add intro lessons on CSS property syntax before selectors
- Add Figure module (images with captions)
- Remove Progress/Meter module (too niche)
- Reduce Tables from 3 to 1 lesson
- Reduce Form Validation from 3 to 1 lesson
- Rename "CSS Selectors" module to "CSS Basics"

Animation improvements:
- Change success text to "Your CODE looks CRISPY!"
- Increase animation duration to 3s
- Fix Firefox glow: use linear-gradient pulse instead of conic rotation
- Fix expected result toggle: match padding to prevent layout jump

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
2026-01-14 01:30:19 +01:00
parent c397397c54
commit f6c7cca63f
6 changed files with 236 additions and 304 deletions

View File

@@ -1,10 +1,57 @@
{
"$schema": "../schemas/code-crispies-module-schema.json",
"id": "css-basic-selectors",
"title": "CSS Selectors",
"description": "CSS selectors are the foundation of styling web pages, allowing you to target specific HTML elements for styling. This module introduces fundamental selector types including element type selectors, class selectors, ID selectors, and the universal selector.",
"title": "CSS Basics",
"description": "Learn the fundamental building blocks of CSS: properties, values, and selectors. This module teaches you the syntax rules that every CSS declaration follows.",
"difficulty": "beginner",
"lessons": [
{
"id": "css-properties",
"title": "CSS Properties",
"description": "CSS styles elements using <strong>declarations</strong> - pairs of properties and values. Every declaration follows the same pattern:<br><br><pre>property: value;</pre><br>The <strong>property</strong> is what you want to change (like <kbd>color</kbd> or <kbd>background</kbd>). The <strong>value</strong> is what you're setting it to. A colon separates them, and a semicolon ends the line.<br><br>Values come in different types:<br>• <strong>Keywords:</strong> <kbd>red</kbd>, <kbd>bold</kbd>, <kbd>center</kbd><br>• <strong>Numbers with units:</strong> <kbd>16px</kbd>, <kbd>2rem</kbd>, <kbd>100%</kbd><br>• <strong>Colors:</strong> <kbd>steelblue</kbd>, <kbd>#ff0000</kbd>",
"task": "Complete the declaration by adding <kbd>color: coral;</kbd> to change the text color.",
"previewHTML": "<p class=\"text\">This text should turn coral.</p>",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } .text { font-size: 1.25rem; }",
"sandboxCSS": "",
"codePrefix": ".text {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"previewContainer": "preview-area",
"solution": "color: coral;",
"validations": [
{
"type": "property_value",
"value": { "property": "color", "expected": "coral" },
"message": "Add <kbd>color: coral;</kbd>"
}
]
},
{
"id": "multiple-properties",
"title": "Multiple Properties",
"description": "A rule can have multiple declarations. Each one goes on its own line, and each needs a semicolon at the end:<br><br><pre>.box {<br> background: gold;<br> color: navy;<br> padding: 1rem;<br>}</pre><br>The order usually doesn't matter - CSS applies them all. When properties conflict, the last one wins.",
"task": "Add two declarations: <kbd>background: lavender;</kbd> and <kbd>padding: 1rem;</kbd>",
"previewHTML": "<div class=\"card\">A styled card with background and padding.</div>",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } .card { border-radius: 8px; }",
"sandboxCSS": "",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"previewContainer": "preview-area",
"solution": "background: lavender;\n padding: 1rem;",
"validations": [
{
"type": "property_value",
"value": { "property": "background", "expected": "lavender" },
"message": "Add <kbd>background: lavender;</kbd>"
},
{
"type": "property_value",
"value": { "property": "padding", "expected": "1rem" },
"message": "Add <kbd>padding: 1rem;</kbd>"
}
]
},
{
"id": "introduction-to-selectors",
"title": "What's a Selector?",

View File

@@ -1,110 +1,32 @@
{
"$schema": "../schemas/code-crispies-module-schema.json",
"id": "html-forms-validation",
"title": "HTML Validation",
"description": "Learn HTML5 built-in form validation attributes",
"title": "Form Validation",
"description": "Use HTML5 built-in validation for better user experience",
"mode": "html",
"difficulty": "intermediate",
"difficulty": "beginner",
"lessons": [
{
"id": "required-fields",
"title": "Required Fields",
"description": "The <kbd>required</kbd> attribute prevents form submission if the field is empty.<br><br>Add it to any input that must be filled:<br><kbd>&lt;input type=\"text\" required&gt;</kbd><br><br>The browser shows a validation message automatically.",
"task": "Make both the name and email fields required by adding the <kbd>required</kbd> attribute.",
"description": "The <kbd>required</kbd> attribute prevents form submission if the field is empty. The browser shows a validation message automatically - no JavaScript needed!<br><br>Add it to any input that must be filled:<br><kbd>&lt;input type=\"text\" required&gt;</kbd>",
"task": "Make both the name and email fields required by adding the <kbd>required</kbd> attribute to each input.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 350px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-of-type { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } input:invalid { border-color: #d32f2f; } button { margin-top: 20px; padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; }",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 320px; } label { display: block; margin-top: 15px; margin-bottom: 5px; font-weight: 500; } label:first-of-type { margin-top: 0; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; box-sizing: border-box; } input:focus { outline: 2px solid steelblue; border-color: transparent; } button { margin-top: 20px; padding: 10px 20px; background: steelblue; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 500; }",
"sandboxCSS": "",
"initialCode": "<form>\n <label for=\"name\">Name: *</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n \n <label for=\"email\">Email: *</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <button type=\"submit\">Submit</button>\n</form>",
"solution": "<form>\n <label for=\"name\">Name: *</label>\n <input type=\"text\" id=\"name\" name=\"name\" required>\n \n <label for=\"email\">Email: *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <button type=\"submit\">Submit</button>\n</form>",
"initialCode": "<form>\n <label for=\"name\">Name *</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n \n <label for=\"email\">Email *</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <button type=\"submit\">Submit</button>\n</form>",
"solution": "<form>\n <label for=\"name\">Name *</label>\n <input type=\"text\" id=\"name\" name=\"name\" required>\n \n <label for=\"email\">Email *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <button type=\"submit\">Submit</button>\n</form>",
"previewContainer": "preview-area",
"validations": [
{
"type": "attribute_value",
"value": { "selector": "input[name='name']", "attr": "required", "value": true },
"message": "Add the <kbd>required</kbd> attribute to the name input"
"message": "Add <kbd>required</kbd> to the name input"
},
{
"type": "attribute_value",
"value": { "selector": "input[name='email']", "attr": "required", "value": true },
"message": "Add the <kbd>required</kbd> attribute to the email input"
}
]
},
{
"id": "input-constraints",
"title": "Constraints",
"description": "Control what users can enter:<br><br><kbd>minlength</kbd> / <kbd>maxlength</kbd> - Text length limits<br><kbd>min</kbd> / <kbd>max</kbd> - Number range<br><kbd>pattern</kbd> - Regex pattern matching<br><kbd>placeholder</kbd> - Hint text (not a label!)",
"task": "Add validation to the password input:<br>1. Add <kbd>minlength=\"8\"</kbd> for minimum length<br>2. Add <kbd>maxlength=\"20\"</kbd> for maximum length<br>3. Add <kbd>placeholder=\"Enter password\"</kbd> as a hint",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 350px; } label { display: block; margin-bottom: 5px; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } input:invalid:not(:placeholder-shown) { border-color: #d32f2f; } small { display: block; font-size: 12px; color: #666; margin-top: 4px; } button { margin-top: 20px; padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; }",
"sandboxCSS": "",
"initialCode": "<form>\n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required aria-describedby=\"password-hint\">\n <small id=\"password-hint\">Must be 8-20 characters</small>\n \n <button type=\"submit\">Create Account</button>\n</form>",
"solution": "<form>\n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required minlength=\"8\" maxlength=\"20\" placeholder=\"Enter password\" aria-describedby=\"password-hint\">\n <small id=\"password-hint\">Must be 8-20 characters</small>\n \n <button type=\"submit\">Create Account</button>\n</form>",
"previewContainer": "preview-area",
"validations": [
{
"type": "attribute_value",
"value": { "selector": "input[type='password']", "attr": "minlength", "value": "8" },
"message": "Add <kbd>minlength</kbd>=\"8\" to the password input"
},
{
"type": "attribute_value",
"value": { "selector": "input[type='password']", "attr": "maxlength", "value": "20" },
"message": "Add <kbd>maxlength</kbd>=\"20\" to the password input"
},
{
"type": "attribute_value",
"value": { "selector": "input[type='password']", "attr": "placeholder", "value": null },
"message": "Add a <kbd>placeholder</kbd> to hint what to enter"
}
]
},
{
"id": "complete-registration",
"title": "Full Form",
"description": "Build a complete registration form with all validation concepts:<br><br>- Required fields marked with *<br>- Email validation (use type=\"email\")<br>- Password with length constraints<br>- Terms checkbox (required)<br>- Submit button",
"task": "Complete the registration form. Add required attributes, proper input types, and validation constraints.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 400px; background: #f5f5f5; padding: 25px; border-radius: 8px; } h2 { margin-top: 0; margin-bottom: 20px; } label { display: block; margin-top: 15px; margin-bottom: 5px; font-weight: 500; } input[type='text'], input[type='email'], input[type='password'] { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; font-size: 14px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; } input[type='checkbox'] { width: auto; margin-right: 8px; vertical-align: middle; } label:has(input[type='checkbox']) { display: flex; align-items: center; font-weight: normal; } button { width: 100%; margin-top: 20px; padding: 12px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: 500; } button:hover { background: #1565c0; }",
"sandboxCSS": "",
"initialCode": "<form>\n <h2>Create Account</h2>\n \n <label for=\"fullname\">Full Name *</label>\n <input type=\"text\" id=\"fullname\" name=\"fullname\">\n \n <label for=\"email\">Email *</label>\n <input id=\"email\" name=\"email\">\n \n <label for=\"password\">Password *</label>\n <input id=\"password\" name=\"password\">\n \n <label>\n <input type=\"checkbox\" id=\"terms\" name=\"terms\">\n I agree to the Terms of Service *\n </label>\n \n <button type=\"submit\">Register</button>\n</form>",
"solution": "<form>\n <h2>Create Account</h2>\n \n <label for=\"fullname\">Full Name *</label>\n <input type=\"text\" id=\"fullname\" name=\"fullname\" required>\n \n <label for=\"email\">Email *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <label for=\"password\">Password *</label>\n <input type=\"password\" id=\"password\" name=\"password\" required minlength=\"8\">\n \n <label>\n <input type=\"checkbox\" id=\"terms\" name=\"terms\" required>\n I agree to the Terms of Service *\n </label>\n \n <button type=\"submit\">Register</button>\n</form>",
"previewContainer": "preview-area",
"validations": [
{
"type": "attribute_value",
"value": { "selector": "#fullname", "attr": "required", "value": true },
"message": "Make the full name field <kbd>required</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "#email", "attr": "type", "value": "email" },
"message": "Set the email input <kbd>type=\"email\"</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "#email", "attr": "required", "value": true },
"message": "Make the email field <kbd>required</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "#password", "attr": "type", "value": "password" },
"message": "Set the password input <kbd>type=\"password\"</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "#password", "attr": "required", "value": true },
"message": "Make the password field <kbd>required</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "#password", "attr": "minlength", "value": "8" },
"message": "Add <kbd>minlength</kbd>=\"8\" to password"
},
{
"type": "attribute_value",
"value": { "selector": "#terms", "attr": "required", "value": true },
"message": "Make the terms checkbox <kbd>required</kbd>"
"message": "Add <kbd>required</kbd> to the email input"
}
]
}

View File

@@ -2,20 +2,20 @@
"$schema": "../schemas/code-crispies-module-schema.json",
"id": "html-tables",
"title": "HTML Tables",
"description": "Create structured data tables with headers and captions",
"description": "Create structured data tables with semantic markup",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "table-basic",
"title": "Basic Table Structure",
"description": "Tables use <kbd>&lt;table&gt;</kbd> with <kbd>&lt;tr&gt;</kbd> for rows. Inside rows, use <kbd>&lt;th&gt;</kbd> for headers and <kbd>&lt;td&gt;</kbd> for data cells.<br><br>The <kbd>&lt;caption&gt;</kbd> element provides an accessible title for the table.",
"task": "Create a simple table with:<br>1. A <kbd>&lt;caption&gt;</kbd> saying <code>Fruit Prices</code><br>2. A header row with <code>Fruit</code> and <code>Price</code> columns<br>3. At least 2 data rows",
"title": "Data Tables",
"description": "Tables display structured data in rows and columns. Use <kbd>&lt;table&gt;</kbd> as the container, <kbd>&lt;tr&gt;</kbd> for rows, <kbd>&lt;th&gt;</kbd> for header cells, and <kbd>&lt;td&gt;</kbd> for data cells.<br><br>Add <kbd>&lt;caption&gt;</kbd> for an accessible title that describes the table's content.",
"task": "Create a pricing table:<br>1. A <kbd>&lt;caption&gt;</kbd> saying <code>Pricing</code><br>2. A header row with <code>Plan</code> and <code>Price</code><br>3. Two data rows for Basic ($9) and Pro ($29)",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px; font-weight: 600; font-size: 1.2rem; color: #333; background: #f8f9fa; } th, td { padding: 12px 20px; text-align: left; border-bottom: 1px solid #eee; } th { background: #3498db; color: white; font-weight: 500; } tr:hover { background: #f8f9fa; } tr:last-child td { border-bottom: none; }",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; } table { border-collapse: collapse; width: 100%; max-width: 350px; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 16px; font-weight: 600; font-size: 1.1rem; color: #333; background: #f8f9fa; } th, td { padding: 14px 20px; text-align: left; border-bottom: 1px solid #eee; } th { background: steelblue; color: white; font-weight: 500; } tr:last-child td { border-bottom: none; } tr:hover td { background: #f8f9fa; }",
"sandboxCSS": "",
"initialCode": "",
"solution": "<table>\n <caption>Fruit Prices</caption>\n <tr>\n <th>Fruit</th>\n <th>Price</th>\n </tr>\n <tr>\n <td>Apple</td>\n <td>$1.50</td>\n </tr>\n <tr>\n <td>Banana</td>\n <td>$0.75</td>\n </tr>\n</table>",
"solution": "<table>\n <caption>Pricing</caption>\n <tr>\n <th>Plan</th>\n <th>Price</th>\n </tr>\n <tr>\n <td>Basic</td>\n <td>$9</td>\n </tr>\n <tr>\n <td>Pro</td>\n <td>$29</td>\n </tr>\n</table>",
"previewContainer": "preview-area",
"validations": [
{
@@ -31,95 +31,12 @@
{
"type": "element_count",
"value": { "selector": "th", "min": 2 },
"message": "Add at least 2 header cells (th)"
"message": "Add header cells (<kbd>&lt;th&gt;</kbd>) for Plan and Price"
},
{
"type": "element_count",
"value": { "selector": "tr", "min": 3 },
"message": "Add at least 3 rows (1 header + 2 data rows)"
}
]
},
{
"id": "table-thead-tbody",
"title": "Table Head & Body",
"description": "Use <kbd>&lt;thead&gt;</kbd> to group header rows and <kbd>&lt;tbody&gt;</kbd> to group data rows. This helps browsers and assistive technology understand the table structure.<br><br>You can also use <kbd>&lt;tfoot&gt;</kbd> for footer rows like totals.",
"task": "Create a structured table:<br>1. A <kbd>&lt;caption&gt;</kbd> with <code>Monthly Sales</code><br>2. A <kbd>&lt;thead&gt;</kbd> with Month and Revenue headers<br>3. A <kbd>&lt;tbody&gt;</kbd> with at least 2 data rows",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } table { border-collapse: collapse; width: 100%; max-width: 450px; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.2); } caption { padding: 20px; font-weight: 700; font-size: 1.3rem; color: white; background: transparent; text-shadow: 0 2px 4px rgba(0,0,0,0.2); caption-side: top; } thead { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } th { padding: 15px 20px; text-align: left; color: white; font-weight: 500; } tbody tr { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } td { padding: 15px 20px; color: #333; } tbody tr:last-child { border-bottom: none; }",
"sandboxCSS": "",
"initialCode": "",
"solution": "<table>\n <caption>Monthly Sales</caption>\n <thead>\n <tr>\n <th>Month</th>\n <th>Revenue</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>January</td>\n <td>$12,500</td>\n </tr>\n <tr>\n <td>February</td>\n <td>$14,200</td>\n </tr>\n </tbody>\n</table>",
"previewContainer": "preview-area",
"validations": [
{
"type": "element_exists",
"value": "table",
"message": "Add a <kbd>&lt;table&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "caption",
"message": "Add a <kbd>&lt;caption&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "thead",
"message": "Add a <kbd>&lt;thead&gt;</kbd> for the header section"
},
{
"type": "element_exists",
"value": "tbody",
"message": "Add a <kbd>&lt;tbody&gt;</kbd> for the data rows"
},
{
"type": "element_count",
"value": { "selector": "tbody tr", "min": 2 },
"message": "Add at least 2 data rows in tbody"
}
]
},
{
"id": "table-complete",
"title": "Complete Table with Footer",
"description": "Add <kbd>&lt;tfoot&gt;</kbd> to create a footer section for totals or summary data. The footer stays at the bottom even if tbody has many rows.<br><br>Combine all sections for a fully structured, accessible table.",
"task": "Create a complete table:<br>1. A <kbd>&lt;caption&gt;</kbd> with <code>Order Summary</code><br>2. A <kbd>&lt;thead&gt;</kbd> with Item and Price headers<br>3. A <kbd>&lt;tbody&gt;</kbd> with 2 items<br>4. A <kbd>&lt;tfoot&gt;</kbd> with a Total row",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px 20px; font-weight: 600; font-size: 1.1rem; color: #333; text-align: left; background: #f8f9fa; border-bottom: 2px solid #eee; } th, td { padding: 12px 20px; text-align: left; } thead th { background: #2c3e50; color: white; } tbody td { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } tfoot { background: #ecf0f1; font-weight: 600; } tfoot td { border-top: 2px solid #2c3e50; color: #2c3e50; }",
"sandboxCSS": "",
"initialCode": "",
"solution": "<table>\n <caption>Order Summary</caption>\n <thead>\n <tr>\n <th>Item</th>\n <th>Price</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Widget</td>\n <td>$25.00</td>\n </tr>\n <tr>\n <td>Gadget</td>\n <td>$35.00</td>\n </tr>\n </tbody>\n <tfoot>\n <tr>\n <td>Total</td>\n <td>$60.00</td>\n </tr>\n </tfoot>\n</table>",
"previewContainer": "preview-area",
"validations": [
{
"type": "element_exists",
"value": "table",
"message": "Add a <kbd>&lt;table&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "caption",
"message": "Add a <kbd>&lt;caption&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "thead",
"message": "Add a <kbd>&lt;thead&gt;</kbd> section"
},
{
"type": "element_exists",
"value": "tbody",
"message": "Add a <kbd>&lt;tbody&gt;</kbd> section"
},
{
"type": "element_exists",
"value": "tfoot",
"message": "Add a <kbd>&lt;tfoot&gt;</kbd> section for the total"
},
{
"type": "element_count",
"value": { "selector": "tbody tr", "min": 2 },
"message": "Add at least 2 item rows in tbody"
"message": "Add 3 rows (1 header + 2 data rows)"
}
]
}