diff --git a/lessons/23-html-details-summary.json b/lessons/23-html-details-summary.json new file mode 100644 index 0000000..3b54580 --- /dev/null +++ b/lessons/23-html-details-summary.json @@ -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 <details> element creates a collapsible section. The <summary> provides the clickable label.

Click the summary to toggle the hidden content - no JavaScript needed!", + "task": "Create a <details> element with:
1. A <summary> saying 'Click to reveal'
2. A <p> with the text 'This content was hidden!'", + "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": "", + "solution": "
\n Click to reveal\n

This content was hidden!

\n
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "details", + "message": "Add a
element" + }, + { + "type": "element_exists", + "value": "summary", + "message": "Add a inside the details" + }, + { + "type": "parent_child", + "value": { "parent": "details", "child": "summary" }, + "message": "The must be inside
" + }, + { + "type": "parent_child", + "value": { "parent": "details", "child": "p" }, + "message": "Add a

inside

for the hidden content" + } + ] + }, + { + "id": "details-open-attribute", + "title": "Pre-expanded Details", + "description": "By default, <details> is closed. Add the open attribute to show the content initially.

This is a boolean attribute - just add open without a value.", + "task": "Add the open attribute to the <details> element to show the content by default.", + "previewHTML": "", + "previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; background: #f9f9f9; } summary { font-weight: 600; cursor: pointer; } details p { margin-top: 15px; color: #666; }", + "sandboxCSS": "", + "initialCode": "
\n FAQ: What is HTML5?\n

HTML5 is the latest version of the HTML standard with new semantic elements and APIs.

\n
", + "solution": "
\n FAQ: What is HTML5?\n

HTML5 is the latest version of the HTML standard with new semantic elements and APIs.

\n
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "attribute_value", + "value": { "selector": "details", "attr": "open", "value": true }, + "message": "Add the 'open' attribute to
" + } + ] + }, + { + "id": "faq-accordion", + "title": "FAQ Accordion", + "description": "Multiple <details> elements create an accordion-style FAQ. Each question can be expanded independently.

This is a common pattern for FAQ pages, documentation, and help sections.", + "task": "Create an FAQ section with:
1. An <h1> saying 'Frequently Asked Questions'
2. Three <details> elements, each with a question in <summary> and an answer in <p>", + "previewHTML": "", + "previewBaseCSS": "body { font-family: system-ui; min-height: 100vh; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); display: flex; flex-direction: column; justify-content: center; padding: 40px; margin: 0; box-sizing: border-box; } h1 { font-size: 2.5rem; color: #4a4a4a; text-align: center; margin: 0 0 30px 0; } details { background: white; border-radius: 12px; margin-bottom: 15px; padding: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } summary { font-size: 1.3rem; font-weight: 600; color: #5c5c5c; cursor: pointer; list-style: none; } summary::before { content: '▸ '; color: #fcb69f; } details[open] summary::before { content: '▾ '; } details p { margin: 15px 0 0 0; color: #666; line-height: 1.6; }", + "sandboxCSS": "", + "initialCode": "", + "solution": "

Frequently Asked Questions

\n\n
\n What is HTML5?\n

HTML5 is the latest version of HTML with new semantic elements and APIs.

\n
\n\n
\n Do I need JavaScript?\n

Many interactive features work with pure HTML5, no JavaScript required!

\n
\n\n
\n Is this accessible?\n

Yes! Native HTML elements have built-in keyboard and screen reader support.

\n
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "h1", + "message": "Add an

heading for the FAQ title" + }, + { + "type": "element_count", + "value": { "selector": "details", "min": 3 }, + "message": "Create at least 3
elements for the FAQ" + }, + { + "type": "element_count", + "value": { "selector": "summary", "min": 3 }, + "message": "Each
needs a for the question" + }, + { + "type": "element_count", + "value": { "selector": "details p", "min": 3 }, + "message": "Each
needs a

for the answer" + } + ] + } + ] +} diff --git a/lessons/24-html-progress-meter.json b/lessons/24-html-progress-meter.json new file mode 100644 index 0000000..b3bbc2a --- /dev/null +++ b/lessons/24-html-progress-meter.json @@ -0,0 +1,102 @@ +{ + "$schema": "../schemas/code-crispies-module-schema.json", + "id": "html-progress-meter", + "title": "Progress & Meter Elements", + "description": "Display completion status and scalar measurements natively", + "mode": "html", + "difficulty": "beginner", + "lessons": [ + { + "id": "progress-basic", + "title": "Progress Bars", + "description": "The <progress> element shows task completion. Use value for current progress and max for the total.

The text inside is fallback for older browsers.", + "task": "Create a progress bar showing 70% completion:
1. Add a <label> saying 'Download:'
2. Add a <progress> with value=\"70\" and max=\"100\"", + "previewHTML": "", + "previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } progress { width: 100%; height: 20px; border-radius: 10px; } progress::-webkit-progress-bar { background: #e0e0e0; border-radius: 10px; } progress::-webkit-progress-value { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; } progress::-moz-progress-bar { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; }", + "sandboxCSS": "", + "initialCode": "", + "solution": "\n70%", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "progress", + "message": "Add a element" + }, + { + "type": "attribute_value", + "value": { "selector": "progress", "attr": "value", "value": "70" }, + "message": "Set value=\"70\" on the progress element" + }, + { + "type": "attribute_value", + "value": { "selector": "progress", "attr": "max", "value": "100" }, + "message": "Set max=\"100\" on the progress element" + }, + { + "type": "element_exists", + "value": "label", + "message": "Add a

Loading...

\n", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "progress", + "message": "Add a element" + }, + { + "type": "element_exists", + "value": "p", + "message": "Add a

with loading text" + } + ] + }, + { + "id": "meter-gauge", + "title": "Meter Gauges", + "description": "The <meter> element displays a scalar value within a range. Use it for measurements like disk space, battery, or ratings.

Set low, high, and optimum to define good/bad ranges - the browser colors it accordingly!", + "task": "Create a battery level meter:
1. Add a <label> saying 'Battery:'
2. Add a <meter> with:
- value=\"0.8\"
- min=\"0\" and max=\"1\"
- low=\"0.2\" and high=\"0.8\"
- optimum=\"1\"", + "previewHTML": "", + "previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }", + "sandboxCSS": "", + "initialCode": "", + "solution": "\n80%", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "meter", + "message": "Add a element" + }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "value", "value": "0.8" }, + "message": "Set value=\"0.8\" on the meter" + }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "low", "value": "0.2" }, + "message": "Set low=\"0.2\" to define the low threshold" + }, + { + "type": "element_exists", + "value": "label", + "message": "Add a

\n

Laptop

\n

A powerful laptop for work and play.

\n
\n\n
\n

T-Shirt

\n

A comfortable cotton t-shirt.

\n
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_count", + "value": { "selector": "article[data-category]", "min": 2 }, + "message": "Create at least 2 articles with data-category attributes" + }, + { + "type": "element_count", + "value": { "selector": "article[data-price]", "min": 2 }, + "message": "Add data-price attribute to your articles" + }, + { + "type": "element_count", + "value": { "selector": "article h2", "min": 2 }, + "message": "Add an

inside each article" + } + ] + }, + { + "id": "data-attributes-css", + "title": "Styling with Data Attributes", + "description": "CSS can select elements by their data attributes using [data-*] selectors. You can even match specific values!

The preview CSS uses [data-status='active'] to style active items differently.", + "task": "Create a task list with 3 <li> items. Give each a data-status attribute:
1. One with data-status=\"completed\"
2. One with data-status=\"active\"
3. One with data-status=\"pending\"", + "previewHTML": "", + "previewBaseCSS": "body { font-family: system-ui; padding: 20px; } ul { list-style: none; padding: 0; } li { padding: 15px; margin: 8px 0; border-radius: 8px; background: #f5f5f5; } li[data-status='completed'] { background: #c8e6c9; text-decoration: line-through; color: #388e3c; } li[data-status='active'] { background: #fff3e0; border-left: 4px solid #ff9800; font-weight: 600; } li[data-status='pending'] { background: #e3f2fd; color: #1976d2; }", + "sandboxCSS": "", + "initialCode": "
    \n \n
", + "solution": "
    \n
  • Buy groceries
  • \n
  • Finish homework
  • \n
  • Call mom
  • \n
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "li[data-status='completed']", + "message": "Add an item with data-status=\"completed\"" + }, + { + "type": "element_exists", + "value": "li[data-status='active']", + "message": "Add an item with data-status=\"active\"" + }, + { + "type": "element_exists", + "value": "li[data-status='pending']", + "message": "Add an item with data-status=\"pending\"" + } + ] + } + ] +} diff --git a/lessons/27-html-dialog.json b/lessons/27-html-dialog.json new file mode 100644 index 0000000..5dbfd9d --- /dev/null +++ b/lessons/27-html-dialog.json @@ -0,0 +1,83 @@ +{ + "$schema": "../schemas/code-crispies-module-schema.json", + "id": "html-dialog", + "title": "Native Dialog Element", + "description": "Create modal dialogs without JavaScript libraries", + "mode": "html", + "difficulty": "beginner", + "lessons": [ + { + "id": "dialog-basic", + "title": "Open Dialog", + "description": "The <dialog> element creates a native modal. Add the open attribute to show it.

Use <form method=\"dialog\"> inside to close it when the form submits - no JavaScript needed!", + "task": "Create a dialog with:
1. The open attribute to show it
2. An <h2> saying 'Welcome!'
3. A <p> with a greeting message
4. A <form method=\"dialog\"> with a close button", + "previewHTML": "", + "previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: #f5f5f5; } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 400px; } dialog::backdrop { background: rgba(0,0,0,0.5); } dialog h2 { margin: 0 0 15px 0; color: #1976d2; } dialog p { color: #666; margin: 0 0 20px 0; } dialog button { background: #1976d2; color: white; border: none; padding: 10px 25px; border-radius: 6px; cursor: pointer; font-size: 16px; } dialog button:hover { background: #1565c0; }", + "sandboxCSS": "", + "initialCode": "", + "solution": "\n

Welcome!

\n

This is a native HTML dialog element.

\n
\n \n
\n
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "dialog", + "message": "Add a element" + }, + { + "type": "attribute_value", + "value": { "selector": "dialog", "attr": "open", "value": true }, + "message": "Add the 'open' attribute to show the dialog" + }, + { + "type": "element_exists", + "value": "dialog h2", + "message": "Add an

heading inside the dialog" + }, + { + "type": "element_exists", + "value": "form[method='dialog']", + "message": "Add a
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 method=\"dialog\" makes the form close the dialog on submit instead of sending data.

This pattern is perfect for confirmation dialogs, quick inputs, or settings panels.", + "task": "Create a confirmation dialog:
1. Add open to show it
2. An <h2> saying 'Confirm Delete'
3. A <p> asking 'Are you sure?'
4. A <form method=\"dialog\"> with Cancel and Delete buttons", + "previewHTML": "", + "previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: linear-gradient(135deg, #fce4ec 0%, #f8bbd9 100%); } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 350px; text-align: center; } dialog h2 { margin: 0 0 10px 0; color: #c62828; } dialog p { color: #666; margin: 0 0 20px 0; } dialog form { display: flex; gap: 10px; justify-content: center; } dialog button { padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; border: none; } dialog button:first-child { background: #e0e0e0; color: #333; } dialog button:last-child { background: #c62828; color: white; } dialog button:hover { opacity: 0.9; }", + "sandboxCSS": "", + "initialCode": "", + "solution": "\n

Confirm Delete

\n

Are you sure you want to delete this item?

\n \n \n \n \n
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "dialog[open]", + "message": "Add a 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
for the buttons" + }, + { + "type": "element_count", + "value": { "selector": "dialog button", "min": 2 }, + "message": "Add at least 2 buttons (Cancel and Confirm)" + } + ] + } + ] +} diff --git a/lessons/28-html-forms-fieldset.json b/lessons/28-html-forms-fieldset.json new file mode 100644 index 0000000..7e6aace --- /dev/null +++ b/lessons/28-html-forms-fieldset.json @@ -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 <fieldset> element groups related form controls together. Add a <legend> as the first child to give the group a title.

This helps with accessibility and visual organization of complex forms.", + "task": "Create a form with a fieldset:
1. A <form> element
2. A <fieldset> inside
3. A <legend> saying 'Personal Info'
4. Two labeled inputs for name and email", + "previewHTML": "", + "previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f0f4f8; } form { max-width: 400px; } fieldset { border: 2px solid #3498db; border-radius: 10px; padding: 20px; background: white; } legend { color: #3498db; font-weight: 600; padding: 0 10px; font-size: 1.1rem; } label { display: block; margin: 15px 0 5px; color: #333; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; } input:focus { outline: 2px solid #3498db; border-color: transparent; }", + "sandboxCSS": "", + "initialCode": "", + "solution": "\n
\n Personal Info\n \n \n \n \n
\n
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "form", + "message": "Add a
element" + }, + { + "type": "element_exists", + "value": "fieldset", + "message": "Add a
inside the form" + }, + { + "type": "element_exists", + "value": "legend", + "message": "Add a to title your fieldset" + }, + { + "type": "element_count", + "value": { "selector": "label", "min": 2 }, + "message": "Add at least 2 labels" + }, + { + "type": "element_count", + "value": { "selector": "input", "min": 2 }, + "message": "Add at least 2 input fields" + } + ] + }, + { + "id": "fieldset-textarea", + "title": "Adding Textarea", + "description": "The <textarea> element creates a multi-line text input, perfect for longer content like messages or descriptions.

Use rows and cols attributes to set default size.", + "task": "Create a contact form:
1. A <fieldset> with <legend> 'Contact Us'
2. A labeled <input> for email
3. A labeled <textarea> for the message
4. A submit <button>", + "previewHTML": "", + "previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } form { max-width: 450px; margin: 0 auto; } fieldset { border: none; border-radius: 15px; padding: 30px; background: white; box-shadow: 0 10px 40px rgba(0,0,0,0.2); } legend { color: #667eea; font-weight: 700; padding: 0; font-size: 1.5rem; margin-bottom: 10px; } label { display: block; margin: 20px 0 8px; color: #333; font-weight: 500; } input, textarea { width: 100%; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: none; border-color: #667eea; } textarea { resize: vertical; min-height: 100px; } button { margin-top: 20px; width: 100%; padding: 14px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; } button:hover { opacity: 0.9; }", + "sandboxCSS": "", + "initialCode": "", + "solution": "\n
\n Contact Us\n \n \n \n \n \n
\n", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "fieldset", + "message": "Add a
element" + }, + { + "type": "element_exists", + "value": "legend", + "message": "Add a element" + }, + { + "type": "element_exists", + "value": "textarea", + "message": "Add a \n
\n \n", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_count", + "value": { "selector": "fieldset", "min": 2 }, + "message": "Create at least 2 fieldsets" + }, + { + "type": "element_count", + "value": { "selector": "legend", "min": 2 }, + "message": "Add a legend to each fieldset" + }, + { + "type": "element_exists", + "value": "textarea", + "message": "Add a textarea for the bio" + }, + { + "type": "element_exists", + "value": "button", + "message": "Add a submit button" + }, + { + "type": "element_count", + "value": { "selector": "input", "min": 2 }, + "message": "Add at least 2 input fields" + } + ] + } + ] +} diff --git a/lessons/29-html-figure.json b/lessons/29-html-figure.json new file mode 100644 index 0000000..41e4545 --- /dev/null +++ b/lessons/29-html-figure.json @@ -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 <figure> element wraps self-contained content like images, diagrams, or code. Add <figcaption> to provide a caption.

This semantic structure helps screen readers and search engines understand your content.", + "task": "Create a figure with:
1. A <figure> element
2. An <img> inside (use placeholder URL)
3. A <figcaption> 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": "", + "solution": "
\n \"A\n
A beautiful mountain landscape at sunset.
\n
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "figure", + "message": "Add a
element" + }, + { + "type": "element_exists", + "value": "figure img", + "message": "Add an inside the figure" + }, + { + "type": "element_exists", + "value": "figcaption", + "message": "Add a
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.

Combine <figure> with <pre> and <code> for code examples.", + "task": "Create a code figure:
1. A <figure> element
2. A <pre> containing <code> with some code
3. A <figcaption> 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": "", + "solution": "
\n
function greet(name) {\n  return `Hello, ${name}!`;\n}
\n
A simple greeting function in JavaScript
\n
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "figure", + "message": "Add a
element" + }, + { + "type": "element_exists", + "value": "pre", + "message": "Add a
 element for preformatted text"
+				},
+				{
+					"type": "element_exists",
+					"value": "code",
+					"message": "Add a  element for the code"
+				},
+				{
+					"type": "element_exists",
+					"value": "figcaption",
+					"message": "Add a 
describing the code" + } + ] + }, + { + "id": "figure-gallery", + "title": "Image Gallery", + "description": "You can put multiple images inside a single <figure> to create a gallery or comparison.

The figcaption describes the entire group.", + "task": "Create a gallery figure:
1. A <figure> element
2. At least 2 <img> elements
3. A <figcaption> 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": "", + "solution": "
\n \"Photo\n \"Photo\n \"Photo\n \"Photo\n
My vacation photo gallery
\n
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "figure", + "message": "Add a
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
for the gallery" + } + ] + } + ] +} diff --git a/lessons/30-html-tables.json b/lessons/30-html-tables.json new file mode 100644 index 0000000..6871d77 --- /dev/null +++ b/lessons/30-html-tables.json @@ -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 <table> with <tr> for rows. Inside rows, use <th> for headers and <td> for data cells.

The <caption> element provides an accessible title for the table.", + "task": "Create a simple table with:
1. A <caption> saying 'Fruit Prices'
2. A header row with 'Fruit' and 'Price' columns
3. At least 2 data rows", + "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": "", + "solution": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n
Fruit Prices
FruitPrice
Apple$1.50
Banana$0.75
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "table", + "message": "Add a element" + }, + { + "type": "element_exists", + "value": "caption", + "message": "Add a
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 <thead> to group header rows and <tbody> to group data rows. This helps browsers and assistive technology understand the table structure.

You can also use <tfoot> for footer rows like totals.", + "task": "Create a structured table:
1. A <caption> with 'Monthly Sales'
2. A <thead> with Month and Revenue headers
3. A <tbody> with at least 2 data rows", + "previewHTML": "", + "previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } table { border-collapse: collapse; width: 100%; max-width: 450px; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.2); } caption { padding: 20px; font-weight: 700; font-size: 1.3rem; color: white; background: transparent; text-shadow: 0 2px 4px rgba(0,0,0,0.2); caption-side: top; } thead { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } th { padding: 15px 20px; text-align: left; color: white; font-weight: 500; } tbody tr { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } td { padding: 15px 20px; color: #333; } tbody tr:last-child { border-bottom: none; }", + "sandboxCSS": "", + "initialCode": "", + "solution": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Monthly Sales
MonthRevenue
January$12,500
February$14,200
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "table", + "message": "Add a element" + }, + { + "type": "element_exists", + "value": "caption", + "message": "Add a for the header section" + }, + { + "type": "element_exists", + "value": "tbody", + "message": "Add a for the data rows" + }, + { + "type": "element_count", + "value": { "selector": "tbody tr", "min": 2 }, + "message": "Add at least 2 data rows in tbody" + } + ] + }, + { + "id": "table-complete", + "title": "Complete Table with Footer", + "description": "Add <tfoot> to create a footer section for totals or summary data. The footer stays at the bottom even if tbody has many rows.

Combine all sections for a fully structured, accessible table.", + "task": "Create a complete table:
1. A <caption> with 'Order Summary'
2. A <thead> with Item and Price headers
3. A <tbody> with 2 items
4. A <tfoot> with a Total row", + "previewHTML": "", + "previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px 20px; font-weight: 600; font-size: 1.1rem; color: #333; text-align: left; background: #f8f9fa; border-bottom: 2px solid #eee; } th, td { padding: 12px 20px; text-align: left; } thead th { background: #2c3e50; color: white; } tbody td { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } tfoot { background: #ecf0f1; font-weight: 600; } tfoot td { border-top: 2px solid #2c3e50; color: #2c3e50; }", + "sandboxCSS": "", + "initialCode": "", + "solution": "
element" + }, + { + "type": "element_exists", + "value": "thead", + "message": "Add a
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Order Summary
ItemPrice
Widget$25.00
Gadget$35.00
Total$60.00
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "table", + "message": "Add a element" + }, + { + "type": "element_exists", + "value": "caption", + "message": "Add a section" + }, + { + "type": "element_exists", + "value": "tbody", + "message": "Add a section" + }, + { + "type": "element_exists", + "value": "tfoot", + "message": "Add a section for the total" + }, + { + "type": "element_count", + "value": { "selector": "tbody tr", "min": 2 }, + "message": "Add at least 2 item rows in tbody" + } + ] + } + ] +} diff --git a/lessons/31-html-marquee.json b/lessons/31-html-marquee.json new file mode 100644 index 0000000..116dd44 --- /dev/null +++ b/lessons/31-html-marquee.json @@ -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 <marquee> element creates scrolling text - a classic from the early web! While deprecated, it still works in most browsers.

Note: For production, use CSS animations instead. But for learning and fun, marquee is great!", + "task": "Create a simple marquee:
1. Add a <marquee> element
2. Put some text inside like 'Welcome to my website!'", + "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": "", + "solution": "Welcome to my website!", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "marquee", + "message": "Add a element" + } + ] + }, + { + "id": "marquee-direction", + "title": "Direction & Behavior", + "description": "Control the marquee with attributes:
direction: left, right, up, down
behavior: scroll (default), slide (stops at edge), alternate (bounces)
scrollamount: speed (default is 6)", + "task": "Create a bouncing marquee:
1. Add a <marquee> element
2. Set behavior=\"alternate\" to make it bounce
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": "", + "solution": "Bounce! Bounce! Bounce!", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "marquee", + "message": "Add a 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!

Remember: This is deprecated HTML. Modern sites use CSS animations, but marquee is great for understanding web history.", + "task": "Create a news ticker:
1. A <marquee> with direction=\"left\"
2. Set scrollamount=\"5\" for smooth scrolling
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": "", + "solution": "BREAKING NEWS: Marquee element still works in browsers!", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "marquee", + "message": "Add a 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" + } + ] + } + ] +} diff --git a/lessons/32-html-svg.json b/lessons/32-html-svg.json new file mode 100644 index 0000000..99f095b --- /dev/null +++ b/lessons/32-html-svg.json @@ -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 <svg> element is the container, with width and height attributes.

Use <circle> with cx, cy (center) and r (radius) to draw circles.", + "task": "Create an SVG with a circle:
1. An <svg> with width=\"200\" and height=\"200\"
2. A <circle> centered at (100,100) with radius 50
3. Add a fill 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": "", + "solution": "\n \n", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "svg", + "message": "Add an element" + }, + { + "type": "element_exists", + "value": "circle", + "message": "Add a 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 <rect> using x, y, width, height.

Draw lines with <line> using x1, y1 (start) and x2, y2 (end). Lines need a stroke color!", + "task": "Create an SVG with:
1. An <svg> (200x150)
2. A <rect> at position (20,20) with size 80x60
3. A <line> 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": "", + "solution": "\n \n \n", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "svg", + "message": "Add an element" + }, + { + "type": "element_exists", + "value": "rect", + "message": "Add a element" + }, + { + "type": "element_exists", + "value": "line", + "message": "Add a element" + } + ] + }, + { + "id": "svg-shapes", + "title": "Multiple Shapes", + "description": "Combine shapes to create simple graphics! Add stroke for outlines and stroke-width for thickness.

Use fill=\"none\" for hollow shapes. Shapes stack in order - later elements appear on top.", + "task": "Create a simple face:
1. An <svg> (200x200)
2. A large <circle> for the face
3. Two small <circle> elements for eyes
4. A <line> 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": "", + "solution": "\n \n \n \n \n", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "svg", + "message": "Add an 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 for the smile" + } + ] + } + ] +} diff --git a/lessons/de/23-html-details-summary.json b/lessons/de/23-html-details-summary.json new file mode 100644 index 0000000..c3d237a --- /dev/null +++ b/lessons/de/23-html-details-summary.json @@ -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 <details>-Element erstellt einen aufklappbaren Bereich. Das <summary> bietet die anklickbare Beschriftung.

Klicke auf die Zusammenfassung, um den versteckten Inhalt anzuzeigen - kein JavaScript nötig!", + "task": "Erstelle ein <details>-Element mit:
1. Einem <summary> mit dem Text 'Klicken zum Aufklappen'
2. Einem <p> mit dem Text 'Dieser Inhalt war versteckt!'", + "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": "", + "solution": "
\n Klicken zum Aufklappen\n

Dieser Inhalt war versteckt!

\n
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "details", + "message": "Füge ein
-Element hinzu" + }, + { + "type": "element_exists", + "value": "summary", + "message": "Füge ein innerhalb von details hinzu" + }, + { + "type": "parent_child", + "value": { "parent": "details", "child": "summary" }, + "message": "Das muss innerhalb von
sein" + }, + { + "type": "parent_child", + "value": { "parent": "details", "child": "p" }, + "message": "Füge ein

innerhalb von

für den versteckten Inhalt hinzu" + } + ] + }, + { + "id": "details-open-attribute", + "title": "Standardmäßig geöffnet", + "description": "Standardmäßig ist <details> geschlossen. Füge das open-Attribut hinzu, um den Inhalt initial anzuzeigen.

Dies ist ein boolesches Attribut - füge einfach open ohne Wert hinzu.", + "task": "Füge das open-Attribut zum <details>-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": "
\n FAQ: Was ist HTML5?\n

HTML5 ist die neueste Version des HTML-Standards mit neuen semantischen Elementen und APIs.

\n
", + "solution": "
\n FAQ: Was ist HTML5?\n

HTML5 ist die neueste Version des HTML-Standards mit neuen semantischen Elementen und APIs.

\n
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "attribute_value", + "value": { "selector": "details", "attr": "open", "value": true }, + "message": "Füge das 'open'-Attribut zu
hinzu" + } + ] + }, + { + "id": "faq-accordion", + "title": "FAQ-Akkordeon", + "description": "Mehrere <details>-Elemente erstellen ein Akkordeon-artiges FAQ. Jede Frage kann unabhängig aufgeklappt werden.

Dies ist ein häufiges Muster für FAQ-Seiten, Dokumentation und Hilfebereiche.", + "task": "Erstelle einen FAQ-Bereich mit:
1. Einer <h1> mit dem Text 'Häufig gestellte Fragen'
2. Drei <details>-Elementen, jeweils mit einer Frage im <summary> und einer Antwort im <p>", + "previewHTML": "", + "previewBaseCSS": "body { font-family: system-ui; min-height: 100vh; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); display: flex; flex-direction: column; justify-content: center; padding: 40px; margin: 0; box-sizing: border-box; } h1 { font-size: 2.5rem; color: #4a4a4a; text-align: center; margin: 0 0 30px 0; } details { background: white; border-radius: 12px; margin-bottom: 15px; padding: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } summary { font-size: 1.3rem; font-weight: 600; color: #5c5c5c; cursor: pointer; list-style: none; } summary::before { content: '▸ '; color: #fcb69f; } details[open] summary::before { content: '▾ '; } details p { margin: 15px 0 0 0; color: #666; line-height: 1.6; }", + "sandboxCSS": "", + "initialCode": "", + "solution": "

Häufig gestellte Fragen

\n\n
\n Was ist HTML5?\n

HTML5 ist die neueste Version von HTML mit neuen semantischen Elementen und APIs.

\n
\n\n
\n Brauche ich JavaScript?\n

Viele interaktive Funktionen funktionieren mit purem HTML5, kein JavaScript nötig!

\n
\n\n
\n Ist das barrierefrei?\n

Ja! Native HTML-Elemente haben eingebaute Tastatur- und Screenreader-Unterstützung.

\n
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "h1", + "message": "Füge eine

-Überschrift für den FAQ-Titel hinzu" + }, + { + "type": "element_count", + "value": { "selector": "details", "min": 3 }, + "message": "Erstelle mindestens 3
-Elemente für das FAQ" + }, + { + "type": "element_count", + "value": { "selector": "summary", "min": 3 }, + "message": "Jedes
braucht ein für die Frage" + }, + { + "type": "element_count", + "value": { "selector": "details p", "min": 3 }, + "message": "Jedes
braucht ein

für die Antwort" + } + ] + } + ] +} diff --git a/lessons/de/24-html-progress-meter.json b/lessons/de/24-html-progress-meter.json new file mode 100644 index 0000000..2928944 --- /dev/null +++ b/lessons/de/24-html-progress-meter.json @@ -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 <progress>-Element zeigt den Aufgabenfortschritt. Verwende value für den aktuellen Stand und max für das Maximum.

Der Text darin ist ein Fallback für ältere Browser.", + "task": "Erstelle einen Fortschrittsbalken mit 70% Fortschritt:
1. Füge ein <label> mit 'Download:' hinzu
2. Füge ein <progress> mit value=\"70\" und max=\"100\" hinzu", + "previewHTML": "", + "previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } progress { width: 100%; height: 20px; border-radius: 10px; } progress::-webkit-progress-bar { background: #e0e0e0; border-radius: 10px; } progress::-webkit-progress-value { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; } progress::-moz-progress-bar { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; }", + "sandboxCSS": "", + "initialCode": "", + "solution": "\n70%", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "progress", + "message": "Füge ein -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

Lädt...

\n", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "progress", + "message": "Füge ein -Element hinzu" + }, + { + "type": "element_exists", + "value": "p", + "message": "Füge ein

mit Ladetext hinzu" + } + ] + }, + { + "id": "meter-gauge", + "title": "Meter-Anzeigen", + "description": "Das <meter>-Element zeigt einen Skalarwert innerhalb eines Bereichs. Verwende es für Messungen wie Speicherplatz, Akku oder Bewertungen.

Setze low, high und optimum, um gute/schlechte Bereiche zu definieren - der Browser färbt es entsprechend ein!", + "task": "Erstelle eine Akku-Anzeige:
1. Füge ein <label> mit 'Akku:' hinzu
2. Füge ein <meter> hinzu mit:
- value=\"0.8\"
- min=\"0\" und max=\"1\"
- low=\"0.2\" und high=\"0.8\"
- optimum=\"1\"", + "previewHTML": "", + "previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }", + "sandboxCSS": "", + "initialCode": "", + "solution": "\n80%", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "meter", + "message": "Füge ein -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

\n

Laptop

\n

Ein leistungsstarker Laptop für Arbeit und Freizeit.

\n
\n\n
\n

T-Shirt

\n

Ein bequemes Baumwoll-T-Shirt.

\n
", + "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

in jeden Article ein" + } + ] + }, + { + "id": "data-attributes-css", + "title": "Styling mit Data-Attributen", + "description": "CSS kann Elemente über ihre Data-Attribute mit [data-*]-Selektoren auswählen. Du kannst sogar bestimmte Werte abgleichen!

Das Vorschau-CSS verwendet [data-status='active'], um aktive Elemente anders zu stylen.", + "task": "Erstelle eine Aufgabenliste mit 3 <li>-Elementen. Gib jedem ein data-status-Attribut:
1. Eines mit data-status=\"completed\"
2. Eines mit data-status=\"active\"
3. Eines mit data-status=\"pending\"", + "previewHTML": "", + "previewBaseCSS": "body { font-family: system-ui; padding: 20px; } ul { list-style: none; padding: 0; } li { padding: 15px; margin: 8px 0; border-radius: 8px; background: #f5f5f5; } li[data-status='completed'] { background: #c8e6c9; text-decoration: line-through; color: #388e3c; } li[data-status='active'] { background: #fff3e0; border-left: 4px solid #ff9800; font-weight: 600; } li[data-status='pending'] { background: #e3f2fd; color: #1976d2; }", + "sandboxCSS": "", + "initialCode": "
    \n \n
", + "solution": "
    \n
  • Einkaufen gehen
  • \n
  • Hausaufgaben fertig machen
  • \n
  • Mama anrufen
  • \n
", + "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" + } + ] + } + ] +} diff --git a/lessons/de/27-html-dialog.json b/lessons/de/27-html-dialog.json new file mode 100644 index 0000000..1bbb62f --- /dev/null +++ b/lessons/de/27-html-dialog.json @@ -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 <dialog>-Element erstellt ein natives Modal. Füge das open-Attribut hinzu, um es anzuzeigen.

Verwende <form method=\"dialog\"> darin, um es beim Absenden des Formulars zu schließen - kein JavaScript nötig!", + "task": "Erstelle einen Dialog mit:
1. Dem open-Attribut, um ihn anzuzeigen
2. Einem <h2> mit 'Willkommen!'
3. Einem <p> mit einer Begrüßungsnachricht
4. Einem <form method=\"dialog\"> mit einem Schließen-Button", + "previewHTML": "", + "previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: #f5f5f5; } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 400px; } dialog::backdrop { background: rgba(0,0,0,0.5); } dialog h2 { margin: 0 0 15px 0; color: #1976d2; } dialog p { color: #666; margin: 0 0 20px 0; } dialog button { background: #1976d2; color: white; border: none; padding: 10px 25px; border-radius: 6px; cursor: pointer; font-size: 16px; } dialog button:hover { background: #1565c0; }", + "sandboxCSS": "", + "initialCode": "", + "solution": "\n

Willkommen!

\n

Dies ist ein natives HTML-Dialog-Element.

\n
\n \n \n
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "dialog", + "message": "Füge ein -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

-Überschrift im Dialog hinzu" + }, + { + "type": "element_exists", + "value": "form[method='dialog']", + "message": "Füge ein
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 method=\"dialog\" lässt das Formular den Dialog beim Absenden schließen, anstatt Daten zu senden.

Dieses Muster ist perfekt für Bestätigungsdialoge, schnelle Eingaben oder Einstellungspanels.", + "task": "Erstelle einen Bestätigungsdialog:
1. Füge open hinzu, um ihn anzuzeigen
2. Ein <h2> mit 'Löschen bestätigen'
3. Ein <p> mit 'Bist du sicher?'
4. Ein <form method=\"dialog\"> mit Abbrechen- und Löschen-Buttons", + "previewHTML": "", + "previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: linear-gradient(135deg, #fce4ec 0%, #f8bbd9 100%); } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 350px; text-align: center; } dialog h2 { margin: 0 0 10px 0; color: #c62828; } dialog p { color: #666; margin: 0 0 20px 0; } dialog form { display: flex; gap: 10px; justify-content: center; } dialog button { padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; border: none; } dialog button:first-child { background: #e0e0e0; color: #333; } dialog button:last-child { background: #c62828; color: white; } dialog button:hover { opacity: 0.9; }", + "sandboxCSS": "", + "initialCode": "", + "solution": "\n

Löschen bestätigen

\n

Bist du sicher, dass du dieses Element löschen möchtest?

\n \n \n \n \n
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "dialog[open]", + "message": "Füge ein 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
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)" + } + ] + } + ] +} diff --git a/lessons/de/28-html-forms-fieldset.json b/lessons/de/28-html-forms-fieldset.json new file mode 100644 index 0000000..c54885c --- /dev/null +++ b/lessons/de/28-html-forms-fieldset.json @@ -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 <fieldset>-Element gruppiert zusammengehörige Formularfelder. Füge ein <legend> als erstes Kind hinzu, um der Gruppe einen Titel zu geben.

Das verbessert die Zugänglichkeit und visuelle Organisation komplexer Formulare.", + "task": "Erstelle ein Formular mit einem Fieldset:
1. Ein <form>-Element
2. Ein <fieldset> darin
3. Ein <legend> mit 'Persönliche Daten'
4. Zwei beschriftete Eingabefelder für Name und E-Mail", + "previewHTML": "", + "previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f0f4f8; } form { max-width: 400px; } fieldset { border: 2px solid #3498db; border-radius: 10px; padding: 20px; background: white; } legend { color: #3498db; font-weight: 600; padding: 0 10px; font-size: 1.1rem; } label { display: block; margin: 15px 0 5px; color: #333; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; } input:focus { outline: 2px solid #3498db; border-color: transparent; }", + "sandboxCSS": "", + "initialCode": "", + "solution": "\n
\n Persönliche Daten\n \n \n \n \n
\n", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "form", + "message": "Füge ein
-Element hinzu" + }, + { + "type": "element_exists", + "value": "fieldset", + "message": "Füge ein
im Formular hinzu" + }, + { + "type": "element_exists", + "value": "legend", + "message": "Füge ein 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 <textarea>-Element erstellt ein mehrzeiliges Textfeld, perfekt für längere Inhalte wie Nachrichten oder Beschreibungen.

Verwende rows und cols Attribute, um die Standardgröße festzulegen.", + "task": "Erstelle ein Kontaktformular:
1. Ein <fieldset> mit <legend> 'Kontaktiere uns'
2. Ein beschriftetes <input> für E-Mail
3. Eine beschriftete <textarea> für die Nachricht
4. Einen Absende-<button>", + "previewHTML": "", + "previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } form { max-width: 450px; margin: 0 auto; } fieldset { border: none; border-radius: 15px; padding: 30px; background: white; box-shadow: 0 10px 40px rgba(0,0,0,0.2); } legend { color: #667eea; font-weight: 700; padding: 0; font-size: 1.5rem; margin-bottom: 10px; } label { display: block; margin: 20px 0 8px; color: #333; font-weight: 500; } input, textarea { width: 100%; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: none; border-color: #667eea; } textarea { resize: vertical; min-height: 100px; } button { margin-top: 20px; width: 100%; padding: 14px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; } button:hover { opacity: 0.9; }", + "sandboxCSS": "", + "initialCode": "", + "solution": "\n
\n Kontaktiere uns\n \n \n \n \n \n
\n", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "fieldset", + "message": "Füge ein
-Element hinzu" + }, + { + "type": "element_exists", + "value": "legend", + "message": "Füge ein -Element hinzu" + }, + { + "type": "element_exists", + "value": "textarea", + "message": "Füge eine \n
\n \n", + "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" + } + ] + } + ] +} diff --git a/lessons/de/29-html-figure.json b/lessons/de/29-html-figure.json new file mode 100644 index 0000000..430c693 --- /dev/null +++ b/lessons/de/29-html-figure.json @@ -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 <figure>-Element umschließt eigenständige Inhalte wie Bilder, Diagramme oder Code. Füge <figcaption> hinzu, um eine Beschriftung zu erstellen.

Diese semantische Struktur hilft Screenreadern und Suchmaschinen, deinen Inhalt zu verstehen.", + "task": "Erstelle eine Figure mit:
1. Einem <figure>-Element
2. Einem <img> darin (verwende eine Platzhalter-URL)
3. Einem <figcaption>, 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": "", + "solution": "
\n \"Eine\n
Eine wunderschöne Berglandschaft bei Sonnenuntergang.
\n
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "figure", + "message": "Füge ein
-Element hinzu" + }, + { + "type": "element_exists", + "value": "figure img", + "message": "Füge ein in die Figure ein" + }, + { + "type": "element_exists", + "value": "figcaption", + "message": "Füge ein
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.

Kombiniere <figure> mit <pre> und <code> für Code-Beispiele.", + "task": "Erstelle eine Code-Figure:
1. Ein <figure>-Element
2. Ein <pre> mit <code> und etwas Code
3. Ein <figcaption>, 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": "", + "solution": "
\n
function greet(name) {\n  return `Hallo, ${name}!`;\n}
\n
Eine einfache Begrüßungsfunktion in JavaScript
\n
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "figure", + "message": "Füge ein
-Element hinzu" + }, + { + "type": "element_exists", + "value": "pre", + "message": "Füge ein
-Element für formatierten Text hinzu"
+				},
+				{
+					"type": "element_exists",
+					"value": "code",
+					"message": "Füge ein -Element für den Code hinzu"
+				},
+				{
+					"type": "element_exists",
+					"value": "figcaption",
+					"message": "Füge ein 
hinzu, das den Code beschreibt" + } + ] + }, + { + "id": "figure-gallery", + "title": "Bildergalerie", + "description": "Du kannst mehrere Bilder in eine einzige <figure> packen, um eine Galerie oder einen Vergleich zu erstellen.

Das figcaption beschreibt die gesamte Gruppe.", + "task": "Erstelle eine Galerie-Figure:
1. Ein <figure>-Element
2. Mindestens 2 <img>-Elemente
3. Ein <figcaption>, 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": "", + "solution": "
\n \"Foto\n \"Foto\n \"Foto\n \"Foto\n
Meine Urlaubsfoto-Galerie
\n
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "figure", + "message": "Füge ein
-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
für die Galerie hinzu" + } + ] + } + ] +} diff --git a/lessons/de/30-html-tables.json b/lessons/de/30-html-tables.json new file mode 100644 index 0000000..4fc2049 --- /dev/null +++ b/lessons/de/30-html-tables.json @@ -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 <table> mit <tr> für Zeilen. In Zeilen nutze <th> für Überschriften und <td> für Datenzellen.

Das <caption>-Element bietet einen zugänglichen Titel für die Tabelle.", + "task": "Erstelle eine einfache Tabelle mit:
1. Einer <caption> mit 'Obstpreise'
2. Einer Kopfzeile mit 'Obst' und 'Preis' Spalten
3. Mindestens 2 Datenzeilen", + "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": "", + "solution": "

element" + }, + { + "type": "element_exists", + "value": "thead", + "message": "Add a
\n \n \n \n \n \n \n \n \n \n \n \n \n \n
Obstpreise
ObstPreis
Apfel1,50 €
Banane0,75 €
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "table", + "message": "Füge ein -Element hinzu" + }, + { + "type": "element_exists", + "value": "caption", + "message": "Füge eine
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 <thead> zum Gruppieren von Kopfzeilen und <tbody> zum Gruppieren von Datenzeilen. Das hilft Browsern und Hilfstechnologien, die Tabellenstruktur zu verstehen.

Du kannst auch <tfoot> für Fußzeilen wie Summen verwenden.", + "task": "Erstelle eine strukturierte Tabelle:
1. Eine <caption> mit 'Monatliche Verkäufe'
2. Ein <thead> mit Monat und Umsatz Überschriften
3. Ein <tbody> mit mindestens 2 Datenzeilen", + "previewHTML": "", + "previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } table { border-collapse: collapse; width: 100%; max-width: 450px; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.2); } caption { padding: 20px; font-weight: 700; font-size: 1.3rem; color: white; background: transparent; text-shadow: 0 2px 4px rgba(0,0,0,0.2); caption-side: top; } thead { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } th { padding: 15px 20px; text-align: left; color: white; font-weight: 500; } tbody tr { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } td { padding: 15px 20px; color: #333; } tbody tr:last-child { border-bottom: none; }", + "sandboxCSS": "", + "initialCode": "", + "solution": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Monatliche Verkäufe
MonatUmsatz
Januar12.500 €
Februar14.200 €
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "table", + "message": "Füge ein -Element hinzu" + }, + { + "type": "element_exists", + "value": "caption", + "message": "Füge ein für den Kopfbereich hinzu" + }, + { + "type": "element_exists", + "value": "tbody", + "message": "Füge ein 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 <tfoot> hinzu, um einen Fußbereich für Summen oder Zusammenfassungen zu erstellen. Der Fuß bleibt unten, auch wenn tbody viele Zeilen hat.

Kombiniere alle Abschnitte für eine vollständig strukturierte, zugängliche Tabelle.", + "task": "Erstelle eine vollständige Tabelle:
1. Eine <caption> mit 'Bestellübersicht'
2. Ein <thead> mit Artikel und Preis Überschriften
3. Ein <tbody> mit 2 Artikeln
4. Ein <tfoot> mit einer Summenzeile", + "previewHTML": "", + "previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px 20px; font-weight: 600; font-size: 1.1rem; color: #333; text-align: left; background: #f8f9fa; border-bottom: 2px solid #eee; } th, td { padding: 12px 20px; text-align: left; } thead th { background: #2c3e50; color: white; } tbody td { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } tfoot { background: #ecf0f1; font-weight: 600; } tfoot td { border-top: 2px solid #2c3e50; color: #2c3e50; }", + "sandboxCSS": "", + "initialCode": "", + "solution": "
-Element hinzu" + }, + { + "type": "element_exists", + "value": "thead", + "message": "Füge ein
\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Bestellübersicht
ArtikelPreis
Widget25,00 €
Gadget35,00 €
Gesamt60,00 €
", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "table", + "message": "Füge ein -Element hinzu" + }, + { + "type": "element_exists", + "value": "caption", + "message": "Füge ein -Abschnitt hinzu" + }, + { + "type": "element_exists", + "value": "tbody", + "message": "Füge einen -Abschnitt hinzu" + }, + { + "type": "element_exists", + "value": "tfoot", + "message": "Füge einen -Abschnitt für die Summe hinzu" + }, + { + "type": "element_count", + "value": { "selector": "tbody tr", "min": 2 }, + "message": "Füge mindestens 2 Artikelzeilen in tbody hinzu" + } + ] + } + ] +} diff --git a/lessons/de/31-html-marquee.json b/lessons/de/31-html-marquee.json new file mode 100644 index 0000000..1736ffb --- /dev/null +++ b/lessons/de/31-html-marquee.json @@ -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 <marquee>-Element erstellt Lauftext - ein Klassiker aus dem frühen Web! Obwohl veraltet, funktioniert es noch in den meisten Browsern.

Hinweis: Für produktive Seiten nutze CSS-Animationen. Aber zum Lernen und Spaß haben ist Marquee super!", + "task": "Erstelle ein einfaches Marquee:
1. Füge ein <marquee>-Element hinzu
2. Schreibe Text hinein wie 'Willkommen auf meiner Website!'", + "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": "", + "solution": "Willkommen auf meiner Website!", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "marquee", + "message": "Füge ein -Element hinzu" + } + ] + }, + { + "id": "marquee-direction", + "title": "Richtung & Verhalten", + "description": "Steuere das Marquee mit Attributen:
direction: left, right, up, down
behavior: scroll (Standard), slide (stoppt am Rand), alternate (springt zurück)
scrollamount: Geschwindigkeit (Standard ist 6)", + "task": "Erstelle ein springendes Marquee:
1. Füge ein <marquee>-Element hinzu
2. Setze behavior=\"alternate\" zum Springen
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": "", + "solution": "Hüpf! Hüpf! Hüpf!", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "marquee", + "message": "Füge ein -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!

Denke daran: Das ist veraltetes HTML. Moderne Seiten nutzen CSS-Animationen, aber Marquee ist toll, um Web-Geschichte zu verstehen.", + "task": "Erstelle einen Nachrichtenticker:
1. Ein <marquee> mit direction=\"left\"
2. Setze scrollamount=\"5\" für flüssiges Scrollen
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": "", + "solution": "EILMELDUNG: Das Marquee-Element funktioniert noch in Browsern!", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "marquee", + "message": "Füge ein -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" + } + ] + } + ] +} diff --git a/lessons/de/32-html-svg.json b/lessons/de/32-html-svg.json new file mode 100644 index 0000000..68caf20 --- /dev/null +++ b/lessons/de/32-html-svg.json @@ -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 <svg>-Element ist der Container mit width und height Attributen.

Verwende <circle> mit cx, cy (Mittelpunkt) und r (Radius) zum Zeichnen von Kreisen.", + "task": "Erstelle ein SVG mit einem Kreis:
1. Ein <svg> mit width=\"200\" und height=\"200\"
2. Ein <circle> zentriert bei (100,100) mit Radius 50
3. Füge eine fill-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": "", + "solution": "\n \n", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "svg", + "message": "Füge ein -Element hinzu" + }, + { + "type": "element_exists", + "value": "circle", + "message": "Füge ein -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 <rect> mit x, y, width, height.

Zeichne Linien mit <line> mit x1, y1 (Start) und x2, y2 (Ende). Linien brauchen eine stroke-Farbe!", + "task": "Erstelle ein SVG mit:
1. Einem <svg> (200x150)
2. Einem <rect> an Position (20,20) mit Größe 80x60
3. Einer <line> 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": "", + "solution": "\n \n \n", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "svg", + "message": "Füge ein -Element hinzu" + }, + { + "type": "element_exists", + "value": "rect", + "message": "Füge ein -Element hinzu" + }, + { + "type": "element_exists", + "value": "line", + "message": "Füge ein -Element hinzu" + } + ] + }, + { + "id": "svg-shapes", + "title": "Mehrere Formen", + "description": "Kombiniere Formen, um einfache Grafiken zu erstellen! Füge stroke für Umrisse und stroke-width für die Dicke hinzu.

Verwende fill=\"none\" für hohle Formen. Formen stapeln sich in Reihenfolge - spätere Elemente erscheinen oben.", + "task": "Erstelle ein einfaches Gesicht:
1. Ein <svg> (200x200)
2. Einen großen <circle> für das Gesicht
3. Zwei kleine <circle>-Elemente für Augen
4. Eine <line> 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": "", + "solution": "\n \n \n \n \n", + "previewContainer": "preview-area", + "validations": [ + { + "type": "element_exists", + "value": "svg", + "message": "Füge ein -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 für das Lächeln hinzu" + } + ] + } + ] +} diff --git a/src/config/lessons.de.js b/src/config/lessons.de.js index 91aee4b..b19e720 100644 --- a/src/config/lessons.de.js +++ b/src/config/lessons.de.js @@ -10,12 +10,32 @@ import tailwindConfig from "../../lessons/de/10-tailwind-basics.json"; import htmlElementsConfig from "../../lessons/de/20-html-elements.json"; import htmlFormsBasicConfig from "../../lessons/de/21-html-forms-basic.json"; import htmlFormsValidationConfig from "../../lessons/de/22-html-forms-validation.json"; +import htmlDetailsSummaryConfig from "../../lessons/de/23-html-details-summary.json"; +import htmlProgressMeterConfig from "../../lessons/de/24-html-progress-meter.json"; +import htmlDatalistConfig from "../../lessons/de/25-html-datalist.json"; +import htmlDataAttributesConfig from "../../lessons/de/26-html-data-attributes.json"; +import htmlDialogConfig from "../../lessons/de/27-html-dialog.json"; +import htmlFormsFieldsetConfig from "../../lessons/de/28-html-forms-fieldset.json"; +import htmlFigureConfig from "../../lessons/de/29-html-figure.json"; +import htmlTablesConfig from "../../lessons/de/30-html-tables.json"; +import htmlMarqueeConfig from "../../lessons/de/31-html-marquee.json"; +import htmlSvgConfig from "../../lessons/de/32-html-svg.json"; // Module store const moduleStore = [ htmlElementsConfig, htmlFormsBasicConfig, htmlFormsValidationConfig, + htmlDetailsSummaryConfig, + htmlProgressMeterConfig, + htmlDatalistConfig, + htmlDataAttributesConfig, + htmlDialogConfig, + htmlFormsFieldsetConfig, + htmlFigureConfig, + htmlTablesConfig, + htmlMarqueeConfig, + htmlSvgConfig, basicSelectorsConfig, advancedSelectorsConfig, tailwindConfig diff --git a/src/config/lessons.js b/src/config/lessons.js index b933c0c..8ffce56 100644 --- a/src/config/lessons.js +++ b/src/config/lessons.js @@ -10,12 +10,32 @@ import tailwindConfig from "../../lessons/10-tailwind-basics.json"; import htmlElementsConfig from "../../lessons/20-html-elements.json"; import htmlFormsBasicConfig from "../../lessons/21-html-forms-basic.json"; import htmlFormsValidationConfig from "../../lessons/22-html-forms-validation.json"; +import htmlDetailsSummaryConfig from "../../lessons/23-html-details-summary.json"; +import htmlProgressMeterConfig from "../../lessons/24-html-progress-meter.json"; +import htmlDatalistConfig from "../../lessons/25-html-datalist.json"; +import htmlDataAttributesConfig from "../../lessons/26-html-data-attributes.json"; +import htmlDialogConfig from "../../lessons/27-html-dialog.json"; +import htmlFormsFieldsetConfig from "../../lessons/28-html-forms-fieldset.json"; +import htmlFigureConfig from "../../lessons/29-html-figure.json"; +import htmlTablesConfig from "../../lessons/30-html-tables.json"; +import htmlMarqueeConfig from "../../lessons/31-html-marquee.json"; +import htmlSvgConfig from "../../lessons/32-html-svg.json"; // Module store const moduleStore = [ htmlElementsConfig, htmlFormsBasicConfig, htmlFormsValidationConfig, + htmlDetailsSummaryConfig, + htmlProgressMeterConfig, + htmlDatalistConfig, + htmlDataAttributesConfig, + htmlDialogConfig, + htmlFormsFieldsetConfig, + htmlFigureConfig, + htmlTablesConfig, + htmlMarqueeConfig, + htmlSvgConfig, basicSelectorsConfig, advancedSelectorsConfig, tailwindConfig
-Element hinzu" + }, + { + "type": "element_exists", + "value": "thead", + "message": "Füge einen