diff --git a/lessons/00-basic-selectors.json b/lessons/00-basic-selectors.json index 4e01bfd..2f6bc6d 100644 --- a/lessons/00-basic-selectors.json +++ b/lessons/00-basic-selectors.json @@ -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 declarations - pairs of properties and values. Every declaration follows the same pattern:

property: value;

The property is what you want to change (like color or background). The value is what you're setting it to. A colon separates them, and a semicolon ends the line.

Values come in different types:
Keywords: red, bold, center
Numbers with units: 16px, 2rem, 100%
Colors: steelblue, #ff0000", + "task": "Complete the declaration by adding color: coral; to change the text color.", + "previewHTML": "

This text should turn coral.

", + "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 color: coral;" + } + ] + }, + { + "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:

.box {
background: gold;
color: navy;
padding: 1rem;
}

The order usually doesn't matter - CSS applies them all. When properties conflict, the last one wins.", + "task": "Add two declarations: background: lavender; and padding: 1rem;", + "previewHTML": "
A styled card with background and padding.
", + "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 background: lavender;" + }, + { + "type": "property_value", + "value": { "property": "padding", "expected": "1rem" }, + "message": "Add padding: 1rem;" + } + ] + }, { "id": "introduction-to-selectors", "title": "What's a Selector?", diff --git a/lessons/22-html-forms-validation.json b/lessons/22-html-forms-validation.json index ef7e909..af4ac0d 100644 --- a/lessons/22-html-forms-validation.json +++ b/lessons/22-html-forms-validation.json @@ -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 required attribute prevents form submission if the field is empty.

Add it to any input that must be filled:
<input type=\"text\" required>

The browser shows a validation message automatically.", - "task": "Make both the name and email fields required by adding the required attribute.", + "description": "The required attribute prevents form submission if the field is empty. The browser shows a validation message automatically - no JavaScript needed!

Add it to any input that must be filled:
<input type=\"text\" required>", + "task": "Make both the name and email fields required by adding the required 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": "
\n \n \n \n \n \n \n \n
", - "solution": "
\n \n \n \n \n \n \n \n
", + "initialCode": "
\n \n \n \n \n \n \n \n
", + "solution": "
\n \n \n \n \n \n \n \n
", "previewContainer": "preview-area", "validations": [ { "type": "attribute_value", "value": { "selector": "input[name='name']", "attr": "required", "value": true }, - "message": "Add the required attribute to the name input" + "message": "Add required to the name input" }, { "type": "attribute_value", "value": { "selector": "input[name='email']", "attr": "required", "value": true }, - "message": "Add the required attribute to the email input" - } - ] - }, - { - "id": "input-constraints", - "title": "Constraints", - "description": "Control what users can enter:

minlength / maxlength - Text length limits
min / max - Number range
pattern - Regex pattern matching
placeholder - Hint text (not a label!)", - "task": "Add validation to the password input:
1. Add minlength=\"8\" for minimum length
2. Add maxlength=\"20\" for maximum length
3. Add placeholder=\"Enter password\" 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": "
\n \n \n Must be 8-20 characters\n \n \n
", - "solution": "
\n \n \n Must be 8-20 characters\n \n \n
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "attribute_value", - "value": { "selector": "input[type='password']", "attr": "minlength", "value": "8" }, - "message": "Add minlength=\"8\" to the password input" - }, - { - "type": "attribute_value", - "value": { "selector": "input[type='password']", "attr": "maxlength", "value": "20" }, - "message": "Add maxlength=\"20\" to the password input" - }, - { - "type": "attribute_value", - "value": { "selector": "input[type='password']", "attr": "placeholder", "value": null }, - "message": "Add a placeholder to hint what to enter" - } - ] - }, - { - "id": "complete-registration", - "title": "Full Form", - "description": "Build a complete registration form with all validation concepts:

- Required fields marked with *
- Email validation (use type=\"email\")
- Password with length constraints
- Terms checkbox (required)
- 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": "
\n

Create Account

\n \n \n \n \n \n \n \n \n \n \n \n \n \n
", - "solution": "
\n

Create Account

\n \n \n \n \n \n \n \n \n \n \n \n \n \n
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "attribute_value", - "value": { "selector": "#fullname", "attr": "required", "value": true }, - "message": "Make the full name field required" - }, - { - "type": "attribute_value", - "value": { "selector": "#email", "attr": "type", "value": "email" }, - "message": "Set the email input type=\"email\"" - }, - { - "type": "attribute_value", - "value": { "selector": "#email", "attr": "required", "value": true }, - "message": "Make the email field required" - }, - { - "type": "attribute_value", - "value": { "selector": "#password", "attr": "type", "value": "password" }, - "message": "Set the password input type=\"password\"" - }, - { - "type": "attribute_value", - "value": { "selector": "#password", "attr": "required", "value": true }, - "message": "Make the password field required" - }, - { - "type": "attribute_value", - "value": { "selector": "#password", "attr": "minlength", "value": "8" }, - "message": "Add minlength=\"8\" to password" - }, - { - "type": "attribute_value", - "value": { "selector": "#terms", "attr": "required", "value": true }, - "message": "Make the terms checkbox required" + "message": "Add required to the email input" } ] } diff --git a/lessons/30-html-tables.json b/lessons/30-html-tables.json index 2d0c6d0..9fd3c63 100644 --- a/lessons/30-html-tables.json +++ b/lessons/30-html-tables.json @@ -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 <table> with <tr> for rows. Inside rows, use <th> for headers and <td> for data cells.

The <caption> element provides an accessible title for the table.", - "task": "Create a simple table with:
1. A <caption> saying Fruit Prices
2. A header row with Fruit and Price columns
3. At least 2 data rows", + "title": "Data Tables", + "description": "Tables display structured data in rows and columns. Use <table> as the container, <tr> for rows, <th> for header cells, and <td> for data cells.

Add <caption> for an accessible title that describes the table's content.", + "task": "Create a pricing table:
1. A <caption> saying Pricing
2. A header row with Plan and Price
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": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n
Fruit Prices
FruitPrice
Apple$1.50
Banana$0.75
", + "solution": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n
Pricing
PlanPrice
Basic$9
Pro$29
", "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 (<th>) 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 <thead> to group header rows and <tbody> to group data rows. This helps browsers and assistive technology understand the table structure.

You can also use <tfoot> for footer rows like totals.", - "task": "Create a structured table:
1. A <caption> with Monthly Sales
2. A <thead> with Month and Revenue headers
3. A <tbody> 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": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Monthly Sales
MonthRevenue
January$12,500
February$14,200
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "element_exists", - "value": "table", - "message": "Add a <table> element" - }, - { - "type": "element_exists", - "value": "caption", - "message": "Add a <caption> element" - }, - { - "type": "element_exists", - "value": "thead", - "message": "Add a <thead> for the header section" - }, - { - "type": "element_exists", - "value": "tbody", - "message": "Add a <tbody> 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 <tfoot> to create a footer section for totals or summary data. The footer stays at the bottom even if tbody has many rows.

Combine all sections for a fully structured, accessible table.", - "task": "Create a complete table:
1. A <caption> with Order Summary
2. A <thead> with Item and Price headers
3. A <tbody> with 2 items
4. A <tfoot> 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": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Order Summary
ItemPrice
Widget$25.00
Gadget$35.00
Total$60.00
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "element_exists", - "value": "table", - "message": "Add a <table> element" - }, - { - "type": "element_exists", - "value": "caption", - "message": "Add a <caption> element" - }, - { - "type": "element_exists", - "value": "thead", - "message": "Add a <thead> section" - }, - { - "type": "element_exists", - "value": "tbody", - "message": "Add a <tbody> section" - }, - { - "type": "element_exists", - "value": "tfoot", - "message": "Add a <tfoot> 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)" } ] } diff --git a/src/app.js b/src/app.js index a3c505d..67c2d5e 100644 --- a/src/app.js +++ b/src/app.js @@ -630,7 +630,7 @@ function runCode() { elements.previewWrapper?.classList.add("matched"); setTimeout(() => { elements.previewWrapper?.classList.remove("matched"); - }, 3000); + }, 3500); updateNavigationButtons(); updateProgressDisplay(); diff --git a/src/config/lessons.js b/src/config/lessons.js index 2b6916d..118949d 100644 --- a/src/config/lessons.js +++ b/src/config/lessons.js @@ -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 ]; diff --git a/src/main.css b/src/main.css index 8f41a75..3d06168 100644 --- a/src/main.css +++ b/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 {