fix(i18n): sync all lesson translations with English source

Synchronizes 72 lesson files across 5 languages (de, pl, es, ar, uk) to match
the English source. This ensures code, solutions, and validations are identical
while only title, description, task, and message fields are translated.

Changes include:
- Box model lessons (01-box-model.json)
- Units and variables (05-units-variables.json)
- Transitions and animations (06-transitions-animations.json)
- Responsive design (08-responsive.json)
- HTML elements (20-html-elements.json)
- HTML forms basic and validation (21, 22)
- HTML details/summary, progress/meter (23, 24)
- HTML datalist, dialog, fieldset (25, 27, 28)
- HTML tables and SVG (30, 32)
- HTML marquee (31)
- Welcome module (00-welcome.json)

Fixes validation inconsistencies and removes extra content that exceeded
English source. German translations were largely correct; Polish, Spanish,
Arabic, and Ukrainian required full translations.
This commit is contained in:
2026-01-14 15:39:22 +01:00
parent cc2faa5104
commit 4bed75eb74
72 changed files with 2206 additions and 2611 deletions

View File

@@ -2,18 +2,18 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "box-model",
"title": "CSS Box Model",
"description": "Beherrsche die Grundprinzipien der Raumverwaltung im Webdesign durch das CSS Box-Modell.",
"description": "Beherrsche die Grundprinzipien der Raumverwaltung im Webdesign durch das CSS Box-Modell. Dieses Modul erkundet, wie Inhalt, Padding, Rahmen und Margins zusammenwirken, um Layout-Strukturen zu erstellen.",
"difficulty": "beginner",
"lessons": [
{
"id": "box-model-1",
"title": "Box-Modell Komponenten",
"description": "Das CSS Box-Modell besteht aus vier konzentrischen Schichten: Inhalt (innerste), Padding, Border und Margin (äußerste). Zu verstehen, wie diese Komponenten zusammenwirken, ist essentiell für präzise Layout-Kontrolle.",
"task": "Setze <kbd>padding</kbd> auf <kbd>1rem</kbd>, um Abstand zwischen Inhalt und Rahmen zu schaffen.",
"previewHTML": "<div class=\"box\">Box-Modell Komponenten</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: lavender; border: 2px dashed slategray; }",
"title": "Padding",
"description": "Jedes Element in CSS ist eine Box mit vier Schichten: Inhalt, Padding, Rahmen und Margin. <strong>Padding</strong> schafft Freiraum zwischen deinem Inhalt und dem Rand der Box.<br><br>Ohne Padding drückt sich Text unangenehm gegen Rahmen. Padding macht Inhalte lesbar und visuell ausgewogen.<br><br><pre>.card {\n padding: 1rem;\n}</pre>",
"task": "Diese Profilkarte sieht beengt aus. Füge <kbd>padding: 1rem</kbd> hinzu, damit der Text Platz zum Atmen hat.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
"sandboxCSS": "",
"codePrefix": ".box {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "padding: 1rem;",
@@ -28,56 +28,56 @@
},
{
"id": "box-model-2",
"title": "Rahmen hinzufügen",
"description": "Rahmen umranden ein Element und schaffen visuelle Trennung. Die border-Kurzschreibweise akzeptiert drei Werte: Breite, Stil und Farbe.",
"task": "Setze <kbd>border</kbd> auf <kbd>2px solid darkslategray</kbd>.",
"previewHTML": "<div class=\"box\">Diese Box braucht einen Rahmen</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: mintcream; padding: 1rem; }",
"title": "Borders",
"description": "Rahmen erstellen visuelle Grenzen um Elemente. Die <kbd>border</kbd>-Kurzschreibweise akzeptiert drei Werte: Breite, Stil und Farbe.<br><br>Häufige Stile: <kbd>solid</kbd>, <kbd>dashed</kbd>, <kbd>dotted</kbd>, <kbd>none</kbd>",
"task": "Füge der Karte einen dezenten linken Akzent hinzu mit <kbd>border-left: 4px solid steelblue</kbd>.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
"sandboxCSS": "",
"codePrefix": ".box {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "border: 2px solid darkslategray;",
"solution": "border-left: 4px solid steelblue;",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "border:\\s*2px\\s+solid\\s+darkslategray",
"message": "Setze <kbd>border: 2px solid darkslategray</kbd>",
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
"message": "Setze <kbd>border-left: 4px solid steelblue</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-3",
"title": "Außenabstände hinzufügen",
"description": "Margins schaffen Abstand zwischen Elementen. Anders als Padding (das den inneren Abstand beeinflusst) existiert Margin außerhalb des Rahmens.",
"task": "Setze <kbd>margin</kbd> auf <kbd>1rem</kbd>, um Abstand zum benachbarten Element zu schaffen.",
"previewHTML": "<div class=\"container\"><div class=\"outer\">Diese Box braucht Margins</div><div class=\"neighbor\">Benachbartes Element</div></div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .outer { background-color: plum; padding: 1rem; border: 2px solid orchid; } .neighbor { background-color: lightblue; padding: 1rem; border: 2px solid steelblue; }",
"title": "Margins",
"description": "Margins schaffen Abstand <em>außerhalb</em> des Elements und trennen es von Nachbarn. Während Padding den Inhalt nach innen drückt, drücken Margins andere Elemente weg.",
"task": "Füge Abstand zwischen diesen beiden Profilkarten hinzu mit <kbd>margin-bottom: 1rem</kbd> auf <kbd>.card</kbd>.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article><article class=\"card\"><h3>Alex Rivera</h3><p>UX Designer</p></article>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; border-left: 4px solid steelblue; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
"sandboxCSS": "",
"codePrefix": ".outer {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "margin: 1rem;",
"solution": "margin-bottom: 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "margin", "expected": "1rem" },
"message": "Setze <kbd>margin: 1rem</kbd>"
"value": { "property": "margin-bottom", "expected": "1rem" },
"message": "Setze <kbd>margin-bottom: 1rem</kbd>"
}
]
},
{
"id": "box-model-4",
"title": "Box-Sizing: Border-Box",
"description": "Die box-sizing Eigenschaft bestimmt, wie Elementdimensionen berechnet werden. 'content-box' (Standard) schließt Padding und Border aus, während 'border-box' sie einschließt.",
"task": "Setze <kbd>box-sizing</kbd> auf <kbd>border-box</kbd>, damit Padding und Border in die Breite einbezogen werden.",
"previewHTML": "<div class=\"sizing-demo\"><div class=\"box default\">Content-box (Standard)</div><div class=\"box sized\">Border-box</div></div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .sizing-demo { display: flex; gap: 1rem; } .box { width: 200px; padding: 1rem; border: 4px solid teal; background: lightcyan; } .default { box-sizing: content-box; }",
"title": "Box Sizing",
"description": "Standardmäßig setzt <kbd>width</kbd> nur die Inhaltsbreite. Padding und Rahmen werden addiert. Das verursacht Layout-Probleme.<br><br><kbd>box-sizing: border-box</kbd> bezieht Padding und Rahmen in die Breite ein, was das Sizing vorhersehbar macht. Die meisten Entwickler wenden dies auf alle Elemente an.",
"task": "Beide Karten haben <kbd>width: 200px</kbd>. Die linke nutzt Standard-Sizing (content-box) und wird breiter als erwartet. Korrigiere die rechte Karte mit <kbd>box-sizing: border-box</kbd>.",
"previewHTML": "<div class=\"demo\"><article class=\"card\">Content-box</article><article class=\"card fix\">Border-box</article></div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .demo { display: flex; gap: 1rem; } .card { width: 200px; padding: 1rem; border: 4px solid steelblue; background: white; border-radius: 8px; }",
"sandboxCSS": "",
"codePrefix": ".sized {\n ",
"codePrefix": ".fix {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "box-sizing: border-box;",
@@ -92,87 +92,98 @@
},
{
"id": "box-model-5",
"title": "Margin-Kollaps",
"description": "Wenn zwei vertikale Margins aufeinandertreffen, kollabieren sie zum größeren der beiden Werte statt zu addieren.",
"task": "Setze <kbd>margin-bottom</kbd> auf <kbd>2rem</kbd>. Der Abstand zwischen den Absätzen beträgt 2rem (nicht 3rem) durch Margin-Kollaps.",
"previewHTML": "<div class=\"collapse-demo\"><p class=\"first\">Dieser Absatz hat einen Bottom-Margin.</p><p class=\"second\">Dieser Absatz hat einen Top-Margin von 1rem.</p></div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .collapse-demo { border: 1px solid silver; padding: 8px; background: ghostwhite; } .second { margin-top: 1rem; background: linen; }",
"title": "Padding Shorthand",
"description": "Padding akzeptiert 1-4 Werte:<br>• 1 Wert: alle Seiten<br>• 2 Werte: vertikal | horizontal<br>• 4 Werte: oben | rechts | unten | links",
"task": "Dieser Button braucht mehr horizontalen als vertikalen Platz. Setze <kbd>padding: 8px 1rem</kbd> (8px oben/unten, 1rem links/rechts).",
"previewHTML": "<button class=\"btn\">Follow</button>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .btn { background: steelblue; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem; }",
"sandboxCSS": "",
"codePrefix": ".first {\n ",
"codePrefix": ".btn {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "margin-bottom: 2rem;",
"solution": "padding: 8px 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "margin-bottom", "expected": "2rem" },
"message": "Setze <kbd>margin-bottom: 2rem</kbd>"
"type": "regex",
"value": "padding:\\s*8px\\s+1rem",
"message": "Setze <kbd>padding: 8px 1rem</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-6",
"title": "Margin-Kurzschreibweise",
"description": "Die Margin-Kurzschreibweise kann alle vier Seiten setzen. Zwei Werte setzen vertikale (oben/unten) und horizontale (links/rechts) Margins.",
"task": "Setze <kbd>margin</kbd> auf <kbd>1rem 2rem</kbd> für 1rem oben/unten und 2rem links/rechts.",
"previewHTML": "<div class=\"container\"><div class=\"spaced\">Diese Box braucht Margins: 1rem oben/unten, 2rem links/rechts</div></div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .spaced { background-color: honeydew; border: 2px solid mediumseagreen; padding: 1rem; }",
"title": "Margin Shorthand",
"description": "Margin nutzt das gleiche Kurzschreibweisen-Muster wie Padding. Ein häufiges Muster ist das horizontale Zentrieren von Block-Elementen mit <kbd>margin: 0 auto</kbd>.",
"task": "Zentriere diese Karte horizontal. Setze <kbd>margin: 0 auto</kbd>, um automatisch gleiche links/rechts-Margins zu berechnen.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { width: 250px; background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; border-left: 4px solid steelblue; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
"sandboxCSS": "",
"codePrefix": ".spaced {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "margin: 1rem 2rem;",
"solution": "margin: 0 auto;",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "margin:\\s*1rem\\s+2rem",
"message": "Setze <kbd>margin: 1rem 2rem</kbd>",
"value": "margin:\\s*0\\s+auto",
"message": "Setze <kbd>margin: 0 auto</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-7",
"title": "Padding-Kurzschreibweise",
"description": "Wie Margin erlaubt Padding-Kurzschreibweise das Setzen aller Seiten. Ein einzelner Wert gilt für alle vier Seiten.",
"task": "Setze <kbd>padding</kbd> auf <kbd>2rem</kbd> für gleichmäßiges Padding.",
"previewHTML": "<div class=\"padded\">Diese Box braucht gleichmäßiges Padding</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .padded { background-color: papayawhip; border: 2px solid orange; }",
"title": "Border Radius",
"description": "Obwohl nicht Teil des klassischen Box-Modells, rundet <kbd>border-radius</kbd> die Ecken der Rahmen-Box eines Elements. Verwende <kbd>50%</kbd> bei einem quadratischen Element, um einen Kreis zu erstellen.",
"task": "Mache das Avatar-Bild rund mit <kbd>border-radius: 50%</kbd>.",
"previewHTML": "<article class=\"card\"><img class=\"avatar\" src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='35' r='25' fill='%23666'/%3E%3Ccircle cx='50' cy='90' r='40' fill='%23666'/%3E%3C/svg%3E\" alt=\"Avatar\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; text-align: center; } .avatar { width: 80px; height: 80px; background: #ddd; margin-bottom: 8px; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
"sandboxCSS": "",
"codePrefix": ".padded {\n ",
"codePrefix": ".avatar {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "padding: 2rem;",
"solution": "border-radius: 50%;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "padding", "expected": "2rem" },
"message": "Setze <kbd>padding: 2rem</kbd>"
"value": { "property": "border-radius", "expected": "50%" },
"message": "Setze <kbd>border-radius: 50%</kbd>"
}
]
},
{
"id": "box-model-8",
"title": "Rahmen auf einzelnen Seiten",
"description": "Für feinere Kontrolle können einzelne Seiten mit border-top, border-right, border-bottom oder border-left angesprochen werden.",
"task": "Setze <kbd>border-bottom</kbd> auf <kbd>4px solid dodgerblue</kbd>.",
"previewHTML": "<div class=\"line\">Dieses Element braucht nur einen unteren Rahmen</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .line { padding: 1rem; background-color: aliceblue; }",
"title": "Complete Card",
"description": "Kombinieren wir alles. Diese Benachrichtigungskarte braucht Styling, um professionell auszusehen.",
"task": "Style die Benachrichtigung: füge <kbd>padding: 1rem</kbd>, <kbd>border-left: 4px solid coral</kbd> und <kbd>border-radius: 4px</kbd> hinzu.",
"previewHTML": "<div class=\"alert\"><strong>New message</strong><p>You have 3 unread notifications</p></div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .alert { background: seashell; } .alert strong { color: coral; } .alert p { margin: 4px 0 0; color: #666; }",
"sandboxCSS": "",
"codePrefix": ".line {\n ",
"codePrefix": ".alert {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "border-bottom: 4px solid dodgerblue;",
"solution": "padding: 1rem;\n border-left: 4px solid coral;\n border-radius: 4px;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "padding", "expected": "1rem" },
"message": "Setze <kbd>padding: 1rem</kbd>"
},
{
"type": "regex",
"value": "border-bottom:\\s*4px\\s+solid\\s+dodgerblue",
"message": "Setze <kbd>border-bottom: 4px solid dodgerblue</kbd>",
"value": "border-left:\\s*4px\\s+solid\\s+coral",
"message": "Setze <kbd>border-left: 4px solid coral</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "border-radius", "expected": "4px" },
"message": "Setze <kbd>border-radius: 4px</kbd>"
}
]
}

View File

@@ -7,119 +7,94 @@
"lessons": [
{
"id": "units-1",
"title": "Absolute vs. Relative Einheiten",
"description": "Lerne den Unterschied zwischen px, rem, em, % und vw/vh für flexible, responsive Layouts.",
"task": "Setze die Breite von <kbd>.box</kbd> auf <kbd>80%</kbd> und max-width auf <kbd>37.5rem</kbd>.",
"previewHTML": "<div class=\"box\">Ändere meine Größe!</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { background: #f5f5f5; padding: 1rem; }",
"title": "Relative Units",
"description": "CSS bietet zwei Arten von Einheiten: <em>absolute</em> (wie <kbd>px</kbd>) und <em>relative</em> (wie <kbd>%</kbd> und <kbd>rem</kbd>). Relative Einheiten passen sich ihrem Kontext an und machen Layouts flexibel und zugänglich.<br><br><strong>Häufige relative Einheiten:</strong><br>• <kbd>%</kbd> Relativ zum Elternelement<br>• <kbd>rem</kbd> Relativ zur Root-Schriftgröße (typisch 16px)<br>• <kbd>em</kbd> Relativ zur Schriftgröße des Elements<br><br>Ein häufiges Muster für lesbaren Inhalt: setze <kbd>width: 100%</kbd>, damit es den verfügbaren Platz füllt, dann <kbd>max-width: 40rem</kbd> um die Zeilenlänge für Lesbarkeit zu begrenzen.",
"task": "Dieser Artikeltext läuft auf großen Bildschirmen zu breit. Füge <kbd>max-width: 40rem</kbd> hinzu für optimale Lesebreite.",
"previewHTML": "<article class=\"article\"><h2>The Art of Typography</h2><p>Good typography is invisible. When text is set well, readers absorb information without noticing the design decisions that make it comfortable to read. Line length is crucial—too wide and eyes get lost, too narrow and reading becomes choppy.</p><p>The ideal line length is 45-75 characters per line. At typical font sizes, this works out to roughly 40rem maximum width.</p></article>",
"previewBaseCSS": "body { font-family: Georgia, serif; padding: 1rem; background: #f9f9f9; } .article { background: white; padding: 2rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .article h2 { margin: 0 0 1rem; color: #333; } .article p { margin: 0 0 1rem; line-height: 1.6; color: #444; } .article p:last-child { margin-bottom: 0; }",
"sandboxCSS": "",
"codePrefix": "/* Setze flexible Größen */\n.box {",
"codePrefix": ".article {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: 80%;\n max-width: 37.5rem;",
"codeSuffix": "\n}",
"solution": "max-width: 40rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "width",
"message": "Verwende die <kbd>width</kbd> Eigenschaft",
"options": { "caseSensitive": false }
},
{ "type": "property_value", "value": { "property": "width", "expected": "80%" }, "message": "Setze width auf <kbd>80%</kbd>" },
{
"type": "contains",
"value": "max-width",
"message": "Verwende die <kbd>max-width</kbd> Eigenschaft",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "max-width", "expected": "37.5rem" },
"message": "Setze max-width auf <kbd>37.5rem</kbd>"
"value": { "property": "max-width", "expected": "40rem" },
"message": "Setze <kbd>max-width: 40rem</kbd>"
}
]
},
{
"id": "units-2",
"title": "CSS Custom Properties",
"description": "Definiere und verwende Variablen (--custom properties) wieder, um deine Theme-Werte zu zentralisieren.",
"task": "Erstelle eine <code>--main-color</code> Variable in :root mit <kbd>#6200ee</kbd> und wende sie als Rahmenfarbe auf <kbd>.themed</kbd> an.",
"previewHTML": "<div class=\"themed\">Variablen-Box</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .themed { padding: 1rem; border: 0.125rem solid #ddd; }",
"title": "CSS Variables",
"description": "CSS Custom Properties (Variablen) erlauben dir, wiederverwendbare Werte zu definieren. Definiere sie mit <kbd>--name</kbd> und verwende sie mit <kbd>var(--name)</kbd>. Variablen auf <kbd>:root</kbd> sind überall verfügbar.",
"task": "Definiere <kbd>--brand: steelblue</kbd> in <kbd>:root</kbd>, dann verwende es als <kbd>background</kbd>-Farbe für <kbd>.btn</kbd>.",
"previewHTML": "<div class=\"actions\"><button class=\"btn\">Subscribe</button><button class=\"btn\">Learn More</button></div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .actions { display: flex; gap: 1rem; } .btn { color: white; border: none; padding: 12px 24px; border-radius: 6px; font-size: 1rem; cursor: pointer; background: #ccc; }",
"sandboxCSS": "",
"codePrefix": "/* Definiere und verwende eine CSS-Variable */\n:root {",
"codePrefix": ":root {\n ",
"initialCode": "",
"codeSuffix": "}\n.themed { }",
"solution": " --main-color: #6200ee;\n}\n.themed {\n border-color: var(--main-color);",
"codeSuffix": "\n}\n\n.btn {\n background: var(--brand);\n}",
"solution": "--brand: steelblue;",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "--main-color",
"message": "Definiere <kbd>--main-color</kbd> in :root",
"value": "--brand",
"message": "Definiere die <kbd>--brand</kbd> Variable",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "var(--main-color)",
"message": "Verwende <kbd>var(--main-color)</kbd>",
"value": "steelblue",
"message": "Setze den Wert auf <kbd>steelblue</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "border", "expected": "var(--main-color)" },
"message": "Wende die Variable auf die Rahmenfarbe an",
"options": { "exact": false }
}
]
},
{
"id": "units-3",
"title": "Einheiten-Berechnungen (calc)",
"description": "Verwende die <code>calc()</code> Funktion, um verschiedene Einheiten in einem Ausdruck zu kombinieren.",
"task": "Setze die Breite von <kbd>.sized</kbd> auf <kbd>calc(100% - 2rem)</kbd> und min-height auf <kbd>calc(10vh + 1rem)</kbd>.",
"previewHTML": "<div class=\"sized\">Calc Demo</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sized { background: #e8f5e9; padding: 1rem; }",
"title": "calc() Function",
"description": "Die <kbd>calc()</kbd> Funktion ermöglicht das Mischen verschiedener Einheiten in Berechnungen. Das ist essenziell für Layouts, die feste und flexible Größen kombinieren, wie ein Sidebar-Layout.",
"task": "Der Hauptinhalt soll den verbleibenden Platz nach der 200px Sidebar füllen. Setze <kbd>width: calc(100% - 200px)</kbd> auf <kbd>.main</kbd>.",
"previewHTML": "<div class=\"layout\"><aside class=\"sidebar\">Sidebar<br>Navigation</aside><main class=\"main\"><h2>Main Content</h2><p>This area should fill the remaining width after accounting for the fixed-width sidebar.</p></main></div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .layout { display: flex; gap: 1rem; } .sidebar { width: 200px; background: #1a1a2e; color: white; padding: 1rem; border-radius: 8px; flex-shrink: 0; } .main { background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .main h2 { margin: 0 0 8px; } .main p { margin: 0; color: #666; }",
"sandboxCSS": "",
"codePrefix": "/* Verwende calc für dynamische Größen */\n.sized {",
"codePrefix": ".main {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: calc(100% - 2rem);\n min-height: calc(10vh + 1rem);",
"codeSuffix": "\n}",
"solution": "width: calc(100% - 200px);",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "calc", "message": "Verwende die <kbd>calc()</kbd> Funktion", "options": { "caseSensitive": false } },
{
"type": "regex",
"value": "width:\\s*calc\\(100% - 2rem\\)",
"message": "Width sollte calc(100% - 2rem) sein",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "min-height:\\s*calc\\(10vh \\+ 1rem\\)",
"message": "Min-height sollte calc(10vh + 1rem) sein",
"value": "width:\\s*calc\\(\\s*100%\\s*-\\s*200px\\s*\\)",
"message": "Setze <kbd>width: calc(100% - 200px)</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "units-4",
"title": "Viewport & Responsive Einheiten",
"description": "Steuere Layouts relativ zur Viewport-Größe mit vw, vh und vmin/vmax Einheiten.",
"task": "Gib <kbd>.view</kbd> eine Breite von <kbd>50vw</kbd> und Höhe von <kbd>20vh</kbd>.",
"previewHTML": "<div class=\"view\">Viewport-Box</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .view { background: #ffe0b2; }",
"title": "Viewport Units",
"description": "Viewport-Einheiten dimensionieren Elemente relativ zum Browserfenster:<br>• <kbd>vw</kbd> 1% der Viewport-Breite<br>• <kbd>vh</kbd> 1% der Viewport-Höhe<br><br>Diese sind perfekt für Vollbild-Sektionen wie Hero-Banner.",
"task": "Mache diese Hero-Sektion so hoch wie das Viewport mit <kbd>min-height: 100vh</kbd>.",
"previewHTML": "<section class=\"hero\"><h1>Welcome</h1><p>Scroll down to explore</p></section>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .hero { background: linear-gradient(135deg, #1a1a2e 0%, steelblue 100%); color: white; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 2rem; } .hero h1 { margin: 0 0 1rem; font-size: 2.5rem; } .hero p { margin: 0; opacity: 0.8; }",
"sandboxCSS": "",
"codePrefix": "/* Verwende Viewport-Einheiten */\n.view {",
"codePrefix": ".hero {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: 50vw;\n height: 20vh;",
"codeSuffix": "\n}",
"solution": "min-height: 100vh;",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "vw", "message": "Verwende <kbd>vw</kbd> Einheit", "options": { "caseSensitive": false } },
{ "type": "contains", "value": "vh", "message": "Verwende <kbd>vh</kbd> Einheit", "options": { "caseSensitive": false } },
{ "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Setze width auf <kbd>50vw</kbd>" },
{ "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Setze height auf <kbd>20vh</kbd>" }
{
"type": "property_value",
"value": { "property": "min-height", "expected": "100vh" },
"message": "Setze <kbd>min-height: 100vh</kbd>"
}
]
}
]

View File

@@ -7,13 +7,13 @@
"lessons": [
{
"id": "transitions-1",
"title": "Einfache Transitions",
"title": "Transitions",
"description": "Lerne, wie du <kbd>transition</kbd> auf Eigenschaften anwendest für sanfte Änderungen bei Zustandswechseln.<br><br><pre>transition: property duration;\n/* z.B. transition: background-color 0.3s; */</pre>",
"task": "Füge <kbd>transition: background-color 0.3s</kbd> zu <kbd>.btn</kbd> hinzu, damit die Farbe beim Hover sanft überblendet.",
"previewHTML": "<button class=\"btn\">Hover mich</button>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: #6200ee; color: white; padding: 0.5rem 1rem; border: none; } .btn:hover { background: #3700b3; }",
"task": "Füge <kbd>transition: background-color 0.3s</kbd> hinzu, damit die Farbe beim Hover sanft überblendet.",
"previewHTML": "<button class=\"btn\">Hover Me</button>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: black; color: white; padding: 0.5rem 1rem; border: none; cursor: pointer; } .btn:hover { background: white; color: black; }",
"sandboxCSS": "",
"codePrefix": "/* Füge Transition hinzu */\n.btn {",
"codePrefix": "/* Add transition */\n.btn {",
"initialCode": "",
"codeSuffix": "}",
"solution": " transition: background-color 0.3s;",
@@ -35,13 +35,13 @@
},
{
"id": "transitions-2",
"title": "Transition Timing-Funktionen",
"title": "Timing Funcs",
"description": "Erkunde Easing-Funktionen wie <kbd>ease</kbd>, <kbd>linear</kbd>, <kbd>ease-in</kbd>, <kbd>ease-out</kbd>, um das Animationstempo zu steuern.",
"task": "Setze <kbd>transition-timing-function</kbd> auf <kbd>ease-in-out</kbd> bei <kbd>.btn</kbd>.",
"task": "Setze <kbd>transition-timing-function</kbd> auf <kbd>ease-in-out</kbd>.",
"previewHTML": "<button class=\"btn\">Timing</button>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: #6200ee; color: white; padding: 0.5rem 1rem; border: none; transition: background-color 0.3s; } .btn:hover { background: #03dac6; }",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: navy; color: gold; padding: 0.5rem 1rem; border: none; cursor: pointer; transition: all 0.5s; } .btn:hover { background: gold; color: navy; transform: scale(1.1); }",
"sandboxCSS": "",
"codePrefix": "/* Setze Timing-Funktion */\n.btn {",
"codePrefix": "/* Set timing function */\n.btn {",
"initialCode": "",
"codeSuffix": "}",
"solution": " transition-timing-function: ease-in-out;",
@@ -56,19 +56,19 @@
{
"type": "property_value",
"value": { "property": "transition-timing-function", "expected": "ease-in-out" },
"message": "Setze <kbd>transition-timing-function: ease-in-out</kbd>"
"message": "Setze timing auf <kbd>ease-in-out</kbd>"
}
]
},
{
"id": "transitions-3",
"title": "Keyframe-Animationen Grundlagen",
"title": "Keyframes",
"description": "Erstelle benannte Animationen mit <kbd>@keyframes</kbd> und wende sie mit der <kbd>animation</kbd> Kurzschreibweise an.<br><br><pre>@keyframes bounce {\n 50% { transform: translateY(-20px); }\n}\n.ball {\n animation: bounce 1s infinite;\n}</pre>",
"task": "Definiere bei <kbd>50%</kbd> ein <kbd>transform: translateY(-20px)</kbd> und wende <kbd>animation: bounce 1s infinite</kbd> auf <kbd>.ball</kbd> an.",
"previewHTML": "<div class=\"ball\"></div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: #ff0266; border-radius: 50%; margin: 2rem auto; }",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: crimson; border-radius: 50%; margin: 2rem auto; }",
"sandboxCSS": "",
"codePrefix": "/* Definiere Keyframes und wende Animation an */\n@keyframes bounce {",
"codePrefix": "/* Define keyframes and apply animation */\n@keyframes bounce {",
"initialCode": "",
"codeSuffix": "}\n.ball { }",
"solution": " 50% { transform: translateY(-20px); }\n}\n.ball {\n animation: bounce 1s infinite;",
@@ -102,35 +102,22 @@
},
{
"id": "transitions-4",
"title": "Animations-Eigenschaften im Detail",
"title": "Animation Properties",
"description": "Verfeinere Animationen mit <kbd>animation-delay</kbd>, <kbd>animation-iteration-count</kbd>, <kbd>animation-direction</kbd> und <kbd>animation-fill-mode</kbd>.",
"task": "Wende die <kbd>fade</kbd> Animation auf <kbd>.box</kbd> an mit <kbd>animation-name: fade</kbd>, <kbd>animation-duration: 2s</kbd>, <kbd>animation-delay: 1s</kbd>, <kbd>animation-iteration-count: 2</kbd> und <kbd>animation-fill-mode: forwards</kbd>.",
"previewHTML": "<div class=\"box\">Fade Demo</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { width: 100px; height: 100px; background: #4caf50; margin: 2rem auto; }",
"task": "Wende die <kbd>pulse</kbd> Animation auf <kbd>.box</kbd> an mit <kbd>animation-name: pulse</kbd>, <kbd>animation-duration: 2s</kbd>, <kbd>animation-delay: 1s</kbd>, <kbd>animation-iteration-count: 2</kbd> und <kbd>animation-fill-mode: forwards</kbd>.",
"previewHTML": "<div class=\"box\">Pulse</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { width: 100px; height: 100px; background: black; color: white; display: flex; align-items: center; justify-content: center; margin: 2rem auto; } @keyframes pulse { 0% { background: black; color: white; transform: scale(1); } 50% { background: white; color: black; transform: scale(1.2); } 100% { background: limegreen; color: black; transform: scale(1); } }",
"sandboxCSS": "",
"codePrefix": "/* Definiere fade und setze Eigenschaften */\n@keyframes fade { from { opacity: 0; } to { opacity: 1; } }\n.box {",
"codePrefix": "/* Apply animation properties */\n.box {",
"initialCode": "",
"codeSuffix": "}",
"solution": " animation-name: fade;\n animation-duration: 2s;\n animation-delay: 1s;\n animation-iteration-count: 2;\n animation-fill-mode: forwards;",
"solution": " animation-name: pulse;\n animation-duration: 2s;\n animation-delay: 1s;\n animation-iteration-count: 2;\n animation-fill-mode: forwards;",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "animation-delay",
"message": "Verwende <kbd>animation-delay</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "animation-iteration-count",
"message": "Verwende <kbd>animation-iteration-count</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "animation-fill-mode",
"message": "Verwende <kbd>animation-fill-mode</kbd>",
"options": { "caseSensitive": false }
"type": "property_value",
"value": { "property": "animation-name", "expected": "pulse" },
"message": "Setze <kbd>animation-name: pulse</kbd>"
},
{
"type": "property_value",

View File

@@ -7,13 +7,13 @@
"lessons": [
{
"id": "responsive-1",
"title": "Einführung in Media Queries",
"description": "Verstehe die Syntax und Anwendungsfälle für CSS Media Queries, um Stile bedingt basierend auf Viewport-Eigenschaften anzuwenden.",
"task": "Schreibe eine Media Query, die gilt, wenn der Viewport maximal 600px breit ist, und ändere den Hintergrund von <kbd>.panel</kbd> auf <kbd>lightcoral</kbd>.",
"previewHTML": "<div class=\"panel\">Ändere die Fenstergröße</div>",
"title": "Media Queries",
"description": "Verstehe die Syntax und Anwendungsfälle für CSS Media Queries, um Stile bedingt basierend auf Viewport-Eigenschaften anzuwenden.<br><br><pre>@media (max-width: 600px) {\n .panel {\n background: lightcoral;\n }\n}</pre>",
"task": "Schreibe eine Media Query mit <kbd>@media (max-width: 600px)</kbd>, die den Hintergrund von <kbd>.panel</kbd> auf <kbd>lightcoral</kbd> ändert.",
"previewHTML": "<div class=\"panel\">Resize the window</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .panel { padding: 1rem; background: lightblue; }",
"sandboxCSS": "",
"codePrefix": "/* Füge deine Media Query unten ein */\n",
"codePrefix": "/* Add your media query below */\n",
"initialCode": "",
"codeSuffix": "",
"solution": "@media (max-width: 600px) {\n .panel {\n background: lightcoral;\n }\n}",
@@ -22,7 +22,7 @@
{
"type": "regex",
"value": "@media\\s*\\(max-width:\\s*600px\\)",
"message": "Verwende eine Media Query für max-width: 600px",
"message": "Verwende <kbd>@media (max-width: 600px)</kbd>",
"options": { "caseSensitive": false }
},
{
@@ -31,68 +31,49 @@
"message": "Adressiere <kbd>.panel</kbd> innerhalb der Media Query",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "background",
"message": "Ändere die <kbd>background</kbd> Eigenschaft",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "background", "expected": "lightcoral" },
"message": "Setze background auf <kbd>lightcoral</kbd>",
"message": "Setze <kbd>background: lightcoral</kbd>",
"options": { "exact": false }
}
]
},
{
"id": "responsive-2",
"title": "Flüssige Typografie",
"description": "Verwende relative Einheiten wie vw, damit Schriftgrößen mit der Viewport-Breite skalieren.",
"task": "Setze die font-size von <kbd>.text</kbd> auf <kbd>5vw</kbd>, damit sie sich mit dem Viewport ändert.",
"previewHTML": "<p class=\"text\">Flüssige Typografie</p>",
"title": "Fluid Type",
"description": "Verwende relative Einheiten wie <kbd>vw</kbd>, damit Schriftgrößen mit der Viewport-Breite skalieren.",
"task": "Setze <kbd>font-size: 5vw</kbd>, damit sie sich mit dem Viewport ändert.",
"previewHTML": "<p class=\"text\">Fluid Typography</p>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; }",
"sandboxCSS": "",
"codePrefix": "/* Wende flüssige Schriftgröße an */\n.text {",
"codePrefix": "/* Apply fluid font sizing */\n.text {",
"initialCode": "",
"codeSuffix": "}",
"solution": " font-size: 5vw;",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "font-size",
"message": "Verwende die <kbd>font-size</kbd> Eigenschaft",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "vw",
"message": "Verwende <kbd>vw</kbd> Einheit für flüssige Größe",
"options": { "caseSensitive": false }
},
{ "type": "property_value", "value": { "property": "font-size", "expected": "5vw" }, "message": "Setze font-size auf <kbd>5vw</kbd>" }
{ "type": "property_value", "value": { "property": "font-size", "expected": "5vw" }, "message": "Setze <kbd>font-size: 5vw</kbd>" }
]
},
{
"id": "responsive-3",
"title": "Flexible Raster",
"description": "Kombiniere CSS Grid mit auto-fit oder auto-fill für responsive Spaltenlayouts.",
"task": "Definiere <kbd>.cards</kbd> mit <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd> und einem gap von <kbd>1rem</kbd>.",
"previewHTML": "<div class=\"cards\"><div>1</div><div>2</div><div>3</div><div>4</div></div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .cards > div { background: #d1c4e9; padding: 1rem; }",
"title": "Responsive Grid",
"description": "Kombiniere CSS Grid mit <kbd>auto-fit</kbd> oder <kbd>auto-fill</kbd> für responsive Spaltenlayouts, die automatisch die Anzahl der Spalten basierend auf verfügbarem Platz anpassen.",
"task": "Füge <kbd>display: grid</kbd>, <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd> und <kbd>gap: 1rem</kbd> hinzu.",
"previewHTML": "<section class=\"features\"><article class=\"feature\"><h3>Fast</h3><p>Lightning quick load times</p></article><article class=\"feature\"><h3>Secure</h3><p>Enterprise-grade security</p></article><article class=\"feature\"><h3>Reliable</h3><p>99.9% uptime guaranteed</p></article><article class=\"feature\"><h3>Support</h3><p>24/7 customer service</p></article></section>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .feature { background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .feature h3 { margin: 0 0 8px; color: steelblue; } .feature p { margin: 0; color: #666; font-size: 0.9rem; }",
"sandboxCSS": "",
"codePrefix": "/* Erstelle ein responsives Raster */\n.cards {",
"codePrefix": ".features {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
"codeSuffix": "\n}",
"solution": "display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "grid-template-columns",
"message": "Definiere <kbd>grid-template-columns</kbd>",
"options": { "caseSensitive": false }
"type": "property_value",
"value": { "property": "display", "expected": "grid" },
"message": "Setze <kbd>display: grid</kbd>"
},
{
"type": "regex",
@@ -100,18 +81,22 @@
"message": "Verwende <kbd>repeat(auto-fit, minmax(200px, 1fr))</kbd>",
"options": { "caseSensitive": false }
},
{ "type": "contains", "value": "gap", "message": "Verwende die <kbd>gap</kbd> Eigenschaft", "options": { "caseSensitive": false } }
{
"type": "property_value",
"value": { "property": "gap", "expected": "1rem" },
"message": "Setze <kbd>gap: 1rem</kbd>"
}
]
},
{
"id": "responsive-4",
"title": "Mobile-First Media Queries",
"title": "Mobile-First",
"description": "Verfolge einen Mobile-First-Ansatz: Schreibe Basis-Stile für kleine Bildschirme und erweitere für größere Viewports.",
"task": "Schreibe eine Media Query für min-width 768px, die die Breite von <kbd>.sidebar</kbd> auf <kbd>250px</kbd> setzt.",
"previewHTML": "<aside class=\"sidebar\">Seitenleiste</aside>",
"task": "Schreibe eine Media Query mit <kbd>@media (min-width: 768px)</kbd>, die die Breite von <kbd>.sidebar</kbd> auf <kbd>250px</kbd> setzt.",
"previewHTML": "<aside class=\"sidebar\">Sidebar</aside>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sidebar { background: #c8e6c9; padding: 1rem; }",
"sandboxCSS": "",
"codePrefix": "/* Füge Mobile-First-Erweiterung hinzu */\n",
"codePrefix": "/* Add mobile-first enhancement */\n",
"initialCode": "",
"codeSuffix": "",
"solution": "@media (min-width: 768px) {\n .sidebar {\n width: 250px;\n }\n}",
@@ -120,7 +105,7 @@
{
"type": "regex",
"value": "@media\\s*\\(min-width:\\s*768px\\)",
"message": "Verwende eine Media Query für min-width: 768px",
"message": "Verwende <kbd>@media (min-width: 768px)</kbd>",
"options": { "caseSensitive": false }
},
{
@@ -132,7 +117,7 @@
{
"type": "property_value",
"value": { "property": "width", "expected": "250px" },
"message": "Setze width auf <kbd>250px</kbd>",
"message": "Setze <kbd>width: 250px</kbd>",
"options": { "exact": false }
}
]

View File

@@ -1,21 +1,21 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-forms-validation",
"title": "HTML Validierung",
"description": "Lerne die eingebauten HTML5-Formular-Validierungsattribute kennen",
"title": "Formularvalidierung",
"description": "Verwende die eingebaute HTML5-Validierung für bessere Benutzererfahrung",
"mode": "html",
"difficulty": "intermediate",
"difficulty": "beginner",
"lessons": [
{
"id": "required-fields",
"title": "Pflichtfelder",
"description": "Das <kbd>required</kbd>-Attribut verhindert das Absenden des Formulars, wenn das Feld leer ist.<br><br>Füge es zu jeder Eingabe hinzu, die ausgefüllt werden muss:<br><kbd>&lt;input type=\"text\" required&gt;</kbd><br><br>Der Browser zeigt automatisch eine Validierungsmeldung an.",
"task": "Mache sowohl das Name- als auch das E-Mail-Feld zu Pflichtfeldern, indem du das <kbd>required</kbd>-Attribut hinzufügst.",
"description": "Das <kbd>required</kbd>-Attribut verhindert das Absenden des Formulars, wenn das Feld leer ist. Der Browser zeigt automatisch eine Validierungsmeldung an - kein JavaScript nötig!<br><br>Füge es zu jeder Eingabe hinzu, die ausgefüllt werden muss:<br><kbd>&lt;input type=\"text\" required&gt;</kbd>",
"task": "Mache sowohl das Name- als auch das E-Mail-Feld zu Pflichtfeldern, indem du das <kbd>required</kbd>-Attribut zu jeder Eingabe hinzufügst.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 350px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-of-type { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } input:invalid { border-color: #d32f2f; } button { margin-top: 20px; padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; }",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 320px; } label { display: block; margin-top: 15px; margin-bottom: 5px; font-weight: 500; } label:first-of-type { margin-top: 0; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; box-sizing: border-box; } input:focus { outline: 2px solid steelblue; border-color: transparent; } button { margin-top: 20px; padding: 10px 20px; background: steelblue; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 500; }",
"sandboxCSS": "",
"initialCode": "<form>\n <label for=\"name\">Name: *</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n \n <label for=\"email\">E-Mail: *</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <button type=\"submit\">Absenden</button>\n</form>",
"solution": "<form>\n <label for=\"name\">Name: *</label>\n <input type=\"text\" id=\"name\" name=\"name\" required>\n \n <label for=\"email\">E-Mail: *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <button type=\"submit\">Absenden</button>\n</form>",
"initialCode": "<form>\n <label for=\"name\">Name *</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n \n <label for=\"email\">E-Mail *</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <button type=\"submit\">Absenden</button>\n</form>",
"solution": "<form>\n <label for=\"name\">Name *</label>\n <input type=\"text\" id=\"name\" name=\"name\" required>\n \n <label for=\"email\">E-Mail *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <button type=\"submit\">Absenden</button>\n</form>",
"previewContainer": "preview-area",
"validations": [
{
@@ -29,84 +29,6 @@
"message": "Füge <kbd>required</kbd> zum E-Mail-Feld hinzu"
}
]
},
{
"id": "input-constraints",
"title": "Eingabebeschränkungen",
"description": "Kontrolliere, was Benutzer eingeben können:<br><br><kbd>minlength</kbd> / <kbd>maxlength</kbd> - Textlängenbegrenzung<br><kbd>min</kbd> / <kbd>max</kbd> - Zahlenbereich<br><kbd>pattern</kbd> - Regex-Musterabgleich<br><kbd>placeholder</kbd> - Hinweistext (kein Label!)",
"task": "Füge Validierung zur Passwort-Eingabe hinzu:<br>1. Füge <kbd>minlength=\"8\"</kbd> für die Mindestlänge hinzu<br>2. Füge <kbd>maxlength=\"20\"</kbd> für die Maximallänge hinzu<br>3. Füge <kbd>placeholder=\"Passwort eingeben\"</kbd> als Hinweis hinzu",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 350px; } label { display: block; margin-bottom: 5px; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } input:invalid:not(:placeholder-shown) { border-color: #d32f2f; } small { display: block; font-size: 12px; color: #666; margin-top: 4px; } button { margin-top: 20px; padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; }",
"sandboxCSS": "",
"initialCode": "<form>\n <label for=\"password\">Passwort:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required aria-describedby=\"password-hint\">\n <small id=\"password-hint\">Muss 8-20 Zeichen lang sein</small>\n \n <button type=\"submit\">Konto erstellen</button>\n</form>",
"solution": "<form>\n <label for=\"password\">Passwort:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required minlength=\"8\" maxlength=\"20\" placeholder=\"Passwort eingeben\" aria-describedby=\"password-hint\">\n <small id=\"password-hint\">Muss 8-20 Zeichen lang sein</small>\n \n <button type=\"submit\">Konto erstellen</button>\n</form>",
"previewContainer": "preview-area",
"validations": [
{
"type": "attribute_value",
"value": { "selector": "input[type='password']", "attr": "minlength", "value": "8" },
"message": "Füge <kbd>minlength</kbd>=\"8\" zum Passwort hinzu"
},
{
"type": "attribute_value",
"value": { "selector": "input[type='password']", "attr": "maxlength", "value": "20" },
"message": "Füge <kbd>maxlength</kbd>=\"20\" zum Passwort hinzu"
},
{
"type": "attribute_value",
"value": { "selector": "input[type='password']", "attr": "placeholder", "value": null },
"message": "Füge einen <kbd>placeholder</kbd> hinzu, der andeutet, was einzugeben ist"
}
]
},
{
"id": "complete-registration",
"title": "Vollständiges Registrierungsformular",
"description": "Erstelle ein vollständiges Registrierungsformular mit allen Validierungskonzepten:<br><br>- Pflichtfelder mit * markiert<br>- E-Mail-Validierung (type=\"email\" verwenden)<br>- Passwort mit Längenbeschränkungen<br>- AGB-Checkbox (Pflichtfeld)<br>- Absende-Button",
"task": "Vervollständige das Registrierungsformular. Füge <kbd>required</kbd>-Attribute, passende Eingabetypen und Validierungsbeschränkungen hinzu.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 400px; background: #f5f5f5; padding: 25px; border-radius: 8px; } h2 { margin-top: 0; margin-bottom: 20px; } label { display: block; margin-top: 15px; margin-bottom: 5px; font-weight: 500; } input[type='text'], input[type='email'], input[type='password'] { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; font-size: 14px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; } input[type='checkbox'] { width: auto; margin-right: 8px; vertical-align: middle; } label:has(input[type='checkbox']) { display: flex; align-items: center; font-weight: normal; } button { width: 100%; margin-top: 20px; padding: 12px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: 500; } button:hover { background: #1565c0; }",
"sandboxCSS": "",
"initialCode": "<form>\n <h2>Konto erstellen</h2>\n \n <label for=\"fullname\">Vollständiger Name *</label>\n <input type=\"text\" id=\"fullname\" name=\"fullname\">\n \n <label for=\"email\">E-Mail *</label>\n <input id=\"email\" name=\"email\">\n \n <label for=\"password\">Passwort *</label>\n <input id=\"password\" name=\"password\">\n \n <label>\n <input type=\"checkbox\" id=\"terms\" name=\"terms\">\n Ich stimme den Nutzungsbedingungen zu *\n </label>\n \n <button type=\"submit\">Registrieren</button>\n</form>",
"solution": "<form>\n <h2>Konto erstellen</h2>\n \n <label for=\"fullname\">Vollständiger Name *</label>\n <input type=\"text\" id=\"fullname\" name=\"fullname\" required>\n \n <label for=\"email\">E-Mail *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <label for=\"password\">Passwort *</label>\n <input type=\"password\" id=\"password\" name=\"password\" required minlength=\"8\">\n \n <label>\n <input type=\"checkbox\" id=\"terms\" name=\"terms\" required>\n Ich stimme den Nutzungsbedingungen zu *\n </label>\n \n <button type=\"submit\">Registrieren</button>\n</form>",
"previewContainer": "preview-area",
"validations": [
{
"type": "attribute_value",
"value": { "selector": "#fullname", "attr": "required", "value": true },
"message": "Mache das Feld für den vollständigen Namen zum Pflichtfeld"
},
{
"type": "attribute_value",
"value": { "selector": "#email", "attr": "type", "value": "email" },
"message": "Setze den Eingabetyp für E-Mail auf <kbd>email</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "#email", "attr": "required", "value": true },
"message": "Mache das E-Mail-Feld zum Pflichtfeld"
},
{
"type": "attribute_value",
"value": { "selector": "#password", "attr": "type", "value": "password" },
"message": "Setze den Eingabetyp für Passwort auf <kbd>password</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "#password", "attr": "required", "value": true },
"message": "Mache das Passwort-Feld zum Pflichtfeld"
},
{
"type": "attribute_value",
"value": { "selector": "#password", "attr": "minlength", "value": "8" },
"message": "Füge <kbd>minlength</kbd>=\"8\" zum Passwort hinzu"
},
{
"type": "attribute_value",
"value": { "selector": "#terms", "attr": "required", "value": true },
"message": "Mache die AGB-Checkbox zum Pflichtfeld"
}
]
}
]
}

View File

@@ -44,12 +44,12 @@
"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>&lt;p&gt;</kbd> mit <code>Lädt...</code> hinzu<br>2. Füge ein <kbd>&lt;progress&gt;</kbd> ohne value-Attribut hinzu",
"task": "Erstelle eine Ladeanzeige:<br>1. Füge ein <kbd>&lt;p&gt;</kbd> mit <code>Loading...</code> hinzu<br>2. Füge ein <kbd>&lt;progress&gt;</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": "",
"solution": "<p>Lädt...</p>\n<progress></progress>",
"solution": "<p>Loading...</p>\n<progress></progress>",
"previewContainer": "preview-area",
"validations": [
{
@@ -68,12 +68,12 @@
"id": "meter-gauge",
"title": "Meter-Anzeigen",
"description": "Das <kbd>&lt;meter&gt;</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>&lt;label&gt;</kbd> mit <code>Akku:</code> hinzu<br>2. Füge ein <kbd>&lt;meter&gt;</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>",
"task": "Erstelle eine Akku-Anzeige:<br>1. Füge ein <kbd>&lt;label&gt;</kbd> mit <code>Battery:</code> hinzu<br>2. Füge ein <kbd>&lt;meter&gt;</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": "",
"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>",
"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": [
{
@@ -86,11 +86,31 @@
"value": { "selector": "meter", "attr": "value", "value": "0.8" },
"message": "Setze <kbd>value=</kbd>\"0.8\" beim Meter"
},
{
"type": "attribute_value",
"value": { "selector": "meter", "attr": "min", "value": "0" },
"message": "Setze <kbd>min=</kbd>\"0\" beim Meter"
},
{
"type": "attribute_value",
"value": { "selector": "meter", "attr": "max", "value": "1" },
"message": "Setze <kbd>max=</kbd>\"1\" beim Meter"
},
{
"type": "attribute_value",
"value": { "selector": "meter", "attr": "low", "value": "0.2" },
"message": "Setze <kbd>low=</kbd>\"0.2\", um den niedrigen Schwellenwert zu definieren"
},
{
"type": "attribute_value",
"value": { "selector": "meter", "attr": "high", "value": "0.8" },
"message": "Setze <kbd>high=</kbd>\"0.8\", um den hohen Schwellenwert zu definieren"
},
{
"type": "attribute_value",
"value": { "selector": "meter", "attr": "optimum", "value": "1" },
"message": "Setze <kbd>optimum=</kbd>\"1\", um den optimalen Wert anzugeben"
},
{
"type": "element_exists",
"value": "label",

View File

@@ -2,20 +2,20 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-tables",
"title": "HTML Tabellen",
"description": "Erstelle strukturierte Datentabellen mit Überschriften und Beschriftungen",
"description": "Erstelle strukturierte Datentabellen mit semantischem Markup",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "table-basic",
"title": "Grundlegende Tabellenstruktur",
"description": "Tabellen verwenden <kbd>&lt;table&gt;</kbd> mit <kbd>&lt;tr&gt;</kbd> für Zeilen. In Zeilen nutze <kbd>&lt;th&gt;</kbd> für Überschriften und <kbd>&lt;td&gt;</kbd> für Datenzellen.<br><br>Das <kbd>&lt;caption&gt;</kbd>-Element bietet einen zugänglichen Titelr die Tabelle.",
"task": "Erstelle eine einfache Tabelle mit:<br>1. Einer <kbd>&lt;caption&gt;</kbd> mit <code>Obstpreise</code><br>2. Einer Kopfzeile mit <code>Obst</code> und <code>Preis</code> Spalten<br>3. Mindestens 2 Datenzeilen",
"title": "Datentabellen",
"description": "Tabellen zeigen strukturierte Daten in Zeilen und Spalten. Verwende <kbd>&lt;table&gt;</kbd> als Container, <kbd>&lt;tr&gt;</kbd> für Zeilen, <kbd>&lt;th&gt;</kbd> für Kopfzellen und <kbd>&lt;td&gt;</kbd> für Datenzellen.<br><br>Füge <kbd>&lt;caption&gt;</kbd> hinzu für einen zugänglichen Titel, der den Tabelleninhalt beschreibt.",
"task": "Erstelle eine Preistabelle:<br>1. Eine <kbd>&lt;caption&gt;</kbd> mit <code>Pricing</code><br>2. Eine Kopfzeile mit <code>Plan</code> und <code>Price</code><br>3. Zwei Datenzeilen für Basic ($9) und Pro ($29)",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px; font-weight: 600; font-size: 1.2rem; color: #333; background: #f8f9fa; } th, td { padding: 12px 20px; text-align: left; border-bottom: 1px solid #eee; } th { background: #3498db; color: white; font-weight: 500; } tr:hover { background: #f8f9fa; } tr:last-child td { border-bottom: none; }",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; } table { border-collapse: collapse; width: 100%; max-width: 350px; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 16px; font-weight: 600; font-size: 1.1rem; color: #333; background: #f8f9fa; } th, td { padding: 14px 20px; text-align: left; border-bottom: 1px solid #eee; } th { background: steelblue; color: white; font-weight: 500; } tr:last-child td { border-bottom: none; } tr:hover td { background: #f8f9fa; }",
"sandboxCSS": "",
"initialCode": "",
"solution": "<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>",
"solution": "<table>\n <caption>Pricing</caption>\n <tr>\n <th>Plan</th>\n <th>Price</th>\n </tr>\n <tr>\n <td>Basic</td>\n <td>$9</td>\n </tr>\n <tr>\n <td>Pro</td>\n <td>$29</td>\n </tr>\n</table>",
"previewContainer": "preview-area",
"validations": [
{
@@ -26,100 +26,17 @@
{
"type": "element_exists",
"value": "caption",
"message": "Füge eine <kbd>&lt;caption&gt;</kbd> als Tabellentitel hinzu"
"message": "Füge eine <kbd>&lt;caption&gt;</kbd> für den Tabellentitel hinzu"
},
{
"type": "element_count",
"value": { "selector": "th", "min": 2 },
"message": "Füge mindestens 2 Überschriftszellen (th) hinzu"
"message": "Füge Kopfzellen (<kbd>&lt;th&gt;</kbd>) für Plan und Price 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>&lt;thead&gt;</kbd> zum Gruppieren von Kopfzeilen und <kbd>&lt;tbody&gt;</kbd> zum Gruppieren von Datenzeilen. Das hilft Browsern und Hilfstechnologien, die Tabellenstruktur zu verstehen.<br><br>Du kannst auch <kbd>&lt;tfoot&gt;</kbd> für Fußzeilen wie Summen verwenden.",
"task": "Erstelle eine strukturierte Tabelle:<br>1. Eine <kbd>&lt;caption&gt;</kbd> mit <code>Monatliche Verkäufe</code><br>2. Ein <kbd>&lt;thead&gt;</kbd> mit Monat und Umsatz Überschriften<br>3. Ein <kbd>&lt;tbody&gt;</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": "",
"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 <kbd>&lt;table&gt;</kbd>-Element hinzu"
},
{
"type": "element_exists",
"value": "caption",
"message": "Füge ein <kbd>&lt;caption&gt;</kbd>-Element hinzu"
},
{
"type": "element_exists",
"value": "thead",
"message": "Füge ein <kbd>&lt;thead&gt;</kbd> für den Kopfbereich hinzu"
},
{
"type": "element_exists",
"value": "tbody",
"message": "Füge ein <kbd>&lt;tbody&gt;</kbd> 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>&lt;tfoot&gt;</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>&lt;caption&gt;</kbd> mit <code>Bestellübersicht</code><br>2. Ein <kbd>&lt;thead&gt;</kbd> mit Artikel und Preis Überschriften<br>3. Ein <kbd>&lt;tbody&gt;</kbd> mit 2 Artikeln<br>4. Ein <kbd>&lt;tfoot&gt;</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": "",
"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 <kbd>&lt;table&gt;</kbd>-Element hinzu"
},
{
"type": "element_exists",
"value": "caption",
"message": "Füge ein <kbd>&lt;caption&gt;</kbd>-Element hinzu"
},
{
"type": "element_exists",
"value": "thead",
"message": "Füge einen <kbd>&lt;thead&gt;</kbd>-Abschnitt hinzu"
},
{
"type": "element_exists",
"value": "tbody",
"message": "Füge einen <kbd>&lt;tbody&gt;</kbd>-Abschnitt hinzu"
},
{
"type": "element_exists",
"value": "tfoot",
"message": "Füge einen <kbd>&lt;tfoot&gt;</kbd>-Abschnitt für die Summe hinzu"
},
{
"type": "element_count",
"value": { "selector": "tbody tr", "min": 2 },
"message": "Füge mindestens 2 Artikelzeilen in tbody hinzu"
"message": "Füge 3 Zeilen hinzu (1 Kopf + 2 Datenzeilen)"
}
]
}

View File

@@ -15,7 +15,7 @@
"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": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"50\" fill=\"#3498db\" />\n</svg>",
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"50\" fill=\"steelblue\" />\n</svg>",
"previewContainer": "preview-area",
"validations": [
{
@@ -28,6 +28,16 @@
"value": "circle",
"message": "Füge ein <kbd>&lt;circle&gt;</kbd>-Element in das SVG ein"
},
{
"type": "attribute_value",
"value": { "selector": "svg", "attr": "width", "value": "200" },
"message": "Setze <kbd>width=</kbd>\"200\" beim SVG"
},
{
"type": "attribute_value",
"value": { "selector": "svg", "attr": "height", "value": "200" },
"message": "Setze <kbd>height=</kbd>\"200\" beim SVG"
},
{
"type": "attribute_value",
"value": { "selector": "circle", "attr": "cx", "value": "100" },
@@ -37,6 +47,11 @@
"type": "attribute_value",
"value": { "selector": "circle", "attr": "cy", "value": "100" },
"message": "Setze <kbd>cy=</kbd>\"100\" für das vertikale Zentrum"
},
{
"type": "attribute_value",
"value": { "selector": "circle", "attr": "r", "value": "50" },
"message": "Setze <kbd>r=</kbd>\"50\" für den Radius"
}
]
},
@@ -49,7 +64,7 @@
"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": "<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>",
"solution": "<svg width=\"200\" height=\"150\">\n <rect x=\"20\" y=\"20\" width=\"80\" height=\"60\" fill=\"tomato\" />\n <line x1=\"120\" y1=\"30\" x2=\"180\" y2=\"90\" stroke=\"slategray\" stroke-width=\"3\" />\n</svg>",
"previewContainer": "preview-area",
"validations": [
{
@@ -66,6 +81,61 @@
"type": "element_exists",
"value": "line",
"message": "Füge ein <kbd>&lt;line&gt;</kbd>-Element hinzu"
},
{
"type": "attribute_value",
"value": { "selector": "svg", "attr": "width", "value": "200" },
"message": "Setze <kbd>width=</kbd>\"200\" beim SVG"
},
{
"type": "attribute_value",
"value": { "selector": "svg", "attr": "height", "value": "150" },
"message": "Setze <kbd>height=</kbd>\"150\" beim SVG"
},
{
"type": "attribute_value",
"value": { "selector": "rect", "attr": "x", "value": "20" },
"message": "Setze <kbd>x=</kbd>\"20\" beim rect"
},
{
"type": "attribute_value",
"value": { "selector": "rect", "attr": "y", "value": "20" },
"message": "Setze <kbd>y=</kbd>\"20\" beim rect"
},
{
"type": "attribute_value",
"value": { "selector": "rect", "attr": "width", "value": "80" },
"message": "Setze <kbd>width=</kbd>\"80\" beim rect"
},
{
"type": "attribute_value",
"value": { "selector": "rect", "attr": "height", "value": "60" },
"message": "Setze <kbd>height=</kbd>\"60\" beim rect"
},
{
"type": "attribute_value",
"value": { "selector": "line", "attr": "x1", "value": "120" },
"message": "Setze <kbd>x1=</kbd>\"120\" bei der line"
},
{
"type": "attribute_value",
"value": { "selector": "line", "attr": "y1", "value": "30" },
"message": "Setze <kbd>y1=</kbd>\"30\" bei der line"
},
{
"type": "attribute_value",
"value": { "selector": "line", "attr": "x2", "value": "180" },
"message": "Setze <kbd>x2=</kbd>\"180\" bei der line"
},
{
"type": "attribute_value",
"value": { "selector": "line", "attr": "y2", "value": "90" },
"message": "Setze <kbd>y2=</kbd>\"90\" bei der line"
},
{
"type": "contains",
"value": "stroke",
"message": "Füge eine <kbd>stroke</kbd>-Farbe zur line hinzu"
}
]
},
@@ -78,7 +148,7 @@
"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": "<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>",
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"80\" fill=\"gold\" stroke=\"orange\" stroke-width=\"4\" />\n <circle cx=\"70\" cy=\"80\" r=\"10\" fill=\"darkslategray\" />\n <circle cx=\"130\" cy=\"80\" r=\"10\" fill=\"darkslategray\" />\n <line x1=\"60\" y1=\"130\" x2=\"140\" y2=\"130\" stroke=\"darkslategray\" stroke-width=\"4\" stroke-linecap=\"round\" />\n</svg>",
"previewContainer": "preview-area",
"validations": [
{