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",
|
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||||
"id": "css-basic-selectors",
|
"id": "css-basic-selectors",
|
||||||
"title": "CSS Selectors",
|
"title": "CSS Basics",
|
||||||
"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.",
|
"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",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"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",
|
"id": "introduction-to-selectors",
|
||||||
"title": "What's a Selector?",
|
"title": "What's a Selector?",
|
||||||
|
|||||||
@@ -1,110 +1,32 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-forms-validation",
|
"id": "html-forms-validation",
|
||||||
"title": "HTML Validation",
|
"title": "Form Validation",
|
||||||
"description": "Learn HTML5 built-in form validation attributes",
|
"description": "Use HTML5 built-in validation for better user experience",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "intermediate",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "required-fields",
|
"id": "required-fields",
|
||||||
"title": "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.",
|
"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.",
|
"task": "Make both the name and email fields required by adding the <kbd>required</kbd> attribute to each input.",
|
||||||
"previewHTML": "",
|
"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": "",
|
"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>",
|
"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>",
|
"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",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "input[name='name']", "attr": "required", "value": true },
|
"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",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "input[name='email']", "attr": "required", "value": true },
|
"value": { "selector": "input[name='email']", "attr": "required", "value": true },
|
||||||
"message": "Add the <kbd>required</kbd> attribute to the email input"
|
"message": "Add <kbd>required</kbd> 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>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,20 @@
|
|||||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-tables",
|
"id": "html-tables",
|
||||||
"title": "HTML Tables",
|
"title": "HTML Tables",
|
||||||
"description": "Create structured data tables with headers and captions",
|
"description": "Create structured data tables with semantic markup",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "table-basic",
|
"id": "table-basic",
|
||||||
"title": "Basic Table Structure",
|
"title": "Data Tables",
|
||||||
"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.",
|
"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 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",
|
"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": "",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"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",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
@@ -31,95 +31,12 @@
|
|||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "th", "min": 2 },
|
"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",
|
"type": "element_count",
|
||||||
"value": { "selector": "tr", "min": 3 },
|
"value": { "selector": "tr", "min": 3 },
|
||||||
"message": "Add at least 3 rows (1 header + 2 data rows)"
|
"message": "Add 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"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -630,7 +630,7 @@ function runCode() {
|
|||||||
elements.previewWrapper?.classList.add("matched");
|
elements.previewWrapper?.classList.add("matched");
|
||||||
setTimeout(() => {
|
setTimeout(() => {
|
||||||
elements.previewWrapper?.classList.remove("matched");
|
elements.previewWrapper?.classList.remove("matched");
|
||||||
}, 3000);
|
}, 3500);
|
||||||
|
|
||||||
updateNavigationButtons();
|
updateNavigationButtons();
|
||||||
updateProgressDisplay();
|
updateProgressDisplay();
|
||||||
|
|||||||
@@ -6,7 +6,6 @@
|
|||||||
// English lesson imports
|
// English lesson imports
|
||||||
import welcomeEN from "../../lessons/00-welcome.json";
|
import welcomeEN from "../../lessons/00-welcome.json";
|
||||||
import basicSelectorsEN from "../../lessons/00-basic-selectors.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 boxModelEN from "../../lessons/01-box-model.json";
|
||||||
import colorsEN from "../../lessons/03-colors.json";
|
import colorsEN from "../../lessons/03-colors.json";
|
||||||
import typographyEN from "../../lessons/04-typography.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 htmlFormsBasicEN from "../../lessons/21-html-forms-basic.json";
|
||||||
import htmlFormsValidationEN from "../../lessons/22-html-forms-validation.json";
|
import htmlFormsValidationEN from "../../lessons/22-html-forms-validation.json";
|
||||||
import htmlDetailsSummaryEN from "../../lessons/23-html-details-summary.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 htmlTablesEN from "../../lessons/30-html-tables.json";
|
||||||
import htmlSvgEN from "../../lessons/32-html-svg.json";
|
import htmlSvgEN from "../../lessons/32-html-svg.json";
|
||||||
import flexboxEN from "../../lessons/flexbox.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 htmlFormsBasicDE from "../../lessons/de/21-html-forms-basic.json";
|
||||||
import htmlFormsValidationDE from "../../lessons/de/22-html-forms-validation.json";
|
import htmlFormsValidationDE from "../../lessons/de/22-html-forms-validation.json";
|
||||||
import htmlDetailsSummaryDE from "../../lessons/de/23-html-details-summary.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 htmlTablesDE from "../../lessons/de/30-html-tables.json";
|
||||||
import htmlSvgDE from "../../lessons/de/32-html-svg.json";
|
import htmlSvgDE from "../../lessons/de/32-html-svg.json";
|
||||||
import flexboxDE from "../../lessons/de/flexbox.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 htmlFormsBasicPL from "../../lessons/pl/21-html-forms-basic.json";
|
||||||
import htmlFormsValidationPL from "../../lessons/pl/22-html-forms-validation.json";
|
import htmlFormsValidationPL from "../../lessons/pl/22-html-forms-validation.json";
|
||||||
import htmlDetailsSummaryPL from "../../lessons/pl/23-html-details-summary.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 htmlTablesPL from "../../lessons/pl/30-html-tables.json";
|
||||||
import htmlSvgPL from "../../lessons/pl/32-html-svg.json";
|
import htmlSvgPL from "../../lessons/pl/32-html-svg.json";
|
||||||
import flexboxPL from "../../lessons/pl/flexbox.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 htmlFormsBasicES from "../../lessons/es/21-html-forms-basic.json";
|
||||||
import htmlFormsValidationES from "../../lessons/es/22-html-forms-validation.json";
|
import htmlFormsValidationES from "../../lessons/es/22-html-forms-validation.json";
|
||||||
import htmlDetailsSummaryES from "../../lessons/es/23-html-details-summary.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 htmlTablesES from "../../lessons/es/30-html-tables.json";
|
||||||
import htmlSvgES from "../../lessons/es/32-html-svg.json";
|
import htmlSvgES from "../../lessons/es/32-html-svg.json";
|
||||||
import flexboxES from "../../lessons/es/flexbox.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 htmlFormsBasicAR from "../../lessons/ar/21-html-forms-basic.json";
|
||||||
import htmlFormsValidationAR from "../../lessons/ar/22-html-forms-validation.json";
|
import htmlFormsValidationAR from "../../lessons/ar/22-html-forms-validation.json";
|
||||||
import htmlDetailsSummaryAR from "../../lessons/ar/23-html-details-summary.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 htmlTablesAR from "../../lessons/ar/30-html-tables.json";
|
||||||
import htmlSvgAR from "../../lessons/ar/32-html-svg.json";
|
import htmlSvgAR from "../../lessons/ar/32-html-svg.json";
|
||||||
import flexboxAR from "../../lessons/ar/flexbox.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 htmlFormsBasicUK from "../../lessons/uk/21-html-forms-basic.json";
|
||||||
import htmlFormsValidationUK from "../../lessons/uk/22-html-forms-validation.json";
|
import htmlFormsValidationUK from "../../lessons/uk/22-html-forms-validation.json";
|
||||||
import htmlDetailsSummaryUK from "../../lessons/uk/23-html-details-summary.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 htmlTablesUK from "../../lessons/uk/30-html-tables.json";
|
||||||
import htmlSvgUK from "../../lessons/uk/32-html-svg.json";
|
import htmlSvgUK from "../../lessons/uk/32-html-svg.json";
|
||||||
import flexboxUK from "../../lessons/uk/flexbox.json";
|
import flexboxUK from "../../lessons/uk/flexbox.json";
|
||||||
|
|
||||||
// English module store - ordered by learning path
|
// English module store - ordered for design students
|
||||||
const moduleStoreEN = [
|
const moduleStoreEN = [
|
||||||
// Welcome
|
// Welcome
|
||||||
welcomeEN,
|
welcomeEN,
|
||||||
// HTML Fundamentals
|
// CSS Visual (immediate impact)
|
||||||
htmlElementsEN,
|
|
||||||
htmlFormsBasicEN,
|
|
||||||
htmlFormsValidationEN,
|
|
||||||
// HTML Interactive
|
|
||||||
htmlDetailsSummaryEN,
|
|
||||||
htmlProgressMeterEN,
|
|
||||||
// HTML Data
|
|
||||||
htmlTablesEN,
|
|
||||||
// CSS Fundamentals
|
|
||||||
basicSelectorsEN,
|
basicSelectorsEN,
|
||||||
advancedSelectorsEN,
|
|
||||||
colorsEN,
|
colorsEN,
|
||||||
typographyEN,
|
typographyEN,
|
||||||
boxModelEN,
|
boxModelEN,
|
||||||
unitsVariablesEN,
|
// CSS Layout
|
||||||
// CSS Graphics
|
|
||||||
htmlSvgEN,
|
|
||||||
// CSS Layouts
|
|
||||||
flexboxEN,
|
flexboxEN,
|
||||||
gridEN,
|
gridEN,
|
||||||
|
unitsVariablesEN,
|
||||||
responsiveEN,
|
responsiveEN,
|
||||||
// CSS Animation
|
// CSS Polish
|
||||||
transitionsAnimationsEN,
|
transitionsAnimationsEN,
|
||||||
|
// HTML Structure
|
||||||
|
htmlElementsEN,
|
||||||
|
htmlFigureEN,
|
||||||
|
htmlSvgEN,
|
||||||
|
// HTML Interactive
|
||||||
|
htmlDetailsSummaryEN,
|
||||||
|
htmlFormsBasicEN,
|
||||||
|
htmlFormsValidationEN,
|
||||||
|
htmlTablesEN,
|
||||||
// Goodbye
|
// Goodbye
|
||||||
goodbyeEN
|
goodbyeEN
|
||||||
];
|
];
|
||||||
|
|
||||||
// German module store - ordered by learning path
|
// German module store - ordered for design students
|
||||||
const moduleStoreDE = [
|
const moduleStoreDE = [
|
||||||
// Welcome
|
// Welcome
|
||||||
welcomeDE,
|
welcomeDE,
|
||||||
// HTML Fundamentals
|
// CSS Visual (immediate impact)
|
||||||
htmlElementsDE,
|
|
||||||
htmlFormsBasicDE,
|
|
||||||
htmlFormsValidationDE,
|
|
||||||
// HTML Interactive
|
|
||||||
htmlDetailsSummaryDE,
|
|
||||||
htmlProgressMeterDE,
|
|
||||||
// HTML Data
|
|
||||||
htmlTablesDE,
|
|
||||||
// CSS Fundamentals
|
|
||||||
basicSelectorsDE,
|
basicSelectorsDE,
|
||||||
advancedSelectorsEN, // Using EN fallback until translated
|
|
||||||
colorsEN, // Using EN fallback until translated
|
colorsEN, // Using EN fallback until translated
|
||||||
typographyEN, // Using EN fallback until translated
|
typographyEN, // Using EN fallback until translated
|
||||||
boxModelDE,
|
boxModelDE,
|
||||||
unitsVariablesDE,
|
// CSS Layout
|
||||||
// CSS Graphics
|
|
||||||
htmlSvgDE,
|
|
||||||
// CSS Layouts
|
|
||||||
flexboxDE,
|
flexboxDE,
|
||||||
gridEN, // Using EN fallback until translated
|
gridEN, // Using EN fallback until translated
|
||||||
|
unitsVariablesDE,
|
||||||
responsiveDE,
|
responsiveDE,
|
||||||
// CSS Animation
|
// CSS Polish
|
||||||
transitionsAnimationsDE,
|
transitionsAnimationsDE,
|
||||||
|
// HTML Structure
|
||||||
|
htmlElementsDE,
|
||||||
|
htmlFigureEN, // Using EN fallback until translated
|
||||||
|
htmlSvgDE,
|
||||||
|
// HTML Interactive
|
||||||
|
htmlDetailsSummaryDE,
|
||||||
|
htmlFormsBasicDE,
|
||||||
|
htmlFormsValidationDE,
|
||||||
|
htmlTablesDE,
|
||||||
// Goodbye
|
// Goodbye
|
||||||
goodbyeEN
|
goodbyeEN
|
||||||
];
|
];
|
||||||
|
|
||||||
// Polish module store - ordered by learning path
|
// Polish module store - ordered for design students
|
||||||
const moduleStorePL = [
|
const moduleStorePL = [
|
||||||
// Welcome
|
// Welcome
|
||||||
welcomePL,
|
welcomePL,
|
||||||
// HTML Fundamentals
|
// CSS Visual (immediate impact)
|
||||||
htmlElementsPL,
|
|
||||||
htmlFormsBasicPL,
|
|
||||||
htmlFormsValidationPL,
|
|
||||||
// HTML Interactive
|
|
||||||
htmlDetailsSummaryPL,
|
|
||||||
htmlProgressMeterPL,
|
|
||||||
// HTML Data
|
|
||||||
htmlTablesPL,
|
|
||||||
// CSS Fundamentals
|
|
||||||
basicSelectorsPL,
|
basicSelectorsPL,
|
||||||
advancedSelectorsEN, // Using EN fallback until translated
|
|
||||||
colorsEN, // Using EN fallback until translated
|
colorsEN, // Using EN fallback until translated
|
||||||
typographyEN, // Using EN fallback until translated
|
typographyEN, // Using EN fallback until translated
|
||||||
boxModelPL,
|
boxModelPL,
|
||||||
unitsVariablesPL,
|
// CSS Layout
|
||||||
// CSS Graphics
|
|
||||||
htmlSvgPL,
|
|
||||||
// CSS Layouts
|
|
||||||
flexboxPL,
|
flexboxPL,
|
||||||
gridEN, // Using EN fallback until translated
|
gridEN, // Using EN fallback until translated
|
||||||
|
unitsVariablesPL,
|
||||||
responsivePL,
|
responsivePL,
|
||||||
// CSS Animation
|
// CSS Polish
|
||||||
transitionsAnimationsPL,
|
transitionsAnimationsPL,
|
||||||
|
// HTML Structure
|
||||||
|
htmlElementsPL,
|
||||||
|
htmlFigureEN, // Using EN fallback until translated
|
||||||
|
htmlSvgPL,
|
||||||
|
// HTML Interactive
|
||||||
|
htmlDetailsSummaryPL,
|
||||||
|
htmlFormsBasicPL,
|
||||||
|
htmlFormsValidationPL,
|
||||||
|
htmlTablesPL,
|
||||||
// Goodbye
|
// Goodbye
|
||||||
goodbyeEN
|
goodbyeEN
|
||||||
];
|
];
|
||||||
|
|
||||||
// Spanish module store - ordered by learning path
|
// Spanish module store - ordered for design students
|
||||||
const moduleStoreES = [
|
const moduleStoreES = [
|
||||||
// Welcome
|
// Welcome
|
||||||
welcomeES,
|
welcomeES,
|
||||||
// HTML Fundamentals
|
// CSS Visual (immediate impact)
|
||||||
htmlElementsES,
|
|
||||||
htmlFormsBasicES,
|
|
||||||
htmlFormsValidationES,
|
|
||||||
// HTML Interactive
|
|
||||||
htmlDetailsSummaryES,
|
|
||||||
htmlProgressMeterES,
|
|
||||||
// HTML Data
|
|
||||||
htmlTablesES,
|
|
||||||
// CSS Fundamentals
|
|
||||||
basicSelectorsES,
|
basicSelectorsES,
|
||||||
advancedSelectorsEN, // Using EN fallback until translated
|
|
||||||
colorsEN, // Using EN fallback until translated
|
colorsEN, // Using EN fallback until translated
|
||||||
typographyEN, // Using EN fallback until translated
|
typographyEN, // Using EN fallback until translated
|
||||||
boxModelES,
|
boxModelES,
|
||||||
unitsVariablesES,
|
// CSS Layout
|
||||||
// CSS Graphics
|
|
||||||
htmlSvgES,
|
|
||||||
// CSS Layouts
|
|
||||||
flexboxES,
|
flexboxES,
|
||||||
gridEN, // Using EN fallback until translated
|
gridEN, // Using EN fallback until translated
|
||||||
|
unitsVariablesES,
|
||||||
responsiveES,
|
responsiveES,
|
||||||
// CSS Animation
|
// CSS Polish
|
||||||
transitionsAnimationsES,
|
transitionsAnimationsES,
|
||||||
|
// HTML Structure
|
||||||
|
htmlElementsES,
|
||||||
|
htmlFigureEN, // Using EN fallback until translated
|
||||||
|
htmlSvgES,
|
||||||
|
// HTML Interactive
|
||||||
|
htmlDetailsSummaryES,
|
||||||
|
htmlFormsBasicES,
|
||||||
|
htmlFormsValidationES,
|
||||||
|
htmlTablesES,
|
||||||
// Goodbye
|
// Goodbye
|
||||||
goodbyeEN
|
goodbyeEN
|
||||||
];
|
];
|
||||||
|
|
||||||
// Arabic module store - ordered by learning path
|
// Arabic module store - ordered for design students
|
||||||
const moduleStoreAR = [
|
const moduleStoreAR = [
|
||||||
// Welcome
|
// Welcome
|
||||||
welcomeAR,
|
welcomeAR,
|
||||||
// HTML Fundamentals
|
// CSS Visual (immediate impact)
|
||||||
htmlElementsAR,
|
|
||||||
htmlFormsBasicAR,
|
|
||||||
htmlFormsValidationAR,
|
|
||||||
// HTML Interactive
|
|
||||||
htmlDetailsSummaryAR,
|
|
||||||
htmlProgressMeterAR,
|
|
||||||
// HTML Data
|
|
||||||
htmlTablesAR,
|
|
||||||
// CSS Fundamentals
|
|
||||||
basicSelectorsAR,
|
basicSelectorsAR,
|
||||||
advancedSelectorsEN, // Using EN fallback until translated
|
|
||||||
colorsEN, // Using EN fallback until translated
|
colorsEN, // Using EN fallback until translated
|
||||||
typographyEN, // Using EN fallback until translated
|
typographyEN, // Using EN fallback until translated
|
||||||
boxModelAR,
|
boxModelAR,
|
||||||
unitsVariablesAR,
|
// CSS Layout
|
||||||
// CSS Graphics
|
|
||||||
htmlSvgAR,
|
|
||||||
// CSS Layouts
|
|
||||||
flexboxAR,
|
flexboxAR,
|
||||||
gridEN, // Using EN fallback until translated
|
gridEN, // Using EN fallback until translated
|
||||||
|
unitsVariablesAR,
|
||||||
responsiveAR,
|
responsiveAR,
|
||||||
// CSS Animation
|
// CSS Polish
|
||||||
transitionsAnimationsAR,
|
transitionsAnimationsAR,
|
||||||
|
// HTML Structure
|
||||||
|
htmlElementsAR,
|
||||||
|
htmlFigureEN, // Using EN fallback until translated
|
||||||
|
htmlSvgAR,
|
||||||
|
// HTML Interactive
|
||||||
|
htmlDetailsSummaryAR,
|
||||||
|
htmlFormsBasicAR,
|
||||||
|
htmlFormsValidationAR,
|
||||||
|
htmlTablesAR,
|
||||||
// Goodbye
|
// Goodbye
|
||||||
goodbyeEN
|
goodbyeEN
|
||||||
];
|
];
|
||||||
|
|
||||||
// Ukrainian module store - ordered by learning path
|
// Ukrainian module store - ordered for design students
|
||||||
const moduleStoreUK = [
|
const moduleStoreUK = [
|
||||||
// Welcome
|
// Welcome
|
||||||
welcomeUK,
|
welcomeUK,
|
||||||
// HTML Fundamentals
|
// CSS Visual (immediate impact)
|
||||||
htmlElementsUK,
|
|
||||||
htmlFormsBasicUK,
|
|
||||||
htmlFormsValidationUK,
|
|
||||||
// HTML Interactive
|
|
||||||
htmlDetailsSummaryUK,
|
|
||||||
htmlProgressMeterUK,
|
|
||||||
// HTML Data
|
|
||||||
htmlTablesUK,
|
|
||||||
// CSS Fundamentals
|
|
||||||
basicSelectorsUK,
|
basicSelectorsUK,
|
||||||
advancedSelectorsEN, // Using EN fallback until translated
|
|
||||||
colorsEN, // Using EN fallback until translated
|
colorsEN, // Using EN fallback until translated
|
||||||
typographyEN, // Using EN fallback until translated
|
typographyEN, // Using EN fallback until translated
|
||||||
boxModelUK,
|
boxModelUK,
|
||||||
unitsVariablesUK,
|
// CSS Layout
|
||||||
// CSS Graphics
|
|
||||||
htmlSvgUK,
|
|
||||||
// CSS Layouts
|
|
||||||
flexboxUK,
|
flexboxUK,
|
||||||
gridEN, // Using EN fallback until translated
|
gridEN, // Using EN fallback until translated
|
||||||
|
unitsVariablesUK,
|
||||||
responsiveUK,
|
responsiveUK,
|
||||||
// CSS Animation
|
// CSS Polish
|
||||||
transitionsAnimationsUK,
|
transitionsAnimationsUK,
|
||||||
|
// HTML Structure
|
||||||
|
htmlElementsUK,
|
||||||
|
htmlFigureEN, // Using EN fallback until translated
|
||||||
|
htmlSvgUK,
|
||||||
|
// HTML Interactive
|
||||||
|
htmlDetailsSummaryUK,
|
||||||
|
htmlFormsBasicUK,
|
||||||
|
htmlFormsValidationUK,
|
||||||
|
htmlTablesUK,
|
||||||
// Goodbye
|
// Goodbye
|
||||||
goodbyeEN
|
goodbyeEN
|
||||||
];
|
];
|
||||||
|
|||||||
96
src/main.css
96
src/main.css
@@ -207,7 +207,7 @@ kbd {
|
|||||||
font-weight: bold;
|
font-weight: bold;
|
||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
color: var(--light-text);
|
color: var(--light-text);
|
||||||
transition: all 0.2s;
|
transition: color 0.2s, border-color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.help-toggle:hover {
|
.help-toggle:hover {
|
||||||
@@ -587,7 +587,7 @@ kbd {
|
|||||||
.expected-frame {
|
.expected-frame {
|
||||||
width: 100%;
|
width: 100%;
|
||||||
height: 100%;
|
height: 100%;
|
||||||
padding: var(--spacing-sm);
|
padding: var(--spacing-xs);
|
||||||
}
|
}
|
||||||
|
|
||||||
.expected-frame iframe {
|
.expected-frame iframe {
|
||||||
@@ -617,17 +617,19 @@ kbd {
|
|||||||
#7c4dff,
|
#7c4dff,
|
||||||
#9b59b6
|
#9b59b6
|
||||||
) border-box;
|
) 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 {
|
.preview-wrapper.matched::before {
|
||||||
content: "";
|
content: "";
|
||||||
position: absolute;
|
position: absolute;
|
||||||
inset: -12px;
|
inset: -12px;
|
||||||
border-radius: calc(var(--border-radius-md) + 12px);
|
border-radius: calc(var(--border-radius-md) + 12px);
|
||||||
background: conic-gradient(
|
/* Multi-color gradient glow - works in all browsers */
|
||||||
from var(--border-angle),
|
background: linear-gradient(
|
||||||
|
135deg,
|
||||||
#9b59b6,
|
#9b59b6,
|
||||||
#e040fb,
|
#e040fb,
|
||||||
#00bcd4,
|
#00bcd4,
|
||||||
@@ -635,9 +637,50 @@ kbd {
|
|||||||
#9b59b6
|
#9b59b6
|
||||||
);
|
);
|
||||||
z-index: -1;
|
z-index: -1;
|
||||||
filter: blur(20px);
|
filter: blur(24px);
|
||||||
opacity: 0;
|
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 {
|
@keyframes spin-border {
|
||||||
@@ -656,6 +699,9 @@ kbd {
|
|||||||
@keyframes spin-glow {
|
@keyframes spin-glow {
|
||||||
0% {
|
0% {
|
||||||
--border-angle: 0deg;
|
--border-angle: 0deg;
|
||||||
|
opacity: 0;
|
||||||
|
}
|
||||||
|
10% {
|
||||||
opacity: 0.8;
|
opacity: 0.8;
|
||||||
}
|
}
|
||||||
80% {
|
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 ================= */
|
||||||
.game-controls {
|
.game-controls {
|
||||||
display: flex;
|
display: flex;
|
||||||
@@ -895,7 +965,7 @@ button.lesson-list-item {
|
|||||||
cursor: pointer;
|
cursor: pointer;
|
||||||
font-family: var(--font-main);
|
font-family: var(--font-main);
|
||||||
font-size: 0.9rem;
|
font-size: 0.9rem;
|
||||||
transition: all 0.2s;
|
transition: background 0.2s, color 0.2s, border-color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.btn:hover {
|
.btn:hover {
|
||||||
@@ -1056,7 +1126,7 @@ button.lesson-list-item {
|
|||||||
height: 20px;
|
height: 20px;
|
||||||
background: #ccc;
|
background: #ccc;
|
||||||
border-radius: 20px;
|
border-radius: 20px;
|
||||||
transition: 0.3s;
|
transition: background 0.3s;
|
||||||
margin-right: 8px;
|
margin-right: 8px;
|
||||||
flex-shrink: 0;
|
flex-shrink: 0;
|
||||||
}
|
}
|
||||||
@@ -1070,7 +1140,7 @@ button.lesson-list-item {
|
|||||||
bottom: 2px;
|
bottom: 2px;
|
||||||
background: white;
|
background: white;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
transition: 0.3s;
|
transition: transform 0.3s;
|
||||||
}
|
}
|
||||||
|
|
||||||
input:checked + .toggle-slider {
|
input:checked + .toggle-slider {
|
||||||
@@ -1134,7 +1204,7 @@ input:checked + .toggle-slider::before {
|
|||||||
align-items: center;
|
align-items: center;
|
||||||
justify-content: center;
|
justify-content: center;
|
||||||
border-radius: 50%;
|
border-radius: 50%;
|
||||||
transition: all 0.2s;
|
transition: background 0.2s, color 0.2s;
|
||||||
}
|
}
|
||||||
|
|
||||||
.dialog-close:hover {
|
.dialog-close:hover {
|
||||||
@@ -1201,7 +1271,7 @@ input:checked + .toggle-slider::before {
|
|||||||
border: 1px solid var(--primary-bg-medium);
|
border: 1px solid var(--primary-bg-medium);
|
||||||
text-decoration: none;
|
text-decoration: none;
|
||||||
color: var(--text-color);
|
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 {
|
.project-card:hover {
|
||||||
|
|||||||
Reference in New Issue
Block a user