diff --git a/lessons/00-welcome.json b/lessons/00-welcome.json
index 0a0ba73..76a48f5 100644
--- a/lessons/00-welcome.json
+++ b/lessons/00-welcome.json
@@ -10,7 +10,7 @@
"id": "get-started",
"title": "Get Started",
"description": "Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required!
What you'll learn: • HTML - Semantic elements, forms, tables, SVG (HTML Block & Inline, HTML Forms, HTML Tables) • CSS - Selectors, box model, flexbox, animations (CSS Selectors, CSS Box Model, CSS Flexbox) • Responsive Design - Media queries and mobile-first layouts
How it works: 1. Read the task in the left panel 2. Write code in the editor 3. See live results in the preview 4. Get instant feedback with hints
Keyboard shortcuts:Ctrl+Z to undo, Ctrl+Shift+Z to redo
More resources: • HTML over JS - Native HTML vs JavaScript solutions • Web Engineering Mandala - JavaScript technology roadmap",
- "task": "Hello World",
+ "task": "Write Hello World",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; } h1 { color: #6366f1; }",
"sandboxCSS": "",
@@ -20,8 +20,8 @@
"validations": [
{
"type": "contains",
- "value": "Hello",
- "message": "Write 'Hello World'"
+ "value": "Hello World",
+ "message": "Write Hello World"
}
]
},
diff --git a/lessons/06-transitions-animations.json b/lessons/06-transitions-animations.json
index 468e21a..2014250 100644
--- a/lessons/06-transitions-animations.json
+++ b/lessons/06-transitions-animations.json
@@ -8,7 +8,7 @@
{
"id": "transitions-1",
"title": "Transitions",
- "description": "Learn how to apply transition to properties for smooth changes on state changes.",
+ "description": "Learn how to apply transition to properties for smooth changes on state changes.
",
"task": "Add transition: background-color 0.3s to .btn so the color fades smoothly on hover.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: black; color: white; padding: 0.5rem 1rem; border: none; cursor: pointer; } .btn:hover { background: white; color: black; }",
@@ -63,7 +63,7 @@
{
"id": "transitions-3",
"title": "Keyframes",
- "description": "Create named animations using @keyframes and apply them via the animation shorthand.",
+ "description": "Create named animations using @keyframes and apply them via the animation shorthand.
",
"task": "Define a keyframe at 50% with transform: translateY(-20px) and apply animation: bounce 1s infinite to .ball.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: crimson; border-radius: 50%; margin: 2rem auto; }",
diff --git a/lessons/20-html-elements.json b/lessons/20-html-elements.json
index 5afbf44..cf5a67f 100644
--- a/lessons/20-html-elements.json
+++ b/lessons/20-html-elements.json
@@ -34,7 +34,7 @@
"id": "semantic-containers",
"title": "Semantic Tags",
"description": "Modern HTML uses semantic containers that describe their content:
<header> - Page or section header <nav> - Navigation links <main> - Main content area <section> - Thematic grouping <article> - Self-contained content <footer> - Page or section footer",
- "task": "Create a basic page structure: 1. Add a <header> with an <h1> containing the text 'My Website' 2. Add a <main> element with a paragraph saying 'Welcome to my site!' 3. Add a <footer> with a paragraph saying 'Copyright 2025'",
+ "task": "Create a basic page structure: 1. Add a <header> with an <h1> containing the text My Website 2. Add a <main> element with a paragraph saying Welcome to my site! 3. Add a <footer> with a paragraph saying Copyright 2025",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; margin: 0; } header { background: #1976d2; color: white; padding: 15px; } main { padding: 20px; min-height: 100px; } footer { background: #424242; color: white; padding: 10px; text-align: center; }",
"sandboxCSS": "",
diff --git a/lessons/21-html-forms-basic.json b/lessons/21-html-forms-basic.json
index ea57a5f..2c80946 100644
--- a/lessons/21-html-forms-basic.json
+++ b/lessons/21-html-forms-basic.json
@@ -10,7 +10,7 @@
"id": "form-structure",
"title": "Form Structure",
"description": "Every form needs a <form> wrapper. Inside, use <label> to describe inputs and <input> for user data entry.
The for attribute on labels should match the id on inputs for accessibility.",
- "task": "Create a form with: 1. A <label> with the text 'Name:' and for=\"name\" attribute 2. A text <input> with id=\"name\" and name=\"name\" attributes",
+ "task": "Create a form with: 1. A <label> with the text Name: and for=\"name\" attribute 2. A text <input> with id=\"name\" and name=\"name\" attributes",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-bottom: 5px; font-weight: 500; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
"sandboxCSS": "",
@@ -77,8 +77,8 @@
{
"id": "submit-button",
"title": "Submit Button",
- "description": "Forms need a way to submit data. Use:
The button text should be action-oriented (e.g., 'Sign In', 'Register', 'Send').",
- "task": "Add a submit button to the form with the text 'Sign In'.",
+ "description": "Forms need a way to submit data. Use:
The button text should be action-oriented (e.g., Sign In, 'Register', 'Send').",
+ "task": "Add a submit button to the form with the text Sign In.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } button { width: 100%; margin-top: 20px; padding: 10px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; } button:hover { background: #1565c0; }",
"sandboxCSS": "",
diff --git a/lessons/23-html-details-summary.json b/lessons/23-html-details-summary.json
index 532eb82..efa6170 100644
--- a/lessons/23-html-details-summary.json
+++ b/lessons/23-html-details-summary.json
@@ -10,7 +10,7 @@
"id": "details-summary-basic",
"title": "First Widget",
"description": "The <details> element creates a collapsible section. The <summary> provides the clickable label.
Click the summary to toggle the hidden content - no JavaScript needed!",
- "task": "Create a <details> element with: 1. A <summary> saying 'Click to reveal' 2. A <p> with the text 'This content was hidden!'",
+ "task": "Create a <details> element with: 1. A <summary> saying Click to reveal 2. A <p> with the text This content was hidden!",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; } summary { font-weight: 600; cursor: pointer; } summary:hover { color: #1976d2; } details p { margin-top: 15px; color: #666; }",
"sandboxCSS": "",
@@ -63,7 +63,7 @@
"id": "faq-accordion",
"title": "FAQ Accordion",
"description": "Multiple <details> elements create an accordion-style FAQ. Each question can be expanded independently.
Pro tip: Type details*3>summary+p and press Tab for Emmet expansion. *3 creates 3 elements, > nests inside, + adds siblings.",
- "task": "Create an FAQ section with: 1. An <h1> saying 'Frequently Asked Questions' 2. Three <details> elements, each with a question in <summary> and an answer in <p>",
+ "task": "Create an FAQ section with: 1. An <h1> saying Frequently Asked Questions 2. Three <details> elements, each with a question in <summary> and an answer in <p>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; min-height: 100vh; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); display: flex; flex-direction: column; justify-content: center; padding: 40px; margin: 0; box-sizing: border-box; } h1 { font-size: 2.5rem; color: #4a4a4a; text-align: center; margin: 0 0 30px 0; } details { background: white; border-radius: 12px; margin-bottom: 15px; padding: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } summary { font-size: 1.3rem; font-weight: 600; color: #5c5c5c; cursor: pointer; list-style: none; } summary::before { content: '▸ '; color: #fcb69f; } details[open] summary::before { content: '▾ '; } details p { margin: 15px 0 0 0; color: #666; line-height: 1.6; }",
"sandboxCSS": "",
diff --git a/lessons/24-html-progress-meter.json b/lessons/24-html-progress-meter.json
index d1a7d48..1bf278f 100644
--- a/lessons/24-html-progress-meter.json
+++ b/lessons/24-html-progress-meter.json
@@ -10,7 +10,7 @@
"id": "progress-basic",
"title": "Progress Bars",
"description": "The <progress> element shows task completion. Use value for current progress and max for the total.
Note: This is not a self-closing tag! Write <progress>...</progress> with fallback text inside for older browsers.",
- "task": "Create a progress bar showing 70% completion: 1. Add a <label> saying 'Download:' 2. Add a <progress> with value=\"70\" and max=\"100\"",
+ "task": "Create a progress bar showing 70% completion: 1. Add a <label> saying Download: 2. Add a <progress> with value=\"70\" and max=\"100\"",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } progress { width: 100%; height: 20px; border-radius: 10px; } progress::-webkit-progress-bar { background: #e0e0e0; border-radius: 10px; } progress::-webkit-progress-value { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; } progress::-moz-progress-bar { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; }",
"sandboxCSS": "",
@@ -44,7 +44,7 @@
"id": "progress-indeterminate",
"title": "Indeterminate Progress",
"description": "When progress is unknown (like loading), omit the value attribute. This creates an animated indeterminate state.
Useful for network requests or processes with unknown duration.",
- "task": "Create a loading indicator: 1. Add a <p> saying 'Loading...' 2. Add a <progress> without a value attribute",
+ "task": "Create a loading indicator: 1. Add a <p> saying Loading... 2. Add a <progress> without a value attribute",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } p { margin-bottom: 10px; color: #666; } progress { width: 100%; height: 8px; border-radius: 4px; } progress::-webkit-progress-bar { background: #e0e0e0; border-radius: 4px; }",
"sandboxCSS": "",
@@ -68,7 +68,7 @@
"id": "meter-gauge",
"title": "Meter Gauges",
"description": "The <meter> element displays a scalar value within a range. Use it for measurements like disk space, battery, or ratings.
Set low, high, and optimum to define good/bad ranges - the browser colors it accordingly!",
- "task": "Create a battery level meter: 1. Add a <label> saying 'Battery:' 2. Add a <meter> with: - value=\"0.8\" - min=\"0\" and max=\"1\" - low=\"0.2\" and high=\"0.8\" - optimum=\"1\"",
+ "task": "Create a battery level meter: 1. Add a <label> saying Battery: 2. Add a <meter> with: - value=\"0.8\" - min=\"0\" and max=\"1\" - low=\"0.2\" and high=\"0.8\" - optimum=\"1\"",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
"sandboxCSS": "",
diff --git a/lessons/25-html-datalist.json b/lessons/25-html-datalist.json
index 9dda987..10b71c6 100644
--- a/lessons/25-html-datalist.json
+++ b/lessons/25-html-datalist.json
@@ -10,7 +10,7 @@
"id": "datalist-basic",
"title": "Input with Suggestions",
"description": "The <datalist> element provides autocomplete suggestions for inputs. Connect it using the list attribute on the input matching the datalist's id.
Users can still type freely - suggestions are just helpers!",
- "task": "Create a browser selector: 1. Add a <label> saying 'Browser:' 2. Add an <input> with list=\"browsers\" 3. Add a <datalist id=\"browsers\"> with options for Chrome, Firefox, and Safari",
+ "task": "Create a browser selector: 1. Add a <label> saying Browser: 2. Add an <input> with list=\"browsers\" 3. Add a <datalist id=\"browsers\"> with options for Chrome, Firefox, and Safari",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; font-size: 16px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; }",
"sandboxCSS": "",
@@ -44,7 +44,7 @@
"id": "datalist-countries",
"title": "Country Selector",
"description": "Datalists work great for long lists like countries. Users can type to filter suggestions instantly.
The value attribute is what gets entered, and you can add display text after it.",
- "task": "Create a country input: 1. Add a <label> saying 'Country:' 2. Add an <input> with list=\"countries\" 3. Add a <datalist id=\"countries\"> with at least 4 country options",
+ "task": "Create a country input: 1. Add a <label> saying Country: 2. Add an <input> with list=\"countries\" 3. Add a <datalist id=\"countries\"> with at least 4 country options",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 30px; background: linear-gradient(135deg, #e0f7fa 0%, #b2ebf2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } label { display: block; margin-bottom: 10px; font-weight: 600; color: #00695c; } input { width: 100%; padding: 12px 15px; border: 2px solid #26a69a; border-radius: 8px; font-size: 16px; background: white; } input:focus { outline: none; border-color: #00695c; box-shadow: 0 0 0 3px rgba(38,166,154,0.2); }",
"sandboxCSS": "",
diff --git a/lessons/27-html-dialog.json b/lessons/27-html-dialog.json
index 28b3501..f31ddd0 100644
--- a/lessons/27-html-dialog.json
+++ b/lessons/27-html-dialog.json
@@ -10,7 +10,7 @@
"id": "dialog-basic",
"title": "Open Dialog",
"description": "The <dialog> element creates a native modal. Add the open attribute to show it.
Use <form method=\"dialog\"> inside to close it when the form submits - no JavaScript needed!",
- "task": "Create a dialog with: 1. The open attribute to show it 2. An <h2> saying 'Welcome!' 3. A <p> with a greeting message 4. A <form method=\"dialog\"> with a close button",
+ "task": "Create a dialog with: 1. The open attribute to show it 2. An <h2> saying Welcome! 3. A <p> with a greeting message 4. A <form method=\"dialog\"> with a close button",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: #f5f5f5; } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 400px; } dialog::backdrop { background: rgba(0,0,0,0.5); } dialog h2 { margin: 0 0 15px 0; color: #1976d2; } dialog p { color: #666; margin: 0 0 20px 0; } dialog button { background: #1976d2; color: white; border: none; padding: 10px 25px; border-radius: 6px; cursor: pointer; font-size: 16px; } dialog button:hover { background: #1565c0; }",
"sandboxCSS": "",
@@ -49,7 +49,7 @@
"id": "dialog-form",
"title": "Dialog + Form",
"description": "Dialogs can contain full forms. The method=\"dialog\" makes the form close the dialog on submit instead of sending data.
This pattern is perfect for confirmation dialogs, quick inputs, or settings panels.",
- "task": "Create a confirmation dialog: 1. Add open to show it 2. An <h2> saying 'Confirm Delete' 3. A <p> asking 'Are you sure?' 4. A <form method=\"dialog\"> with Cancel and Delete buttons",
+ "task": "Create a confirmation dialog: 1. Add open to show it 2. An <h2> saying Confirm Delete 3. A <p> asking Are you sure? 4. A <form method=\"dialog\"> with Cancel and Delete buttons",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: linear-gradient(135deg, #fce4ec 0%, #f8bbd9 100%); } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 350px; text-align: center; } dialog h2 { margin: 0 0 10px 0; color: #c62828; } dialog p { color: #666; margin: 0 0 20px 0; } dialog form { display: flex; gap: 10px; justify-content: center; } dialog button { padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; border: none; } dialog button:first-child { background: #e0e0e0; color: #333; } dialog button:last-child { background: #c62828; color: white; } dialog button:hover { opacity: 0.9; }",
"sandboxCSS": "",
diff --git a/lessons/28-html-forms-fieldset.json b/lessons/28-html-forms-fieldset.json
index 6cb24ea..24d528b 100644
--- a/lessons/28-html-forms-fieldset.json
+++ b/lessons/28-html-forms-fieldset.json
@@ -10,7 +10,7 @@
"id": "fieldset-basic",
"title": "Grouping with Fieldset",
"description": "The <fieldset> element groups related form controls together. Add a <legend> as the first child to give the group a title.
This helps with accessibility and visual organization of complex forms.",
- "task": "Create a form with a fieldset: 1. A <form> element 2. A <fieldset> inside 3. A <legend> saying 'Personal Info' 4. Two labeled inputs for name and email",
+ "task": "Create a form with a fieldset: 1. A <form> element 2. A <fieldset> inside 3. A <legend> saying Personal Info 4. Two labeled inputs for name and email",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f0f4f8; } form { max-width: 400px; } fieldset { border: 2px solid #3498db; border-radius: 10px; padding: 20px; background: white; } legend { color: #3498db; font-weight: 600; padding: 0 10px; font-size: 1.1rem; } label { display: block; margin: 15px 0 5px; color: #333; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; } input:focus { outline: 2px solid #3498db; border-color: transparent; }",
"sandboxCSS": "",
@@ -49,7 +49,7 @@
"id": "fieldset-textarea",
"title": "Adding Textarea",
"description": "The <textarea> element creates a multi-line text input, perfect for longer content like messages or descriptions.
Use rows and cols attributes to set default size.",
- "task": "Create a contact form: 1. A <fieldset> with <legend> 'Contact Us' 2. A labeled <input> for email 3. A labeled <textarea> for the message 4. A submit <button>",
+ "task": "Create a contact form: 1. A <fieldset> with <legend>Contact Us 2. A labeled <input> for email 3. A labeled <textarea> for the message 4. A submit <button>",
"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; } form { max-width: 450px; margin: 0 auto; } fieldset { border: none; border-radius: 15px; padding: 30px; background: white; box-shadow: 0 10px 40px rgba(0,0,0,0.2); } legend { color: #667eea; font-weight: 700; padding: 0; font-size: 1.5rem; margin-bottom: 10px; } label { display: block; margin: 20px 0 8px; color: #333; font-weight: 500; } input, textarea { width: 100%; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: none; border-color: #667eea; } textarea { resize: vertical; min-height: 100px; } button { margin-top: 20px; width: 100%; padding: 14px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; } button:hover { opacity: 0.9; }",
"sandboxCSS": "",
@@ -88,7 +88,7 @@
"id": "fieldset-multiple",
"title": "Multiple Fieldsets",
"description": "Complex forms can use multiple <fieldset> elements to organize different sections.
This improves usability for long forms like registration or checkout pages.",
- "task": "Create a registration form with 2 fieldsets: 1. 'Account Info' with username and password inputs 2. 'Preferences' with a textarea for bio 3. A submit button outside the fieldsets",
+ "task": "Create a registration form with 2 fieldsets: 1. Account Info with username and password inputs 2. Preferences with a textarea for bio 3. A submit button outside the fieldsets",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } form { max-width: 500px; } fieldset { border: 1px solid #ddd; border-radius: 10px; padding: 20px; margin-bottom: 20px; background: white; } legend { color: #2c3e50; font-weight: 600; padding: 0 10px; } label { display: block; margin: 15px 0 5px; color: #555; } input, textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: 2px solid #3498db; border-color: transparent; } textarea { resize: vertical; min-height: 80px; } button { width: 100%; padding: 14px; background: #2c3e50; color: white; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; } button:hover { background: #34495e; }",
"sandboxCSS": "",
diff --git a/lessons/30-html-tables.json b/lessons/30-html-tables.json
index a60d32c..2d0c6d0 100644
--- a/lessons/30-html-tables.json
+++ b/lessons/30-html-tables.json
@@ -10,7 +10,7 @@
"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",
+ "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",
"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; }",
"sandboxCSS": "",
@@ -44,7 +44,7 @@
"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",
+ "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": "",
@@ -83,7 +83,7 @@
"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",
+ "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": "",
diff --git a/lessons/31-html-marquee.json b/lessons/31-html-marquee.json
index 78538ea..29c0d99 100644
--- a/lessons/31-html-marquee.json
+++ b/lessons/31-html-marquee.json
@@ -10,7 +10,7 @@
"id": "marquee-basic",
"title": "Scrolling Text",
"description": "The <marquee> element creates scrolling text - a classic from the early web! While deprecated, it still works in most browsers.
Note: For production, use CSS animations instead. But for learning and fun, marquee is great!",
- "task": "Create a simple marquee: 1. Add a <marquee> element 2. Put some text inside like 'Welcome to my website!'",
+ "task": "Create a simple marquee: 1. Add a <marquee> element 2. Put some text inside like Welcome to my website!",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2rem; color: #00ff00; text-shadow: 0 0 10px #00ff00, 0 0 20px #00ff00; font-family: 'Courier New', monospace; }",
"sandboxCSS": "",
diff --git a/lessons/ar/00-welcome.json b/lessons/ar/00-welcome.json
index 4ed3039..c3f0db8 100644
--- a/lessons/ar/00-welcome.json
+++ b/lessons/ar/00-welcome.json
@@ -10,7 +10,7 @@
"id": "get-started",
"title": "Get Started",
"description": "Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required!
What you'll learn: • HTML - Semantic elements, forms, tables, SVG (HTML Block & Inline, HTML Forms, HTML Tables) • CSS - Selectors, box model, flexbox, animations (CSS Selectors, CSS Box Model, CSS Flexbox) • Responsive Design - Media queries and mobile-first layouts
How it works: 1. Read the task in the left panel 2. Write code in the editor 3. See live results in the preview 4. Get instant feedback with hints
Keyboard shortcuts:Ctrl+Z to undo, Ctrl+Shift+Z to redo
More resources: • HTML over JS - Native HTML vs JavaScript solutions • Web Engineering Mandala - JavaScript technology roadmap",
- "task": "Hello World",
+ "task": "Write Hello World",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; } h1 { color: #6366f1; }",
"sandboxCSS": "",
@@ -20,8 +20,8 @@
"validations": [
{
"type": "contains",
- "value": "Hello",
- "message": "Write 'Hello World'"
+ "value": "Hello World",
+ "message": "Write Hello World"
}
]
},
@@ -39,7 +39,7 @@
"validations": [
{
"type": "contains",
- "value": "Hello",
+ "value": "Hello World",
"message": "Click Next to continue"
}
]
diff --git a/lessons/ar/06-transitions-animations.json b/lessons/ar/06-transitions-animations.json
index 2697fd7..2c10321 100644
--- a/lessons/ar/06-transitions-animations.json
+++ b/lessons/ar/06-transitions-animations.json
@@ -8,7 +8,7 @@
{
"id": "transitions-1",
"title": "Transitions",
- "description": "Learn how to apply transition to properties for smooth changes on state changes.",
+ "description": "Learn how to apply transition to properties for smooth changes on state changes.
",
"task": "Add transition: background-color 0.3s to .btn so the color fades smoothly on hover.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: black; color: white; padding: 0.5rem 1rem; border: none; cursor: pointer; } .btn:hover { background: white; color: black; }",
@@ -63,7 +63,7 @@
{
"id": "transitions-3",
"title": "Keyframes",
- "description": "Create named animations using @keyframes and apply them via the animation shorthand.",
+ "description": "Create named animations using @keyframes and apply them via the animation shorthand.
",
"task": "Define a keyframe at 50% with transform: translateY(-20px) and apply animation: bounce 1s infinite to .ball.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: crimson; border-radius: 50%; margin: 2rem auto; }",
diff --git a/lessons/ar/20-html-elements.json b/lessons/ar/20-html-elements.json
index 93f9214..97c569d 100644
--- a/lessons/ar/20-html-elements.json
+++ b/lessons/ar/20-html-elements.json
@@ -34,7 +34,7 @@
"id": "semantic-containers",
"title": "Semantic Tags",
"description": "Modern HTML uses semantic containers that describe their content:
<header> - Page or section header <nav> - Navigation links <main> - Main content area <section> - Thematic grouping <article> - Self-contained content <footer> - Page or section footer",
- "task": "Create a basic page structure: 1. Add a <header> with an <h1> containing the text 'My Website' 2. Add a <main> element with a paragraph saying 'Welcome to my site!' 3. Add a <footer> with a paragraph saying 'Copyright 2025'",
+ "task": "Create a basic page structure: 1. Add a <header> with an <h1> containing the text My Website 2. Add a <main> element with a paragraph saying Welcome to my site! 3. Add a <footer> with a paragraph saying Copyright 2025",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; margin: 0; } header { background: #1976d2; color: white; padding: 15px; } main { padding: 20px; min-height: 100px; } footer { background: #424242; color: white; padding: 10px; text-align: center; }",
"sandboxCSS": "",
diff --git a/lessons/ar/21-html-forms-basic.json b/lessons/ar/21-html-forms-basic.json
index d971cbf..e92962f 100644
--- a/lessons/ar/21-html-forms-basic.json
+++ b/lessons/ar/21-html-forms-basic.json
@@ -10,7 +10,7 @@
"id": "form-structure",
"title": "Form Structure",
"description": "Every form needs a <form> wrapper. Inside, use <label> to describe inputs and <input> for user data entry.
The for attribute on labels should match the id on inputs for accessibility.",
- "task": "Create a form with: 1. A <label> with the text 'Name:' and for=\"name\" attribute 2. A text <input> with id=\"name\" and name=\"name\" attributes",
+ "task": "Create a form with: 1. A <label> with the text Name: and for=\"name\" attribute 2. A text <input> with id=\"name\" and name=\"name\" attributes",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-bottom: 5px; font-weight: 500; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
"sandboxCSS": "",
@@ -77,8 +77,8 @@
{
"id": "submit-button",
"title": "Submit Button",
- "description": "Forms need a way to submit data. Use:
The button text should be action-oriented (e.g., 'Sign In', 'Register', 'Send').",
- "task": "Add a submit button to the form with the text 'Sign In'.",
+ "description": "Forms need a way to submit data. Use:
The button text should be action-oriented (e.g., Sign In, 'Register', 'Send').",
+ "task": "Add a submit button to the form with the text Sign In.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } button { width: 100%; margin-top: 20px; padding: 10px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; } button:hover { background: #1565c0; }",
"sandboxCSS": "",
diff --git a/lessons/ar/23-html-details-summary.json b/lessons/ar/23-html-details-summary.json
index 3c60ad0..ff83fc3 100644
--- a/lessons/ar/23-html-details-summary.json
+++ b/lessons/ar/23-html-details-summary.json
@@ -10,7 +10,7 @@
"id": "details-summary-basic",
"title": "First Widget",
"description": "The <details> element creates a collapsible section. The <summary> provides the clickable label.
Click the summary to toggle the hidden content - no JavaScript needed!",
- "task": "Create a <details> element with: 1. A <summary> saying 'Click to reveal' 2. A <p> with the text 'This content was hidden!'",
+ "task": "Create a <details> element with: 1. A <summary> saying Click to reveal 2. A <p> with the text This content was hidden!",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; } summary { font-weight: 600; cursor: pointer; } summary:hover { color: #1976d2; } details p { margin-top: 15px; color: #666; }",
"sandboxCSS": "",
@@ -63,7 +63,7 @@
"id": "faq-accordion",
"title": "FAQ Accordion",
"description": "Multiple <details> elements create an accordion-style FAQ. Each question can be expanded independently.
Pro tip: Type details*3>summary+p and press Tab for Emmet expansion. *3 creates 3 elements, > nests inside, + adds siblings.",
- "task": "Create an FAQ section with: 1. An <h1> saying 'Frequently Asked Questions' 2. Three <details> elements, each with a question in <summary> and an answer in <p>",
+ "task": "Create an FAQ section with: 1. An <h1> saying Frequently Asked Questions 2. Three <details> elements, each with a question in <summary> and an answer in <p>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; min-height: 100vh; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); display: flex; flex-direction: column; justify-content: center; padding: 40px; margin: 0; box-sizing: border-box; } h1 { font-size: 2.5rem; color: #4a4a4a; text-align: center; margin: 0 0 30px 0; } details { background: white; border-radius: 12px; margin-bottom: 15px; padding: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } summary { font-size: 1.3rem; font-weight: 600; color: #5c5c5c; cursor: pointer; list-style: none; } summary::before { content: '▸ '; color: #fcb69f; } details[open] summary::before { content: '▾ '; } details p { margin: 15px 0 0 0; color: #666; line-height: 1.6; }",
"sandboxCSS": "",
diff --git a/lessons/ar/24-html-progress-meter.json b/lessons/ar/24-html-progress-meter.json
index 28b3bf3..3a89bc3 100644
--- a/lessons/ar/24-html-progress-meter.json
+++ b/lessons/ar/24-html-progress-meter.json
@@ -10,7 +10,7 @@
"id": "progress-basic",
"title": "Progress Bars",
"description": "The <progress> element shows task completion. Use value for current progress and max for the total.
Note: This is not a self-closing tag! Write <progress>...</progress> with fallback text inside for older browsers.",
- "task": "Create a progress bar showing 70% completion: 1. Add a <label> saying 'Download:' 2. Add a <progress> with value=\"70\" and max=\"100\"",
+ "task": "Create a progress bar showing 70% completion: 1. Add a <label> saying Download: 2. Add a <progress> with value=\"70\" and max=\"100\"",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } progress { width: 100%; height: 20px; border-radius: 10px; } progress::-webkit-progress-bar { background: #e0e0e0; border-radius: 10px; } progress::-webkit-progress-value { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; } progress::-moz-progress-bar { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; }",
"sandboxCSS": "",
@@ -44,7 +44,7 @@
"id": "progress-indeterminate",
"title": "Indeterminate Progress",
"description": "When progress is unknown (like loading), omit the value attribute. This creates an animated indeterminate state.
Useful for network requests or processes with unknown duration.",
- "task": "Create a loading indicator: 1. Add a <p> saying 'Loading...' 2. Add a <progress> without a value attribute",
+ "task": "Create a loading indicator: 1. Add a <p> saying Loading... 2. Add a <progress> without a value attribute",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } p { margin-bottom: 10px; color: #666; } progress { width: 100%; height: 8px; border-radius: 4px; } progress::-webkit-progress-bar { background: #e0e0e0; border-radius: 4px; }",
"sandboxCSS": "",
@@ -68,7 +68,7 @@
"id": "meter-gauge",
"title": "Meter Gauges",
"description": "The <meter> element displays a scalar value within a range. Use it for measurements like disk space, battery, or ratings.
Set low, high, and optimum to define good/bad ranges - the browser colors it accordingly!",
- "task": "Create a battery level meter: 1. Add a <label> saying 'Battery:' 2. Add a <meter> with: - value=\"0.8\" - min=\"0\" and max=\"1\" - low=\"0.2\" and high=\"0.8\" - optimum=\"1\"",
+ "task": "Create a battery level meter: 1. Add a <label> saying Battery: 2. Add a <meter> with: - value=\"0.8\" - min=\"0\" and max=\"1\" - low=\"0.2\" and high=\"0.8\" - optimum=\"1\"",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
"sandboxCSS": "",
diff --git a/lessons/ar/25-html-datalist.json b/lessons/ar/25-html-datalist.json
new file mode 100644
index 0000000..8d33f10
--- /dev/null
+++ b/lessons/ar/25-html-datalist.json
@@ -0,0 +1,78 @@
+{
+ "$schema": "../../schemas/code-crispies-module-schema.json",
+ "id": "html-datalist",
+ "title": "Datalist",
+ "description": "Provide suggestions for text inputs without JavaScript",
+ "mode": "html",
+ "difficulty": "beginner",
+ "lessons": [
+ {
+ "id": "datalist-basic",
+ "title": "Input with Suggestions",
+ "description": "The <datalist> element provides autocomplete suggestions for inputs. Connect it using the list attribute on the input matching the datalist's id.
Users can still type freely - suggestions are just helpers!",
+ "task": "Create a browser selector: 1. Add a <label> saying Browser: 2. Add an <input> with list=\"browsers\" 3. Add a <datalist id=\"browsers\"> with options for Chrome, Firefox, and Safari",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; font-size: 16px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "\n\n",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "datalist",
+ "message": "Add a <datalist> element"
+ },
+ {
+ "type": "attribute_value",
+ "value": { "selector": "input", "attr": "list", "value": "browsers" },
+ "message": "Connect the input to datalist using list=\"browsers\""
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "option", "min": 3 },
+ "message": "Add at least 3 <option> elements inside <datalist>"
+ },
+ {
+ "type": "element_exists",
+ "value": "label",
+ "message": "Add a <label> for the input"
+ }
+ ]
+ },
+ {
+ "id": "datalist-countries",
+ "title": "Country Selector",
+ "description": "Datalists work great for long lists like countries. Users can type to filter suggestions instantly.
The value attribute is what gets entered, and you can add display text after it.",
+ "task": "Create a country input: 1. Add a <label> saying Country: 2. Add an <input> with list=\"countries\" 3. Add a <datalist id=\"countries\"> with at least 4 country options",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 30px; background: linear-gradient(135deg, #e0f7fa 0%, #b2ebf2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } label { display: block; margin-bottom: 10px; font-weight: 600; color: #00695c; } input { width: 100%; padding: 12px 15px; border: 2px solid #26a69a; border-radius: 8px; font-size: 16px; background: white; } input:focus { outline: none; border-color: #00695c; box-shadow: 0 0 0 3px rgba(38,166,154,0.2); }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "\n\n",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "datalist",
+ "message": "Add a <datalist> element"
+ },
+ {
+ "type": "attribute_value",
+ "value": { "selector": "datalist", "attr": "id", "value": "countries" },
+ "message": "Set id=\"countries\" on the datalist"
+ },
+ {
+ "type": "attribute_value",
+ "value": { "selector": "input", "attr": "list", "value": "countries" },
+ "message": "Connect the input using list=\"countries\""
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "option", "min": 4 },
+ "message": "Add at least 4 country options"
+ }
+ ]
+ }
+ ]
+}
diff --git a/lessons/ar/27-html-dialog.json b/lessons/ar/27-html-dialog.json
new file mode 100644
index 0000000..4b721c7
--- /dev/null
+++ b/lessons/ar/27-html-dialog.json
@@ -0,0 +1,83 @@
+{
+ "$schema": "../../schemas/code-crispies-module-schema.json",
+ "id": "html-dialog",
+ "title": "Dialogs",
+ "description": "Create modal dialogs without JavaScript libraries",
+ "mode": "html",
+ "difficulty": "beginner",
+ "lessons": [
+ {
+ "id": "dialog-basic",
+ "title": "Open Dialog",
+ "description": "The <dialog> element creates a native modal. Add the open attribute to show it.
Use <form method=\"dialog\"> inside to close it when the form submits - no JavaScript needed!",
+ "task": "Create a dialog with: 1. The open attribute to show it 2. An <h2> saying Welcome! 3. A <p> with a greeting message 4. A <form method=\"dialog\"> with a close button",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: #f5f5f5; } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 400px; } dialog::backdrop { background: rgba(0,0,0,0.5); } dialog h2 { margin: 0 0 15px 0; color: #1976d2; } dialog p { color: #666; margin: 0 0 20px 0; } dialog button { background: #1976d2; color: white; border: none; padding: 10px 25px; border-radius: 6px; cursor: pointer; font-size: 16px; } dialog button:hover { background: #1565c0; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "dialog",
+ "message": "Add a <dialog> element"
+ },
+ {
+ "type": "attribute_value",
+ "value": { "selector": "dialog", "attr": "open", "value": true },
+ "message": "Add the open attribute to show the dialog"
+ },
+ {
+ "type": "element_exists",
+ "value": "dialog h2",
+ "message": "Add an <h2> heading inside the dialog"
+ },
+ {
+ "type": "element_exists",
+ "value": "form[method='dialog']",
+ "message": "Add a <form method=\"dialog\"> for closing"
+ },
+ {
+ "type": "element_exists",
+ "value": "dialog button",
+ "message": "Add a close button inside the form"
+ }
+ ]
+ },
+ {
+ "id": "dialog-form",
+ "title": "Dialog + Form",
+ "description": "Dialogs can contain full forms. The method=\"dialog\" makes the form close the dialog on submit instead of sending data.
This pattern is perfect for confirmation dialogs, quick inputs, or settings panels.",
+ "task": "Create a confirmation dialog: 1. Add open to show it 2. An <h2> saying Confirm Delete 3. A <p> asking Are you sure? 4. A <form method=\"dialog\"> with Cancel and Delete buttons",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: linear-gradient(135deg, #fce4ec 0%, #f8bbd9 100%); } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 350px; text-align: center; } dialog h2 { margin: 0 0 10px 0; color: #c62828; } dialog p { color: #666; margin: 0 0 20px 0; } dialog form { display: flex; gap: 10px; justify-content: center; } dialog button { padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; border: none; } dialog button:first-child { background: #e0e0e0; color: #333; } dialog button:last-child { background: #c62828; color: white; } dialog button:hover { opacity: 0.9; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "dialog[open]",
+ "message": "Add a <dialog> with the open attribute"
+ },
+ {
+ "type": "element_exists",
+ "value": "dialog h2",
+ "message": "Add a heading to the dialog"
+ },
+ {
+ "type": "element_exists",
+ "value": "form[method='dialog']",
+ "message": "Add a <form method=\"dialog\"> for the buttons"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "dialog button", "min": 2 },
+ "message": "Add at least 2 buttons (Cancel and Confirm)"
+ }
+ ]
+ }
+ ]
+}
diff --git a/lessons/ar/28-html-forms-fieldset.json b/lessons/ar/28-html-forms-fieldset.json
new file mode 100644
index 0000000..add7ef9
--- /dev/null
+++ b/lessons/ar/28-html-forms-fieldset.json
@@ -0,0 +1,127 @@
+{
+ "$schema": "../../schemas/code-crispies-module-schema.json",
+ "id": "html-forms-fieldset",
+ "title": "Fieldsets",
+ "description": "Group form controls with fieldset and legend elements",
+ "mode": "html",
+ "difficulty": "beginner",
+ "lessons": [
+ {
+ "id": "fieldset-basic",
+ "title": "Grouping with Fieldset",
+ "description": "The <fieldset> element groups related form controls together. Add a <legend> as the first child to give the group a title.
This helps with accessibility and visual organization of complex forms.",
+ "task": "Create a form with a fieldset: 1. A <form> element 2. A <fieldset> inside 3. A <legend> saying Personal Info 4. Two labeled inputs for name and email",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f0f4f8; } form { max-width: 400px; } fieldset { border: 2px solid #3498db; border-radius: 10px; padding: 20px; background: white; } legend { color: #3498db; font-weight: 600; padding: 0 10px; font-size: 1.1rem; } label { display: block; margin: 15px 0 5px; color: #333; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; } input:focus { outline: 2px solid #3498db; border-color: transparent; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "form",
+ "message": "Add a <form> element"
+ },
+ {
+ "type": "element_exists",
+ "value": "fieldset",
+ "message": "Add a <fieldset> inside the form"
+ },
+ {
+ "type": "element_exists",
+ "value": "legend",
+ "message": "Add a <legend> to title your fieldset"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "label", "min": 2 },
+ "message": "Add at least 2 labels"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "input", "min": 2 },
+ "message": "Add at least 2 input fields"
+ }
+ ]
+ },
+ {
+ "id": "fieldset-textarea",
+ "title": "Adding Textarea",
+ "description": "The <textarea> element creates a multi-line text input, perfect for longer content like messages or descriptions.
Use rows and cols attributes to set default size.",
+ "task": "Create a contact form: 1. A <fieldset> with <legend>Contact Us 2. A labeled <input> for email 3. A labeled <textarea> for the message 4. A submit <button>",
+ "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; } form { max-width: 450px; margin: 0 auto; } fieldset { border: none; border-radius: 15px; padding: 30px; background: white; box-shadow: 0 10px 40px rgba(0,0,0,0.2); } legend { color: #667eea; font-weight: 700; padding: 0; font-size: 1.5rem; margin-bottom: 10px; } label { display: block; margin: 20px 0 8px; color: #333; font-weight: 500; } input, textarea { width: 100%; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: none; border-color: #667eea; } textarea { resize: vertical; min-height: 100px; } button { margin-top: 20px; width: 100%; padding: 14px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; } button:hover { opacity: 0.9; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "fieldset",
+ "message": "Add a <fieldset> element"
+ },
+ {
+ "type": "element_exists",
+ "value": "legend",
+ "message": "Add a <legend> element"
+ },
+ {
+ "type": "element_exists",
+ "value": "textarea",
+ "message": "Add a <textarea> for the message"
+ },
+ {
+ "type": "element_exists",
+ "value": "button",
+ "message": "Add a submit button"
+ },
+ {
+ "type": "element_exists",
+ "value": "input",
+ "message": "Add an input field for email"
+ }
+ ]
+ },
+ {
+ "id": "fieldset-multiple",
+ "title": "Multiple Fieldsets",
+ "description": "Complex forms can use multiple <fieldset> elements to organize different sections.
This improves usability for long forms like registration or checkout pages.",
+ "task": "Create a registration form with 2 fieldsets: 1. Account Info with username and password inputs 2. Preferences with a textarea for bio 3. A submit button outside the fieldsets",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } form { max-width: 500px; } fieldset { border: 1px solid #ddd; border-radius: 10px; padding: 20px; margin-bottom: 20px; background: white; } legend { color: #2c3e50; font-weight: 600; padding: 0 10px; } label { display: block; margin: 15px 0 5px; color: #555; } input, textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: 2px solid #3498db; border-color: transparent; } textarea { resize: vertical; min-height: 80px; } button { width: 100%; padding: 14px; background: #2c3e50; color: white; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; } button:hover { background: #34495e; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_count",
+ "value": { "selector": "fieldset", "min": 2 },
+ "message": "Create at least 2 fieldsets"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "legend", "min": 2 },
+ "message": "Add a legend to each fieldset"
+ },
+ {
+ "type": "element_exists",
+ "value": "textarea",
+ "message": "Add a textarea for the bio"
+ },
+ {
+ "type": "element_exists",
+ "value": "button",
+ "message": "Add a submit button"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "input", "min": 2 },
+ "message": "Add at least 2 input fields"
+ }
+ ]
+ }
+ ]
+}
diff --git a/lessons/ar/30-html-tables.json b/lessons/ar/30-html-tables.json
index 1bdfaeb..f4d29cf 100644
--- a/lessons/ar/30-html-tables.json
+++ b/lessons/ar/30-html-tables.json
@@ -10,7 +10,7 @@
"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",
+ "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",
"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; }",
"sandboxCSS": "",
@@ -44,7 +44,7 @@
"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",
+ "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": "",
@@ -83,7 +83,7 @@
"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",
+ "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": "",
diff --git a/lessons/ar/31-html-marquee.json b/lessons/ar/31-html-marquee.json
index 7c2939a..ec3a486 100644
--- a/lessons/ar/31-html-marquee.json
+++ b/lessons/ar/31-html-marquee.json
@@ -10,7 +10,7 @@
"id": "marquee-basic",
"title": "Scrolling Text",
"description": "The <marquee> element creates scrolling text - a classic from the early web! While deprecated, it still works in most browsers.
Note: For production, use CSS animations instead. But for learning and fun, marquee is great!",
- "task": "Create a simple marquee: 1. Add a <marquee> element 2. Put some text inside like 'Welcome to my website!'",
+ "task": "Create a simple marquee: 1. Add a <marquee> element 2. Put some text inside like Welcome to my website!",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2rem; color: #00ff00; text-shadow: 0 0 10px #00ff00, 0 0 20px #00ff00; font-family: 'Courier New', monospace; }",
"sandboxCSS": "",
diff --git a/lessons/de/00-welcome.json b/lessons/de/00-welcome.json
index a9d4376..5c43d29 100644
--- a/lessons/de/00-welcome.json
+++ b/lessons/de/00-welcome.json
@@ -10,7 +10,7 @@
"id": "get-started",
"title": "Los geht's",
"description": "Code Crispies ist eine kostenlose Open-Source-Plattform zum Erlernen von Webentwicklung durch praktische Übungen. Kein Konto erforderlich!
Was du lernst: • HTML - Semantische Elemente, Formulare, Tabellen, SVG (HTML Block & Inline, HTML Formulare, HTML Tabellen) • CSS - Selektoren, Box-Model, Flexbox, Animationen (CSS Selektoren, CSS Box-Model, CSS Flexbox) • Responsive Design - Media Queries und Mobile-First Layouts
So funktioniert's: 1. Lies die Aufgabe im linken Bereich 2. Schreibe Code im Editor 3. Sieh Live-Ergebnisse in der Vorschau 4. Bekomme sofortiges Feedback mit Hinweisen
",
"task": "Definiere bei 50% ein transform: translateY(-20px) und wende animation: bounce 1s infinite auf .ball an.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: #ff0266; border-radius: 50%; margin: 2rem auto; }",
diff --git a/lessons/de/20-html-elements.json b/lessons/de/20-html-elements.json
index 7a53d02..4c60803 100644
--- a/lessons/de/20-html-elements.json
+++ b/lessons/de/20-html-elements.json
@@ -34,7 +34,7 @@
"id": "semantic-containers",
"title": "Semantische Container-Elemente",
"description": "Modernes HTML verwendet semantische Container, die ihren Inhalt beschreiben:
<header> - Kopfbereich der Seite oder eines Abschnitts <nav> - Navigationslinks <main> - Hauptinhalt <section> - Thematische Gruppierung <article> - Eigenständiger Inhalt <footer> - Fußbereich der Seite oder eines Abschnitts",
- "task": "Erstelle eine einfache Seitenstruktur: 1. Füge ein <header> mit einem <h1> hinzu, das den Text 'Meine Webseite' enthält 2. Füge ein <main>-Element mit einem Absatz hinzu, der 'Willkommen auf meiner Seite!' sagt 3. Füge ein <footer> mit einem Absatz hinzu, der 'Copyright 2025' sagt",
+ "task": "Erstelle eine einfache Seitenstruktur: 1. Füge ein <header> mit einem <h1> hinzu, das den Text Meine Webseite enthält 2. Füge ein <main>-Element mit einem Absatz hinzu, der Willkommen auf meiner Seite! sagt 3. Füge ein <footer> mit einem Absatz hinzu, der Copyright 2025 sagt",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; margin: 0; } header { background: #1976d2; color: white; padding: 15px; } main { padding: 20px; min-height: 100px; } footer { background: #424242; color: white; padding: 10px; text-align: center; }",
"sandboxCSS": "",
diff --git a/lessons/de/21-html-forms-basic.json b/lessons/de/21-html-forms-basic.json
index df53167..0e32d64 100644
--- a/lessons/de/21-html-forms-basic.json
+++ b/lessons/de/21-html-forms-basic.json
@@ -10,7 +10,7 @@
"id": "form-structure",
"title": "Formularstruktur",
"description": "Jedes Formular benötigt einen <form>-Wrapper. Innerhalb verwendest du <label> zur Beschreibung der Eingaben und <input> für die Dateneingabe.
Das for-Attribut bei Labels sollte mit der id der Eingaben übereinstimmen für bessere Zugänglichkeit.",
- "task": "Erstelle ein Formular mit: 1. Einem <label> mit dem Text 'Name:' und dem for=\"name\"-Attribut 2. Einem Text-<input> mit id=\"name\" und name=\"name\"-Attributen",
+ "task": "Erstelle ein Formular mit: 1. Einem <label> mit dem Text Name: und dem for=\"name\"-Attribut 2. Einem Text-<input> mit id=\"name\" und name=\"name\"-Attributen",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-bottom: 5px; font-weight: 500; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
"sandboxCSS": "",
@@ -77,8 +77,8 @@
{
"id": "submit-button",
"title": "Absende-Button",
- "description": "Formulare benötigen eine Möglichkeit zum Absenden. Verwende:
Der Button-Text sollte handlungsorientiert sein (z.B. 'Anmelden', 'Registrieren', 'Senden').",
- "task": "Füge dem Formular einen Absende-Button mit dem Text 'Anmelden' hinzu.",
+ "description": "Formulare benötigen eine Möglichkeit zum Absenden. Verwende:
Klicke auf die Zusammenfassung, um den versteckten Inhalt anzuzeigen - kein JavaScript nötig!",
- "task": "Erstelle ein <details>-Element mit: 1. Einem <summary> mit dem Text 'Klicken zum Aufklappen' 2. Einem <p> mit dem Text 'Dieser Inhalt war versteckt!'",
+ "task": "Erstelle ein <details>-Element mit: 1. Einem <summary> mit dem Text Klicken zum Aufklappen 2. Einem <p> mit dem Text Dieser Inhalt war versteckt!",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; } summary { font-weight: 600; cursor: pointer; } summary:hover { color: #1976d2; } details p { margin-top: 15px; color: #666; }",
"sandboxCSS": "",
@@ -63,7 +63,7 @@
"id": "faq-accordion",
"title": "FAQ-Akkordeon",
"description": "Mehrere <details>-Elemente erstellen ein Akkordeon-artiges FAQ. Jede Frage kann unabhängig aufgeklappt werden.
Pro-Tipp: Tippe details*3>summary+p und drücke Tab für Emmet-Expansion. Das *3 erstellt 3 Elemente, > geht eine Ebene tiefer, + fügt Geschwister hinzu.",
- "task": "Erstelle einen FAQ-Bereich mit: 1. Einer <h1> mit dem Text 'Häufig gestellte Fragen' 2. Drei <details>-Elementen, jeweils mit einer Frage im <summary> und einer Antwort im <p>",
+ "task": "Erstelle einen FAQ-Bereich mit: 1. Einer <h1> mit dem Text Häufig gestellte Fragen 2. Drei <details>-Elementen, jeweils mit einer Frage im <summary> und einer Antwort im <p>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; min-height: 100vh; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); display: flex; flex-direction: column; justify-content: center; padding: 40px; margin: 0; box-sizing: border-box; } h1 { font-size: 2.5rem; color: #4a4a4a; text-align: center; margin: 0 0 30px 0; } details { background: white; border-radius: 12px; margin-bottom: 15px; padding: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } summary { font-size: 1.3rem; font-weight: 600; color: #5c5c5c; cursor: pointer; list-style: none; } summary::before { content: '▸ '; color: #fcb69f; } details[open] summary::before { content: '▾ '; } details p { margin: 15px 0 0 0; color: #666; line-height: 1.6; }",
"sandboxCSS": "",
diff --git a/lessons/de/24-html-progress-meter.json b/lessons/de/24-html-progress-meter.json
index a5f781e..bb0fe69 100644
--- a/lessons/de/24-html-progress-meter.json
+++ b/lessons/de/24-html-progress-meter.json
@@ -10,7 +10,7 @@
"id": "progress-basic",
"title": "Fortschrittsbalken",
"description": "Das <progress>-Element zeigt den Aufgabenfortschritt. Verwende value für den aktuellen Stand und max für das Maximum.
Wichtig: Dies ist kein selbstschließendes Tag! Schreibe <progress>...</progress> mit einem Fallback-Text dazwischen für ältere Browser.",
- "task": "Erstelle einen Fortschrittsbalken mit 70% Fortschritt: 1. Füge ein <label> mit 'Download:' hinzu 2. Füge ein <progress> mit value=\"70\" und max=\"100\" hinzu",
+ "task": "Erstelle einen Fortschrittsbalken mit 70% Fortschritt: 1. Füge ein <label> mit Download: hinzu 2. Füge ein <progress> mit value=\"70\" und max=\"100\" hinzu",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } progress { width: 100%; height: 20px; border-radius: 10px; } progress::-webkit-progress-bar { background: #e0e0e0; border-radius: 10px; } progress::-webkit-progress-value { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; } progress::-moz-progress-bar { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; }",
"sandboxCSS": "",
@@ -44,7 +44,7 @@
"id": "progress-indeterminate",
"title": "Unbestimmter Fortschritt",
"description": "Wenn der Fortschritt unbekannt ist (wie beim Laden), lasse das value-Attribut weg. Dies erstellt einen animierten unbestimmten Zustand.
Nützlich für Netzwerkanfragen oder Prozesse mit unbekannter Dauer.",
- "task": "Erstelle eine Ladeanzeige: 1. Füge ein <p> mit 'Lädt...' hinzu 2. Füge ein <progress> ohne value-Attribut hinzu",
+ "task": "Erstelle eine Ladeanzeige: 1. Füge ein <p> mit Lädt... hinzu 2. Füge ein <progress> ohne value-Attribut hinzu",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } p { margin-bottom: 10px; color: #666; } progress { width: 100%; height: 8px; border-radius: 4px; } progress::-webkit-progress-bar { background: #e0e0e0; border-radius: 4px; }",
"sandboxCSS": "",
@@ -68,7 +68,7 @@
"id": "meter-gauge",
"title": "Meter-Anzeigen",
"description": "Das <meter>-Element zeigt einen Skalarwert innerhalb eines Bereichs. Verwende es für Messungen wie Speicherplatz, Akku oder Bewertungen.
Setze low, high und optimum, um gute/schlechte Bereiche zu definieren - der Browser färbt es entsprechend ein!",
- "task": "Erstelle eine Akku-Anzeige: 1. Füge ein <label> mit 'Akku:' hinzu 2. Füge ein <meter> hinzu mit: - value=\"0.8\" - min=\"0\" und max=\"1\" - low=\"0.2\" und high=\"0.8\" - optimum=\"1\"",
+ "task": "Erstelle eine Akku-Anzeige: 1. Füge ein <label> mit Akku: hinzu 2. Füge ein <meter> hinzu mit: - value=\"0.8\" - min=\"0\" und max=\"1\" - low=\"0.2\" und high=\"0.8\" - optimum=\"1\"",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
"sandboxCSS": "",
diff --git a/lessons/de/25-html-datalist.json b/lessons/de/25-html-datalist.json
index 8fc2da1..185c7af 100644
--- a/lessons/de/25-html-datalist.json
+++ b/lessons/de/25-html-datalist.json
@@ -10,7 +10,7 @@
"id": "datalist-basic",
"title": "Eingabe mit Vorschlägen",
"description": "Das <datalist>-Element bietet Autovervollständigungs-Vorschläge für Eingabefelder. Verbinde es mit dem list-Attribut am Input, das zur id der Datalist passt.
Benutzer können trotzdem frei tippen - Vorschläge sind nur Hilfen!",
- "task": "Erstelle eine Browser-Auswahl: 1. Füge ein <label> mit 'Browser:' hinzu 2. Füge ein <input> mit list=\"browsers\" hinzu 3. Füge eine <datalist id=\"browsers\"> mit Optionen für Chrome, Firefox und Safari hinzu",
+ "task": "Erstelle eine Browser-Auswahl: 1. Füge ein <label> mit Browser: hinzu 2. Füge ein <input> mit list=\"browsers\" hinzu 3. Füge eine <datalist id=\"browsers\"> mit Optionen für Chrome, Firefox und Safari hinzu",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; font-size: 16px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; }",
"sandboxCSS": "",
@@ -44,7 +44,7 @@
"id": "datalist-countries",
"title": "Länderauswahl",
"description": "Datalists funktionieren super für lange Listen wie Länder. Benutzer können tippen, um Vorschläge sofort zu filtern.
Das value-Attribut ist das, was eingegeben wird, und du kannst Anzeigetext dahinter hinzufügen.",
- "task": "Erstelle eine Länder-Eingabe: 1. Füge ein <label> mit 'Land:' hinzu 2. Füge ein <input> mit list=\"countries\" hinzu 3. Füge eine <datalist id=\"countries\"> mit mindestens 4 Länder-Optionen hinzu",
+ "task": "Erstelle eine Länder-Eingabe: 1. Füge ein <label> mit Land: hinzu 2. Füge ein <input> mit list=\"countries\" hinzu 3. Füge eine <datalist id=\"countries\"> mit mindestens 4 Länder-Optionen hinzu",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 30px; background: linear-gradient(135deg, #e0f7fa 0%, #b2ebf2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } label { display: block; margin-bottom: 10px; font-weight: 600; color: #00695c; } input { width: 100%; padding: 12px 15px; border: 2px solid #26a69a; border-radius: 8px; font-size: 16px; background: white; } input:focus { outline: none; border-color: #00695c; box-shadow: 0 0 0 3px rgba(38,166,154,0.2); }",
"sandboxCSS": "",
diff --git a/lessons/de/27-html-dialog.json b/lessons/de/27-html-dialog.json
index e459b4f..3af172d 100644
--- a/lessons/de/27-html-dialog.json
+++ b/lessons/de/27-html-dialog.json
@@ -10,7 +10,7 @@
"id": "dialog-basic",
"title": "Dialog öffnen",
"description": "Das <dialog>-Element erstellt ein natives Modal. Füge das open-Attribut hinzu, um es anzuzeigen.
Verwende <form method=\"dialog\"> darin, um es beim Absenden des Formulars zu schließen - kein JavaScript nötig!",
- "task": "Erstelle einen Dialog mit: 1. Dem open-Attribut, um ihn anzuzeigen 2. Einem <h2> mit 'Willkommen!' 3. Einem <p> mit einer Begrüßungsnachricht 4. Einem <form method=\"dialog\"> mit einem Schließen-Button",
+ "task": "Erstelle einen Dialog mit: 1. Dem open-Attribut, um ihn anzuzeigen 2. Einem <h2> mit Willkommen! 3. Einem <p> mit einer Begrüßungsnachricht 4. Einem <form method=\"dialog\"> mit einem Schließen-Button",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: #f5f5f5; } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 400px; } dialog::backdrop { background: rgba(0,0,0,0.5); } dialog h2 { margin: 0 0 15px 0; color: #1976d2; } dialog p { color: #666; margin: 0 0 20px 0; } dialog button { background: #1976d2; color: white; border: none; padding: 10px 25px; border-radius: 6px; cursor: pointer; font-size: 16px; } dialog button:hover { background: #1565c0; }",
"sandboxCSS": "",
@@ -49,7 +49,7 @@
"id": "dialog-form",
"title": "Dialog mit Formular",
"description": "Dialoge können vollständige Formulare enthalten. Das method=\"dialog\" lässt das Formular den Dialog beim Absenden schließen, anstatt Daten zu senden.
Dieses Muster ist perfekt für Bestätigungsdialoge, schnelle Eingaben oder Einstellungspanels.",
- "task": "Erstelle einen Bestätigungsdialog: 1. Füge open hinzu, um ihn anzuzeigen 2. Ein <h2> mit 'Löschen bestätigen' 3. Ein <p> mit 'Bist du sicher?' 4. Ein <form method=\"dialog\"> mit Abbrechen- und Löschen-Buttons",
+ "task": "Erstelle einen Bestätigungsdialog: 1. Füge open hinzu, um ihn anzuzeigen 2. Ein <h2> mit Löschen bestätigen 3. Ein <p> mit Bist du sicher? 4. Ein <form method=\"dialog\"> mit Abbrechen- und Löschen-Buttons",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: linear-gradient(135deg, #fce4ec 0%, #f8bbd9 100%); } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 350px; text-align: center; } dialog h2 { margin: 0 0 10px 0; color: #c62828; } dialog p { color: #666; margin: 0 0 20px 0; } dialog form { display: flex; gap: 10px; justify-content: center; } dialog button { padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; border: none; } dialog button:first-child { background: #e0e0e0; color: #333; } dialog button:last-child { background: #c62828; color: white; } dialog button:hover { opacity: 0.9; }",
"sandboxCSS": "",
diff --git a/lessons/de/28-html-forms-fieldset.json b/lessons/de/28-html-forms-fieldset.json
index 83ffcea..b9e6222 100644
--- a/lessons/de/28-html-forms-fieldset.json
+++ b/lessons/de/28-html-forms-fieldset.json
@@ -10,7 +10,7 @@
"id": "fieldset-basic",
"title": "Gruppieren mit Fieldset",
"description": "Das <fieldset>-Element gruppiert zusammengehörige Formularfelder. Füge ein <legend> als erstes Kind hinzu, um der Gruppe einen Titel zu geben.
Das verbessert die Zugänglichkeit und visuelle Organisation komplexer Formulare.",
- "task": "Erstelle ein Formular mit einem Fieldset: 1. Ein <form>-Element 2. Ein <fieldset> darin 3. Ein <legend> mit 'Persönliche Daten' 4. Zwei beschriftete Eingabefelder für Name und E-Mail",
+ "task": "Erstelle ein Formular mit einem Fieldset: 1. Ein <form>-Element 2. Ein <fieldset> darin 3. Ein <legend> mit Persönliche Daten 4. Zwei beschriftete Eingabefelder für Name und E-Mail",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f0f4f8; } form { max-width: 400px; } fieldset { border: 2px solid #3498db; border-radius: 10px; padding: 20px; background: white; } legend { color: #3498db; font-weight: 600; padding: 0 10px; font-size: 1.1rem; } label { display: block; margin: 15px 0 5px; color: #333; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; } input:focus { outline: 2px solid #3498db; border-color: transparent; }",
"sandboxCSS": "",
@@ -49,7 +49,7 @@
"id": "fieldset-textarea",
"title": "Textarea hinzufügen",
"description": "Das <textarea>-Element erstellt ein mehrzeiliges Textfeld, perfekt für längere Inhalte wie Nachrichten oder Beschreibungen.
Verwende rows und cols Attribute, um die Standardgröße festzulegen.",
- "task": "Erstelle ein Kontaktformular: 1. Ein <fieldset> mit <legend> 'Kontaktiere uns' 2. Ein beschriftetes <input> für E-Mail 3. Eine beschriftete <textarea> für die Nachricht 4. Einen Absende-<button>",
+ "task": "Erstelle ein Kontaktformular: 1. Ein <fieldset> mit <legend>Kontaktiere uns 2. Ein beschriftetes <input> für E-Mail 3. Eine beschriftete <textarea> für die Nachricht 4. Einen Absende-<button>",
"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; } form { max-width: 450px; margin: 0 auto; } fieldset { border: none; border-radius: 15px; padding: 30px; background: white; box-shadow: 0 10px 40px rgba(0,0,0,0.2); } legend { color: #667eea; font-weight: 700; padding: 0; font-size: 1.5rem; margin-bottom: 10px; } label { display: block; margin: 20px 0 8px; color: #333; font-weight: 500; } input, textarea { width: 100%; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: none; border-color: #667eea; } textarea { resize: vertical; min-height: 100px; } button { margin-top: 20px; width: 100%; padding: 14px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; } button:hover { opacity: 0.9; }",
"sandboxCSS": "",
@@ -88,7 +88,7 @@
"id": "fieldset-multiple",
"title": "Mehrere Fieldsets",
"description": "Komplexe Formulare können mehrere <fieldset>-Elemente verwenden, um verschiedene Abschnitte zu organisieren.
Das verbessert die Benutzerfreundlichkeit bei langen Formularen wie Registrierung oder Checkout.",
- "task": "Erstelle ein Registrierungsformular mit 2 Fieldsets: 1. 'Kontodaten' mit Benutzername und Passwort 2. 'Einstellungen' mit einer Textarea für Bio 3. Einen Absende-Button außerhalb der Fieldsets",
+ "task": "Erstelle ein Registrierungsformular mit 2 Fieldsets: 1. Kontodaten mit Benutzername und Passwort 2. Einstellungen mit einer Textarea für Bio 3. Einen Absende-Button außerhalb der Fieldsets",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } form { max-width: 500px; } fieldset { border: 1px solid #ddd; border-radius: 10px; padding: 20px; margin-bottom: 20px; background: white; } legend { color: #2c3e50; font-weight: 600; padding: 0 10px; } label { display: block; margin: 15px 0 5px; color: #555; } input, textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: 2px solid #3498db; border-color: transparent; } textarea { resize: vertical; min-height: 80px; } button { width: 100%; padding: 14px; background: #2c3e50; color: white; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; } button:hover { background: #34495e; }",
"sandboxCSS": "",
diff --git a/lessons/de/30-html-tables.json b/lessons/de/30-html-tables.json
index 5134298..d10c687 100644
--- a/lessons/de/30-html-tables.json
+++ b/lessons/de/30-html-tables.json
@@ -10,7 +10,7 @@
"id": "table-basic",
"title": "Grundlegende Tabellenstruktur",
"description": "Tabellen verwenden <table> mit <tr> für Zeilen. In Zeilen nutze <th> für Überschriften und <td> für Datenzellen.
Das <caption>-Element bietet einen zugänglichen Titel für die Tabelle.",
- "task": "Erstelle eine einfache Tabelle mit: 1. Einer <caption> mit 'Obstpreise' 2. Einer Kopfzeile mit 'Obst' und 'Preis' Spalten 3. Mindestens 2 Datenzeilen",
+ "task": "Erstelle eine einfache Tabelle mit: 1. Einer <caption> mit Obstpreise 2. Einer Kopfzeile mit Obst und Preis Spalten 3. Mindestens 2 Datenzeilen",
"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; }",
"sandboxCSS": "",
@@ -44,7 +44,7 @@
"id": "table-thead-tbody",
"title": "Tabellenkopf & -körper",
"description": "Verwende <thead> zum Gruppieren von Kopfzeilen und <tbody> zum Gruppieren von Datenzeilen. Das hilft Browsern und Hilfstechnologien, die Tabellenstruktur zu verstehen.
Du kannst auch <tfoot> für Fußzeilen wie Summen verwenden.",
- "task": "Erstelle eine strukturierte Tabelle: 1. Eine <caption> mit 'Monatliche Verkäufe' 2. Ein <thead> mit Monat und Umsatz Überschriften 3. Ein <tbody> mit mindestens 2 Datenzeilen",
+ "task": "Erstelle eine strukturierte Tabelle: 1. Eine <caption> mit Monatliche Verkäufe 2. Ein <thead> mit Monat und Umsatz Überschriften 3. Ein <tbody> mit mindestens 2 Datenzeilen",
"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": "",
@@ -83,7 +83,7 @@
"id": "table-complete",
"title": "Vollständige Tabelle mit Fuß",
"description": "Füge <tfoot> hinzu, um einen Fußbereich für Summen oder Zusammenfassungen zu erstellen. Der Fuß bleibt unten, auch wenn tbody viele Zeilen hat.
Kombiniere alle Abschnitte für eine vollständig strukturierte, zugängliche Tabelle.",
- "task": "Erstelle eine vollständige Tabelle: 1. Eine <caption> mit 'Bestellübersicht' 2. Ein <thead> mit Artikel und Preis Überschriften 3. Ein <tbody> mit 2 Artikeln 4. Ein <tfoot> mit einer Summenzeile",
+ "task": "Erstelle eine vollständige Tabelle: 1. Eine <caption> mit Bestellübersicht 2. Ein <thead> mit Artikel und Preis Überschriften 3. Ein <tbody> mit 2 Artikeln 4. Ein <tfoot> mit einer Summenzeile",
"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": "",
diff --git a/lessons/de/31-html-marquee.json b/lessons/de/31-html-marquee.json
index 62985f7..1f07d55 100644
--- a/lessons/de/31-html-marquee.json
+++ b/lessons/de/31-html-marquee.json
@@ -10,7 +10,7 @@
"id": "marquee-basic",
"title": "Lauftext",
"description": "Das <marquee>-Element erstellt Lauftext - ein Klassiker aus dem frühen Web! Obwohl veraltet, funktioniert es noch in den meisten Browsern.
Hinweis: Für produktive Seiten nutze CSS-Animationen. Aber zum Lernen und Spaß haben ist Marquee super!",
- "task": "Erstelle ein einfaches Marquee: 1. Füge ein <marquee>-Element hinzu 2. Schreibe Text hinein wie 'Willkommen auf meiner Website!'",
+ "task": "Erstelle ein einfaches Marquee: 1. Füge ein <marquee>-Element hinzu 2. Schreibe Text hinein wie Willkommen auf meiner Website!",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2rem; color: #00ff00; text-shadow: 0 0 10px #00ff00, 0 0 20px #00ff00; font-family: 'Courier New', monospace; }",
"sandboxCSS": "",
diff --git a/lessons/es/00-welcome.json b/lessons/es/00-welcome.json
index 4ed3039..c3f0db8 100644
--- a/lessons/es/00-welcome.json
+++ b/lessons/es/00-welcome.json
@@ -10,7 +10,7 @@
"id": "get-started",
"title": "Get Started",
"description": "Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required!
What you'll learn: • HTML - Semantic elements, forms, tables, SVG (HTML Block & Inline, HTML Forms, HTML Tables) • CSS - Selectors, box model, flexbox, animations (CSS Selectors, CSS Box Model, CSS Flexbox) • Responsive Design - Media queries and mobile-first layouts
How it works: 1. Read the task in the left panel 2. Write code in the editor 3. See live results in the preview 4. Get instant feedback with hints
Keyboard shortcuts:Ctrl+Z to undo, Ctrl+Shift+Z to redo
More resources: • HTML over JS - Native HTML vs JavaScript solutions • Web Engineering Mandala - JavaScript technology roadmap",
- "task": "Hello World",
+ "task": "Write Hello World",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; } h1 { color: #6366f1; }",
"sandboxCSS": "",
@@ -20,8 +20,8 @@
"validations": [
{
"type": "contains",
- "value": "Hello",
- "message": "Write 'Hello World'"
+ "value": "Hello World",
+ "message": "Write Hello World"
}
]
},
@@ -39,7 +39,7 @@
"validations": [
{
"type": "contains",
- "value": "Hello",
+ "value": "Hello World",
"message": "Click Next to continue"
}
]
diff --git a/lessons/es/06-transitions-animations.json b/lessons/es/06-transitions-animations.json
index 2697fd7..2c10321 100644
--- a/lessons/es/06-transitions-animations.json
+++ b/lessons/es/06-transitions-animations.json
@@ -8,7 +8,7 @@
{
"id": "transitions-1",
"title": "Transitions",
- "description": "Learn how to apply transition to properties for smooth changes on state changes.",
+ "description": "Learn how to apply transition to properties for smooth changes on state changes.
",
"task": "Add transition: background-color 0.3s to .btn so the color fades smoothly on hover.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: black; color: white; padding: 0.5rem 1rem; border: none; cursor: pointer; } .btn:hover { background: white; color: black; }",
@@ -63,7 +63,7 @@
{
"id": "transitions-3",
"title": "Keyframes",
- "description": "Create named animations using @keyframes and apply them via the animation shorthand.",
+ "description": "Create named animations using @keyframes and apply them via the animation shorthand.
",
"task": "Define a keyframe at 50% with transform: translateY(-20px) and apply animation: bounce 1s infinite to .ball.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: crimson; border-radius: 50%; margin: 2rem auto; }",
diff --git a/lessons/es/20-html-elements.json b/lessons/es/20-html-elements.json
index 93f9214..97c569d 100644
--- a/lessons/es/20-html-elements.json
+++ b/lessons/es/20-html-elements.json
@@ -34,7 +34,7 @@
"id": "semantic-containers",
"title": "Semantic Tags",
"description": "Modern HTML uses semantic containers that describe their content:
<header> - Page or section header <nav> - Navigation links <main> - Main content area <section> - Thematic grouping <article> - Self-contained content <footer> - Page or section footer",
- "task": "Create a basic page structure: 1. Add a <header> with an <h1> containing the text 'My Website' 2. Add a <main> element with a paragraph saying 'Welcome to my site!' 3. Add a <footer> with a paragraph saying 'Copyright 2025'",
+ "task": "Create a basic page structure: 1. Add a <header> with an <h1> containing the text My Website 2. Add a <main> element with a paragraph saying Welcome to my site! 3. Add a <footer> with a paragraph saying Copyright 2025",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; margin: 0; } header { background: #1976d2; color: white; padding: 15px; } main { padding: 20px; min-height: 100px; } footer { background: #424242; color: white; padding: 10px; text-align: center; }",
"sandboxCSS": "",
diff --git a/lessons/es/21-html-forms-basic.json b/lessons/es/21-html-forms-basic.json
index d971cbf..e92962f 100644
--- a/lessons/es/21-html-forms-basic.json
+++ b/lessons/es/21-html-forms-basic.json
@@ -10,7 +10,7 @@
"id": "form-structure",
"title": "Form Structure",
"description": "Every form needs a <form> wrapper. Inside, use <label> to describe inputs and <input> for user data entry.
The for attribute on labels should match the id on inputs for accessibility.",
- "task": "Create a form with: 1. A <label> with the text 'Name:' and for=\"name\" attribute 2. A text <input> with id=\"name\" and name=\"name\" attributes",
+ "task": "Create a form with: 1. A <label> with the text Name: and for=\"name\" attribute 2. A text <input> with id=\"name\" and name=\"name\" attributes",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-bottom: 5px; font-weight: 500; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
"sandboxCSS": "",
@@ -77,8 +77,8 @@
{
"id": "submit-button",
"title": "Submit Button",
- "description": "Forms need a way to submit data. Use:
The button text should be action-oriented (e.g., 'Sign In', 'Register', 'Send').",
- "task": "Add a submit button to the form with the text 'Sign In'.",
+ "description": "Forms need a way to submit data. Use:
The button text should be action-oriented (e.g., Sign In, 'Register', 'Send').",
+ "task": "Add a submit button to the form with the text Sign In.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } button { width: 100%; margin-top: 20px; padding: 10px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; } button:hover { background: #1565c0; }",
"sandboxCSS": "",
diff --git a/lessons/es/23-html-details-summary.json b/lessons/es/23-html-details-summary.json
index 3c60ad0..ff83fc3 100644
--- a/lessons/es/23-html-details-summary.json
+++ b/lessons/es/23-html-details-summary.json
@@ -10,7 +10,7 @@
"id": "details-summary-basic",
"title": "First Widget",
"description": "The <details> element creates a collapsible section. The <summary> provides the clickable label.
Click the summary to toggle the hidden content - no JavaScript needed!",
- "task": "Create a <details> element with: 1. A <summary> saying 'Click to reveal' 2. A <p> with the text 'This content was hidden!'",
+ "task": "Create a <details> element with: 1. A <summary> saying Click to reveal 2. A <p> with the text This content was hidden!",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; } summary { font-weight: 600; cursor: pointer; } summary:hover { color: #1976d2; } details p { margin-top: 15px; color: #666; }",
"sandboxCSS": "",
@@ -63,7 +63,7 @@
"id": "faq-accordion",
"title": "FAQ Accordion",
"description": "Multiple <details> elements create an accordion-style FAQ. Each question can be expanded independently.
Pro tip: Type details*3>summary+p and press Tab for Emmet expansion. *3 creates 3 elements, > nests inside, + adds siblings.",
- "task": "Create an FAQ section with: 1. An <h1> saying 'Frequently Asked Questions' 2. Three <details> elements, each with a question in <summary> and an answer in <p>",
+ "task": "Create an FAQ section with: 1. An <h1> saying Frequently Asked Questions 2. Three <details> elements, each with a question in <summary> and an answer in <p>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; min-height: 100vh; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); display: flex; flex-direction: column; justify-content: center; padding: 40px; margin: 0; box-sizing: border-box; } h1 { font-size: 2.5rem; color: #4a4a4a; text-align: center; margin: 0 0 30px 0; } details { background: white; border-radius: 12px; margin-bottom: 15px; padding: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } summary { font-size: 1.3rem; font-weight: 600; color: #5c5c5c; cursor: pointer; list-style: none; } summary::before { content: '▸ '; color: #fcb69f; } details[open] summary::before { content: '▾ '; } details p { margin: 15px 0 0 0; color: #666; line-height: 1.6; }",
"sandboxCSS": "",
diff --git a/lessons/es/24-html-progress-meter.json b/lessons/es/24-html-progress-meter.json
index 28b3bf3..3a89bc3 100644
--- a/lessons/es/24-html-progress-meter.json
+++ b/lessons/es/24-html-progress-meter.json
@@ -10,7 +10,7 @@
"id": "progress-basic",
"title": "Progress Bars",
"description": "The <progress> element shows task completion. Use value for current progress and max for the total.
Note: This is not a self-closing tag! Write <progress>...</progress> with fallback text inside for older browsers.",
- "task": "Create a progress bar showing 70% completion: 1. Add a <label> saying 'Download:' 2. Add a <progress> with value=\"70\" and max=\"100\"",
+ "task": "Create a progress bar showing 70% completion: 1. Add a <label> saying Download: 2. Add a <progress> with value=\"70\" and max=\"100\"",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } progress { width: 100%; height: 20px; border-radius: 10px; } progress::-webkit-progress-bar { background: #e0e0e0; border-radius: 10px; } progress::-webkit-progress-value { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; } progress::-moz-progress-bar { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; }",
"sandboxCSS": "",
@@ -44,7 +44,7 @@
"id": "progress-indeterminate",
"title": "Indeterminate Progress",
"description": "When progress is unknown (like loading), omit the value attribute. This creates an animated indeterminate state.
Useful for network requests or processes with unknown duration.",
- "task": "Create a loading indicator: 1. Add a <p> saying 'Loading...' 2. Add a <progress> without a value attribute",
+ "task": "Create a loading indicator: 1. Add a <p> saying Loading... 2. Add a <progress> without a value attribute",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } p { margin-bottom: 10px; color: #666; } progress { width: 100%; height: 8px; border-radius: 4px; } progress::-webkit-progress-bar { background: #e0e0e0; border-radius: 4px; }",
"sandboxCSS": "",
@@ -68,7 +68,7 @@
"id": "meter-gauge",
"title": "Meter Gauges",
"description": "The <meter> element displays a scalar value within a range. Use it for measurements like disk space, battery, or ratings.
Set low, high, and optimum to define good/bad ranges - the browser colors it accordingly!",
- "task": "Create a battery level meter: 1. Add a <label> saying 'Battery:' 2. Add a <meter> with: - value=\"0.8\" - min=\"0\" and max=\"1\" - low=\"0.2\" and high=\"0.8\" - optimum=\"1\"",
+ "task": "Create a battery level meter: 1. Add a <label> saying Battery: 2. Add a <meter> with: - value=\"0.8\" - min=\"0\" and max=\"1\" - low=\"0.2\" and high=\"0.8\" - optimum=\"1\"",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
"sandboxCSS": "",
diff --git a/lessons/es/25-html-datalist.json b/lessons/es/25-html-datalist.json
new file mode 100644
index 0000000..8d33f10
--- /dev/null
+++ b/lessons/es/25-html-datalist.json
@@ -0,0 +1,78 @@
+{
+ "$schema": "../../schemas/code-crispies-module-schema.json",
+ "id": "html-datalist",
+ "title": "Datalist",
+ "description": "Provide suggestions for text inputs without JavaScript",
+ "mode": "html",
+ "difficulty": "beginner",
+ "lessons": [
+ {
+ "id": "datalist-basic",
+ "title": "Input with Suggestions",
+ "description": "The <datalist> element provides autocomplete suggestions for inputs. Connect it using the list attribute on the input matching the datalist's id.
Users can still type freely - suggestions are just helpers!",
+ "task": "Create a browser selector: 1. Add a <label> saying Browser: 2. Add an <input> with list=\"browsers\" 3. Add a <datalist id=\"browsers\"> with options for Chrome, Firefox, and Safari",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; font-size: 16px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "\n\n",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "datalist",
+ "message": "Add a <datalist> element"
+ },
+ {
+ "type": "attribute_value",
+ "value": { "selector": "input", "attr": "list", "value": "browsers" },
+ "message": "Connect the input to datalist using list=\"browsers\""
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "option", "min": 3 },
+ "message": "Add at least 3 <option> elements inside <datalist>"
+ },
+ {
+ "type": "element_exists",
+ "value": "label",
+ "message": "Add a <label> for the input"
+ }
+ ]
+ },
+ {
+ "id": "datalist-countries",
+ "title": "Country Selector",
+ "description": "Datalists work great for long lists like countries. Users can type to filter suggestions instantly.
The value attribute is what gets entered, and you can add display text after it.",
+ "task": "Create a country input: 1. Add a <label> saying Country: 2. Add an <input> with list=\"countries\" 3. Add a <datalist id=\"countries\"> with at least 4 country options",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 30px; background: linear-gradient(135deg, #e0f7fa 0%, #b2ebf2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } label { display: block; margin-bottom: 10px; font-weight: 600; color: #00695c; } input { width: 100%; padding: 12px 15px; border: 2px solid #26a69a; border-radius: 8px; font-size: 16px; background: white; } input:focus { outline: none; border-color: #00695c; box-shadow: 0 0 0 3px rgba(38,166,154,0.2); }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "\n\n",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "datalist",
+ "message": "Add a <datalist> element"
+ },
+ {
+ "type": "attribute_value",
+ "value": { "selector": "datalist", "attr": "id", "value": "countries" },
+ "message": "Set id=\"countries\" on the datalist"
+ },
+ {
+ "type": "attribute_value",
+ "value": { "selector": "input", "attr": "list", "value": "countries" },
+ "message": "Connect the input using list=\"countries\""
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "option", "min": 4 },
+ "message": "Add at least 4 country options"
+ }
+ ]
+ }
+ ]
+}
diff --git a/lessons/es/27-html-dialog.json b/lessons/es/27-html-dialog.json
new file mode 100644
index 0000000..4b721c7
--- /dev/null
+++ b/lessons/es/27-html-dialog.json
@@ -0,0 +1,83 @@
+{
+ "$schema": "../../schemas/code-crispies-module-schema.json",
+ "id": "html-dialog",
+ "title": "Dialogs",
+ "description": "Create modal dialogs without JavaScript libraries",
+ "mode": "html",
+ "difficulty": "beginner",
+ "lessons": [
+ {
+ "id": "dialog-basic",
+ "title": "Open Dialog",
+ "description": "The <dialog> element creates a native modal. Add the open attribute to show it.
Use <form method=\"dialog\"> inside to close it when the form submits - no JavaScript needed!",
+ "task": "Create a dialog with: 1. The open attribute to show it 2. An <h2> saying Welcome! 3. A <p> with a greeting message 4. A <form method=\"dialog\"> with a close button",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: #f5f5f5; } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 400px; } dialog::backdrop { background: rgba(0,0,0,0.5); } dialog h2 { margin: 0 0 15px 0; color: #1976d2; } dialog p { color: #666; margin: 0 0 20px 0; } dialog button { background: #1976d2; color: white; border: none; padding: 10px 25px; border-radius: 6px; cursor: pointer; font-size: 16px; } dialog button:hover { background: #1565c0; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "dialog",
+ "message": "Add a <dialog> element"
+ },
+ {
+ "type": "attribute_value",
+ "value": { "selector": "dialog", "attr": "open", "value": true },
+ "message": "Add the open attribute to show the dialog"
+ },
+ {
+ "type": "element_exists",
+ "value": "dialog h2",
+ "message": "Add an <h2> heading inside the dialog"
+ },
+ {
+ "type": "element_exists",
+ "value": "form[method='dialog']",
+ "message": "Add a <form method=\"dialog\"> for closing"
+ },
+ {
+ "type": "element_exists",
+ "value": "dialog button",
+ "message": "Add a close button inside the form"
+ }
+ ]
+ },
+ {
+ "id": "dialog-form",
+ "title": "Dialog + Form",
+ "description": "Dialogs can contain full forms. The method=\"dialog\" makes the form close the dialog on submit instead of sending data.
This pattern is perfect for confirmation dialogs, quick inputs, or settings panels.",
+ "task": "Create a confirmation dialog: 1. Add open to show it 2. An <h2> saying Confirm Delete 3. A <p> asking Are you sure? 4. A <form method=\"dialog\"> with Cancel and Delete buttons",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: linear-gradient(135deg, #fce4ec 0%, #f8bbd9 100%); } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 350px; text-align: center; } dialog h2 { margin: 0 0 10px 0; color: #c62828; } dialog p { color: #666; margin: 0 0 20px 0; } dialog form { display: flex; gap: 10px; justify-content: center; } dialog button { padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; border: none; } dialog button:first-child { background: #e0e0e0; color: #333; } dialog button:last-child { background: #c62828; color: white; } dialog button:hover { opacity: 0.9; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "dialog[open]",
+ "message": "Add a <dialog> with the open attribute"
+ },
+ {
+ "type": "element_exists",
+ "value": "dialog h2",
+ "message": "Add a heading to the dialog"
+ },
+ {
+ "type": "element_exists",
+ "value": "form[method='dialog']",
+ "message": "Add a <form method=\"dialog\"> for the buttons"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "dialog button", "min": 2 },
+ "message": "Add at least 2 buttons (Cancel and Confirm)"
+ }
+ ]
+ }
+ ]
+}
diff --git a/lessons/es/28-html-forms-fieldset.json b/lessons/es/28-html-forms-fieldset.json
new file mode 100644
index 0000000..add7ef9
--- /dev/null
+++ b/lessons/es/28-html-forms-fieldset.json
@@ -0,0 +1,127 @@
+{
+ "$schema": "../../schemas/code-crispies-module-schema.json",
+ "id": "html-forms-fieldset",
+ "title": "Fieldsets",
+ "description": "Group form controls with fieldset and legend elements",
+ "mode": "html",
+ "difficulty": "beginner",
+ "lessons": [
+ {
+ "id": "fieldset-basic",
+ "title": "Grouping with Fieldset",
+ "description": "The <fieldset> element groups related form controls together. Add a <legend> as the first child to give the group a title.
This helps with accessibility and visual organization of complex forms.",
+ "task": "Create a form with a fieldset: 1. A <form> element 2. A <fieldset> inside 3. A <legend> saying Personal Info 4. Two labeled inputs for name and email",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f0f4f8; } form { max-width: 400px; } fieldset { border: 2px solid #3498db; border-radius: 10px; padding: 20px; background: white; } legend { color: #3498db; font-weight: 600; padding: 0 10px; font-size: 1.1rem; } label { display: block; margin: 15px 0 5px; color: #333; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; } input:focus { outline: 2px solid #3498db; border-color: transparent; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "form",
+ "message": "Add a <form> element"
+ },
+ {
+ "type": "element_exists",
+ "value": "fieldset",
+ "message": "Add a <fieldset> inside the form"
+ },
+ {
+ "type": "element_exists",
+ "value": "legend",
+ "message": "Add a <legend> to title your fieldset"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "label", "min": 2 },
+ "message": "Add at least 2 labels"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "input", "min": 2 },
+ "message": "Add at least 2 input fields"
+ }
+ ]
+ },
+ {
+ "id": "fieldset-textarea",
+ "title": "Adding Textarea",
+ "description": "The <textarea> element creates a multi-line text input, perfect for longer content like messages or descriptions.
Use rows and cols attributes to set default size.",
+ "task": "Create a contact form: 1. A <fieldset> with <legend>Contact Us 2. A labeled <input> for email 3. A labeled <textarea> for the message 4. A submit <button>",
+ "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; } form { max-width: 450px; margin: 0 auto; } fieldset { border: none; border-radius: 15px; padding: 30px; background: white; box-shadow: 0 10px 40px rgba(0,0,0,0.2); } legend { color: #667eea; font-weight: 700; padding: 0; font-size: 1.5rem; margin-bottom: 10px; } label { display: block; margin: 20px 0 8px; color: #333; font-weight: 500; } input, textarea { width: 100%; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: none; border-color: #667eea; } textarea { resize: vertical; min-height: 100px; } button { margin-top: 20px; width: 100%; padding: 14px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; } button:hover { opacity: 0.9; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "fieldset",
+ "message": "Add a <fieldset> element"
+ },
+ {
+ "type": "element_exists",
+ "value": "legend",
+ "message": "Add a <legend> element"
+ },
+ {
+ "type": "element_exists",
+ "value": "textarea",
+ "message": "Add a <textarea> for the message"
+ },
+ {
+ "type": "element_exists",
+ "value": "button",
+ "message": "Add a submit button"
+ },
+ {
+ "type": "element_exists",
+ "value": "input",
+ "message": "Add an input field for email"
+ }
+ ]
+ },
+ {
+ "id": "fieldset-multiple",
+ "title": "Multiple Fieldsets",
+ "description": "Complex forms can use multiple <fieldset> elements to organize different sections.
This improves usability for long forms like registration or checkout pages.",
+ "task": "Create a registration form with 2 fieldsets: 1. Account Info with username and password inputs 2. Preferences with a textarea for bio 3. A submit button outside the fieldsets",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } form { max-width: 500px; } fieldset { border: 1px solid #ddd; border-radius: 10px; padding: 20px; margin-bottom: 20px; background: white; } legend { color: #2c3e50; font-weight: 600; padding: 0 10px; } label { display: block; margin: 15px 0 5px; color: #555; } input, textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: 2px solid #3498db; border-color: transparent; } textarea { resize: vertical; min-height: 80px; } button { width: 100%; padding: 14px; background: #2c3e50; color: white; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; } button:hover { background: #34495e; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_count",
+ "value": { "selector": "fieldset", "min": 2 },
+ "message": "Create at least 2 fieldsets"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "legend", "min": 2 },
+ "message": "Add a legend to each fieldset"
+ },
+ {
+ "type": "element_exists",
+ "value": "textarea",
+ "message": "Add a textarea for the bio"
+ },
+ {
+ "type": "element_exists",
+ "value": "button",
+ "message": "Add a submit button"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "input", "min": 2 },
+ "message": "Add at least 2 input fields"
+ }
+ ]
+ }
+ ]
+}
diff --git a/lessons/es/30-html-tables.json b/lessons/es/30-html-tables.json
index 1bdfaeb..f4d29cf 100644
--- a/lessons/es/30-html-tables.json
+++ b/lessons/es/30-html-tables.json
@@ -10,7 +10,7 @@
"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",
+ "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",
"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; }",
"sandboxCSS": "",
@@ -44,7 +44,7 @@
"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",
+ "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": "",
@@ -83,7 +83,7 @@
"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",
+ "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": "",
diff --git a/lessons/es/31-html-marquee.json b/lessons/es/31-html-marquee.json
index 7c2939a..ec3a486 100644
--- a/lessons/es/31-html-marquee.json
+++ b/lessons/es/31-html-marquee.json
@@ -10,7 +10,7 @@
"id": "marquee-basic",
"title": "Scrolling Text",
"description": "The <marquee> element creates scrolling text - a classic from the early web! While deprecated, it still works in most browsers.
Note: For production, use CSS animations instead. But for learning and fun, marquee is great!",
- "task": "Create a simple marquee: 1. Add a <marquee> element 2. Put some text inside like 'Welcome to my website!'",
+ "task": "Create a simple marquee: 1. Add a <marquee> element 2. Put some text inside like Welcome to my website!",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2rem; color: #00ff00; text-shadow: 0 0 10px #00ff00, 0 0 20px #00ff00; font-family: 'Courier New', monospace; }",
"sandboxCSS": "",
diff --git a/lessons/pl/00-welcome.json b/lessons/pl/00-welcome.json
index 4ed3039..c3f0db8 100644
--- a/lessons/pl/00-welcome.json
+++ b/lessons/pl/00-welcome.json
@@ -10,7 +10,7 @@
"id": "get-started",
"title": "Get Started",
"description": "Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required!
What you'll learn: • HTML - Semantic elements, forms, tables, SVG (HTML Block & Inline, HTML Forms, HTML Tables) • CSS - Selectors, box model, flexbox, animations (CSS Selectors, CSS Box Model, CSS Flexbox) • Responsive Design - Media queries and mobile-first layouts
How it works: 1. Read the task in the left panel 2. Write code in the editor 3. See live results in the preview 4. Get instant feedback with hints
Keyboard shortcuts:Ctrl+Z to undo, Ctrl+Shift+Z to redo
More resources: • HTML over JS - Native HTML vs JavaScript solutions • Web Engineering Mandala - JavaScript technology roadmap",
- "task": "Hello World",
+ "task": "Write Hello World",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; } h1 { color: #6366f1; }",
"sandboxCSS": "",
@@ -20,8 +20,8 @@
"validations": [
{
"type": "contains",
- "value": "Hello",
- "message": "Write 'Hello World'"
+ "value": "Hello World",
+ "message": "Write Hello World"
}
]
},
@@ -39,7 +39,7 @@
"validations": [
{
"type": "contains",
- "value": "Hello",
+ "value": "Hello World",
"message": "Click Next to continue"
}
]
diff --git a/lessons/pl/06-transitions-animations.json b/lessons/pl/06-transitions-animations.json
index 2697fd7..2c10321 100644
--- a/lessons/pl/06-transitions-animations.json
+++ b/lessons/pl/06-transitions-animations.json
@@ -8,7 +8,7 @@
{
"id": "transitions-1",
"title": "Transitions",
- "description": "Learn how to apply transition to properties for smooth changes on state changes.",
+ "description": "Learn how to apply transition to properties for smooth changes on state changes.
",
"task": "Add transition: background-color 0.3s to .btn so the color fades smoothly on hover.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: black; color: white; padding: 0.5rem 1rem; border: none; cursor: pointer; } .btn:hover { background: white; color: black; }",
@@ -63,7 +63,7 @@
{
"id": "transitions-3",
"title": "Keyframes",
- "description": "Create named animations using @keyframes and apply them via the animation shorthand.",
+ "description": "Create named animations using @keyframes and apply them via the animation shorthand.
",
"task": "Define a keyframe at 50% with transform: translateY(-20px) and apply animation: bounce 1s infinite to .ball.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: crimson; border-radius: 50%; margin: 2rem auto; }",
diff --git a/lessons/pl/20-html-elements.json b/lessons/pl/20-html-elements.json
index 93f9214..97c569d 100644
--- a/lessons/pl/20-html-elements.json
+++ b/lessons/pl/20-html-elements.json
@@ -34,7 +34,7 @@
"id": "semantic-containers",
"title": "Semantic Tags",
"description": "Modern HTML uses semantic containers that describe their content:
<header> - Page or section header <nav> - Navigation links <main> - Main content area <section> - Thematic grouping <article> - Self-contained content <footer> - Page or section footer",
- "task": "Create a basic page structure: 1. Add a <header> with an <h1> containing the text 'My Website' 2. Add a <main> element with a paragraph saying 'Welcome to my site!' 3. Add a <footer> with a paragraph saying 'Copyright 2025'",
+ "task": "Create a basic page structure: 1. Add a <header> with an <h1> containing the text My Website 2. Add a <main> element with a paragraph saying Welcome to my site! 3. Add a <footer> with a paragraph saying Copyright 2025",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; margin: 0; } header { background: #1976d2; color: white; padding: 15px; } main { padding: 20px; min-height: 100px; } footer { background: #424242; color: white; padding: 10px; text-align: center; }",
"sandboxCSS": "",
diff --git a/lessons/pl/21-html-forms-basic.json b/lessons/pl/21-html-forms-basic.json
index d971cbf..e92962f 100644
--- a/lessons/pl/21-html-forms-basic.json
+++ b/lessons/pl/21-html-forms-basic.json
@@ -10,7 +10,7 @@
"id": "form-structure",
"title": "Form Structure",
"description": "Every form needs a <form> wrapper. Inside, use <label> to describe inputs and <input> for user data entry.
The for attribute on labels should match the id on inputs for accessibility.",
- "task": "Create a form with: 1. A <label> with the text 'Name:' and for=\"name\" attribute 2. A text <input> with id=\"name\" and name=\"name\" attributes",
+ "task": "Create a form with: 1. A <label> with the text Name: and for=\"name\" attribute 2. A text <input> with id=\"name\" and name=\"name\" attributes",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-bottom: 5px; font-weight: 500; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
"sandboxCSS": "",
@@ -77,8 +77,8 @@
{
"id": "submit-button",
"title": "Submit Button",
- "description": "Forms need a way to submit data. Use:
The button text should be action-oriented (e.g., 'Sign In', 'Register', 'Send').",
- "task": "Add a submit button to the form with the text 'Sign In'.",
+ "description": "Forms need a way to submit data. Use:
The button text should be action-oriented (e.g., Sign In, 'Register', 'Send').",
+ "task": "Add a submit button to the form with the text Sign In.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } button { width: 100%; margin-top: 20px; padding: 10px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; } button:hover { background: #1565c0; }",
"sandboxCSS": "",
diff --git a/lessons/pl/23-html-details-summary.json b/lessons/pl/23-html-details-summary.json
index 3c60ad0..ff83fc3 100644
--- a/lessons/pl/23-html-details-summary.json
+++ b/lessons/pl/23-html-details-summary.json
@@ -10,7 +10,7 @@
"id": "details-summary-basic",
"title": "First Widget",
"description": "The <details> element creates a collapsible section. The <summary> provides the clickable label.
Click the summary to toggle the hidden content - no JavaScript needed!",
- "task": "Create a <details> element with: 1. A <summary> saying 'Click to reveal' 2. A <p> with the text 'This content was hidden!'",
+ "task": "Create a <details> element with: 1. A <summary> saying Click to reveal 2. A <p> with the text This content was hidden!",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; } summary { font-weight: 600; cursor: pointer; } summary:hover { color: #1976d2; } details p { margin-top: 15px; color: #666; }",
"sandboxCSS": "",
@@ -63,7 +63,7 @@
"id": "faq-accordion",
"title": "FAQ Accordion",
"description": "Multiple <details> elements create an accordion-style FAQ. Each question can be expanded independently.
Pro tip: Type details*3>summary+p and press Tab for Emmet expansion. *3 creates 3 elements, > nests inside, + adds siblings.",
- "task": "Create an FAQ section with: 1. An <h1> saying 'Frequently Asked Questions' 2. Three <details> elements, each with a question in <summary> and an answer in <p>",
+ "task": "Create an FAQ section with: 1. An <h1> saying Frequently Asked Questions 2. Three <details> elements, each with a question in <summary> and an answer in <p>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; min-height: 100vh; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); display: flex; flex-direction: column; justify-content: center; padding: 40px; margin: 0; box-sizing: border-box; } h1 { font-size: 2.5rem; color: #4a4a4a; text-align: center; margin: 0 0 30px 0; } details { background: white; border-radius: 12px; margin-bottom: 15px; padding: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } summary { font-size: 1.3rem; font-weight: 600; color: #5c5c5c; cursor: pointer; list-style: none; } summary::before { content: '▸ '; color: #fcb69f; } details[open] summary::before { content: '▾ '; } details p { margin: 15px 0 0 0; color: #666; line-height: 1.6; }",
"sandboxCSS": "",
diff --git a/lessons/pl/24-html-progress-meter.json b/lessons/pl/24-html-progress-meter.json
index 28b3bf3..3a89bc3 100644
--- a/lessons/pl/24-html-progress-meter.json
+++ b/lessons/pl/24-html-progress-meter.json
@@ -10,7 +10,7 @@
"id": "progress-basic",
"title": "Progress Bars",
"description": "The <progress> element shows task completion. Use value for current progress and max for the total.
Note: This is not a self-closing tag! Write <progress>...</progress> with fallback text inside for older browsers.",
- "task": "Create a progress bar showing 70% completion: 1. Add a <label> saying 'Download:' 2. Add a <progress> with value=\"70\" and max=\"100\"",
+ "task": "Create a progress bar showing 70% completion: 1. Add a <label> saying Download: 2. Add a <progress> with value=\"70\" and max=\"100\"",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } progress { width: 100%; height: 20px; border-radius: 10px; } progress::-webkit-progress-bar { background: #e0e0e0; border-radius: 10px; } progress::-webkit-progress-value { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; } progress::-moz-progress-bar { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; }",
"sandboxCSS": "",
@@ -44,7 +44,7 @@
"id": "progress-indeterminate",
"title": "Indeterminate Progress",
"description": "When progress is unknown (like loading), omit the value attribute. This creates an animated indeterminate state.
Useful for network requests or processes with unknown duration.",
- "task": "Create a loading indicator: 1. Add a <p> saying 'Loading...' 2. Add a <progress> without a value attribute",
+ "task": "Create a loading indicator: 1. Add a <p> saying Loading... 2. Add a <progress> without a value attribute",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } p { margin-bottom: 10px; color: #666; } progress { width: 100%; height: 8px; border-radius: 4px; } progress::-webkit-progress-bar { background: #e0e0e0; border-radius: 4px; }",
"sandboxCSS": "",
@@ -68,7 +68,7 @@
"id": "meter-gauge",
"title": "Meter Gauges",
"description": "The <meter> element displays a scalar value within a range. Use it for measurements like disk space, battery, or ratings.
Set low, high, and optimum to define good/bad ranges - the browser colors it accordingly!",
- "task": "Create a battery level meter: 1. Add a <label> saying 'Battery:' 2. Add a <meter> with: - value=\"0.8\" - min=\"0\" and max=\"1\" - low=\"0.2\" and high=\"0.8\" - optimum=\"1\"",
+ "task": "Create a battery level meter: 1. Add a <label> saying Battery: 2. Add a <meter> with: - value=\"0.8\" - min=\"0\" and max=\"1\" - low=\"0.2\" and high=\"0.8\" - optimum=\"1\"",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
"sandboxCSS": "",
diff --git a/lessons/pl/25-html-datalist.json b/lessons/pl/25-html-datalist.json
new file mode 100644
index 0000000..8d33f10
--- /dev/null
+++ b/lessons/pl/25-html-datalist.json
@@ -0,0 +1,78 @@
+{
+ "$schema": "../../schemas/code-crispies-module-schema.json",
+ "id": "html-datalist",
+ "title": "Datalist",
+ "description": "Provide suggestions for text inputs without JavaScript",
+ "mode": "html",
+ "difficulty": "beginner",
+ "lessons": [
+ {
+ "id": "datalist-basic",
+ "title": "Input with Suggestions",
+ "description": "The <datalist> element provides autocomplete suggestions for inputs. Connect it using the list attribute on the input matching the datalist's id.
Users can still type freely - suggestions are just helpers!",
+ "task": "Create a browser selector: 1. Add a <label> saying Browser: 2. Add an <input> with list=\"browsers\" 3. Add a <datalist id=\"browsers\"> with options for Chrome, Firefox, and Safari",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; font-size: 16px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "\n\n",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "datalist",
+ "message": "Add a <datalist> element"
+ },
+ {
+ "type": "attribute_value",
+ "value": { "selector": "input", "attr": "list", "value": "browsers" },
+ "message": "Connect the input to datalist using list=\"browsers\""
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "option", "min": 3 },
+ "message": "Add at least 3 <option> elements inside <datalist>"
+ },
+ {
+ "type": "element_exists",
+ "value": "label",
+ "message": "Add a <label> for the input"
+ }
+ ]
+ },
+ {
+ "id": "datalist-countries",
+ "title": "Country Selector",
+ "description": "Datalists work great for long lists like countries. Users can type to filter suggestions instantly.
The value attribute is what gets entered, and you can add display text after it.",
+ "task": "Create a country input: 1. Add a <label> saying Country: 2. Add an <input> with list=\"countries\" 3. Add a <datalist id=\"countries\"> with at least 4 country options",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 30px; background: linear-gradient(135deg, #e0f7fa 0%, #b2ebf2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } label { display: block; margin-bottom: 10px; font-weight: 600; color: #00695c; } input { width: 100%; padding: 12px 15px; border: 2px solid #26a69a; border-radius: 8px; font-size: 16px; background: white; } input:focus { outline: none; border-color: #00695c; box-shadow: 0 0 0 3px rgba(38,166,154,0.2); }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "\n\n",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "datalist",
+ "message": "Add a <datalist> element"
+ },
+ {
+ "type": "attribute_value",
+ "value": { "selector": "datalist", "attr": "id", "value": "countries" },
+ "message": "Set id=\"countries\" on the datalist"
+ },
+ {
+ "type": "attribute_value",
+ "value": { "selector": "input", "attr": "list", "value": "countries" },
+ "message": "Connect the input using list=\"countries\""
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "option", "min": 4 },
+ "message": "Add at least 4 country options"
+ }
+ ]
+ }
+ ]
+}
diff --git a/lessons/pl/27-html-dialog.json b/lessons/pl/27-html-dialog.json
new file mode 100644
index 0000000..4b721c7
--- /dev/null
+++ b/lessons/pl/27-html-dialog.json
@@ -0,0 +1,83 @@
+{
+ "$schema": "../../schemas/code-crispies-module-schema.json",
+ "id": "html-dialog",
+ "title": "Dialogs",
+ "description": "Create modal dialogs without JavaScript libraries",
+ "mode": "html",
+ "difficulty": "beginner",
+ "lessons": [
+ {
+ "id": "dialog-basic",
+ "title": "Open Dialog",
+ "description": "The <dialog> element creates a native modal. Add the open attribute to show it.
Use <form method=\"dialog\"> inside to close it when the form submits - no JavaScript needed!",
+ "task": "Create a dialog with: 1. The open attribute to show it 2. An <h2> saying Welcome! 3. A <p> with a greeting message 4. A <form method=\"dialog\"> with a close button",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: #f5f5f5; } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 400px; } dialog::backdrop { background: rgba(0,0,0,0.5); } dialog h2 { margin: 0 0 15px 0; color: #1976d2; } dialog p { color: #666; margin: 0 0 20px 0; } dialog button { background: #1976d2; color: white; border: none; padding: 10px 25px; border-radius: 6px; cursor: pointer; font-size: 16px; } dialog button:hover { background: #1565c0; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "dialog",
+ "message": "Add a <dialog> element"
+ },
+ {
+ "type": "attribute_value",
+ "value": { "selector": "dialog", "attr": "open", "value": true },
+ "message": "Add the open attribute to show the dialog"
+ },
+ {
+ "type": "element_exists",
+ "value": "dialog h2",
+ "message": "Add an <h2> heading inside the dialog"
+ },
+ {
+ "type": "element_exists",
+ "value": "form[method='dialog']",
+ "message": "Add a <form method=\"dialog\"> for closing"
+ },
+ {
+ "type": "element_exists",
+ "value": "dialog button",
+ "message": "Add a close button inside the form"
+ }
+ ]
+ },
+ {
+ "id": "dialog-form",
+ "title": "Dialog + Form",
+ "description": "Dialogs can contain full forms. The method=\"dialog\" makes the form close the dialog on submit instead of sending data.
This pattern is perfect for confirmation dialogs, quick inputs, or settings panels.",
+ "task": "Create a confirmation dialog: 1. Add open to show it 2. An <h2> saying Confirm Delete 3. A <p> asking Are you sure? 4. A <form method=\"dialog\"> with Cancel and Delete buttons",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: linear-gradient(135deg, #fce4ec 0%, #f8bbd9 100%); } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 350px; text-align: center; } dialog h2 { margin: 0 0 10px 0; color: #c62828; } dialog p { color: #666; margin: 0 0 20px 0; } dialog form { display: flex; gap: 10px; justify-content: center; } dialog button { padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; border: none; } dialog button:first-child { background: #e0e0e0; color: #333; } dialog button:last-child { background: #c62828; color: white; } dialog button:hover { opacity: 0.9; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "dialog[open]",
+ "message": "Add a <dialog> with the open attribute"
+ },
+ {
+ "type": "element_exists",
+ "value": "dialog h2",
+ "message": "Add a heading to the dialog"
+ },
+ {
+ "type": "element_exists",
+ "value": "form[method='dialog']",
+ "message": "Add a <form method=\"dialog\"> for the buttons"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "dialog button", "min": 2 },
+ "message": "Add at least 2 buttons (Cancel and Confirm)"
+ }
+ ]
+ }
+ ]
+}
diff --git a/lessons/pl/28-html-forms-fieldset.json b/lessons/pl/28-html-forms-fieldset.json
new file mode 100644
index 0000000..add7ef9
--- /dev/null
+++ b/lessons/pl/28-html-forms-fieldset.json
@@ -0,0 +1,127 @@
+{
+ "$schema": "../../schemas/code-crispies-module-schema.json",
+ "id": "html-forms-fieldset",
+ "title": "Fieldsets",
+ "description": "Group form controls with fieldset and legend elements",
+ "mode": "html",
+ "difficulty": "beginner",
+ "lessons": [
+ {
+ "id": "fieldset-basic",
+ "title": "Grouping with Fieldset",
+ "description": "The <fieldset> element groups related form controls together. Add a <legend> as the first child to give the group a title.
This helps with accessibility and visual organization of complex forms.",
+ "task": "Create a form with a fieldset: 1. A <form> element 2. A <fieldset> inside 3. A <legend> saying Personal Info 4. Two labeled inputs for name and email",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f0f4f8; } form { max-width: 400px; } fieldset { border: 2px solid #3498db; border-radius: 10px; padding: 20px; background: white; } legend { color: #3498db; font-weight: 600; padding: 0 10px; font-size: 1.1rem; } label { display: block; margin: 15px 0 5px; color: #333; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; } input:focus { outline: 2px solid #3498db; border-color: transparent; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "form",
+ "message": "Add a <form> element"
+ },
+ {
+ "type": "element_exists",
+ "value": "fieldset",
+ "message": "Add a <fieldset> inside the form"
+ },
+ {
+ "type": "element_exists",
+ "value": "legend",
+ "message": "Add a <legend> to title your fieldset"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "label", "min": 2 },
+ "message": "Add at least 2 labels"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "input", "min": 2 },
+ "message": "Add at least 2 input fields"
+ }
+ ]
+ },
+ {
+ "id": "fieldset-textarea",
+ "title": "Adding Textarea",
+ "description": "The <textarea> element creates a multi-line text input, perfect for longer content like messages or descriptions.
Use rows and cols attributes to set default size.",
+ "task": "Create a contact form: 1. A <fieldset> with <legend>Contact Us 2. A labeled <input> for email 3. A labeled <textarea> for the message 4. A submit <button>",
+ "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; } form { max-width: 450px; margin: 0 auto; } fieldset { border: none; border-radius: 15px; padding: 30px; background: white; box-shadow: 0 10px 40px rgba(0,0,0,0.2); } legend { color: #667eea; font-weight: 700; padding: 0; font-size: 1.5rem; margin-bottom: 10px; } label { display: block; margin: 20px 0 8px; color: #333; font-weight: 500; } input, textarea { width: 100%; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: none; border-color: #667eea; } textarea { resize: vertical; min-height: 100px; } button { margin-top: 20px; width: 100%; padding: 14px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; } button:hover { opacity: 0.9; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "fieldset",
+ "message": "Add a <fieldset> element"
+ },
+ {
+ "type": "element_exists",
+ "value": "legend",
+ "message": "Add a <legend> element"
+ },
+ {
+ "type": "element_exists",
+ "value": "textarea",
+ "message": "Add a <textarea> for the message"
+ },
+ {
+ "type": "element_exists",
+ "value": "button",
+ "message": "Add a submit button"
+ },
+ {
+ "type": "element_exists",
+ "value": "input",
+ "message": "Add an input field for email"
+ }
+ ]
+ },
+ {
+ "id": "fieldset-multiple",
+ "title": "Multiple Fieldsets",
+ "description": "Complex forms can use multiple <fieldset> elements to organize different sections.
This improves usability for long forms like registration or checkout pages.",
+ "task": "Create a registration form with 2 fieldsets: 1. Account Info with username and password inputs 2. Preferences with a textarea for bio 3. A submit button outside the fieldsets",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } form { max-width: 500px; } fieldset { border: 1px solid #ddd; border-radius: 10px; padding: 20px; margin-bottom: 20px; background: white; } legend { color: #2c3e50; font-weight: 600; padding: 0 10px; } label { display: block; margin: 15px 0 5px; color: #555; } input, textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: 2px solid #3498db; border-color: transparent; } textarea { resize: vertical; min-height: 80px; } button { width: 100%; padding: 14px; background: #2c3e50; color: white; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; } button:hover { background: #34495e; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_count",
+ "value": { "selector": "fieldset", "min": 2 },
+ "message": "Create at least 2 fieldsets"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "legend", "min": 2 },
+ "message": "Add a legend to each fieldset"
+ },
+ {
+ "type": "element_exists",
+ "value": "textarea",
+ "message": "Add a textarea for the bio"
+ },
+ {
+ "type": "element_exists",
+ "value": "button",
+ "message": "Add a submit button"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "input", "min": 2 },
+ "message": "Add at least 2 input fields"
+ }
+ ]
+ }
+ ]
+}
diff --git a/lessons/pl/30-html-tables.json b/lessons/pl/30-html-tables.json
index 1bdfaeb..f4d29cf 100644
--- a/lessons/pl/30-html-tables.json
+++ b/lessons/pl/30-html-tables.json
@@ -10,7 +10,7 @@
"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",
+ "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",
"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; }",
"sandboxCSS": "",
@@ -44,7 +44,7 @@
"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",
+ "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": "",
@@ -83,7 +83,7 @@
"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",
+ "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": "",
diff --git a/lessons/pl/31-html-marquee.json b/lessons/pl/31-html-marquee.json
index 7c2939a..ec3a486 100644
--- a/lessons/pl/31-html-marquee.json
+++ b/lessons/pl/31-html-marquee.json
@@ -10,7 +10,7 @@
"id": "marquee-basic",
"title": "Scrolling Text",
"description": "The <marquee> element creates scrolling text - a classic from the early web! While deprecated, it still works in most browsers.
Note: For production, use CSS animations instead. But for learning and fun, marquee is great!",
- "task": "Create a simple marquee: 1. Add a <marquee> element 2. Put some text inside like 'Welcome to my website!'",
+ "task": "Create a simple marquee: 1. Add a <marquee> element 2. Put some text inside like Welcome to my website!",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2rem; color: #00ff00; text-shadow: 0 0 10px #00ff00, 0 0 20px #00ff00; font-family: 'Courier New', monospace; }",
"sandboxCSS": "",
diff --git a/lessons/uk/00-welcome.json b/lessons/uk/00-welcome.json
index 4ed3039..c3f0db8 100644
--- a/lessons/uk/00-welcome.json
+++ b/lessons/uk/00-welcome.json
@@ -10,7 +10,7 @@
"id": "get-started",
"title": "Get Started",
"description": "Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required!
What you'll learn: • HTML - Semantic elements, forms, tables, SVG (HTML Block & Inline, HTML Forms, HTML Tables) • CSS - Selectors, box model, flexbox, animations (CSS Selectors, CSS Box Model, CSS Flexbox) • Responsive Design - Media queries and mobile-first layouts
How it works: 1. Read the task in the left panel 2. Write code in the editor 3. See live results in the preview 4. Get instant feedback with hints
Keyboard shortcuts:Ctrl+Z to undo, Ctrl+Shift+Z to redo
More resources: • HTML over JS - Native HTML vs JavaScript solutions • Web Engineering Mandala - JavaScript technology roadmap",
- "task": "Hello World",
+ "task": "Write Hello World",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; } h1 { color: #6366f1; }",
"sandboxCSS": "",
@@ -20,8 +20,8 @@
"validations": [
{
"type": "contains",
- "value": "Hello",
- "message": "Write 'Hello World'"
+ "value": "Hello World",
+ "message": "Write Hello World"
}
]
},
@@ -39,7 +39,7 @@
"validations": [
{
"type": "contains",
- "value": "Hello",
+ "value": "Hello World",
"message": "Click Next to continue"
}
]
diff --git a/lessons/uk/06-transitions-animations.json b/lessons/uk/06-transitions-animations.json
index 2697fd7..2c10321 100644
--- a/lessons/uk/06-transitions-animations.json
+++ b/lessons/uk/06-transitions-animations.json
@@ -8,7 +8,7 @@
{
"id": "transitions-1",
"title": "Transitions",
- "description": "Learn how to apply transition to properties for smooth changes on state changes.",
+ "description": "Learn how to apply transition to properties for smooth changes on state changes.
",
"task": "Add transition: background-color 0.3s to .btn so the color fades smoothly on hover.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: black; color: white; padding: 0.5rem 1rem; border: none; cursor: pointer; } .btn:hover { background: white; color: black; }",
@@ -63,7 +63,7 @@
{
"id": "transitions-3",
"title": "Keyframes",
- "description": "Create named animations using @keyframes and apply them via the animation shorthand.",
+ "description": "Create named animations using @keyframes and apply them via the animation shorthand.
",
"task": "Define a keyframe at 50% with transform: translateY(-20px) and apply animation: bounce 1s infinite to .ball.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: crimson; border-radius: 50%; margin: 2rem auto; }",
diff --git a/lessons/uk/20-html-elements.json b/lessons/uk/20-html-elements.json
index 93f9214..97c569d 100644
--- a/lessons/uk/20-html-elements.json
+++ b/lessons/uk/20-html-elements.json
@@ -34,7 +34,7 @@
"id": "semantic-containers",
"title": "Semantic Tags",
"description": "Modern HTML uses semantic containers that describe their content:
<header> - Page or section header <nav> - Navigation links <main> - Main content area <section> - Thematic grouping <article> - Self-contained content <footer> - Page or section footer",
- "task": "Create a basic page structure: 1. Add a <header> with an <h1> containing the text 'My Website' 2. Add a <main> element with a paragraph saying 'Welcome to my site!' 3. Add a <footer> with a paragraph saying 'Copyright 2025'",
+ "task": "Create a basic page structure: 1. Add a <header> with an <h1> containing the text My Website 2. Add a <main> element with a paragraph saying Welcome to my site! 3. Add a <footer> with a paragraph saying Copyright 2025",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; margin: 0; } header { background: #1976d2; color: white; padding: 15px; } main { padding: 20px; min-height: 100px; } footer { background: #424242; color: white; padding: 10px; text-align: center; }",
"sandboxCSS": "",
diff --git a/lessons/uk/21-html-forms-basic.json b/lessons/uk/21-html-forms-basic.json
index d971cbf..e92962f 100644
--- a/lessons/uk/21-html-forms-basic.json
+++ b/lessons/uk/21-html-forms-basic.json
@@ -10,7 +10,7 @@
"id": "form-structure",
"title": "Form Structure",
"description": "Every form needs a <form> wrapper. Inside, use <label> to describe inputs and <input> for user data entry.
The for attribute on labels should match the id on inputs for accessibility.",
- "task": "Create a form with: 1. A <label> with the text 'Name:' and for=\"name\" attribute 2. A text <input> with id=\"name\" and name=\"name\" attributes",
+ "task": "Create a form with: 1. A <label> with the text Name: and for=\"name\" attribute 2. A text <input> with id=\"name\" and name=\"name\" attributes",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-bottom: 5px; font-weight: 500; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
"sandboxCSS": "",
@@ -77,8 +77,8 @@
{
"id": "submit-button",
"title": "Submit Button",
- "description": "Forms need a way to submit data. Use:
The button text should be action-oriented (e.g., 'Sign In', 'Register', 'Send').",
- "task": "Add a submit button to the form with the text 'Sign In'.",
+ "description": "Forms need a way to submit data. Use:
The button text should be action-oriented (e.g., Sign In, 'Register', 'Send').",
+ "task": "Add a submit button to the form with the text Sign In.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } button { width: 100%; margin-top: 20px; padding: 10px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; } button:hover { background: #1565c0; }",
"sandboxCSS": "",
diff --git a/lessons/uk/23-html-details-summary.json b/lessons/uk/23-html-details-summary.json
index 3c60ad0..ff83fc3 100644
--- a/lessons/uk/23-html-details-summary.json
+++ b/lessons/uk/23-html-details-summary.json
@@ -10,7 +10,7 @@
"id": "details-summary-basic",
"title": "First Widget",
"description": "The <details> element creates a collapsible section. The <summary> provides the clickable label.
Click the summary to toggle the hidden content - no JavaScript needed!",
- "task": "Create a <details> element with: 1. A <summary> saying 'Click to reveal' 2. A <p> with the text 'This content was hidden!'",
+ "task": "Create a <details> element with: 1. A <summary> saying Click to reveal 2. A <p> with the text This content was hidden!",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; } summary { font-weight: 600; cursor: pointer; } summary:hover { color: #1976d2; } details p { margin-top: 15px; color: #666; }",
"sandboxCSS": "",
@@ -63,7 +63,7 @@
"id": "faq-accordion",
"title": "FAQ Accordion",
"description": "Multiple <details> elements create an accordion-style FAQ. Each question can be expanded independently.
Pro tip: Type details*3>summary+p and press Tab for Emmet expansion. *3 creates 3 elements, > nests inside, + adds siblings.",
- "task": "Create an FAQ section with: 1. An <h1> saying 'Frequently Asked Questions' 2. Three <details> elements, each with a question in <summary> and an answer in <p>",
+ "task": "Create an FAQ section with: 1. An <h1> saying Frequently Asked Questions 2. Three <details> elements, each with a question in <summary> and an answer in <p>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; min-height: 100vh; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); display: flex; flex-direction: column; justify-content: center; padding: 40px; margin: 0; box-sizing: border-box; } h1 { font-size: 2.5rem; color: #4a4a4a; text-align: center; margin: 0 0 30px 0; } details { background: white; border-radius: 12px; margin-bottom: 15px; padding: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } summary { font-size: 1.3rem; font-weight: 600; color: #5c5c5c; cursor: pointer; list-style: none; } summary::before { content: '▸ '; color: #fcb69f; } details[open] summary::before { content: '▾ '; } details p { margin: 15px 0 0 0; color: #666; line-height: 1.6; }",
"sandboxCSS": "",
diff --git a/lessons/uk/24-html-progress-meter.json b/lessons/uk/24-html-progress-meter.json
index 28b3bf3..3a89bc3 100644
--- a/lessons/uk/24-html-progress-meter.json
+++ b/lessons/uk/24-html-progress-meter.json
@@ -10,7 +10,7 @@
"id": "progress-basic",
"title": "Progress Bars",
"description": "The <progress> element shows task completion. Use value for current progress and max for the total.
Note: This is not a self-closing tag! Write <progress>...</progress> with fallback text inside for older browsers.",
- "task": "Create a progress bar showing 70% completion: 1. Add a <label> saying 'Download:' 2. Add a <progress> with value=\"70\" and max=\"100\"",
+ "task": "Create a progress bar showing 70% completion: 1. Add a <label> saying Download: 2. Add a <progress> with value=\"70\" and max=\"100\"",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } progress { width: 100%; height: 20px; border-radius: 10px; } progress::-webkit-progress-bar { background: #e0e0e0; border-radius: 10px; } progress::-webkit-progress-value { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; } progress::-moz-progress-bar { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; }",
"sandboxCSS": "",
@@ -44,7 +44,7 @@
"id": "progress-indeterminate",
"title": "Indeterminate Progress",
"description": "When progress is unknown (like loading), omit the value attribute. This creates an animated indeterminate state.
Useful for network requests or processes with unknown duration.",
- "task": "Create a loading indicator: 1. Add a <p> saying 'Loading...' 2. Add a <progress> without a value attribute",
+ "task": "Create a loading indicator: 1. Add a <p> saying Loading... 2. Add a <progress> without a value attribute",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } p { margin-bottom: 10px; color: #666; } progress { width: 100%; height: 8px; border-radius: 4px; } progress::-webkit-progress-bar { background: #e0e0e0; border-radius: 4px; }",
"sandboxCSS": "",
@@ -68,7 +68,7 @@
"id": "meter-gauge",
"title": "Meter Gauges",
"description": "The <meter> element displays a scalar value within a range. Use it for measurements like disk space, battery, or ratings.
Set low, high, and optimum to define good/bad ranges - the browser colors it accordingly!",
- "task": "Create a battery level meter: 1. Add a <label> saying 'Battery:' 2. Add a <meter> with: - value=\"0.8\" - min=\"0\" and max=\"1\" - low=\"0.2\" and high=\"0.8\" - optimum=\"1\"",
+ "task": "Create a battery level meter: 1. Add a <label> saying Battery: 2. Add a <meter> with: - value=\"0.8\" - min=\"0\" and max=\"1\" - low=\"0.2\" and high=\"0.8\" - optimum=\"1\"",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
"sandboxCSS": "",
diff --git a/lessons/uk/25-html-datalist.json b/lessons/uk/25-html-datalist.json
new file mode 100644
index 0000000..8d33f10
--- /dev/null
+++ b/lessons/uk/25-html-datalist.json
@@ -0,0 +1,78 @@
+{
+ "$schema": "../../schemas/code-crispies-module-schema.json",
+ "id": "html-datalist",
+ "title": "Datalist",
+ "description": "Provide suggestions for text inputs without JavaScript",
+ "mode": "html",
+ "difficulty": "beginner",
+ "lessons": [
+ {
+ "id": "datalist-basic",
+ "title": "Input with Suggestions",
+ "description": "The <datalist> element provides autocomplete suggestions for inputs. Connect it using the list attribute on the input matching the datalist's id.
Users can still type freely - suggestions are just helpers!",
+ "task": "Create a browser selector: 1. Add a <label> saying Browser: 2. Add an <input> with list=\"browsers\" 3. Add a <datalist id=\"browsers\"> with options for Chrome, Firefox, and Safari",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; font-size: 16px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "\n\n",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "datalist",
+ "message": "Add a <datalist> element"
+ },
+ {
+ "type": "attribute_value",
+ "value": { "selector": "input", "attr": "list", "value": "browsers" },
+ "message": "Connect the input to datalist using list=\"browsers\""
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "option", "min": 3 },
+ "message": "Add at least 3 <option> elements inside <datalist>"
+ },
+ {
+ "type": "element_exists",
+ "value": "label",
+ "message": "Add a <label> for the input"
+ }
+ ]
+ },
+ {
+ "id": "datalist-countries",
+ "title": "Country Selector",
+ "description": "Datalists work great for long lists like countries. Users can type to filter suggestions instantly.
The value attribute is what gets entered, and you can add display text after it.",
+ "task": "Create a country input: 1. Add a <label> saying Country: 2. Add an <input> with list=\"countries\" 3. Add a <datalist id=\"countries\"> with at least 4 country options",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 30px; background: linear-gradient(135deg, #e0f7fa 0%, #b2ebf2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } label { display: block; margin-bottom: 10px; font-weight: 600; color: #00695c; } input { width: 100%; padding: 12px 15px; border: 2px solid #26a69a; border-radius: 8px; font-size: 16px; background: white; } input:focus { outline: none; border-color: #00695c; box-shadow: 0 0 0 3px rgba(38,166,154,0.2); }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "\n\n",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "datalist",
+ "message": "Add a <datalist> element"
+ },
+ {
+ "type": "attribute_value",
+ "value": { "selector": "datalist", "attr": "id", "value": "countries" },
+ "message": "Set id=\"countries\" on the datalist"
+ },
+ {
+ "type": "attribute_value",
+ "value": { "selector": "input", "attr": "list", "value": "countries" },
+ "message": "Connect the input using list=\"countries\""
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "option", "min": 4 },
+ "message": "Add at least 4 country options"
+ }
+ ]
+ }
+ ]
+}
diff --git a/lessons/uk/27-html-dialog.json b/lessons/uk/27-html-dialog.json
new file mode 100644
index 0000000..4b721c7
--- /dev/null
+++ b/lessons/uk/27-html-dialog.json
@@ -0,0 +1,83 @@
+{
+ "$schema": "../../schemas/code-crispies-module-schema.json",
+ "id": "html-dialog",
+ "title": "Dialogs",
+ "description": "Create modal dialogs without JavaScript libraries",
+ "mode": "html",
+ "difficulty": "beginner",
+ "lessons": [
+ {
+ "id": "dialog-basic",
+ "title": "Open Dialog",
+ "description": "The <dialog> element creates a native modal. Add the open attribute to show it.
Use <form method=\"dialog\"> inside to close it when the form submits - no JavaScript needed!",
+ "task": "Create a dialog with: 1. The open attribute to show it 2. An <h2> saying Welcome! 3. A <p> with a greeting message 4. A <form method=\"dialog\"> with a close button",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: #f5f5f5; } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 400px; } dialog::backdrop { background: rgba(0,0,0,0.5); } dialog h2 { margin: 0 0 15px 0; color: #1976d2; } dialog p { color: #666; margin: 0 0 20px 0; } dialog button { background: #1976d2; color: white; border: none; padding: 10px 25px; border-radius: 6px; cursor: pointer; font-size: 16px; } dialog button:hover { background: #1565c0; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "dialog",
+ "message": "Add a <dialog> element"
+ },
+ {
+ "type": "attribute_value",
+ "value": { "selector": "dialog", "attr": "open", "value": true },
+ "message": "Add the open attribute to show the dialog"
+ },
+ {
+ "type": "element_exists",
+ "value": "dialog h2",
+ "message": "Add an <h2> heading inside the dialog"
+ },
+ {
+ "type": "element_exists",
+ "value": "form[method='dialog']",
+ "message": "Add a <form method=\"dialog\"> for closing"
+ },
+ {
+ "type": "element_exists",
+ "value": "dialog button",
+ "message": "Add a close button inside the form"
+ }
+ ]
+ },
+ {
+ "id": "dialog-form",
+ "title": "Dialog + Form",
+ "description": "Dialogs can contain full forms. The method=\"dialog\" makes the form close the dialog on submit instead of sending data.
This pattern is perfect for confirmation dialogs, quick inputs, or settings panels.",
+ "task": "Create a confirmation dialog: 1. Add open to show it 2. An <h2> saying Confirm Delete 3. A <p> asking Are you sure? 4. A <form method=\"dialog\"> with Cancel and Delete buttons",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: linear-gradient(135deg, #fce4ec 0%, #f8bbd9 100%); } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 350px; text-align: center; } dialog h2 { margin: 0 0 10px 0; color: #c62828; } dialog p { color: #666; margin: 0 0 20px 0; } dialog form { display: flex; gap: 10px; justify-content: center; } dialog button { padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; border: none; } dialog button:first-child { background: #e0e0e0; color: #333; } dialog button:last-child { background: #c62828; color: white; } dialog button:hover { opacity: 0.9; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "dialog[open]",
+ "message": "Add a <dialog> with the open attribute"
+ },
+ {
+ "type": "element_exists",
+ "value": "dialog h2",
+ "message": "Add a heading to the dialog"
+ },
+ {
+ "type": "element_exists",
+ "value": "form[method='dialog']",
+ "message": "Add a <form method=\"dialog\"> for the buttons"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "dialog button", "min": 2 },
+ "message": "Add at least 2 buttons (Cancel and Confirm)"
+ }
+ ]
+ }
+ ]
+}
diff --git a/lessons/uk/28-html-forms-fieldset.json b/lessons/uk/28-html-forms-fieldset.json
new file mode 100644
index 0000000..add7ef9
--- /dev/null
+++ b/lessons/uk/28-html-forms-fieldset.json
@@ -0,0 +1,127 @@
+{
+ "$schema": "../../schemas/code-crispies-module-schema.json",
+ "id": "html-forms-fieldset",
+ "title": "Fieldsets",
+ "description": "Group form controls with fieldset and legend elements",
+ "mode": "html",
+ "difficulty": "beginner",
+ "lessons": [
+ {
+ "id": "fieldset-basic",
+ "title": "Grouping with Fieldset",
+ "description": "The <fieldset> element groups related form controls together. Add a <legend> as the first child to give the group a title.
This helps with accessibility and visual organization of complex forms.",
+ "task": "Create a form with a fieldset: 1. A <form> element 2. A <fieldset> inside 3. A <legend> saying Personal Info 4. Two labeled inputs for name and email",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f0f4f8; } form { max-width: 400px; } fieldset { border: 2px solid #3498db; border-radius: 10px; padding: 20px; background: white; } legend { color: #3498db; font-weight: 600; padding: 0 10px; font-size: 1.1rem; } label { display: block; margin: 15px 0 5px; color: #333; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; } input:focus { outline: 2px solid #3498db; border-color: transparent; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "form",
+ "message": "Add a <form> element"
+ },
+ {
+ "type": "element_exists",
+ "value": "fieldset",
+ "message": "Add a <fieldset> inside the form"
+ },
+ {
+ "type": "element_exists",
+ "value": "legend",
+ "message": "Add a <legend> to title your fieldset"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "label", "min": 2 },
+ "message": "Add at least 2 labels"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "input", "min": 2 },
+ "message": "Add at least 2 input fields"
+ }
+ ]
+ },
+ {
+ "id": "fieldset-textarea",
+ "title": "Adding Textarea",
+ "description": "The <textarea> element creates a multi-line text input, perfect for longer content like messages or descriptions.
Use rows and cols attributes to set default size.",
+ "task": "Create a contact form: 1. A <fieldset> with <legend>Contact Us 2. A labeled <input> for email 3. A labeled <textarea> for the message 4. A submit <button>",
+ "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; } form { max-width: 450px; margin: 0 auto; } fieldset { border: none; border-radius: 15px; padding: 30px; background: white; box-shadow: 0 10px 40px rgba(0,0,0,0.2); } legend { color: #667eea; font-weight: 700; padding: 0; font-size: 1.5rem; margin-bottom: 10px; } label { display: block; margin: 20px 0 8px; color: #333; font-weight: 500; } input, textarea { width: 100%; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: none; border-color: #667eea; } textarea { resize: vertical; min-height: 100px; } button { margin-top: 20px; width: 100%; padding: 14px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; } button:hover { opacity: 0.9; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "fieldset",
+ "message": "Add a <fieldset> element"
+ },
+ {
+ "type": "element_exists",
+ "value": "legend",
+ "message": "Add a <legend> element"
+ },
+ {
+ "type": "element_exists",
+ "value": "textarea",
+ "message": "Add a <textarea> for the message"
+ },
+ {
+ "type": "element_exists",
+ "value": "button",
+ "message": "Add a submit button"
+ },
+ {
+ "type": "element_exists",
+ "value": "input",
+ "message": "Add an input field for email"
+ }
+ ]
+ },
+ {
+ "id": "fieldset-multiple",
+ "title": "Multiple Fieldsets",
+ "description": "Complex forms can use multiple <fieldset> elements to organize different sections.
This improves usability for long forms like registration or checkout pages.",
+ "task": "Create a registration form with 2 fieldsets: 1. Account Info with username and password inputs 2. Preferences with a textarea for bio 3. A submit button outside the fieldsets",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } form { max-width: 500px; } fieldset { border: 1px solid #ddd; border-radius: 10px; padding: 20px; margin-bottom: 20px; background: white; } legend { color: #2c3e50; font-weight: 600; padding: 0 10px; } label { display: block; margin: 15px 0 5px; color: #555; } input, textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: 2px solid #3498db; border-color: transparent; } textarea { resize: vertical; min-height: 80px; } button { width: 100%; padding: 14px; background: #2c3e50; color: white; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; } button:hover { background: #34495e; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_count",
+ "value": { "selector": "fieldset", "min": 2 },
+ "message": "Create at least 2 fieldsets"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "legend", "min": 2 },
+ "message": "Add a legend to each fieldset"
+ },
+ {
+ "type": "element_exists",
+ "value": "textarea",
+ "message": "Add a textarea for the bio"
+ },
+ {
+ "type": "element_exists",
+ "value": "button",
+ "message": "Add a submit button"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "input", "min": 2 },
+ "message": "Add at least 2 input fields"
+ }
+ ]
+ }
+ ]
+}
diff --git a/lessons/uk/30-html-tables.json b/lessons/uk/30-html-tables.json
index 1bdfaeb..f4d29cf 100644
--- a/lessons/uk/30-html-tables.json
+++ b/lessons/uk/30-html-tables.json
@@ -10,7 +10,7 @@
"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",
+ "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",
"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; }",
"sandboxCSS": "",
@@ -44,7 +44,7 @@
"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",
+ "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": "",
@@ -83,7 +83,7 @@
"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",
+ "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": "",
diff --git a/lessons/uk/31-html-marquee.json b/lessons/uk/31-html-marquee.json
index 7c2939a..ec3a486 100644
--- a/lessons/uk/31-html-marquee.json
+++ b/lessons/uk/31-html-marquee.json
@@ -10,7 +10,7 @@
"id": "marquee-basic",
"title": "Scrolling Text",
"description": "The <marquee> element creates scrolling text - a classic from the early web! While deprecated, it still works in most browsers.
Note: For production, use CSS animations instead. But for learning and fun, marquee is great!",
- "task": "Create a simple marquee: 1. Add a <marquee> element 2. Put some text inside like 'Welcome to my website!'",
+ "task": "Create a simple marquee: 1. Add a <marquee> element 2. Put some text inside like Welcome to my website!",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2rem; color: #00ff00; text-shadow: 0 0 10px #00ff00, 0 0 20px #00ff00; font-family: 'Courier New', monospace; }",
"sandboxCSS": "",