"
+ },
+ {
+ "type": "parent_child",
+ "value": { "parent": "details", "child": "p" },
+ "message": "Add a inside for the hidden content"
+ }
+ ]
+ },
+ {
+ "id": "details-open-attribute",
+ "title": "Pre-expanded Details",
+ "description": "By default, <details> is closed. Add the open attribute to show the content initially. This is a boolean attribute - just add open without a value.",
+ "task": "Add the open attribute to the <details> element to show the content by default.",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; background: #f9f9f9; } summary { font-weight: 600; cursor: pointer; } details p { margin-top: 15px; color: #666; }",
+ "sandboxCSS": "",
+ "initialCode": "\n FAQ: What is HTML5? \n HTML5 is the latest version of the HTML standard with new semantic elements and APIs.
\n ",
+ "solution": "\n FAQ: What is HTML5? \n HTML5 is the latest version of the HTML standard with new semantic elements and APIs.
\n ",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "attribute_value",
+ "value": { "selector": "details", "attr": "open", "value": true },
+ "message": "Add the 'open' attribute to "
+ }
+ ]
+ },
+ {
+ "id": "faq-accordion",
+ "title": "FAQ Accordion",
+ "description": "Multiple <details> elements create an accordion-style FAQ. Each question can be expanded independently. This is a common pattern for FAQ pages, documentation, and help sections.",
+ "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": "",
+ "initialCode": "",
+ "solution": "Frequently Asked Questions \n\n\n What is HTML5? \n HTML5 is the latest version of HTML with new semantic elements and APIs.
\n \n\n\n Do I need JavaScript? \n Many interactive features work with pure HTML5, no JavaScript required!
\n \n\n\n Is this accessible? \n Yes! Native HTML elements have built-in keyboard and screen reader support.
\n ",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "h1",
+ "message": "Add an heading for the FAQ title"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "details", "min": 3 },
+ "message": "Create at least 3 elements for the FAQ"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "summary", "min": 3 },
+ "message": "Each needs a for the question"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "details p", "min": 3 },
+ "message": "Each needs a for the answer"
+ }
+ ]
+ }
+ ]
+}
diff --git a/lessons/24-html-progress-meter.json b/lessons/24-html-progress-meter.json
new file mode 100644
index 0000000..b3bbc2a
--- /dev/null
+++ b/lessons/24-html-progress-meter.json
@@ -0,0 +1,102 @@
+{
+ "$schema": "../schemas/code-crispies-module-schema.json",
+ "id": "html-progress-meter",
+ "title": "Progress & Meter Elements",
+ "description": "Display completion status and scalar measurements natively",
+ "mode": "html",
+ "difficulty": "beginner",
+ "lessons": [
+ {
+ "id": "progress-basic",
+ "title": "Progress Bars",
+ "description": "The <progress> element shows task completion. Use value for current progress and max for the total. The text inside is fallback 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\" ",
+ "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": "",
+ "initialCode": "",
+ "solution": "Download: \n70% ",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "progress",
+ "message": "Add a element"
+ },
+ {
+ "type": "attribute_value",
+ "value": { "selector": "progress", "attr": "value", "value": "70" },
+ "message": "Set value=\"70\" on the progress element"
+ },
+ {
+ "type": "attribute_value",
+ "value": { "selector": "progress", "attr": "max", "value": "100" },
+ "message": "Set max=\"100\" on the progress element"
+ },
+ {
+ "type": "element_exists",
+ "value": "label",
+ "message": "Add a for the progress bar"
+ }
+ ]
+ },
+ {
+ "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",
+ "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": "",
+ "initialCode": "",
+ "solution": "Loading...
\n ",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "progress",
+ "message": "Add a element"
+ },
+ {
+ "type": "element_exists",
+ "value": "p",
+ "message": "Add a with loading text"
+ }
+ ]
+ },
+ {
+ "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\" ",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "Battery: \n80% ",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "meter",
+ "message": "Add a element"
+ },
+ {
+ "type": "attribute_value",
+ "value": { "selector": "meter", "attr": "value", "value": "0.8" },
+ "message": "Set value=\"0.8\" on the meter"
+ },
+ {
+ "type": "attribute_value",
+ "value": { "selector": "meter", "attr": "low", "value": "0.2" },
+ "message": "Set low=\"0.2\" to define the low threshold"
+ },
+ {
+ "type": "element_exists",
+ "value": "label",
+ "message": "Add a for the meter"
+ }
+ ]
+ }
+ ]
+}
diff --git a/lessons/25-html-datalist.json b/lessons/25-html-datalist.json
new file mode 100644
index 0000000..0cb9c20
--- /dev/null
+++ b/lessons/25-html-datalist.json
@@ -0,0 +1,78 @@
+{
+ "$schema": "../schemas/code-crispies-module-schema.json",
+ "id": "html-datalist",
+ "title": "Datalist: Autocomplete Inputs",
+ "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": "Browser: \n \n\n \n \n \n ",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "datalist",
+ "message": "Add a 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 elements inside datalist"
+ },
+ {
+ "type": "element_exists",
+ "value": "label",
+ "message": "Add a 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": "Country: \n \n\n \n \n \n \n ",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "datalist",
+ "message": "Add a 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/26-html-data-attributes.json b/lessons/26-html-data-attributes.json
new file mode 100644
index 0000000..e4f04cf
--- /dev/null
+++ b/lessons/26-html-data-attributes.json
@@ -0,0 +1,68 @@
+{
+ "$schema": "../schemas/code-crispies-module-schema.json",
+ "id": "html-data-attributes",
+ "title": "Custom Data Attributes",
+ "description": "Store custom data on HTML elements with data-* attributes",
+ "mode": "html",
+ "difficulty": "beginner",
+ "lessons": [
+ {
+ "id": "data-attributes-intro",
+ "title": "Adding Data Attributes",
+ "description": "Custom data-* attributes let you store extra information on any HTML element. The attribute name starts with data- followed by your custom name. Examples: data-id , data-category , data-price ",
+ "task": "Create two product cards using <article> elements. Each should have: 1. A data-category attribute (e.g., 'electronics' or 'clothing') 2. A data-price attribute with a number 3. An <h2> with the product name",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; display: grid; gap: 15px; } article { padding: 20px; border-radius: 10px; background: #f5f5f5; border-left: 4px solid #ccc; } article[data-category='electronics'] { border-left-color: #2196f3; background: #e3f2fd; } article[data-category='clothing'] { border-left-color: #e91e63; background: #fce4ec; } h2 { margin: 0 0 10px 0; } article::after { content: '€' attr(data-price); display: block; font-weight: bold; color: #4caf50; margin-top: 10px; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "\n Laptop \n A powerful laptop for work and play.
\n \n\n\n T-Shirt \n A comfortable cotton t-shirt.
\n ",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_count",
+ "value": { "selector": "article[data-category]", "min": 2 },
+ "message": "Create at least 2 articles with data-category attributes"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "article[data-price]", "min": 2 },
+ "message": "Add data-price attribute to your articles"
+ },
+ {
+ "type": "element_count",
+ "value": { "selector": "article h2", "min": 2 },
+ "message": "Add an inside each article"
+ }
+ ]
+ },
+ {
+ "id": "data-attributes-css",
+ "title": "Styling with Data Attributes",
+ "description": "CSS can select elements by their data attributes using [data-*] selectors. You can even match specific values! The preview CSS uses [data-status='active'] to style active items differently.",
+ "task": "Create a task list with 3 <li> items. Give each a data-status attribute: 1. One with data-status=\"completed\" 2. One with data-status=\"active\" 3. One with data-status=\"pending\" ",
+ "previewHTML": "",
+ "previewBaseCSS": "body { font-family: system-ui; padding: 20px; } ul { list-style: none; padding: 0; } li { padding: 15px; margin: 8px 0; border-radius: 8px; background: #f5f5f5; } li[data-status='completed'] { background: #c8e6c9; text-decoration: line-through; color: #388e3c; } li[data-status='active'] { background: #fff3e0; border-left: 4px solid #ff9800; font-weight: 600; } li[data-status='pending'] { background: #e3f2fd; color: #1976d2; }",
+ "sandboxCSS": "",
+ "initialCode": "",
+ "solution": "\n Buy groceries \n Finish homework \n Call mom \n ",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "li[data-status='completed']",
+ "message": "Add an item with data-status=\"completed\""
+ },
+ {
+ "type": "element_exists",
+ "value": "li[data-status='active']",
+ "message": "Add an item with data-status=\"active\""
+ },
+ {
+ "type": "element_exists",
+ "value": "li[data-status='pending']",
+ "message": "Add an item with data-status=\"pending\""
+ }
+ ]
+ }
+ ]
+}
diff --git a/lessons/27-html-dialog.json b/lessons/27-html-dialog.json
new file mode 100644
index 0000000..5dbfd9d
--- /dev/null
+++ b/lessons/27-html-dialog.json
@@ -0,0 +1,83 @@
+{
+ "$schema": "../schemas/code-crispies-module-schema.json",
+ "id": "html-dialog",
+ "title": "Native Dialog Element",
+ "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": "\n Welcome! \n This is a native HTML dialog element.
\n \n ",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "dialog",
+ "message": "Add a 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 heading inside the dialog"
+ },
+ {
+ "type": "element_exists",
+ "value": "form[method='dialog']",
+ "message": "Add a