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 2a96ba9d00
commit 2cd94caafb
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)"
}
]
}

View File

@@ -630,7 +630,7 @@ function runCode() {
elements.previewWrapper?.classList.add("matched");
setTimeout(() => {
elements.previewWrapper?.classList.remove("matched");
}, 3000);
}, 3500);
updateNavigationButtons();
updateProgressDisplay();

View File

@@ -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
];

View File

@@ -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 {