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:
@@ -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?",
|
||||
|
||||
@@ -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><input type=\"text\" required></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><input type=\"text\" required></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"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -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><table></kbd> with <kbd><tr></kbd> for rows. Inside rows, use <kbd><th></kbd> for headers and <kbd><td></kbd> for data cells.<br><br>The <kbd><caption></kbd> element provides an accessible title for the table.",
|
||||
"task": "Create a simple table with:<br>1. A <kbd><caption></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><table></kbd> as the container, <kbd><tr></kbd> for rows, <kbd><th></kbd> for header cells, and <kbd><td></kbd> for data cells.<br><br>Add <kbd><caption></kbd> for an accessible title that describes the table's content.",
|
||||
"task": "Create a pricing table:<br>1. A <kbd><caption></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><th></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><thead></kbd> to group header rows and <kbd><tbody></kbd> to group data rows. This helps browsers and assistive technology understand the table structure.<br><br>You can also use <kbd><tfoot></kbd> for footer rows like totals.",
|
||||
"task": "Create a structured table:<br>1. A <kbd><caption></kbd> with <code>Monthly Sales</code><br>2. A <kbd><thead></kbd> with Month and Revenue headers<br>3. A <kbd><tbody></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><table></kbd> element"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "caption",
|
||||
"message": "Add a <kbd><caption></kbd> element"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "thead",
|
||||
"message": "Add a <kbd><thead></kbd> for the header section"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "tbody",
|
||||
"message": "Add a <kbd><tbody></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><tfoot></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><caption></kbd> with <code>Order Summary</code><br>2. A <kbd><thead></kbd> with Item and Price headers<br>3. A <kbd><tbody></kbd> with 2 items<br>4. A <kbd><tfoot></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><table></kbd> element"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "caption",
|
||||
"message": "Add a <kbd><caption></kbd> element"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "thead",
|
||||
"message": "Add a <kbd><thead></kbd> section"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "tbody",
|
||||
"message": "Add a <kbd><tbody></kbd> section"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "tfoot",
|
||||
"message": "Add a <kbd><tfoot></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)"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -630,7 +630,7 @@ function runCode() {
|
||||
elements.previewWrapper?.classList.add("matched");
|
||||
setTimeout(() => {
|
||||
elements.previewWrapper?.classList.remove("matched");
|
||||
}, 3000);
|
||||
}, 3500);
|
||||
|
||||
updateNavigationButtons();
|
||||
updateProgressDisplay();
|
||||
|
||||
@@ -6,7 +6,6 @@
|
||||
// English lesson imports
|
||||
import welcomeEN from "../../lessons/00-welcome.json";
|
||||
import basicSelectorsEN from "../../lessons/00-basic-selectors.json";
|
||||
import advancedSelectorsEN from "../../lessons/01-advanced-selectors.json";
|
||||
import boxModelEN from "../../lessons/01-box-model.json";
|
||||
import colorsEN from "../../lessons/03-colors.json";
|
||||
import typographyEN from "../../lessons/04-typography.json";
|
||||
@@ -17,7 +16,7 @@ import htmlElementsEN from "../../lessons/20-html-elements.json";
|
||||
import htmlFormsBasicEN from "../../lessons/21-html-forms-basic.json";
|
||||
import htmlFormsValidationEN from "../../lessons/22-html-forms-validation.json";
|
||||
import htmlDetailsSummaryEN from "../../lessons/23-html-details-summary.json";
|
||||
import htmlProgressMeterEN from "../../lessons/24-html-progress-meter.json";
|
||||
import htmlFigureEN from "../../lessons/29-html-figure.json";
|
||||
import htmlTablesEN from "../../lessons/30-html-tables.json";
|
||||
import htmlSvgEN from "../../lessons/32-html-svg.json";
|
||||
import flexboxEN from "../../lessons/flexbox.json";
|
||||
@@ -35,7 +34,6 @@ import htmlElementsDE from "../../lessons/de/20-html-elements.json";
|
||||
import htmlFormsBasicDE from "../../lessons/de/21-html-forms-basic.json";
|
||||
import htmlFormsValidationDE from "../../lessons/de/22-html-forms-validation.json";
|
||||
import htmlDetailsSummaryDE from "../../lessons/de/23-html-details-summary.json";
|
||||
import htmlProgressMeterDE from "../../lessons/de/24-html-progress-meter.json";
|
||||
import htmlTablesDE from "../../lessons/de/30-html-tables.json";
|
||||
import htmlSvgDE from "../../lessons/de/32-html-svg.json";
|
||||
import flexboxDE from "../../lessons/de/flexbox.json";
|
||||
@@ -51,7 +49,6 @@ import htmlElementsPL from "../../lessons/pl/20-html-elements.json";
|
||||
import htmlFormsBasicPL from "../../lessons/pl/21-html-forms-basic.json";
|
||||
import htmlFormsValidationPL from "../../lessons/pl/22-html-forms-validation.json";
|
||||
import htmlDetailsSummaryPL from "../../lessons/pl/23-html-details-summary.json";
|
||||
import htmlProgressMeterPL from "../../lessons/pl/24-html-progress-meter.json";
|
||||
import htmlTablesPL from "../../lessons/pl/30-html-tables.json";
|
||||
import htmlSvgPL from "../../lessons/pl/32-html-svg.json";
|
||||
import flexboxPL from "../../lessons/pl/flexbox.json";
|
||||
@@ -67,7 +64,6 @@ import htmlElementsES from "../../lessons/es/20-html-elements.json";
|
||||
import htmlFormsBasicES from "../../lessons/es/21-html-forms-basic.json";
|
||||
import htmlFormsValidationES from "../../lessons/es/22-html-forms-validation.json";
|
||||
import htmlDetailsSummaryES from "../../lessons/es/23-html-details-summary.json";
|
||||
import htmlProgressMeterES from "../../lessons/es/24-html-progress-meter.json";
|
||||
import htmlTablesES from "../../lessons/es/30-html-tables.json";
|
||||
import htmlSvgES from "../../lessons/es/32-html-svg.json";
|
||||
import flexboxES from "../../lessons/es/flexbox.json";
|
||||
@@ -83,7 +79,6 @@ import htmlElementsAR from "../../lessons/ar/20-html-elements.json";
|
||||
import htmlFormsBasicAR from "../../lessons/ar/21-html-forms-basic.json";
|
||||
import htmlFormsValidationAR from "../../lessons/ar/22-html-forms-validation.json";
|
||||
import htmlDetailsSummaryAR from "../../lessons/ar/23-html-details-summary.json";
|
||||
import htmlProgressMeterAR from "../../lessons/ar/24-html-progress-meter.json";
|
||||
import htmlTablesAR from "../../lessons/ar/30-html-tables.json";
|
||||
import htmlSvgAR from "../../lessons/ar/32-html-svg.json";
|
||||
import flexboxAR from "../../lessons/ar/flexbox.json";
|
||||
@@ -99,199 +94,180 @@ import htmlElementsUK from "../../lessons/uk/20-html-elements.json";
|
||||
import htmlFormsBasicUK from "../../lessons/uk/21-html-forms-basic.json";
|
||||
import htmlFormsValidationUK from "../../lessons/uk/22-html-forms-validation.json";
|
||||
import htmlDetailsSummaryUK from "../../lessons/uk/23-html-details-summary.json";
|
||||
import htmlProgressMeterUK from "../../lessons/uk/24-html-progress-meter.json";
|
||||
import htmlTablesUK from "../../lessons/uk/30-html-tables.json";
|
||||
import htmlSvgUK from "../../lessons/uk/32-html-svg.json";
|
||||
import flexboxUK from "../../lessons/uk/flexbox.json";
|
||||
|
||||
// English module store - ordered by learning path
|
||||
// English module store - ordered for design students
|
||||
const moduleStoreEN = [
|
||||
// Welcome
|
||||
welcomeEN,
|
||||
// HTML Fundamentals
|
||||
htmlElementsEN,
|
||||
htmlFormsBasicEN,
|
||||
htmlFormsValidationEN,
|
||||
// HTML Interactive
|
||||
htmlDetailsSummaryEN,
|
||||
htmlProgressMeterEN,
|
||||
// HTML Data
|
||||
htmlTablesEN,
|
||||
// CSS Fundamentals
|
||||
// CSS Visual (immediate impact)
|
||||
basicSelectorsEN,
|
||||
advancedSelectorsEN,
|
||||
colorsEN,
|
||||
typographyEN,
|
||||
boxModelEN,
|
||||
unitsVariablesEN,
|
||||
// CSS Graphics
|
||||
htmlSvgEN,
|
||||
// CSS Layouts
|
||||
// CSS Layout
|
||||
flexboxEN,
|
||||
gridEN,
|
||||
unitsVariablesEN,
|
||||
responsiveEN,
|
||||
// CSS Animation
|
||||
// CSS Polish
|
||||
transitionsAnimationsEN,
|
||||
// HTML Structure
|
||||
htmlElementsEN,
|
||||
htmlFigureEN,
|
||||
htmlSvgEN,
|
||||
// HTML Interactive
|
||||
htmlDetailsSummaryEN,
|
||||
htmlFormsBasicEN,
|
||||
htmlFormsValidationEN,
|
||||
htmlTablesEN,
|
||||
// Goodbye
|
||||
goodbyeEN
|
||||
];
|
||||
|
||||
// German module store - ordered by learning path
|
||||
// German module store - ordered for design students
|
||||
const moduleStoreDE = [
|
||||
// Welcome
|
||||
welcomeDE,
|
||||
// HTML Fundamentals
|
||||
htmlElementsDE,
|
||||
htmlFormsBasicDE,
|
||||
htmlFormsValidationDE,
|
||||
// HTML Interactive
|
||||
htmlDetailsSummaryDE,
|
||||
htmlProgressMeterDE,
|
||||
// HTML Data
|
||||
htmlTablesDE,
|
||||
// CSS Fundamentals
|
||||
// CSS Visual (immediate impact)
|
||||
basicSelectorsDE,
|
||||
advancedSelectorsEN, // Using EN fallback until translated
|
||||
colorsEN, // Using EN fallback until translated
|
||||
typographyEN, // Using EN fallback until translated
|
||||
boxModelDE,
|
||||
unitsVariablesDE,
|
||||
// CSS Graphics
|
||||
htmlSvgDE,
|
||||
// CSS Layouts
|
||||
// CSS Layout
|
||||
flexboxDE,
|
||||
gridEN, // Using EN fallback until translated
|
||||
unitsVariablesDE,
|
||||
responsiveDE,
|
||||
// CSS Animation
|
||||
// CSS Polish
|
||||
transitionsAnimationsDE,
|
||||
// HTML Structure
|
||||
htmlElementsDE,
|
||||
htmlFigureEN, // Using EN fallback until translated
|
||||
htmlSvgDE,
|
||||
// HTML Interactive
|
||||
htmlDetailsSummaryDE,
|
||||
htmlFormsBasicDE,
|
||||
htmlFormsValidationDE,
|
||||
htmlTablesDE,
|
||||
// Goodbye
|
||||
goodbyeEN
|
||||
];
|
||||
|
||||
// Polish module store - ordered by learning path
|
||||
// Polish module store - ordered for design students
|
||||
const moduleStorePL = [
|
||||
// Welcome
|
||||
welcomePL,
|
||||
// HTML Fundamentals
|
||||
htmlElementsPL,
|
||||
htmlFormsBasicPL,
|
||||
htmlFormsValidationPL,
|
||||
// HTML Interactive
|
||||
htmlDetailsSummaryPL,
|
||||
htmlProgressMeterPL,
|
||||
// HTML Data
|
||||
htmlTablesPL,
|
||||
// CSS Fundamentals
|
||||
// CSS Visual (immediate impact)
|
||||
basicSelectorsPL,
|
||||
advancedSelectorsEN, // Using EN fallback until translated
|
||||
colorsEN, // Using EN fallback until translated
|
||||
typographyEN, // Using EN fallback until translated
|
||||
boxModelPL,
|
||||
unitsVariablesPL,
|
||||
// CSS Graphics
|
||||
htmlSvgPL,
|
||||
// CSS Layouts
|
||||
// CSS Layout
|
||||
flexboxPL,
|
||||
gridEN, // Using EN fallback until translated
|
||||
unitsVariablesPL,
|
||||
responsivePL,
|
||||
// CSS Animation
|
||||
// CSS Polish
|
||||
transitionsAnimationsPL,
|
||||
// HTML Structure
|
||||
htmlElementsPL,
|
||||
htmlFigureEN, // Using EN fallback until translated
|
||||
htmlSvgPL,
|
||||
// HTML Interactive
|
||||
htmlDetailsSummaryPL,
|
||||
htmlFormsBasicPL,
|
||||
htmlFormsValidationPL,
|
||||
htmlTablesPL,
|
||||
// Goodbye
|
||||
goodbyeEN
|
||||
];
|
||||
|
||||
// Spanish module store - ordered by learning path
|
||||
// Spanish module store - ordered for design students
|
||||
const moduleStoreES = [
|
||||
// Welcome
|
||||
welcomeES,
|
||||
// HTML Fundamentals
|
||||
htmlElementsES,
|
||||
htmlFormsBasicES,
|
||||
htmlFormsValidationES,
|
||||
// HTML Interactive
|
||||
htmlDetailsSummaryES,
|
||||
htmlProgressMeterES,
|
||||
// HTML Data
|
||||
htmlTablesES,
|
||||
// CSS Fundamentals
|
||||
// CSS Visual (immediate impact)
|
||||
basicSelectorsES,
|
||||
advancedSelectorsEN, // Using EN fallback until translated
|
||||
colorsEN, // Using EN fallback until translated
|
||||
typographyEN, // Using EN fallback until translated
|
||||
boxModelES,
|
||||
unitsVariablesES,
|
||||
// CSS Graphics
|
||||
htmlSvgES,
|
||||
// CSS Layouts
|
||||
// CSS Layout
|
||||
flexboxES,
|
||||
gridEN, // Using EN fallback until translated
|
||||
unitsVariablesES,
|
||||
responsiveES,
|
||||
// CSS Animation
|
||||
// CSS Polish
|
||||
transitionsAnimationsES,
|
||||
// HTML Structure
|
||||
htmlElementsES,
|
||||
htmlFigureEN, // Using EN fallback until translated
|
||||
htmlSvgES,
|
||||
// HTML Interactive
|
||||
htmlDetailsSummaryES,
|
||||
htmlFormsBasicES,
|
||||
htmlFormsValidationES,
|
||||
htmlTablesES,
|
||||
// Goodbye
|
||||
goodbyeEN
|
||||
];
|
||||
|
||||
// Arabic module store - ordered by learning path
|
||||
// Arabic module store - ordered for design students
|
||||
const moduleStoreAR = [
|
||||
// Welcome
|
||||
welcomeAR,
|
||||
// HTML Fundamentals
|
||||
htmlElementsAR,
|
||||
htmlFormsBasicAR,
|
||||
htmlFormsValidationAR,
|
||||
// HTML Interactive
|
||||
htmlDetailsSummaryAR,
|
||||
htmlProgressMeterAR,
|
||||
// HTML Data
|
||||
htmlTablesAR,
|
||||
// CSS Fundamentals
|
||||
// CSS Visual (immediate impact)
|
||||
basicSelectorsAR,
|
||||
advancedSelectorsEN, // Using EN fallback until translated
|
||||
colorsEN, // Using EN fallback until translated
|
||||
typographyEN, // Using EN fallback until translated
|
||||
boxModelAR,
|
||||
unitsVariablesAR,
|
||||
// CSS Graphics
|
||||
htmlSvgAR,
|
||||
// CSS Layouts
|
||||
// CSS Layout
|
||||
flexboxAR,
|
||||
gridEN, // Using EN fallback until translated
|
||||
unitsVariablesAR,
|
||||
responsiveAR,
|
||||
// CSS Animation
|
||||
// CSS Polish
|
||||
transitionsAnimationsAR,
|
||||
// HTML Structure
|
||||
htmlElementsAR,
|
||||
htmlFigureEN, // Using EN fallback until translated
|
||||
htmlSvgAR,
|
||||
// HTML Interactive
|
||||
htmlDetailsSummaryAR,
|
||||
htmlFormsBasicAR,
|
||||
htmlFormsValidationAR,
|
||||
htmlTablesAR,
|
||||
// Goodbye
|
||||
goodbyeEN
|
||||
];
|
||||
|
||||
// Ukrainian module store - ordered by learning path
|
||||
// Ukrainian module store - ordered for design students
|
||||
const moduleStoreUK = [
|
||||
// Welcome
|
||||
welcomeUK,
|
||||
// HTML Fundamentals
|
||||
htmlElementsUK,
|
||||
htmlFormsBasicUK,
|
||||
htmlFormsValidationUK,
|
||||
// HTML Interactive
|
||||
htmlDetailsSummaryUK,
|
||||
htmlProgressMeterUK,
|
||||
// HTML Data
|
||||
htmlTablesUK,
|
||||
// CSS Fundamentals
|
||||
// CSS Visual (immediate impact)
|
||||
basicSelectorsUK,
|
||||
advancedSelectorsEN, // Using EN fallback until translated
|
||||
colorsEN, // Using EN fallback until translated
|
||||
typographyEN, // Using EN fallback until translated
|
||||
boxModelUK,
|
||||
unitsVariablesUK,
|
||||
// CSS Graphics
|
||||
htmlSvgUK,
|
||||
// CSS Layouts
|
||||
// CSS Layout
|
||||
flexboxUK,
|
||||
gridEN, // Using EN fallback until translated
|
||||
unitsVariablesUK,
|
||||
responsiveUK,
|
||||
// CSS Animation
|
||||
// CSS Polish
|
||||
transitionsAnimationsUK,
|
||||
// HTML Structure
|
||||
htmlElementsUK,
|
||||
htmlFigureEN, // Using EN fallback until translated
|
||||
htmlSvgUK,
|
||||
// HTML Interactive
|
||||
htmlDetailsSummaryUK,
|
||||
htmlFormsBasicUK,
|
||||
htmlFormsValidationUK,
|
||||
htmlTablesUK,
|
||||
// Goodbye
|
||||
goodbyeEN
|
||||
];
|
||||
|
||||
96
src/main.css
96
src/main.css
@@ -207,7 +207,7 @@ kbd {
|
||||
font-weight: bold;
|
||||
cursor: pointer;
|
||||
color: var(--light-text);
|
||||
transition: all 0.2s;
|
||||
transition: color 0.2s, border-color 0.2s;
|
||||
}
|
||||
|
||||
.help-toggle:hover {
|
||||
@@ -587,7 +587,7 @@ kbd {
|
||||
.expected-frame {
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
padding: var(--spacing-sm);
|
||||
padding: var(--spacing-xs);
|
||||
}
|
||||
|
||||
.expected-frame iframe {
|
||||
@@ -617,17 +617,19 @@ kbd {
|
||||
#7c4dff,
|
||||
#9b59b6
|
||||
) border-box;
|
||||
animation: spin-border 2.5s linear forwards;
|
||||
animation: spin-border 3s ease-out forwards;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
/* Colorful glow effect layer */
|
||||
/* Colorful glow effect layer on the preview area */
|
||||
.preview-wrapper.matched::before {
|
||||
content: "";
|
||||
position: absolute;
|
||||
inset: -12px;
|
||||
border-radius: calc(var(--border-radius-md) + 12px);
|
||||
background: conic-gradient(
|
||||
from var(--border-angle),
|
||||
/* Multi-color gradient glow - works in all browsers */
|
||||
background: linear-gradient(
|
||||
135deg,
|
||||
#9b59b6,
|
||||
#e040fb,
|
||||
#00bcd4,
|
||||
@@ -635,9 +637,50 @@ kbd {
|
||||
#9b59b6
|
||||
);
|
||||
z-index: -1;
|
||||
filter: blur(20px);
|
||||
filter: blur(24px);
|
||||
opacity: 0;
|
||||
animation: spin-glow 2.5s linear forwards;
|
||||
animation: glow-pulse 3s ease-out forwards;
|
||||
pointer-events: none;
|
||||
}
|
||||
|
||||
@keyframes glow-pulse {
|
||||
0% {
|
||||
opacity: 0;
|
||||
transform: scale(0.95);
|
||||
}
|
||||
15% {
|
||||
opacity: 0.8;
|
||||
transform: scale(1);
|
||||
}
|
||||
60% {
|
||||
opacity: 0.6;
|
||||
}
|
||||
100% {
|
||||
opacity: 0;
|
||||
transform: scale(1.02);
|
||||
}
|
||||
}
|
||||
|
||||
/* Animated CRISPY badge (matches logo style) */
|
||||
.preview-wrapper.matched::after {
|
||||
content: "Your CODE looks CRISPY!";
|
||||
position: absolute;
|
||||
left: 50%;
|
||||
top: 0;
|
||||
transform: translateX(-50%) translateY(-100%);
|
||||
font-family: system-ui, -apple-system, sans-serif;
|
||||
font-size: 2rem;
|
||||
font-weight: 800;
|
||||
letter-spacing: 0.05em;
|
||||
color: white;
|
||||
background: var(--primary-color);
|
||||
padding: 0.5rem 1.25rem;
|
||||
border-radius: 8px;
|
||||
z-index: 10;
|
||||
pointer-events: none;
|
||||
animation: crispy-fall 3s ease-in-out forwards;
|
||||
opacity: 0;
|
||||
white-space: nowrap;
|
||||
}
|
||||
|
||||
@keyframes spin-border {
|
||||
@@ -656,6 +699,9 @@ kbd {
|
||||
@keyframes spin-glow {
|
||||
0% {
|
||||
--border-angle: 0deg;
|
||||
opacity: 0;
|
||||
}
|
||||
10% {
|
||||
opacity: 0.8;
|
||||
}
|
||||
80% {
|
||||
@@ -668,6 +714,30 @@ kbd {
|
||||
}
|
||||
}
|
||||
|
||||
@keyframes crispy-fall {
|
||||
0% {
|
||||
top: 0;
|
||||
transform: translateX(-50%) translateY(-100%);
|
||||
opacity: 0;
|
||||
}
|
||||
15% {
|
||||
opacity: 1;
|
||||
}
|
||||
50% {
|
||||
top: 50%;
|
||||
transform: translateX(-50%) translateY(-50%);
|
||||
opacity: 1;
|
||||
}
|
||||
85% {
|
||||
opacity: 1;
|
||||
}
|
||||
100% {
|
||||
top: 100%;
|
||||
transform: translateX(-50%) translateY(0%);
|
||||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
/* ================= GAME CONTROLS ================= */
|
||||
.game-controls {
|
||||
display: flex;
|
||||
@@ -895,7 +965,7 @@ button.lesson-list-item {
|
||||
cursor: pointer;
|
||||
font-family: var(--font-main);
|
||||
font-size: 0.9rem;
|
||||
transition: all 0.2s;
|
||||
transition: background 0.2s, color 0.2s, border-color 0.2s;
|
||||
}
|
||||
|
||||
.btn:hover {
|
||||
@@ -1056,7 +1126,7 @@ button.lesson-list-item {
|
||||
height: 20px;
|
||||
background: #ccc;
|
||||
border-radius: 20px;
|
||||
transition: 0.3s;
|
||||
transition: background 0.3s;
|
||||
margin-right: 8px;
|
||||
flex-shrink: 0;
|
||||
}
|
||||
@@ -1070,7 +1140,7 @@ button.lesson-list-item {
|
||||
bottom: 2px;
|
||||
background: white;
|
||||
border-radius: 50%;
|
||||
transition: 0.3s;
|
||||
transition: transform 0.3s;
|
||||
}
|
||||
|
||||
input:checked + .toggle-slider {
|
||||
@@ -1134,7 +1204,7 @@ input:checked + .toggle-slider::before {
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
border-radius: 50%;
|
||||
transition: all 0.2s;
|
||||
transition: background 0.2s, color 0.2s;
|
||||
}
|
||||
|
||||
.dialog-close:hover {
|
||||
@@ -1201,7 +1271,7 @@ input:checked + .toggle-slider::before {
|
||||
border: 1px solid var(--primary-bg-medium);
|
||||
text-decoration: none;
|
||||
color: var(--text-color);
|
||||
transition: all 0.2s ease;
|
||||
transition: background 0.2s, border-color 0.2s, transform 0.2s, box-shadow 0.2s;
|
||||
}
|
||||
|
||||
.project-card:hover {
|
||||
|
||||
Reference in New Issue
Block a user