feat: add 10 new HTML5 lesson modules (EN + DE)
New lesson modules covering native HTML5 features: - Details & Summary: disclosure widgets - Progress & Meter: progress bars and gauges - Datalist: autocomplete inputs - Data Attributes: custom data-* attributes - Dialog: native modal dialogs - Forms with Fieldset: grouped form controls - Figure & Figcaption: self-contained content - Tables: structured data with caption, thead, tbody, tfoot - Marquee: classic scrolling text (deprecated but fun) - SVG Basics: drawing circles, rectangles, and lines Each module includes 2-3 progressive lessons with fancy styling (pastel gradients, 100vh layouts, etc).
This commit is contained in:
97
lessons/23-html-details-summary.json
Normal file
97
lessons/23-html-details-summary.json
Normal file
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-details-summary",
|
||||
"title": "Details & Summary: Disclosure Widgets",
|
||||
"description": "Create expandable content sections without JavaScript",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "details-summary-basic",
|
||||
"title": "Your First Disclosure Widget",
|
||||
"description": "The <kbd><details></kbd> element creates a collapsible section. The <kbd><summary></kbd> provides the clickable label.<br><br>Click the summary to toggle the hidden content - no JavaScript needed!",
|
||||
"task": "Create a <kbd><details></kbd> element with:<br>1. A <kbd><summary></kbd> saying 'Click to reveal'<br>2. A <kbd><p></kbd> 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": "",
|
||||
"initialCode": "<!-- Create your disclosure widget here -->",
|
||||
"solution": "<details>\n <summary>Click to reveal</summary>\n <p>This content was hidden!</p>\n</details>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "details",
|
||||
"message": "Add a <details> element"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "summary",
|
||||
"message": "Add a <summary> inside the details"
|
||||
},
|
||||
{
|
||||
"type": "parent_child",
|
||||
"value": { "parent": "details", "child": "summary" },
|
||||
"message": "The <summary> must be inside <details>"
|
||||
},
|
||||
{
|
||||
"type": "parent_child",
|
||||
"value": { "parent": "details", "child": "p" },
|
||||
"message": "Add a <p> inside <details> for the hidden content"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "details-open-attribute",
|
||||
"title": "Pre-expanded Details",
|
||||
"description": "By default, <kbd><details></kbd> is closed. Add the <kbd>open</kbd> attribute to show the content initially.<br><br>This is a boolean attribute - just add <kbd>open</kbd> without a value.",
|
||||
"task": "Add the <kbd>open</kbd> attribute to the <kbd><details></kbd> 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": "<details>\n <summary>FAQ: What is HTML5?</summary>\n <p>HTML5 is the latest version of the HTML standard with new semantic elements and APIs.</p>\n</details>",
|
||||
"solution": "<details open>\n <summary>FAQ: What is HTML5?</summary>\n <p>HTML5 is the latest version of the HTML standard with new semantic elements and APIs.</p>\n</details>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "details", "attr": "open", "value": true },
|
||||
"message": "Add the 'open' attribute to <details>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "faq-accordion",
|
||||
"title": "FAQ Accordion",
|
||||
"description": "Multiple <kbd><details></kbd> elements create an accordion-style FAQ. Each question can be expanded independently.<br><br>This is a common pattern for FAQ pages, documentation, and help sections.",
|
||||
"task": "Create an FAQ section with:<br>1. An <kbd><h1></kbd> saying 'Frequently Asked Questions'<br>2. Three <kbd><details></kbd> elements, each with a question in <kbd><summary></kbd> and an answer in <kbd><p></kbd>",
|
||||
"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": "<!-- Create your FAQ section here -->",
|
||||
"solution": "<h1>Frequently Asked Questions</h1>\n\n<details>\n <summary>What is HTML5?</summary>\n <p>HTML5 is the latest version of HTML with new semantic elements and APIs.</p>\n</details>\n\n<details>\n <summary>Do I need JavaScript?</summary>\n <p>Many interactive features work with pure HTML5, no JavaScript required!</p>\n</details>\n\n<details>\n <summary>Is this accessible?</summary>\n <p>Yes! Native HTML elements have built-in keyboard and screen reader support.</p>\n</details>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "h1",
|
||||
"message": "Add an <h1> heading for the FAQ title"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "details", "min": 3 },
|
||||
"message": "Create at least 3 <details> elements for the FAQ"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "summary", "min": 3 },
|
||||
"message": "Each <details> needs a <summary> for the question"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "details p", "min": 3 },
|
||||
"message": "Each <details> needs a <p> for the answer"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
102
lessons/24-html-progress-meter.json
Normal file
102
lessons/24-html-progress-meter.json
Normal file
@@ -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 <kbd><progress></kbd> element shows task completion. Use <kbd>value</kbd> for current progress and <kbd>max</kbd> for the total.<br><br>The text inside is fallback for older browsers.",
|
||||
"task": "Create a progress bar showing 70% completion:<br>1. Add a <kbd><label></kbd> saying 'Download:'<br>2. Add a <kbd><progress></kbd> with <kbd>value=\"70\"</kbd> and <kbd>max=\"100\"</kbd>",
|
||||
"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": "<!-- Create a progress bar here -->",
|
||||
"solution": "<label for=\"download\">Download:</label>\n<progress id=\"download\" value=\"70\" max=\"100\">70%</progress>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "progress",
|
||||
"message": "Add a <progress> 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 <label> for the progress bar"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "progress-indeterminate",
|
||||
"title": "Indeterminate Progress",
|
||||
"description": "When progress is unknown (like loading), omit the <kbd>value</kbd> attribute. This creates an animated indeterminate state.<br><br>Useful for network requests or processes with unknown duration.",
|
||||
"task": "Create a loading indicator:<br>1. Add a <kbd><p></kbd> saying 'Loading...'<br>2. Add a <kbd><progress></kbd> 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": "<!-- Create an indeterminate loading indicator -->",
|
||||
"solution": "<p>Loading...</p>\n<progress></progress>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "progress",
|
||||
"message": "Add a <progress> element"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "p",
|
||||
"message": "Add a <p> with loading text"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "meter-gauge",
|
||||
"title": "Meter Gauges",
|
||||
"description": "The <kbd><meter></kbd> element displays a scalar value within a range. Use it for measurements like disk space, battery, or ratings.<br><br>Set <kbd>low</kbd>, <kbd>high</kbd>, and <kbd>optimum</kbd> to define good/bad ranges - the browser colors it accordingly!",
|
||||
"task": "Create a battery level meter:<br>1. Add a <kbd><label></kbd> saying 'Battery:'<br>2. Add a <kbd><meter></kbd> with:<br> - <kbd>value=\"0.8\"</kbd><br> - <kbd>min=\"0\"</kbd> and <kbd>max=\"1\"</kbd><br> - <kbd>low=\"0.2\"</kbd> and <kbd>high=\"0.8\"</kbd><br> - <kbd>optimum=\"1\"</kbd>",
|
||||
"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": "<!-- Create a battery meter here -->",
|
||||
"solution": "<label for=\"battery\">Battery:</label>\n<meter id=\"battery\" value=\"0.8\" min=\"0\" max=\"1\" low=\"0.2\" high=\"0.8\" optimum=\"1\">80%</meter>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "meter",
|
||||
"message": "Add a <meter> 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 <label> for the meter"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
78
lessons/25-html-datalist.json
Normal file
78
lessons/25-html-datalist.json
Normal file
@@ -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 <kbd><datalist></kbd> element provides autocomplete suggestions for inputs. Connect it using the <kbd>list</kbd> attribute on the input matching the datalist's <kbd>id</kbd>.<br><br>Users can still type freely - suggestions are just helpers!",
|
||||
"task": "Create a browser selector:<br>1. Add a <kbd><label></kbd> saying 'Browser:'<br>2. Add an <kbd><input></kbd> with <kbd>list=\"browsers\"</kbd><br>3. Add a <kbd><datalist id=\"browsers\"></kbd> 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": "<!-- Create an input with datalist suggestions -->",
|
||||
"solution": "<label for=\"browser\">Browser:</label>\n<input type=\"text\" id=\"browser\" list=\"browsers\">\n<datalist id=\"browsers\">\n <option value=\"Chrome\">\n <option value=\"Firefox\">\n <option value=\"Safari\">\n</datalist>",
|
||||
"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.<br><br>The <kbd>value</kbd> attribute is what gets entered, and you can add display text after it.",
|
||||
"task": "Create a country input:<br>1. Add a <kbd><label></kbd> saying 'Country:'<br>2. Add an <kbd><input></kbd> with <kbd>list=\"countries\"</kbd><br>3. Add a <kbd><datalist id=\"countries\"></kbd> 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": "<!-- Create a country selector with datalist -->",
|
||||
"solution": "<label for=\"country\">Country:</label>\n<input type=\"text\" id=\"country\" list=\"countries\" placeholder=\"Start typing...\">\n<datalist id=\"countries\">\n <option value=\"Germany\">\n <option value=\"France\">\n <option value=\"Spain\">\n <option value=\"Italy\">\n</datalist>",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
68
lessons/26-html-data-attributes.json
Normal file
68
lessons/26-html-data-attributes.json
Normal file
@@ -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 <kbd>data-*</kbd> attributes let you store extra information on any HTML element. The attribute name starts with <kbd>data-</kbd> followed by your custom name.<br><br>Examples: <kbd>data-id</kbd>, <kbd>data-category</kbd>, <kbd>data-price</kbd>",
|
||||
"task": "Create two product cards using <kbd><article></kbd> elements. Each should have:<br>1. A <kbd>data-category</kbd> attribute (e.g., 'electronics' or 'clothing')<br>2. A <kbd>data-price</kbd> attribute with a number<br>3. An <kbd><h2></kbd> 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": "<!-- Create product cards with data attributes -->",
|
||||
"solution": "<article data-category=\"electronics\" data-price=\"299\">\n <h2>Laptop</h2>\n <p>A powerful laptop for work and play.</p>\n</article>\n\n<article data-category=\"clothing\" data-price=\"49\">\n <h2>T-Shirt</h2>\n <p>A comfortable cotton t-shirt.</p>\n</article>",
|
||||
"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 <h2> inside each article"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "data-attributes-css",
|
||||
"title": "Styling with Data Attributes",
|
||||
"description": "CSS can select elements by their data attributes using <kbd>[data-*]</kbd> selectors. You can even match specific values!<br><br>The preview CSS uses <kbd>[data-status='active']</kbd> to style active items differently.",
|
||||
"task": "Create a task list with 3 <kbd><li></kbd> items. Give each a <kbd>data-status</kbd> attribute:<br>1. One with <kbd>data-status=\"completed\"</kbd><br>2. One with <kbd>data-status=\"active\"</kbd><br>3. One with <kbd>data-status=\"pending\"</kbd>",
|
||||
"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": "<ul>\n <!-- Add list items with data-status attributes -->\n</ul>",
|
||||
"solution": "<ul>\n <li data-status=\"completed\">Buy groceries</li>\n <li data-status=\"active\">Finish homework</li>\n <li data-status=\"pending\">Call mom</li>\n</ul>",
|
||||
"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\""
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
83
lessons/27-html-dialog.json
Normal file
83
lessons/27-html-dialog.json
Normal file
@@ -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 <kbd><dialog></kbd> element creates a native modal. Add the <kbd>open</kbd> attribute to show it.<br><br>Use <kbd><form method=\"dialog\"></kbd> inside to close it when the form submits - no JavaScript needed!",
|
||||
"task": "Create a dialog with:<br>1. The <kbd>open</kbd> attribute to show it<br>2. An <kbd><h2></kbd> saying 'Welcome!'<br>3. A <kbd><p></kbd> with a greeting message<br>4. A <kbd><form method=\"dialog\"></kbd> 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": "<!-- Create a dialog with the open attribute -->",
|
||||
"solution": "<dialog open>\n <h2>Welcome!</h2>\n <p>This is a native HTML dialog element.</p>\n <form method=\"dialog\">\n <button>Close</button>\n </form>\n</dialog>",
|
||||
"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 with Form",
|
||||
"description": "Dialogs can contain full forms. The <kbd>method=\"dialog\"</kbd> makes the form close the dialog on submit instead of sending data.<br><br>This pattern is perfect for confirmation dialogs, quick inputs, or settings panels.",
|
||||
"task": "Create a confirmation dialog:<br>1. Add <kbd>open</kbd> to show it<br>2. An <kbd><h2></kbd> saying 'Confirm Delete'<br>3. A <kbd><p></kbd> asking 'Are you sure?'<br>4. A <kbd><form method=\"dialog\"></kbd> 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": "<!-- Create a confirmation dialog -->",
|
||||
"solution": "<dialog open>\n <h2>Confirm Delete</h2>\n <p>Are you sure you want to delete this item?</p>\n <form method=\"dialog\">\n <button value=\"cancel\">Cancel</button>\n <button value=\"delete\">Delete</button>\n </form>\n</dialog>",
|
||||
"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)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
127
lessons/28-html-forms-fieldset.json
Normal file
127
lessons/28-html-forms-fieldset.json
Normal file
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-forms-fieldset",
|
||||
"title": "Forms with Fieldsets",
|
||||
"description": "Group form controls with fieldset and legend elements",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "fieldset-basic",
|
||||
"title": "Grouping with Fieldset",
|
||||
"description": "The <kbd><fieldset></kbd> element groups related form controls together. Add a <kbd><legend></kbd> as the first child to give the group a title.<br><br>This helps with accessibility and visual organization of complex forms.",
|
||||
"task": "Create a form with a fieldset:<br>1. A <kbd><form></kbd> element<br>2. A <kbd><fieldset></kbd> inside<br>3. A <kbd><legend></kbd> saying 'Personal Info'<br>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": "<!-- Create a form with a fieldset group -->",
|
||||
"solution": "<form>\n <fieldset>\n <legend>Personal Info</legend>\n <label for=\"name\">Name:</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n </fieldset>\n</form>",
|
||||
"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 <kbd><textarea></kbd> element creates a multi-line text input, perfect for longer content like messages or descriptions.<br><br>Use <kbd>rows</kbd> and <kbd>cols</kbd> attributes to set default size.",
|
||||
"task": "Create a contact form:<br>1. A <kbd><fieldset></kbd> with <kbd><legend></kbd> 'Contact Us'<br>2. A labeled <kbd><input></kbd> for email<br>3. A labeled <kbd><textarea></kbd> for the message<br>4. A submit <kbd><button></kbd>",
|
||||
"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": "<!-- Create a contact form with textarea -->",
|
||||
"solution": "<form>\n <fieldset>\n <legend>Contact Us</legend>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n <label for=\"message\">Message:</label>\n <textarea id=\"message\" name=\"message\" rows=\"4\"></textarea>\n <button type=\"submit\">Send Message</button>\n </fieldset>\n</form>",
|
||||
"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 <kbd><fieldset></kbd> elements to organize different sections.<br><br>This improves usability for long forms like registration or checkout pages.",
|
||||
"task": "Create a registration form with 2 fieldsets:<br>1. 'Account Info' with username and password inputs<br>2. 'Preferences' with a textarea for bio<br>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": "<!-- Create a form with multiple fieldsets -->",
|
||||
"solution": "<form>\n <fieldset>\n <legend>Account Info</legend>\n <label for=\"username\">Username:</label>\n <input type=\"text\" id=\"username\" name=\"username\">\n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\">\n </fieldset>\n <fieldset>\n <legend>Preferences</legend>\n <label for=\"bio\">Bio:</label>\n <textarea id=\"bio\" name=\"bio\"></textarea>\n </fieldset>\n <button type=\"submit\">Register</button>\n</form>",
|
||||
"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"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
102
lessons/29-html-figure.json
Normal file
102
lessons/29-html-figure.json
Normal file
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-figure",
|
||||
"title": "Figure & Figcaption",
|
||||
"description": "Create self-contained content with captions",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "figure-basic",
|
||||
"title": "Image with Caption",
|
||||
"description": "The <kbd><figure></kbd> element wraps self-contained content like images, diagrams, or code. Add <kbd><figcaption></kbd> to provide a caption.<br><br>This semantic structure helps screen readers and search engines understand your content.",
|
||||
"task": "Create a figure with:<br>1. A <kbd><figure></kbd> element<br>2. An <kbd><img></kbd> inside (use placeholder URL)<br>3. A <kbd><figcaption></kbd> describing the image",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; } figure { margin: 0; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); max-width: 400px; } figure img { width: 100%; height: 200px; object-fit: cover; display: block; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } figcaption { padding: 15px 20px; color: #555; font-size: 0.95rem; border-top: 1px solid #eee; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<!-- Create a figure with an image and caption -->",
|
||||
"solution": "<figure>\n <img src=\"https://picsum.photos/400/200\" alt=\"A beautiful landscape\">\n <figcaption>A beautiful mountain landscape at sunset.</figcaption>\n</figure>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "figure",
|
||||
"message": "Add a <figure> element"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "figure img",
|
||||
"message": "Add an <img> inside the figure"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "figcaption",
|
||||
"message": "Add a <figcaption> for the caption"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "figure-code",
|
||||
"title": "Code Figure",
|
||||
"description": "Figures aren't just for images! You can use them for code snippets, quotes, or any self-contained content.<br><br>Combine <kbd><figure></kbd> with <kbd><pre></kbd> and <kbd><code></kbd> for code examples.",
|
||||
"task": "Create a code figure:<br>1. A <kbd><figure></kbd> element<br>2. A <kbd><pre></kbd> containing <kbd><code></kbd> with some code<br>3. A <kbd><figcaption></kbd> describing the code",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #1e1e1e; } figure { margin: 0; background: #2d2d2d; border-radius: 10px; overflow: hidden; max-width: 500px; } pre { margin: 0; padding: 20px; overflow-x: auto; } code { color: #9cdcfe; font-family: 'Fira Code', 'Consolas', monospace; font-size: 14px; line-height: 1.5; } figcaption { padding: 12px 20px; color: #888; font-size: 0.85rem; background: #252525; border-top: 1px solid #3d3d3d; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<!-- Create a figure with a code snippet -->",
|
||||
"solution": "<figure>\n <pre><code>function greet(name) {\n return `Hello, ${name}!`;\n}</code></pre>\n <figcaption>A simple greeting function in JavaScript</figcaption>\n</figure>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "figure",
|
||||
"message": "Add a <figure> element"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "pre",
|
||||
"message": "Add a <pre> element for preformatted text"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "code",
|
||||
"message": "Add a <code> element for the code"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "figcaption",
|
||||
"message": "Add a <figcaption> describing the code"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "figure-gallery",
|
||||
"title": "Image Gallery",
|
||||
"description": "You can put multiple images inside a single <kbd><figure></kbd> to create a gallery or comparison.<br><br>The figcaption describes the entire group.",
|
||||
"task": "Create a gallery figure:<br>1. A <kbd><figure></kbd> element<br>2. At least 2 <kbd><img></kbd> elements<br>3. A <kbd><figcaption></kbd> describing the gallery",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } figure { margin: 0; background: white; border-radius: 15px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.15); max-width: 500px; padding: 15px; display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; } figure img { width: 100%; height: 120px; object-fit: cover; border-radius: 8px; background: linear-gradient(45deg, #ff6b6b, #feca57); } figcaption { grid-column: 1 / -1; padding: 10px 5px 5px; color: #666; font-size: 0.9rem; text-align: center; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<!-- Create a gallery with multiple images -->",
|
||||
"solution": "<figure>\n <img src=\"https://picsum.photos/200/120?1\" alt=\"Photo 1\">\n <img src=\"https://picsum.photos/200/120?2\" alt=\"Photo 2\">\n <img src=\"https://picsum.photos/200/120?3\" alt=\"Photo 3\">\n <img src=\"https://picsum.photos/200/120?4\" alt=\"Photo 4\">\n <figcaption>My vacation photo gallery</figcaption>\n</figure>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "figure",
|
||||
"message": "Add a <figure> element"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "figure img", "min": 2 },
|
||||
"message": "Add at least 2 images inside the figure"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "figcaption",
|
||||
"message": "Add a <figcaption> for the gallery"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
127
lessons/30-html-tables.json
Normal file
127
lessons/30-html-tables.json
Normal file
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-tables",
|
||||
"title": "HTML Tables",
|
||||
"description": "Create structured data tables with headers and captions",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "table-basic",
|
||||
"title": "Basic Table Structure",
|
||||
"description": "Tables use <kbd><table></kbd> with <kbd><tr></kbd> for rows. Inside rows, use <kbd><th></kbd> for headers and <kbd><td></kbd> for data cells.<br><br>The <kbd><caption></kbd> element provides an accessible title for the table.",
|
||||
"task": "Create a simple table with:<br>1. A <kbd><caption></kbd> saying 'Fruit Prices'<br>2. A header row with 'Fruit' and 'Price' columns<br>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": "",
|
||||
"initialCode": "<!-- Create a table with caption and headers -->",
|
||||
"solution": "<table>\n <caption>Fruit Prices</caption>\n <tr>\n <th>Fruit</th>\n <th>Price</th>\n </tr>\n <tr>\n <td>Apple</td>\n <td>$1.50</td>\n </tr>\n <tr>\n <td>Banana</td>\n <td>$0.75</td>\n </tr>\n</table>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "table",
|
||||
"message": "Add a <table> element"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "caption",
|
||||
"message": "Add a <caption> for the table title"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "th", "min": 2 },
|
||||
"message": "Add at least 2 header cells (th)"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "tr", "min": 3 },
|
||||
"message": "Add at least 3 rows (1 header + 2 data rows)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "table-thead-tbody",
|
||||
"title": "Table Head & Body",
|
||||
"description": "Use <kbd><thead></kbd> to group header rows and <kbd><tbody></kbd> to group data rows. This helps browsers and assistive technology understand the table structure.<br><br>You can also use <kbd><tfoot></kbd> for footer rows like totals.",
|
||||
"task": "Create a structured table:<br>1. A <kbd><caption></kbd> with 'Monthly Sales'<br>2. A <kbd><thead></kbd> with Month and Revenue headers<br>3. A <kbd><tbody></kbd> with at least 2 data rows",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } table { border-collapse: collapse; width: 100%; max-width: 450px; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.2); } caption { padding: 20px; font-weight: 700; font-size: 1.3rem; color: white; background: transparent; text-shadow: 0 2px 4px rgba(0,0,0,0.2); caption-side: top; } thead { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } th { padding: 15px 20px; text-align: left; color: white; font-weight: 500; } tbody tr { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } td { padding: 15px 20px; color: #333; } tbody tr:last-child { border-bottom: none; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<!-- Create a table with thead and tbody -->",
|
||||
"solution": "<table>\n <caption>Monthly Sales</caption>\n <thead>\n <tr>\n <th>Month</th>\n <th>Revenue</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>January</td>\n <td>$12,500</td>\n </tr>\n <tr>\n <td>February</td>\n <td>$14,200</td>\n </tr>\n </tbody>\n</table>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "table",
|
||||
"message": "Add a <table> element"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "caption",
|
||||
"message": "Add a <caption> element"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "thead",
|
||||
"message": "Add a <thead> for the header section"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "tbody",
|
||||
"message": "Add a <tbody> for the data rows"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "tbody tr", "min": 2 },
|
||||
"message": "Add at least 2 data rows in tbody"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "table-complete",
|
||||
"title": "Complete Table with Footer",
|
||||
"description": "Add <kbd><tfoot></kbd> to create a footer section for totals or summary data. The footer stays at the bottom even if tbody has many rows.<br><br>Combine all sections for a fully structured, accessible table.",
|
||||
"task": "Create a complete table:<br>1. A <kbd><caption></kbd> with 'Order Summary'<br>2. A <kbd><thead></kbd> with Item and Price headers<br>3. A <kbd><tbody></kbd> with 2 items<br>4. A <kbd><tfoot></kbd> with a Total row",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px 20px; font-weight: 600; font-size: 1.1rem; color: #333; text-align: left; background: #f8f9fa; border-bottom: 2px solid #eee; } th, td { padding: 12px 20px; text-align: left; } thead th { background: #2c3e50; color: white; } tbody td { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } tfoot { background: #ecf0f1; font-weight: 600; } tfoot td { border-top: 2px solid #2c3e50; color: #2c3e50; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<!-- Create a complete table with tfoot -->",
|
||||
"solution": "<table>\n <caption>Order Summary</caption>\n <thead>\n <tr>\n <th>Item</th>\n <th>Price</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Widget</td>\n <td>$25.00</td>\n </tr>\n <tr>\n <td>Gadget</td>\n <td>$35.00</td>\n </tr>\n </tbody>\n <tfoot>\n <tr>\n <td>Total</td>\n <td>$60.00</td>\n </tr>\n </tfoot>\n</table>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "table",
|
||||
"message": "Add a <table> element"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "caption",
|
||||
"message": "Add a <caption> element"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "thead",
|
||||
"message": "Add a <thead> section"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "tbody",
|
||||
"message": "Add a <tbody> section"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "tfoot",
|
||||
"message": "Add a <tfoot> section for the total"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "tbody tr", "min": 2 },
|
||||
"message": "Add at least 2 item rows in tbody"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
82
lessons/31-html-marquee.json
Normal file
82
lessons/31-html-marquee.json
Normal file
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-marquee",
|
||||
"title": "The Marquee Element",
|
||||
"description": "Create scrolling text with the classic (deprecated but fun!) marquee element",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "marquee-basic",
|
||||
"title": "Scrolling Text",
|
||||
"description": "The <kbd><marquee></kbd> element creates scrolling text - a classic from the early web! While deprecated, it still works in most browsers.<br><br>Note: For production, use CSS animations instead. But for learning and fun, marquee is great!",
|
||||
"task": "Create a simple marquee:<br>1. Add a <kbd><marquee></kbd> element<br>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": "",
|
||||
"initialCode": "<!-- Create a scrolling marquee -->",
|
||||
"solution": "<marquee>Welcome to my website!</marquee>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "marquee",
|
||||
"message": "Add a <marquee> element"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "marquee-direction",
|
||||
"title": "Direction & Behavior",
|
||||
"description": "Control the marquee with attributes:<br>• <kbd>direction</kbd>: left, right, up, down<br>• <kbd>behavior</kbd>: scroll (default), slide (stops at edge), alternate (bounces)<br>• <kbd>scrollamount</kbd>: speed (default is 6)",
|
||||
"task": "Create a bouncing marquee:<br>1. Add a <kbd><marquee></kbd> element<br>2. Set <kbd>behavior=\"alternate\"</kbd> to make it bounce<br>3. Add some fun text",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2.5rem; color: white; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); font-weight: bold; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<!-- Create a bouncing marquee -->",
|
||||
"solution": "<marquee behavior=\"alternate\">Bounce! Bounce! Bounce!</marquee>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "marquee",
|
||||
"message": "Add a <marquee> element"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "marquee", "attr": "behavior", "value": "alternate" },
|
||||
"message": "Add behavior=\"alternate\" to make it bounce"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "marquee-retro",
|
||||
"title": "Retro News Ticker",
|
||||
"description": "Combine multiple marquee attributes for a classic news ticker effect. You can even put multiple elements inside!<br><br>Remember: This is deprecated HTML. Modern sites use CSS animations, but marquee is great for understanding web history.",
|
||||
"task": "Create a news ticker:<br>1. A <kbd><marquee></kbd> with <kbd>direction=\"left\"</kbd><br>2. Set <kbd>scrollamount=\"5\"</kbd> for smooth scrolling<br>3. Add a breaking news headline inside",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 0; margin: 0; background: #1a1a2e; } marquee { background: linear-gradient(90deg, #c0392b 0%, #e74c3c 50%, #c0392b 100%); padding: 15px 0; font-size: 1.3rem; color: white; font-weight: 500; text-transform: uppercase; letter-spacing: 1px; border-top: 3px solid #f1c40f; border-bottom: 3px solid #f1c40f; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<!-- Create a retro news ticker -->",
|
||||
"solution": "<marquee direction=\"left\" scrollamount=\"5\">BREAKING NEWS: Marquee element still works in browsers!</marquee>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "marquee",
|
||||
"message": "Add a <marquee> element"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "marquee", "attr": "direction", "value": "left" },
|
||||
"message": "Add direction=\"left\" for horizontal scrolling"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "marquee", "attr": "scrollamount", "value": "5" },
|
||||
"message": "Add scrollamount=\"5\" for smooth speed"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
102
lessons/32-html-svg.json
Normal file
102
lessons/32-html-svg.json
Normal file
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-svg",
|
||||
"title": "SVG Basics",
|
||||
"description": "Draw scalable vector graphics directly in HTML",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "svg-circle",
|
||||
"title": "Drawing Circles",
|
||||
"description": "SVG (Scalable Vector Graphics) lets you draw shapes directly in HTML. The <kbd><svg></kbd> element is the container, with <kbd>width</kbd> and <kbd>height</kbd> attributes.<br><br>Use <kbd><circle></kbd> with <kbd>cx</kbd>, <kbd>cy</kbd> (center) and <kbd>r</kbd> (radius) to draw circles.",
|
||||
"task": "Create an SVG with a circle:<br>1. An <kbd><svg></kbd> with width=\"200\" and height=\"200\"<br>2. A <kbd><circle></kbd> centered at (100,100) with radius 50<br>3. Add a <kbd>fill</kbd> color",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; display: flex; justify-content: center; } svg { background: white; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<!-- Draw a circle with SVG -->",
|
||||
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"50\" fill=\"#3498db\" />\n</svg>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "svg",
|
||||
"message": "Add an <svg> element"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "circle",
|
||||
"message": "Add a <circle> element inside the SVG"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "circle", "attr": "cx", "value": "100" },
|
||||
"message": "Set cx=\"100\" for the circle's horizontal center"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "circle", "attr": "cy", "value": "100" },
|
||||
"message": "Set cy=\"100\" for the circle's vertical center"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "svg-rect-line",
|
||||
"title": "Rectangles & Lines",
|
||||
"description": "Draw rectangles with <kbd><rect></kbd> using <kbd>x</kbd>, <kbd>y</kbd>, <kbd>width</kbd>, <kbd>height</kbd>.<br><br>Draw lines with <kbd><line></kbd> using <kbd>x1</kbd>, <kbd>y1</kbd> (start) and <kbd>x2</kbd>, <kbd>y2</kbd> (end). Lines need a <kbd>stroke</kbd> color!",
|
||||
"task": "Create an SVG with:<br>1. An <kbd><svg></kbd> (200x150)<br>2. A <kbd><rect></kbd> at position (20,20) with size 80x60<br>3. A <kbd><line></kbd> from (120,30) to (180,90) with a stroke color",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 200px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<!-- Draw a rectangle and a line -->",
|
||||
"solution": "<svg width=\"200\" height=\"150\">\n <rect x=\"20\" y=\"20\" width=\"80\" height=\"60\" fill=\"#e74c3c\" />\n <line x1=\"120\" y1=\"30\" x2=\"180\" y2=\"90\" stroke=\"#2c3e50\" stroke-width=\"3\" />\n</svg>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "svg",
|
||||
"message": "Add an <svg> element"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "rect",
|
||||
"message": "Add a <rect> element"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "line",
|
||||
"message": "Add a <line> element"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "svg-shapes",
|
||||
"title": "Multiple Shapes",
|
||||
"description": "Combine shapes to create simple graphics! Add <kbd>stroke</kbd> for outlines and <kbd>stroke-width</kbd> for thickness.<br><br>Use <kbd>fill=\"none\"</kbd> for hollow shapes. Shapes stack in order - later elements appear on top.",
|
||||
"task": "Create a simple face:<br>1. An <kbd><svg></kbd> (200x200)<br>2. A large <kbd><circle></kbd> for the face<br>3. Two small <kbd><circle></kbd> elements for eyes<br>4. A <kbd><line></kbd> for the smile",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); min-height: 250px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<!-- Draw a simple smiley face -->",
|
||||
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"80\" fill=\"#f1c40f\" stroke=\"#e67e22\" stroke-width=\"4\" />\n <circle cx=\"70\" cy=\"80\" r=\"10\" fill=\"#2c3e50\" />\n <circle cx=\"130\" cy=\"80\" r=\"10\" fill=\"#2c3e50\" />\n <line x1=\"60\" y1=\"130\" x2=\"140\" y2=\"130\" stroke=\"#2c3e50\" stroke-width=\"4\" stroke-linecap=\"round\" />\n</svg>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "svg",
|
||||
"message": "Add an <svg> element"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "circle", "min": 3 },
|
||||
"message": "Add at least 3 circles (1 face + 2 eyes)"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "line",
|
||||
"message": "Add a <line> for the smile"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
97
lessons/de/23-html-details-summary.json
Normal file
97
lessons/de/23-html-details-summary.json
Normal file
@@ -0,0 +1,97 @@
|
||||
{
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-details-summary",
|
||||
"title": "Details & Summary: Aufklapp-Elemente",
|
||||
"description": "Erstelle aufklappbare Inhaltsbereiche ohne JavaScript",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "details-summary-basic",
|
||||
"title": "Dein erstes Aufklapp-Element",
|
||||
"description": "Das <kbd><details></kbd>-Element erstellt einen aufklappbaren Bereich. Das <kbd><summary></kbd> bietet die anklickbare Beschriftung.<br><br>Klicke auf die Zusammenfassung, um den versteckten Inhalt anzuzeigen - kein JavaScript nötig!",
|
||||
"task": "Erstelle ein <kbd><details></kbd>-Element mit:<br>1. Einem <kbd><summary></kbd> mit dem Text 'Klicken zum Aufklappen'<br>2. Einem <kbd><p></kbd> 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": "",
|
||||
"initialCode": "<!-- Erstelle hier dein Aufklapp-Element -->",
|
||||
"solution": "<details>\n <summary>Klicken zum Aufklappen</summary>\n <p>Dieser Inhalt war versteckt!</p>\n</details>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "details",
|
||||
"message": "Füge ein <details>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "summary",
|
||||
"message": "Füge ein <summary> innerhalb von details hinzu"
|
||||
},
|
||||
{
|
||||
"type": "parent_child",
|
||||
"value": { "parent": "details", "child": "summary" },
|
||||
"message": "Das <summary> muss innerhalb von <details> sein"
|
||||
},
|
||||
{
|
||||
"type": "parent_child",
|
||||
"value": { "parent": "details", "child": "p" },
|
||||
"message": "Füge ein <p> innerhalb von <details> für den versteckten Inhalt hinzu"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "details-open-attribute",
|
||||
"title": "Standardmäßig geöffnet",
|
||||
"description": "Standardmäßig ist <kbd><details></kbd> geschlossen. Füge das <kbd>open</kbd>-Attribut hinzu, um den Inhalt initial anzuzeigen.<br><br>Dies ist ein boolesches Attribut - füge einfach <kbd>open</kbd> ohne Wert hinzu.",
|
||||
"task": "Füge das <kbd>open</kbd>-Attribut zum <kbd><details></kbd>-Element hinzu, um den Inhalt standardmäßig anzuzeigen.",
|
||||
"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": "<details>\n <summary>FAQ: Was ist HTML5?</summary>\n <p>HTML5 ist die neueste Version des HTML-Standards mit neuen semantischen Elementen und APIs.</p>\n</details>",
|
||||
"solution": "<details open>\n <summary>FAQ: Was ist HTML5?</summary>\n <p>HTML5 ist die neueste Version des HTML-Standards mit neuen semantischen Elementen und APIs.</p>\n</details>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "details", "attr": "open", "value": true },
|
||||
"message": "Füge das 'open'-Attribut zu <details> hinzu"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "faq-accordion",
|
||||
"title": "FAQ-Akkordeon",
|
||||
"description": "Mehrere <kbd><details></kbd>-Elemente erstellen ein Akkordeon-artiges FAQ. Jede Frage kann unabhängig aufgeklappt werden.<br><br>Dies ist ein häufiges Muster für FAQ-Seiten, Dokumentation und Hilfebereiche.",
|
||||
"task": "Erstelle einen FAQ-Bereich mit:<br>1. Einer <kbd><h1></kbd> mit dem Text 'Häufig gestellte Fragen'<br>2. Drei <kbd><details></kbd>-Elementen, jeweils mit einer Frage im <kbd><summary></kbd> und einer Antwort im <kbd><p></kbd>",
|
||||
"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": "<!-- Erstelle hier deinen FAQ-Bereich -->",
|
||||
"solution": "<h1>Häufig gestellte Fragen</h1>\n\n<details>\n <summary>Was ist HTML5?</summary>\n <p>HTML5 ist die neueste Version von HTML mit neuen semantischen Elementen und APIs.</p>\n</details>\n\n<details>\n <summary>Brauche ich JavaScript?</summary>\n <p>Viele interaktive Funktionen funktionieren mit purem HTML5, kein JavaScript nötig!</p>\n</details>\n\n<details>\n <summary>Ist das barrierefrei?</summary>\n <p>Ja! Native HTML-Elemente haben eingebaute Tastatur- und Screenreader-Unterstützung.</p>\n</details>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "h1",
|
||||
"message": "Füge eine <h1>-Überschrift für den FAQ-Titel hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "details", "min": 3 },
|
||||
"message": "Erstelle mindestens 3 <details>-Elemente für das FAQ"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "summary", "min": 3 },
|
||||
"message": "Jedes <details> braucht ein <summary> für die Frage"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "details p", "min": 3 },
|
||||
"message": "Jedes <details> braucht ein <p> für die Antwort"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
102
lessons/de/24-html-progress-meter.json
Normal file
102
lessons/de/24-html-progress-meter.json
Normal file
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-progress-meter",
|
||||
"title": "Progress & Meter Elemente",
|
||||
"description": "Zeige Fortschritt und Messwerte nativ an",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "progress-basic",
|
||||
"title": "Fortschrittsbalken",
|
||||
"description": "Das <kbd><progress></kbd>-Element zeigt den Aufgabenfortschritt. Verwende <kbd>value</kbd> für den aktuellen Stand und <kbd>max</kbd> für das Maximum.<br><br>Der Text darin ist ein Fallback für ältere Browser.",
|
||||
"task": "Erstelle einen Fortschrittsbalken mit 70% Fortschritt:<br>1. Füge ein <kbd><label></kbd> mit 'Download:' hinzu<br>2. Füge ein <kbd><progress></kbd> mit <kbd>value=\"70\"</kbd> und <kbd>max=\"100\"</kbd> 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": "",
|
||||
"initialCode": "<!-- Erstelle hier einen Fortschrittsbalken -->",
|
||||
"solution": "<label for=\"download\">Download:</label>\n<progress id=\"download\" value=\"70\" max=\"100\">70%</progress>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "progress",
|
||||
"message": "Füge ein <progress>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "progress", "attr": "value", "value": "70" },
|
||||
"message": "Setze value=\"70\" beim Progress-Element"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "progress", "attr": "max", "value": "100" },
|
||||
"message": "Setze max=\"100\" beim Progress-Element"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "label",
|
||||
"message": "Füge ein <label> für den Fortschrittsbalken hinzu"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "progress-indeterminate",
|
||||
"title": "Unbestimmter Fortschritt",
|
||||
"description": "Wenn der Fortschritt unbekannt ist (wie beim Laden), lasse das <kbd>value</kbd>-Attribut weg. Dies erstellt einen animierten unbestimmten Zustand.<br><br>Nützlich für Netzwerkanfragen oder Prozesse mit unbekannter Dauer.",
|
||||
"task": "Erstelle eine Ladeanzeige:<br>1. Füge ein <kbd><p></kbd> mit 'Lädt...' hinzu<br>2. Füge ein <kbd><progress></kbd> 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": "",
|
||||
"initialCode": "<!-- Erstelle eine unbestimmte Ladeanzeige -->",
|
||||
"solution": "<p>Lädt...</p>\n<progress></progress>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "progress",
|
||||
"message": "Füge ein <progress>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "p",
|
||||
"message": "Füge ein <p> mit Ladetext hinzu"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "meter-gauge",
|
||||
"title": "Meter-Anzeigen",
|
||||
"description": "Das <kbd><meter></kbd>-Element zeigt einen Skalarwert innerhalb eines Bereichs. Verwende es für Messungen wie Speicherplatz, Akku oder Bewertungen.<br><br>Setze <kbd>low</kbd>, <kbd>high</kbd> und <kbd>optimum</kbd>, um gute/schlechte Bereiche zu definieren - der Browser färbt es entsprechend ein!",
|
||||
"task": "Erstelle eine Akku-Anzeige:<br>1. Füge ein <kbd><label></kbd> mit 'Akku:' hinzu<br>2. Füge ein <kbd><meter></kbd> hinzu mit:<br> - <kbd>value=\"0.8\"</kbd><br> - <kbd>min=\"0\"</kbd> und <kbd>max=\"1\"</kbd><br> - <kbd>low=\"0.2\"</kbd> und <kbd>high=\"0.8\"</kbd><br> - <kbd>optimum=\"1\"</kbd>",
|
||||
"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": "<!-- Erstelle hier eine Akku-Anzeige -->",
|
||||
"solution": "<label for=\"battery\">Akku:</label>\n<meter id=\"battery\" value=\"0.8\" min=\"0\" max=\"1\" low=\"0.2\" high=\"0.8\" optimum=\"1\">80%</meter>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "meter",
|
||||
"message": "Füge ein <meter>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "meter", "attr": "value", "value": "0.8" },
|
||||
"message": "Setze value=\"0.8\" beim Meter"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "meter", "attr": "low", "value": "0.2" },
|
||||
"message": "Setze low=\"0.2\", um den niedrigen Schwellenwert zu definieren"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "label",
|
||||
"message": "Füge ein <label> für das Meter hinzu"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
78
lessons/de/25-html-datalist.json
Normal file
78
lessons/de/25-html-datalist.json
Normal file
@@ -0,0 +1,78 @@
|
||||
{
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-datalist",
|
||||
"title": "Datalist: Autovervollständigung",
|
||||
"description": "Biete Vorschläge für Texteingaben ohne JavaScript",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "datalist-basic",
|
||||
"title": "Eingabe mit Vorschlägen",
|
||||
"description": "Das <kbd><datalist></kbd>-Element bietet Autovervollständigungs-Vorschläge für Eingabefelder. Verbinde es mit dem <kbd>list</kbd>-Attribut am Input, das zur <kbd>id</kbd> der Datalist passt.<br><br>Benutzer können trotzdem frei tippen - Vorschläge sind nur Hilfen!",
|
||||
"task": "Erstelle eine Browser-Auswahl:<br>1. Füge ein <kbd><label></kbd> mit 'Browser:' hinzu<br>2. Füge ein <kbd><input></kbd> mit <kbd>list=\"browsers\"</kbd> hinzu<br>3. Füge eine <kbd><datalist id=\"browsers\"></kbd> 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": "",
|
||||
"initialCode": "<!-- Erstelle eine Eingabe mit Datalist-Vorschlägen -->",
|
||||
"solution": "<label for=\"browser\">Browser:</label>\n<input type=\"text\" id=\"browser\" list=\"browsers\">\n<datalist id=\"browsers\">\n <option value=\"Chrome\">\n <option value=\"Firefox\">\n <option value=\"Safari\">\n</datalist>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "datalist",
|
||||
"message": "Füge ein <datalist>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "input", "attr": "list", "value": "browsers" },
|
||||
"message": "Verbinde das Input mit der Datalist über list=\"browsers\""
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "option", "min": 3 },
|
||||
"message": "Füge mindestens 3 <option>-Elemente in die Datalist ein"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "label",
|
||||
"message": "Füge ein <label> für die Eingabe hinzu"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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.<br><br>Das <kbd>value</kbd>-Attribut ist das, was eingegeben wird, und du kannst Anzeigetext dahinter hinzufügen.",
|
||||
"task": "Erstelle eine Länder-Eingabe:<br>1. Füge ein <kbd><label></kbd> mit 'Land:' hinzu<br>2. Füge ein <kbd><input></kbd> mit <kbd>list=\"countries\"</kbd> hinzu<br>3. Füge eine <kbd><datalist id=\"countries\"></kbd> 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": "",
|
||||
"initialCode": "<!-- Erstelle eine Länderauswahl mit Datalist -->",
|
||||
"solution": "<label for=\"country\">Land:</label>\n<input type=\"text\" id=\"country\" list=\"countries\" placeholder=\"Tippen zum Suchen...\">\n<datalist id=\"countries\">\n <option value=\"Deutschland\">\n <option value=\"Frankreich\">\n <option value=\"Spanien\">\n <option value=\"Italien\">\n</datalist>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "datalist",
|
||||
"message": "Füge ein <datalist>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "datalist", "attr": "id", "value": "countries" },
|
||||
"message": "Setze id=\"countries\" bei der Datalist"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "input", "attr": "list", "value": "countries" },
|
||||
"message": "Verbinde das Input über list=\"countries\""
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "option", "min": 4 },
|
||||
"message": "Füge mindestens 4 Länder-Optionen hinzu"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
68
lessons/de/26-html-data-attributes.json
Normal file
68
lessons/de/26-html-data-attributes.json
Normal file
@@ -0,0 +1,68 @@
|
||||
{
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-data-attributes",
|
||||
"title": "Benutzerdefinierte Data-Attribute",
|
||||
"description": "Speichere eigene Daten auf HTML-Elementen mit data-* Attributen",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "data-attributes-intro",
|
||||
"title": "Data-Attribute hinzufügen",
|
||||
"description": "Benutzerdefinierte <kbd>data-*</kbd>-Attribute ermöglichen es, zusätzliche Informationen auf jedem HTML-Element zu speichern. Der Attributname beginnt mit <kbd>data-</kbd> gefolgt von deinem eigenen Namen.<br><br>Beispiele: <kbd>data-id</kbd>, <kbd>data-category</kbd>, <kbd>data-price</kbd>",
|
||||
"task": "Erstelle zwei Produkt-Karten mit <kbd><article></kbd>-Elementen. Jede sollte haben:<br>1. Ein <kbd>data-category</kbd>-Attribut (z.B. 'electronics' oder 'clothing')<br>2. Ein <kbd>data-price</kbd>-Attribut mit einer Zahl<br>3. Ein <kbd><h2></kbd> mit dem Produktnamen",
|
||||
"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": "<!-- Erstelle Produkt-Karten mit Data-Attributen -->",
|
||||
"solution": "<article data-category=\"electronics\" data-price=\"299\">\n <h2>Laptop</h2>\n <p>Ein leistungsstarker Laptop für Arbeit und Freizeit.</p>\n</article>\n\n<article data-category=\"clothing\" data-price=\"49\">\n <h2>T-Shirt</h2>\n <p>Ein bequemes Baumwoll-T-Shirt.</p>\n</article>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "article[data-category]", "min": 2 },
|
||||
"message": "Erstelle mindestens 2 Articles mit data-category-Attributen"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "article[data-price]", "min": 2 },
|
||||
"message": "Füge data-price-Attribute zu deinen Articles hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "article h2", "min": 2 },
|
||||
"message": "Füge ein <h2> in jeden Article ein"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "data-attributes-css",
|
||||
"title": "Styling mit Data-Attributen",
|
||||
"description": "CSS kann Elemente über ihre Data-Attribute mit <kbd>[data-*]</kbd>-Selektoren auswählen. Du kannst sogar bestimmte Werte abgleichen!<br><br>Das Vorschau-CSS verwendet <kbd>[data-status='active']</kbd>, um aktive Elemente anders zu stylen.",
|
||||
"task": "Erstelle eine Aufgabenliste mit 3 <kbd><li></kbd>-Elementen. Gib jedem ein <kbd>data-status</kbd>-Attribut:<br>1. Eines mit <kbd>data-status=\"completed\"</kbd><br>2. Eines mit <kbd>data-status=\"active\"</kbd><br>3. Eines mit <kbd>data-status=\"pending\"</kbd>",
|
||||
"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": "<ul>\n <!-- Füge Listenelemente mit data-status-Attributen hinzu -->\n</ul>",
|
||||
"solution": "<ul>\n <li data-status=\"completed\">Einkaufen gehen</li>\n <li data-status=\"active\">Hausaufgaben fertig machen</li>\n <li data-status=\"pending\">Mama anrufen</li>\n</ul>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "li[data-status='completed']",
|
||||
"message": "Füge ein Element mit data-status=\"completed\" hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "li[data-status='active']",
|
||||
"message": "Füge ein Element mit data-status=\"active\" hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "li[data-status='pending']",
|
||||
"message": "Füge ein Element mit data-status=\"pending\" hinzu"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
83
lessons/de/27-html-dialog.json
Normal file
83
lessons/de/27-html-dialog.json
Normal file
@@ -0,0 +1,83 @@
|
||||
{
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-dialog",
|
||||
"title": "Natives Dialog-Element",
|
||||
"description": "Erstelle modale Dialoge ohne JavaScript-Bibliotheken",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "dialog-basic",
|
||||
"title": "Dialog öffnen",
|
||||
"description": "Das <kbd><dialog></kbd>-Element erstellt ein natives Modal. Füge das <kbd>open</kbd>-Attribut hinzu, um es anzuzeigen.<br><br>Verwende <kbd><form method=\"dialog\"></kbd> darin, um es beim Absenden des Formulars zu schließen - kein JavaScript nötig!",
|
||||
"task": "Erstelle einen Dialog mit:<br>1. Dem <kbd>open</kbd>-Attribut, um ihn anzuzeigen<br>2. Einem <kbd><h2></kbd> mit 'Willkommen!'<br>3. Einem <kbd><p></kbd> mit einer Begrüßungsnachricht<br>4. Einem <kbd><form method=\"dialog\"></kbd> 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": "",
|
||||
"initialCode": "<!-- Erstelle einen Dialog mit dem open-Attribut -->",
|
||||
"solution": "<dialog open>\n <h2>Willkommen!</h2>\n <p>Dies ist ein natives HTML-Dialog-Element.</p>\n <form method=\"dialog\">\n <button>Schließen</button>\n </form>\n</dialog>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "dialog",
|
||||
"message": "Füge ein <dialog>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "dialog", "attr": "open", "value": true },
|
||||
"message": "Füge das 'open'-Attribut hinzu, um den Dialog anzuzeigen"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "dialog h2",
|
||||
"message": "Füge eine <h2>-Überschrift im Dialog hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "form[method='dialog']",
|
||||
"message": "Füge ein <form method=\"dialog\"> zum Schließen hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "dialog button",
|
||||
"message": "Füge einen Schließen-Button im Formular hinzu"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "dialog-form",
|
||||
"title": "Dialog mit Formular",
|
||||
"description": "Dialoge können vollständige Formulare enthalten. Das <kbd>method=\"dialog\"</kbd> lässt das Formular den Dialog beim Absenden schließen, anstatt Daten zu senden.<br><br>Dieses Muster ist perfekt für Bestätigungsdialoge, schnelle Eingaben oder Einstellungspanels.",
|
||||
"task": "Erstelle einen Bestätigungsdialog:<br>1. Füge <kbd>open</kbd> hinzu, um ihn anzuzeigen<br>2. Ein <kbd><h2></kbd> mit 'Löschen bestätigen'<br>3. Ein <kbd><p></kbd> mit 'Bist du sicher?'<br>4. Ein <kbd><form method=\"dialog\"></kbd> 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": "",
|
||||
"initialCode": "<!-- Erstelle einen Bestätigungsdialog -->",
|
||||
"solution": "<dialog open>\n <h2>Löschen bestätigen</h2>\n <p>Bist du sicher, dass du dieses Element löschen möchtest?</p>\n <form method=\"dialog\">\n <button value=\"cancel\">Abbrechen</button>\n <button value=\"delete\">Löschen</button>\n </form>\n</dialog>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "dialog[open]",
|
||||
"message": "Füge ein <dialog> mit dem open-Attribut hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "dialog h2",
|
||||
"message": "Füge eine Überschrift zum Dialog hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "form[method='dialog']",
|
||||
"message": "Füge ein <form method=\"dialog\"> für die Buttons hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "dialog button", "min": 2 },
|
||||
"message": "Füge mindestens 2 Buttons hinzu (Abbrechen und Bestätigen)"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
127
lessons/de/28-html-forms-fieldset.json
Normal file
127
lessons/de/28-html-forms-fieldset.json
Normal file
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-forms-fieldset",
|
||||
"title": "Formulare mit Fieldsets",
|
||||
"description": "Gruppiere Formularelemente mit fieldset und legend",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "fieldset-basic",
|
||||
"title": "Gruppieren mit Fieldset",
|
||||
"description": "Das <kbd><fieldset></kbd>-Element gruppiert zusammengehörige Formularfelder. Füge ein <kbd><legend></kbd> als erstes Kind hinzu, um der Gruppe einen Titel zu geben.<br><br>Das verbessert die Zugänglichkeit und visuelle Organisation komplexer Formulare.",
|
||||
"task": "Erstelle ein Formular mit einem Fieldset:<br>1. Ein <kbd><form></kbd>-Element<br>2. Ein <kbd><fieldset></kbd> darin<br>3. Ein <kbd><legend></kbd> mit 'Persönliche Daten'<br>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": "",
|
||||
"initialCode": "<!-- Erstelle ein Formular mit einer Fieldset-Gruppe -->",
|
||||
"solution": "<form>\n <fieldset>\n <legend>Persönliche Daten</legend>\n <label for=\"name\">Name:</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n <label for=\"email\">E-Mail:</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n </fieldset>\n</form>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "form",
|
||||
"message": "Füge ein <form>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "fieldset",
|
||||
"message": "Füge ein <fieldset> im Formular hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "legend",
|
||||
"message": "Füge ein <legend> für den Titel hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "label", "min": 2 },
|
||||
"message": "Füge mindestens 2 Labels hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "input", "min": 2 },
|
||||
"message": "Füge mindestens 2 Eingabefelder hinzu"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "fieldset-textarea",
|
||||
"title": "Textarea hinzufügen",
|
||||
"description": "Das <kbd><textarea></kbd>-Element erstellt ein mehrzeiliges Textfeld, perfekt für längere Inhalte wie Nachrichten oder Beschreibungen.<br><br>Verwende <kbd>rows</kbd> und <kbd>cols</kbd> Attribute, um die Standardgröße festzulegen.",
|
||||
"task": "Erstelle ein Kontaktformular:<br>1. Ein <kbd><fieldset></kbd> mit <kbd><legend></kbd> 'Kontaktiere uns'<br>2. Ein beschriftetes <kbd><input></kbd> für E-Mail<br>3. Eine beschriftete <kbd><textarea></kbd> für die Nachricht<br>4. Einen Absende-<kbd><button></kbd>",
|
||||
"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": "<!-- Erstelle ein Kontaktformular mit Textarea -->",
|
||||
"solution": "<form>\n <fieldset>\n <legend>Kontaktiere uns</legend>\n <label for=\"email\">E-Mail:</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n <label for=\"message\">Nachricht:</label>\n <textarea id=\"message\" name=\"message\" rows=\"4\"></textarea>\n <button type=\"submit\">Nachricht senden</button>\n </fieldset>\n</form>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "fieldset",
|
||||
"message": "Füge ein <fieldset>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "legend",
|
||||
"message": "Füge ein <legend>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "textarea",
|
||||
"message": "Füge eine <textarea> für die Nachricht hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "button",
|
||||
"message": "Füge einen Absende-Button hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "input",
|
||||
"message": "Füge ein Eingabefeld für E-Mail hinzu"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "fieldset-multiple",
|
||||
"title": "Mehrere Fieldsets",
|
||||
"description": "Komplexe Formulare können mehrere <kbd><fieldset></kbd>-Elemente verwenden, um verschiedene Abschnitte zu organisieren.<br><br>Das verbessert die Benutzerfreundlichkeit bei langen Formularen wie Registrierung oder Checkout.",
|
||||
"task": "Erstelle ein Registrierungsformular mit 2 Fieldsets:<br>1. 'Kontodaten' mit Benutzername und Passwort<br>2. 'Einstellungen' mit einer Textarea für Bio<br>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": "",
|
||||
"initialCode": "<!-- Erstelle ein Formular mit mehreren Fieldsets -->",
|
||||
"solution": "<form>\n <fieldset>\n <legend>Kontodaten</legend>\n <label for=\"username\">Benutzername:</label>\n <input type=\"text\" id=\"username\" name=\"username\">\n <label for=\"password\">Passwort:</label>\n <input type=\"password\" id=\"password\" name=\"password\">\n </fieldset>\n <fieldset>\n <legend>Einstellungen</legend>\n <label for=\"bio\">Bio:</label>\n <textarea id=\"bio\" name=\"bio\"></textarea>\n </fieldset>\n <button type=\"submit\">Registrieren</button>\n</form>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "fieldset", "min": 2 },
|
||||
"message": "Erstelle mindestens 2 Fieldsets"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "legend", "min": 2 },
|
||||
"message": "Füge zu jedem Fieldset ein Legend hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "textarea",
|
||||
"message": "Füge eine Textarea für die Bio hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "button",
|
||||
"message": "Füge einen Absende-Button hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "input", "min": 2 },
|
||||
"message": "Füge mindestens 2 Eingabefelder hinzu"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
102
lessons/de/29-html-figure.json
Normal file
102
lessons/de/29-html-figure.json
Normal file
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-figure",
|
||||
"title": "Figure & Figcaption",
|
||||
"description": "Erstelle eigenständige Inhalte mit Beschriftungen",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "figure-basic",
|
||||
"title": "Bild mit Beschriftung",
|
||||
"description": "Das <kbd><figure></kbd>-Element umschließt eigenständige Inhalte wie Bilder, Diagramme oder Code. Füge <kbd><figcaption></kbd> hinzu, um eine Beschriftung zu erstellen.<br><br>Diese semantische Struktur hilft Screenreadern und Suchmaschinen, deinen Inhalt zu verstehen.",
|
||||
"task": "Erstelle eine Figure mit:<br>1. Einem <kbd><figure></kbd>-Element<br>2. Einem <kbd><img></kbd> darin (verwende eine Platzhalter-URL)<br>3. Einem <kbd><figcaption></kbd>, das das Bild beschreibt",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; } figure { margin: 0; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); max-width: 400px; } figure img { width: 100%; height: 200px; object-fit: cover; display: block; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } figcaption { padding: 15px 20px; color: #555; font-size: 0.95rem; border-top: 1px solid #eee; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<!-- Erstelle eine Figure mit Bild und Beschriftung -->",
|
||||
"solution": "<figure>\n <img src=\"https://picsum.photos/400/200\" alt=\"Eine schöne Landschaft\">\n <figcaption>Eine wunderschöne Berglandschaft bei Sonnenuntergang.</figcaption>\n</figure>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "figure",
|
||||
"message": "Füge ein <figure>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "figure img",
|
||||
"message": "Füge ein <img> in die Figure ein"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "figcaption",
|
||||
"message": "Füge ein <figcaption> für die Beschriftung hinzu"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "figure-code",
|
||||
"title": "Code-Figure",
|
||||
"description": "Figures sind nicht nur für Bilder! Du kannst sie für Code-Schnipsel, Zitate oder jeden eigenständigen Inhalt verwenden.<br><br>Kombiniere <kbd><figure></kbd> mit <kbd><pre></kbd> und <kbd><code></kbd> für Code-Beispiele.",
|
||||
"task": "Erstelle eine Code-Figure:<br>1. Ein <kbd><figure></kbd>-Element<br>2. Ein <kbd><pre></kbd> mit <kbd><code></kbd> und etwas Code<br>3. Ein <kbd><figcaption></kbd>, das den Code beschreibt",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #1e1e1e; } figure { margin: 0; background: #2d2d2d; border-radius: 10px; overflow: hidden; max-width: 500px; } pre { margin: 0; padding: 20px; overflow-x: auto; } code { color: #9cdcfe; font-family: 'Fira Code', 'Consolas', monospace; font-size: 14px; line-height: 1.5; } figcaption { padding: 12px 20px; color: #888; font-size: 0.85rem; background: #252525; border-top: 1px solid #3d3d3d; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<!-- Erstelle eine Figure mit Code-Schnipsel -->",
|
||||
"solution": "<figure>\n <pre><code>function greet(name) {\n return `Hallo, ${name}!`;\n}</code></pre>\n <figcaption>Eine einfache Begrüßungsfunktion in JavaScript</figcaption>\n</figure>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "figure",
|
||||
"message": "Füge ein <figure>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "pre",
|
||||
"message": "Füge ein <pre>-Element für formatierten Text hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "code",
|
||||
"message": "Füge ein <code>-Element für den Code hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "figcaption",
|
||||
"message": "Füge ein <figcaption> hinzu, das den Code beschreibt"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "figure-gallery",
|
||||
"title": "Bildergalerie",
|
||||
"description": "Du kannst mehrere Bilder in eine einzige <kbd><figure></kbd> packen, um eine Galerie oder einen Vergleich zu erstellen.<br><br>Das figcaption beschreibt die gesamte Gruppe.",
|
||||
"task": "Erstelle eine Galerie-Figure:<br>1. Ein <kbd><figure></kbd>-Element<br>2. Mindestens 2 <kbd><img></kbd>-Elemente<br>3. Ein <kbd><figcaption></kbd>, das die Galerie beschreibt",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } figure { margin: 0; background: white; border-radius: 15px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.15); max-width: 500px; padding: 15px; display: grid; grid-template-columns: repeat(2, 1fr); gap: 10px; } figure img { width: 100%; height: 120px; object-fit: cover; border-radius: 8px; background: linear-gradient(45deg, #ff6b6b, #feca57); } figcaption { grid-column: 1 / -1; padding: 10px 5px 5px; color: #666; font-size: 0.9rem; text-align: center; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<!-- Erstelle eine Galerie mit mehreren Bildern -->",
|
||||
"solution": "<figure>\n <img src=\"https://picsum.photos/200/120?1\" alt=\"Foto 1\">\n <img src=\"https://picsum.photos/200/120?2\" alt=\"Foto 2\">\n <img src=\"https://picsum.photos/200/120?3\" alt=\"Foto 3\">\n <img src=\"https://picsum.photos/200/120?4\" alt=\"Foto 4\">\n <figcaption>Meine Urlaubsfoto-Galerie</figcaption>\n</figure>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "figure",
|
||||
"message": "Füge ein <figure>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "figure img", "min": 2 },
|
||||
"message": "Füge mindestens 2 Bilder in die Figure ein"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "figcaption",
|
||||
"message": "Füge ein <figcaption> für die Galerie hinzu"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
127
lessons/de/30-html-tables.json
Normal file
127
lessons/de/30-html-tables.json
Normal file
@@ -0,0 +1,127 @@
|
||||
{
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-tables",
|
||||
"title": "HTML-Tabellen",
|
||||
"description": "Erstelle strukturierte Datentabellen mit Überschriften und Beschriftungen",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "table-basic",
|
||||
"title": "Grundlegende Tabellenstruktur",
|
||||
"description": "Tabellen verwenden <kbd><table></kbd> mit <kbd><tr></kbd> für Zeilen. In Zeilen nutze <kbd><th></kbd> für Überschriften und <kbd><td></kbd> für Datenzellen.<br><br>Das <kbd><caption></kbd>-Element bietet einen zugänglichen Titel für die Tabelle.",
|
||||
"task": "Erstelle eine einfache Tabelle mit:<br>1. Einer <kbd><caption></kbd> mit 'Obstpreise'<br>2. Einer Kopfzeile mit 'Obst' und 'Preis' Spalten<br>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": "",
|
||||
"initialCode": "<!-- Erstelle eine Tabelle mit caption und Überschriften -->",
|
||||
"solution": "<table>\n <caption>Obstpreise</caption>\n <tr>\n <th>Obst</th>\n <th>Preis</th>\n </tr>\n <tr>\n <td>Apfel</td>\n <td>1,50 €</td>\n </tr>\n <tr>\n <td>Banane</td>\n <td>0,75 €</td>\n </tr>\n</table>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "table",
|
||||
"message": "Füge ein <table>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "caption",
|
||||
"message": "Füge eine <caption> als Tabellentitel hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "th", "min": 2 },
|
||||
"message": "Füge mindestens 2 Überschriftszellen (th) hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "tr", "min": 3 },
|
||||
"message": "Füge mindestens 3 Zeilen hinzu (1 Kopf + 2 Daten)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "table-thead-tbody",
|
||||
"title": "Tabellenkopf & -körper",
|
||||
"description": "Verwende <kbd><thead></kbd> zum Gruppieren von Kopfzeilen und <kbd><tbody></kbd> zum Gruppieren von Datenzeilen. Das hilft Browsern und Hilfstechnologien, die Tabellenstruktur zu verstehen.<br><br>Du kannst auch <kbd><tfoot></kbd> für Fußzeilen wie Summen verwenden.",
|
||||
"task": "Erstelle eine strukturierte Tabelle:<br>1. Eine <kbd><caption></kbd> mit 'Monatliche Verkäufe'<br>2. Ein <kbd><thead></kbd> mit Monat und Umsatz Überschriften<br>3. Ein <kbd><tbody></kbd> 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": "",
|
||||
"initialCode": "<!-- Erstelle eine Tabelle mit thead und tbody -->",
|
||||
"solution": "<table>\n <caption>Monatliche Verkäufe</caption>\n <thead>\n <tr>\n <th>Monat</th>\n <th>Umsatz</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Januar</td>\n <td>12.500 €</td>\n </tr>\n <tr>\n <td>Februar</td>\n <td>14.200 €</td>\n </tr>\n </tbody>\n</table>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "table",
|
||||
"message": "Füge ein <table>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "caption",
|
||||
"message": "Füge ein <caption>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "thead",
|
||||
"message": "Füge ein <thead> für den Kopfbereich hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "tbody",
|
||||
"message": "Füge ein <tbody> für die Datenzeilen hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "tbody tr", "min": 2 },
|
||||
"message": "Füge mindestens 2 Datenzeilen in tbody hinzu"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "table-complete",
|
||||
"title": "Vollständige Tabelle mit Fuß",
|
||||
"description": "Füge <kbd><tfoot></kbd> hinzu, um einen Fußbereich für Summen oder Zusammenfassungen zu erstellen. Der Fuß bleibt unten, auch wenn tbody viele Zeilen hat.<br><br>Kombiniere alle Abschnitte für eine vollständig strukturierte, zugängliche Tabelle.",
|
||||
"task": "Erstelle eine vollständige Tabelle:<br>1. Eine <kbd><caption></kbd> mit 'Bestellübersicht'<br>2. Ein <kbd><thead></kbd> mit Artikel und Preis Überschriften<br>3. Ein <kbd><tbody></kbd> mit 2 Artikeln<br>4. Ein <kbd><tfoot></kbd> 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": "",
|
||||
"initialCode": "<!-- Erstelle eine vollständige Tabelle mit tfoot -->",
|
||||
"solution": "<table>\n <caption>Bestellübersicht</caption>\n <thead>\n <tr>\n <th>Artikel</th>\n <th>Preis</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Widget</td>\n <td>25,00 €</td>\n </tr>\n <tr>\n <td>Gadget</td>\n <td>35,00 €</td>\n </tr>\n </tbody>\n <tfoot>\n <tr>\n <td>Gesamt</td>\n <td>60,00 €</td>\n </tr>\n </tfoot>\n</table>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "table",
|
||||
"message": "Füge ein <table>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "caption",
|
||||
"message": "Füge ein <caption>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "thead",
|
||||
"message": "Füge einen <thead>-Abschnitt hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "tbody",
|
||||
"message": "Füge einen <tbody>-Abschnitt hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "tfoot",
|
||||
"message": "Füge einen <tfoot>-Abschnitt für die Summe hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "tbody tr", "min": 2 },
|
||||
"message": "Füge mindestens 2 Artikelzeilen in tbody hinzu"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
82
lessons/de/31-html-marquee.json
Normal file
82
lessons/de/31-html-marquee.json
Normal file
@@ -0,0 +1,82 @@
|
||||
{
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-marquee",
|
||||
"title": "Das Marquee-Element",
|
||||
"description": "Erstelle Lauftext mit dem klassischen (veralteten aber lustigen!) Marquee-Element",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "marquee-basic",
|
||||
"title": "Lauftext",
|
||||
"description": "Das <kbd><marquee></kbd>-Element erstellt Lauftext - ein Klassiker aus dem frühen Web! Obwohl veraltet, funktioniert es noch in den meisten Browsern.<br><br>Hinweis: Für produktive Seiten nutze CSS-Animationen. Aber zum Lernen und Spaß haben ist Marquee super!",
|
||||
"task": "Erstelle ein einfaches Marquee:<br>1. Füge ein <kbd><marquee></kbd>-Element hinzu<br>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": "",
|
||||
"initialCode": "<!-- Erstelle ein scrollendes Marquee -->",
|
||||
"solution": "<marquee>Willkommen auf meiner Website!</marquee>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "marquee",
|
||||
"message": "Füge ein <marquee>-Element hinzu"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "marquee-direction",
|
||||
"title": "Richtung & Verhalten",
|
||||
"description": "Steuere das Marquee mit Attributen:<br>• <kbd>direction</kbd>: left, right, up, down<br>• <kbd>behavior</kbd>: scroll (Standard), slide (stoppt am Rand), alternate (springt zurück)<br>• <kbd>scrollamount</kbd>: Geschwindigkeit (Standard ist 6)",
|
||||
"task": "Erstelle ein springendes Marquee:<br>1. Füge ein <kbd><marquee></kbd>-Element hinzu<br>2. Setze <kbd>behavior=\"alternate\"</kbd> zum Springen<br>3. Füge lustigen Text hinzu",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2.5rem; color: white; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); font-weight: bold; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<!-- Erstelle ein springendes Marquee -->",
|
||||
"solution": "<marquee behavior=\"alternate\">Hüpf! Hüpf! Hüpf!</marquee>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "marquee",
|
||||
"message": "Füge ein <marquee>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "marquee", "attr": "behavior", "value": "alternate" },
|
||||
"message": "Füge behavior=\"alternate\" zum Springen hinzu"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "marquee-retro",
|
||||
"title": "Retro-Nachrichtenticker",
|
||||
"description": "Kombiniere mehrere Marquee-Attribute für einen klassischen Nachrichtenticker-Effekt. Du kannst sogar mehrere Elemente hineinpacken!<br><br>Denke daran: Das ist veraltetes HTML. Moderne Seiten nutzen CSS-Animationen, aber Marquee ist toll, um Web-Geschichte zu verstehen.",
|
||||
"task": "Erstelle einen Nachrichtenticker:<br>1. Ein <kbd><marquee></kbd> mit <kbd>direction=\"left\"</kbd><br>2. Setze <kbd>scrollamount=\"5\"</kbd> für flüssiges Scrollen<br>3. Füge eine Eilmeldung hinein",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 0; margin: 0; background: #1a1a2e; } marquee { background: linear-gradient(90deg, #c0392b 0%, #e74c3c 50%, #c0392b 100%); padding: 15px 0; font-size: 1.3rem; color: white; font-weight: 500; text-transform: uppercase; letter-spacing: 1px; border-top: 3px solid #f1c40f; border-bottom: 3px solid #f1c40f; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<!-- Erstelle einen Retro-Nachrichtenticker -->",
|
||||
"solution": "<marquee direction=\"left\" scrollamount=\"5\">EILMELDUNG: Das Marquee-Element funktioniert noch in Browsern!</marquee>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "marquee",
|
||||
"message": "Füge ein <marquee>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "marquee", "attr": "direction", "value": "left" },
|
||||
"message": "Füge direction=\"left\" für horizontales Scrollen hinzu"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "marquee", "attr": "scrollamount", "value": "5" },
|
||||
"message": "Füge scrollamount=\"5\" für flüssige Geschwindigkeit hinzu"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
102
lessons/de/32-html-svg.json
Normal file
102
lessons/de/32-html-svg.json
Normal file
@@ -0,0 +1,102 @@
|
||||
{
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-svg",
|
||||
"title": "SVG Grundlagen",
|
||||
"description": "Zeichne skalierbare Vektorgrafiken direkt in HTML",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "svg-circle",
|
||||
"title": "Kreise zeichnen",
|
||||
"description": "SVG (Scalable Vector Graphics) ermöglicht es, Formen direkt in HTML zu zeichnen. Das <kbd><svg></kbd>-Element ist der Container mit <kbd>width</kbd> und <kbd>height</kbd> Attributen.<br><br>Verwende <kbd><circle></kbd> mit <kbd>cx</kbd>, <kbd>cy</kbd> (Mittelpunkt) und <kbd>r</kbd> (Radius) zum Zeichnen von Kreisen.",
|
||||
"task": "Erstelle ein SVG mit einem Kreis:<br>1. Ein <kbd><svg></kbd> mit width=\"200\" und height=\"200\"<br>2. Ein <kbd><circle></kbd> zentriert bei (100,100) mit Radius 50<br>3. Füge eine <kbd>fill</kbd>-Farbe hinzu",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; display: flex; justify-content: center; } svg { background: white; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<!-- Zeichne einen Kreis mit SVG -->",
|
||||
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"50\" fill=\"#3498db\" />\n</svg>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "svg",
|
||||
"message": "Füge ein <svg>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "circle",
|
||||
"message": "Füge ein <circle>-Element in das SVG ein"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "circle", "attr": "cx", "value": "100" },
|
||||
"message": "Setze cx=\"100\" für das horizontale Zentrum"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "circle", "attr": "cy", "value": "100" },
|
||||
"message": "Setze cy=\"100\" für das vertikale Zentrum"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "svg-rect-line",
|
||||
"title": "Rechtecke & Linien",
|
||||
"description": "Zeichne Rechtecke mit <kbd><rect></kbd> mit <kbd>x</kbd>, <kbd>y</kbd>, <kbd>width</kbd>, <kbd>height</kbd>.<br><br>Zeichne Linien mit <kbd><line></kbd> mit <kbd>x1</kbd>, <kbd>y1</kbd> (Start) und <kbd>x2</kbd>, <kbd>y2</kbd> (Ende). Linien brauchen eine <kbd>stroke</kbd>-Farbe!",
|
||||
"task": "Erstelle ein SVG mit:<br>1. Einem <kbd><svg></kbd> (200x150)<br>2. Einem <kbd><rect></kbd> an Position (20,20) mit Größe 80x60<br>3. Einer <kbd><line></kbd> von (120,30) nach (180,90) mit Strichfarbe",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 200px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<!-- Zeichne ein Rechteck und eine Linie -->",
|
||||
"solution": "<svg width=\"200\" height=\"150\">\n <rect x=\"20\" y=\"20\" width=\"80\" height=\"60\" fill=\"#e74c3c\" />\n <line x1=\"120\" y1=\"30\" x2=\"180\" y2=\"90\" stroke=\"#2c3e50\" stroke-width=\"3\" />\n</svg>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "svg",
|
||||
"message": "Füge ein <svg>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "rect",
|
||||
"message": "Füge ein <rect>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "line",
|
||||
"message": "Füge ein <line>-Element hinzu"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "svg-shapes",
|
||||
"title": "Mehrere Formen",
|
||||
"description": "Kombiniere Formen, um einfache Grafiken zu erstellen! Füge <kbd>stroke</kbd> für Umrisse und <kbd>stroke-width</kbd> für die Dicke hinzu.<br><br>Verwende <kbd>fill=\"none\"</kbd> für hohle Formen. Formen stapeln sich in Reihenfolge - spätere Elemente erscheinen oben.",
|
||||
"task": "Erstelle ein einfaches Gesicht:<br>1. Ein <kbd><svg></kbd> (200x200)<br>2. Einen großen <kbd><circle></kbd> für das Gesicht<br>3. Zwei kleine <kbd><circle></kbd>-Elemente für Augen<br>4. Eine <kbd><line></kbd> für das Lächeln",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); min-height: 250px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<!-- Zeichne ein einfaches Smiley-Gesicht -->",
|
||||
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"80\" fill=\"#f1c40f\" stroke=\"#e67e22\" stroke-width=\"4\" />\n <circle cx=\"70\" cy=\"80\" r=\"10\" fill=\"#2c3e50\" />\n <circle cx=\"130\" cy=\"80\" r=\"10\" fill=\"#2c3e50\" />\n <line x1=\"60\" y1=\"130\" x2=\"140\" y2=\"130\" stroke=\"#2c3e50\" stroke-width=\"4\" stroke-linecap=\"round\" />\n</svg>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "svg",
|
||||
"message": "Füge ein <svg>-Element hinzu"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "circle", "min": 3 },
|
||||
"message": "Füge mindestens 3 Kreise hinzu (1 Gesicht + 2 Augen)"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "line",
|
||||
"message": "Füge eine <line> für das Lächeln hinzu"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
Reference in New Issue
Block a user