feat: add CSS lessons matching lecture content (box model, flexbox, responsive, transitions, variables)

- Add 5 CSS modules to lessons.js config (EN)
- Create German translations for all 5 CSS modules
- Add CSS modules to lessons.de.js config
- Fix test to use toBeGreaterThanOrEqual for module count
This commit is contained in:
2025-12-29 15:26:59 +01:00
parent 2944b769e5
commit 6ea81a67e9
8 changed files with 805 additions and 2 deletions

View File

@@ -0,0 +1,226 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "box-model",
"title": "Padding, Borders und Margins",
"description": "Beherrsche die Grundprinzipien der Raumverwaltung im Webdesign durch das CSS Box-Modell.",
"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": "Füge dem Box-Element ein Padding von '1.25rem' hinzu, um Abstand zwischen Inhalt und Rahmen zu schaffen.",
"previewHTML": "<div class=\"box\">Box-Modell Komponenten</div>",
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1rem; } .box { background-color: #f0f0f0; border: 0.125rem dashed #aaa; }",
"sandboxCSS": "",
"codePrefix": "/* Füge Padding zum Box-Element hinzu */\n.box {\n /* Füge deinen Code unten ein */\n ",
"initialCode": "",
"codeSuffix": "\n}",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "padding",
"message": "Verwende die 'padding' Eigenschaft",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "padding", "expected": "1.25rem" },
"message": "Setze padding auf genau '1.25rem'"
}
]
},
{
"id": "box-model-2",
"title": "Rahmen hinzufügen",
"description": "Rahmen umranden ein Element und schaffen visuelle Trennung. CSS erlaubt die Kontrolle von Dicke, Stil (solid, dashed, dotted, etc.) und Farbe.",
"task": "Füge einen durchgezogenen Rahmen mit Dicke '0.125rem' und Farbe '#333' hinzu. Die border-Eigenschaft akzeptiert drei Werte: Breite, Stil und Farbe.",
"previewHTML": "<div class=\"box\">Diese Box braucht einen Rahmen</div>",
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1rem; } .box { background-color: #f0f0f0; padding: 1.25rem; }",
"sandboxCSS": "",
"codePrefix": "/* Füge einen Rahmen zur Box hinzu */\n.box {\n /* Füge deinen Code unten ein */\n ",
"initialCode": "",
"codeSuffix": "\n}",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "border",
"message": "Verwende die 'border' Eigenschaft",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "border:\\s*0.125rem\\s+solid\\s+#333",
"message": "Setze border auf '0.125rem solid #333'",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "solid",
"message": "Verwende 'solid' als Rahmenstil",
"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": "Füge dem Box-Element einen Margin von '1rem' hinzu, um Abstand zu benachbarten Elementen zu schaffen.",
"previewHTML": "<div class=\"container\"><div class=\"margin-box\">Diese Box braucht Margins</div><div class=\"neighbor\">Benachbartes Element</div></div>",
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1rem; } .container { background-color: #f8f8f8; padding: 0.5rem; } .margin-box { background-color: #d1c4e9; padding: 1rem; border: 0.125rem solid #7e57c2; } .neighbor { background-color: #bbdefb; padding: 1rem; border: 0.125rem solid #42a5f5; }",
"sandboxCSS": "",
"codePrefix": "/* Füge Margin zur Box hinzu */\n.margin-box {\n /* Füge deinen Code unten ein */\n ",
"initialCode": "",
"codeSuffix": "\n}",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "margin",
"message": "Verwende die 'margin' Eigenschaft",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "margin", "expected": "1rem" },
"message": "Setze margin auf '1rem'"
}
]
},
{
"id": "box-model-4",
"title": "Box-Sizing: Content-Box vs. 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 box-sizing auf 'border-box'. Dadurch werden Padding und Border in die angegebene Breite und Höhe einbezogen.",
"previewHTML": "<div class=\"sizing-demo\"><div class=\"box content-box\">Content-box (Standard)</div><div class=\"box border-box\">Border-box</div></div>",
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1rem; } .sizing-demo { display: flex; gap: 1rem; } .box { width: 200px; padding: 1rem; border: 0.25rem solid #333; background: #f5f5f5; } .content-box { box-sizing: content-box; } .border-box { /* Dein Code hier */ }",
"sandboxCSS": "",
"codePrefix": "/* Setze die box-sizing Eigenschaft */\n.border-box {\n /* Füge deinen Code unten ein */\n ",
"initialCode": "",
"codeSuffix": "\n}",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "box-sizing",
"message": "Verwende die 'box-sizing' Eigenschaft",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "box-sizing", "expected": "border-box" },
"message": "Setze box-sizing auf 'border-box'"
}
]
},
{
"id": "box-model-5",
"title": "Margin-Kollaps",
"description": "Ein wichtiges Verhalten des Box-Modells: Wenn zwei vertikale Margins aufeinandertreffen, kollabieren sie zum größeren der beiden Werte.",
"task": "Füge dem ersten Absatz einen bottom-margin von '2rem' hinzu. Der Abstand zwischen den Absätzen beträgt 2rem (nicht 3rem) - das ist 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, -apple-system, sans-serif; padding: 1rem; } .collapse-demo { border: 0.0625rem solid #ddd; padding: 0.5rem; background: #f9f9f9; } .second { margin-top: 1rem; background: #f0f0f0; }",
"sandboxCSS": "",
"codePrefix": "/* Füge Margin hinzu, um Margin-Kollaps zu beobachten */\n.first {\n /* Füge deinen Code unten ein */\n ",
"initialCode": "",
"codeSuffix": "\n}",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "margin-bottom",
"message": "Verwende die 'margin-bottom' Eigenschaft",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "margin-bottom", "expected": "2rem" },
"message": "Setze margin-bottom auf '2rem'"
}
]
},
{
"id": "box-model-6",
"title": "Margin-Kurzschreibweise",
"description": "CSS bietet Kurzschreibweisen, um mehrere Eigenschaften gleichzeitig zu setzen. Die Margin-Kurzschreibweise setzt alle vier Seiten (oben, rechts, unten, links) im Uhrzeigersinn.",
"task": "Verwende die Margin-Kurzschreibweise, um Top/Bottom auf '1rem' und Left/Right auf '2rem' zu setzen.",
"previewHTML": "<div class=\"container\"><div class=\"shorthand-box\">Diese Box braucht Margins: 1rem oben/unten, 2rem links/rechts</div></div>",
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1rem; } .container { background-color: #f5f5f5; padding: 0.5rem; } .shorthand-box { background-color: #e8f5e9; border: 0.125rem solid #66bb6a; padding: 1rem; }",
"sandboxCSS": "",
"codePrefix": "/* Verwende die Margin-Kurzschreibweise */\n.shorthand-box {\n /* Füge deinen Code unten ein */\n ",
"initialCode": "",
"codeSuffix": "\n}",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "margin",
"message": "Verwende die 'margin' Eigenschaft",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "margin:\\s*1rem\\s+2rem",
"message": "Verwende 'margin: 1rem 2rem' für vertikale und horizontale Margins",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-7",
"title": "Padding-Kurzschreibweise",
"description": "Ähnlich wie Margin erlaubt Padding-Kurzschreibweise das Setzen aller vier Seiten. Die Syntax folgt dem gleichen Muster: oben, rechts, unten, links (TRouBLe).",
"task": "Verwende Padding-Kurzschreibweise, um alle Seiten auf '1.5rem' zu setzen. Ein einzelner Wert gilt für alle vier Seiten.",
"previewHTML": "<div class=\"padding-box\">Diese Box braucht gleichmäßiges Padding</div>",
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1rem; } .padding-box { background-color: #fff3e0; border: 0.125rem solid #ff9800; }",
"sandboxCSS": "",
"codePrefix": "/* Verwende die Padding-Kurzschreibweise */\n.padding-box {\n /* Füge deinen Code unten ein */\n ",
"initialCode": "",
"codeSuffix": "\n}",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "padding",
"message": "Verwende die 'padding' Eigenschaft",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "padding", "expected": "1.5rem" },
"message": "Setze padding auf '1.5rem' für alle Seiten"
}
]
},
{
"id": "box-model-8",
"title": "Border-Kurzschreibweise und Einzeleigenschaften",
"description": "Border-Eigenschaften können einzeln (border-width, border-style, border-color) oder als Kurzschreibweise gesetzt werden. Für noch mehr Kontrolle können einzelne Seiten angesprochen werden.",
"task": "Setze nur den unteren Rahmen auf '0.25rem solid #2196f3'. Verwende border-bottom statt der allgemeinen border-Eigenschaft.",
"previewHTML": "<div class=\"border-demo\">Dieses Element braucht nur einen unteren Rahmen</div>",
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1rem; } .border-demo { padding: 1rem; background-color: #e3f2fd; }",
"sandboxCSS": "",
"codePrefix": "/* Füge nur einen unteren Rahmen hinzu */\n.border-demo {\n /* Füge deinen Code unten ein */\n ",
"initialCode": "",
"codeSuffix": "\n}",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "border-bottom",
"message": "Verwende die 'border-bottom' Eigenschaft",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "border-bottom:\\s*0.25rem\\s+solid\\s+#2196f3",
"message": "Setze border-bottom auf '0.25rem solid #2196f3'",
"options": { "caseSensitive": false }
}
]
}
]
}

View File

@@ -0,0 +1,98 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "units-variables",
"title": "Einheiten, var() und calc()",
"description": "Verstehe die Vielfalt der CSS-Maßeinheiten und wie du Custom Properties für wartbare Stile definierst und verwendest.",
"difficulty": "beginner",
"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 '.unit-box' auf 80% und max-width auf 37.5rem.",
"previewHTML": "<div class=\"unit-box\">Ändere meine Größe!</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .unit-box { background: #f5f5f5; padding: 1rem; }",
"sandboxCSS": "",
"codePrefix": "/* Setze flexible Größen */\n.unit-box {",
"initialCode": "",
"codeSuffix": "}",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "width", "message": "Verwende die 'width' Eigenschaft", "options": { "caseSensitive": false } },
{ "type": "property_value", "value": { "property": "width", "expected": "80%" }, "message": "Setze width auf '80%'" },
{ "type": "contains", "value": "max-width", "message": "Verwende die 'max-width' Eigenschaft", "options": { "caseSensitive": false } },
{ "type": "property_value", "value": { "property": "max-width", "expected": "37.5rem" }, "message": "Setze max-width auf '37.5rem'" }
]
},
{
"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 #6200ee und wende sie als Rahmenfarbe auf '.var-box' an.",
"previewHTML": "<div class=\"var-box\">Variablen-Box</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .var-box { padding: 1rem; border: 0.125rem solid #ddd; }",
"sandboxCSS": "",
"codePrefix": "/* Definiere und verwende eine CSS-Variable */\n:root {",
"initialCode": "",
"codeSuffix": "}\n.var-box { }",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "--main-color", "message": "Definiere '--main-color' in :root", "options": { "caseSensitive": false } },
{ "type": "contains", "value": "var(--main-color)", "message": "Verwende var(--main-color)", "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 '.calc-box' auf calc(100% - 2rem) und min-height auf calc(10vh + 1rem).",
"previewHTML": "<div class=\"calc-box\">Calc Demo</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .calc-box { background: #e8f5e9; padding: 1rem; }",
"sandboxCSS": "",
"codePrefix": "/* Verwende calc für dynamische Größen */\n.calc-box {",
"initialCode": "",
"codeSuffix": "}",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "calc", "message": "Verwende die 'calc()' 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",
"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 '.viewport-box' eine Breite von 50vw und Höhe von 20vh.",
"previewHTML": "<div class=\"viewport-box\">Viewport-Box</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .viewport-box { background: #ffe0b2; }",
"sandboxCSS": "",
"codePrefix": "/* Verwende Viewport-Einheiten */\n.viewport-box {",
"initialCode": "",
"codeSuffix": "}",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "vw", "message": "Verwende 'vw' Einheit", "options": { "caseSensitive": false } },
{ "type": "contains", "value": "vh", "message": "Verwende 'vh' Einheit", "options": { "caseSensitive": false } },
{ "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Setze width auf '50vw'" },
{ "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Setze height auf '20vh'" }
]
}
]
}

View File

@@ -0,0 +1,131 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "transitions-animations",
"title": "Transitions & Animationen",
"description": "Bringe Interaktivität in dein UI durch sanfte Eigenschaftsübergänge und Keyframe-gesteuerte Animationen.",
"difficulty": "beginner",
"lessons": [
{
"id": "transitions-1",
"title": "Einfache Transitions",
"description": "Lerne, wie du <code>transition</code> auf Eigenschaften anwendest für sanfte Änderungen bei Zustandswechseln.",
"task": "Füge eine Hover-Transition auf einen Button hinzu, sodass seine Hintergrundfarbe über 0.3s ü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; }",
"sandboxCSS": "",
"codePrefix": "/* Füge Transition hinzu */\n.btn {",
"initialCode": "",
"codeSuffix": "}",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "transition", "message": "Verwende die 'transition' Eigenschaft", "options": { "caseSensitive": false } },
{
"type": "regex",
"value": "transition:\\s*background-color\\s*0\\.3s",
"message": "Transition background-color über 0.3s",
"options": { "caseSensitive": false }
}
]
},
{
"id": "transitions-2",
"title": "Transition Timing-Funktionen",
"description": "Erkunde Easing-Funktionen wie ease, linear, ease-in, ease-out, um das Animationstempo zu steuern.",
"task": "Ändere den Button, um 'ease-in-out' Timing für seine Transition zu verwenden.",
"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; }",
"sandboxCSS": "",
"codePrefix": "/* Setze Timing-Funktion */\n.btn {",
"initialCode": "",
"codeSuffix": "}",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "transition-timing-function",
"message": "Verwende 'transition-timing-function'",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "transition-timing-function", "expected": "ease-in-out" },
"message": "Setze Timing auf 'ease-in-out'"
}
]
},
{
"id": "transitions-3",
"title": "Keyframe-Animationen Grundlagen",
"description": "Erstelle benannte Animationen mit <code>@keyframes</code> und wende sie mit der <code>animation</code> Kurzschreibweise an.",
"task": "Definiere ein Keyframe namens 'bounce', das ein Element bei 50% um 20px nach oben bewegt, und wende es auf '.ball' über 1s infinite 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; }",
"sandboxCSS": "",
"codePrefix": "/* Definiere Keyframes und wende Animation an */\n@keyframes bounce {",
"initialCode": "",
"codeSuffix": "}\n.ball { }",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "@keyframes bounce", "message": "Definiere '@keyframes bounce'", "options": { "caseSensitive": false } },
{
"type": "regex",
"value": "50%.*transform: translateY\\(-20px\\)",
"message": "Bei 50%, bewege um 20px nach oben",
"options": { "caseSensitive": false }
},
{ "type": "contains", "value": "animation", "message": "Verwende 'animation' Eigenschaft auf .ball", "options": { "caseSensitive": false } },
{
"type": "regex",
"value": "animation:.*bounce.*1s.*infinite",
"message": "Wende 'bounce 1s infinite' an",
"options": { "caseSensitive": false }
}
]
},
{
"id": "transitions-4",
"title": "Animations-Eigenschaften im Detail",
"description": "Verfeinere Animationen mit delay, iteration-count, direction und fill-mode.",
"task": "Animiere '.box' mit einem 'fade' Keyframe über 2s, Verzögerung 1s, zweimalige Ausführung und bleibe danach sichtbar.",
"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; }",
"sandboxCSS": "",
"codePrefix": "/* Definiere fade und setze Eigenschaften */\n@keyframes fade { from { opacity: 0; } to { opacity: 1; } }\n.box {",
"initialCode": "",
"codeSuffix": "}",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "animation-delay",
"message": "Verwende 'animation-delay' Eigenschaft",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "animation-iteration-count",
"message": "Verwende 'animation-iteration-count' Eigenschaft",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "animation-fill-mode",
"message": "Verwende 'animation-fill-mode' Eigenschaft",
"options": { "caseSensitive": false }
},
{ "type": "property_value", "value": { "property": "animation-duration", "expected": "2s" }, "message": "Duration sollte 2s sein" },
{ "type": "property_value", "value": { "property": "animation-delay", "expected": "1s" }, "message": "Delay sollte 1s sein" },
{
"type": "property_value",
"value": { "property": "animation-iteration-count", "expected": "2" },
"message": "Iteration-count sollte 2 sein"
},
{
"type": "property_value",
"value": { "property": "animation-fill-mode", "expected": "forwards" },
"message": "Fill-mode sollte forwards sein"
}
]
}
]
}

View File

@@ -0,0 +1,117 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "responsive-design",
"title": "Responsive Design & Media Queries",
"description": "Passe deine Layouts an verschiedene Bildschirmgrößen an mit Media Queries und flüssigen Design-Techniken.",
"difficulty": "intermediate",
"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 '.responsive-box' auf lightcoral.",
"previewHTML": "<div class=\"responsive-box\">Ändere die Fenstergröße</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .responsive-box { padding: 1rem; background: lightblue; }",
"sandboxCSS": "",
"codePrefix": "/* Füge deine Media Query unten ein */\n",
"initialCode": "",
"codeSuffix": "",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "@media\\s*\\(max-width:\\s*600px\\)",
"message": "Verwende eine Media Query für max-width: 600px",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": ".responsive-box",
"message": "Adressiere '.responsive-box' innerhalb der Media Query",
"options": { "caseSensitive": false }
},
{ "type": "contains", "value": "background", "message": "Ändere die 'background' Eigenschaft", "options": { "caseSensitive": false } },
{
"type": "property_value",
"value": { "property": "background", "expected": "lightcoral" },
"message": "Setze background auf 'lightcoral'",
"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 '.fluid-text' auf 5vw, damit sie sich mit dem Viewport ändert.",
"previewHTML": "<p class=\"fluid-text\">Flüssige Typografie</p>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; }",
"sandboxCSS": "",
"codePrefix": "/* Wende flüssige Schriftgröße an */\n.fluid-text {",
"initialCode": "",
"codeSuffix": "}",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "font-size", "message": "Verwende die 'font-size' Eigenschaft", "options": { "caseSensitive": false } },
{ "type": "contains", "value": "vw", "message": "Verwende 'vw' Einheit für flüssige Größe", "options": { "caseSensitive": false } },
{ "type": "property_value", "value": { "property": "font-size", "expected": "5vw" }, "message": "Setze font-size auf '5vw'" }
]
},
{
"id": "responsive-3",
"title": "Flexible Raster",
"description": "Kombiniere CSS Grid mit auto-fit oder auto-fill für responsive Spaltenlayouts.",
"task": "Definiere '.grid-responsive' mit grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); und einem gap von 1rem.",
"previewHTML": "<div class=\"grid-responsive\"><div>1</div><div>2</div><div>3</div><div>4</div></div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .grid-responsive > div { background: #d1c4e9; padding: 1rem; }",
"sandboxCSS": "",
"codePrefix": "/* Erstelle ein responsives Raster */\n.grid-responsive {",
"initialCode": "",
"codeSuffix": "}",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "grid-template-columns",
"message": "Definiere 'grid-template-columns'",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "repeat\\(auto-fit,\\s*minmax\\(200px,\\s*1fr\\)\\)",
"message": "Verwende repeat(auto-fit, minmax(200px, 1fr))",
"options": { "caseSensitive": false }
},
{ "type": "contains", "value": "gap", "message": "Verwende die 'gap' Eigenschaft", "options": { "caseSensitive": false } }
]
},
{
"id": "responsive-4",
"title": "Mobile-First Media Queries",
"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 '.sidebar' auf 250px setzt.",
"previewHTML": "<aside class=\"sidebar\">Seitenleiste</aside>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sidebar { background: #c8e6c9; padding: 1rem; }",
"sandboxCSS": "",
"codePrefix": "/* Füge Mobile-First-Erweiterung hinzu */\n",
"initialCode": "",
"codeSuffix": "",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "@media\\s*\\(min-width:\\s*768px\\)",
"message": "Verwende eine Media Query für min-width: 768px",
"options": { "caseSensitive": false }
},
{ "type": "contains", "value": ".sidebar", "message": "Adressiere '.sidebar' in der Media Query", "options": { "caseSensitive": false } },
{
"type": "property_value",
"value": { "property": "width", "expected": "250px" },
"message": "Setze width auf '250px'",
"options": { "exact": false }
}
]
}
]
}

207
lessons/de/flexbox.json Normal file
View File

@@ -0,0 +1,207 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "flexbox",
"title": "CSS Flexbox",
"description": "Beherrsche das flexible Box-Layout-Modell für moderne responsive Designs",
"difficulty": "intermediate",
"lessons": [
{
"id": "flexbox-1",
"title": "Flexbox Container Grundlagen",
"description": "Lerne, wie du einen Flex-Container erstellst und die Haupt- und Querachse verstehst.",
"task": "Wandle das übergeordnete div in einen Flex-Container um und stelle es auf Reihenanzeige (Standard).",
"previewHTML": "<div class='flex-container'><div class='box'>1</div><div class='box'>2</div><div class='box'>3</div></div>",
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1.25rem; } .box { background-color: #3498db; color: white; padding: 1.25rem; margin: 0.5rem; text-align: center; font-weight: bold; }",
"sandboxCSS": ".flex-container { border: 0.125rem dashed #ccc; padding: 1rem; }",
"codePrefix": "/* Wandle den Container in Flexbox um */\n",
"initialCode": "",
"codeSuffix": "",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": ".flex-container",
"message": "Verwende den '.flex-container' Klassenselektor",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "display",
"message": "Verwende die 'display' Eigenschaft",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "display", "expected": "flex" },
"message": "Setze display auf 'flex'",
"options": { "exact": true }
}
]
},
{
"id": "flexbox-2",
"title": "Flex-Richtung und Umbruch",
"description": "Steuere die Richtung und den Umbruch von Flex-Elementen innerhalb eines Containers.",
"task": "Stelle den Flex-Container auf Spaltenanzeige ein und erlaube bei Bedarf Umbrüche.",
"previewHTML": "<div class='flex-container'><div class='box'>1</div><div class='box'>2</div><div class='box'>3</div><div class='box'>4</div><div class='box'>5</div></div>",
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1.25rem; } .box { background-color: #3498db; color: white; padding: 1.25rem; margin: 0.5rem; text-align: center; font-weight: bold; width: 3rem; height: 3rem; display: flex; align-items: center; justify-content: center; }",
"sandboxCSS": ".flex-container { border: 0.125rem dashed #ccc; padding: 1rem; height: 20rem; display: flex; }",
"codePrefix": "/* Setze flex-direction auf column und aktiviere Umbruch */\n.flex-container {\n /* Füge deinen Code unten ein */\n",
"initialCode": "",
"codeSuffix": "\n}",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "flex-direction",
"message": "Verwende die 'flex-direction' Eigenschaft",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "flex-wrap",
"message": "Verwende die 'flex-wrap' Eigenschaft",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "flex-direction", "expected": "column" },
"message": "Setze flex-direction auf 'column'",
"options": { "exact": true }
},
{
"type": "property_value",
"value": { "property": "flex-wrap", "expected": "wrap" },
"message": "Setze flex-wrap auf 'wrap'",
"options": { "exact": true }
}
]
},
{
"id": "flexbox-3",
"title": "Justify Content",
"description": "Lerne, wie du Flex-Elemente entlang der Hauptachse des Containers ausrichtest.",
"task": "Verteile die Flex-Elemente gleichmäßig entlang der Hauptachse mit Abstand dazwischen.",
"previewHTML": "<div class='flex-container'><div class='box'>1</div><div class='box'>2</div><div class='box'>3</div></div>",
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1.25rem; } .box { background-color: #3498db; color: white; padding: 1.25rem; text-align: center; font-weight: bold; width: 3rem; height: 3rem; display: flex; align-items: center; justify-content: center; }",
"sandboxCSS": ".flex-container { border: 0.125rem dashed #ccc; padding: 1rem; display: flex; }",
"codePrefix": "/* Verteile die Elemente mit Abstand dazwischen */\n.flex-container {\n /* Füge deinen Code unten ein */\n",
"initialCode": "",
"codeSuffix": "\n}",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "justify-content",
"message": "Verwende die 'justify-content' Eigenschaft",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "justify-content", "expected": "space-between" },
"message": "Setze justify-content auf 'space-between'",
"options": { "exact": true }
}
]
},
{
"id": "flexbox-4",
"title": "Align Items",
"description": "Steuere, wie Flex-Elemente entlang der Querachse des Containers ausgerichtet werden.",
"task": "Zentriere die Flex-Elemente vertikal entlang der Querachse.",
"previewHTML": "<div class='flex-container'><div class='box tall'>1</div><div class='box'>2</div><div class='box short'>3</div></div>",
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1.25rem; } .box { background-color: #3498db; color: white; padding: 1.25rem; margin: 0.5rem; text-align: center; font-weight: bold; width: 3rem; display: flex; justify-content: center; } .tall { height: 8rem; align-items: flex-start; } .short { height: 3rem; align-items: flex-start; }",
"sandboxCSS": ".flex-container { border: 0.125rem dashed #ccc; padding: 1rem; display: flex; height: 12rem; }",
"codePrefix": "/* Zentriere die Elemente vertikal */\n.flex-container {\n /* Füge deinen Code unten ein */\n",
"initialCode": "",
"codeSuffix": "\n}",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "align-items",
"message": "Verwende die 'align-items' Eigenschaft",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "align-items", "expected": "center" },
"message": "Setze align-items auf 'center'",
"options": { "exact": true }
}
]
},
{
"id": "flexbox-5",
"title": "Flex-Element Eigenschaften",
"description": "Steuere, wie einzelne Flex-Elemente wachsen, schrumpfen und ihre Basisgröße festlegen.",
"task": "Lass die zweite Box doppelt so stark wachsen wie die anderen und verhindere, dass die dritte Box schrumpft.",
"previewHTML": "<div class='flex-container'><div class='box box1'>1</div><div class='box box2'>2</div><div class='box box3'>3</div></div>",
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1.25rem; } .box { background-color: #3498db; color: white; padding: 1.25rem; margin: 0.5rem; text-align: center; font-weight: bold; height: 3rem; display: flex; align-items: center; justify-content: center; } .box1 { background-color: #e74c3c; } .box2 { background-color: #2ecc71; } .box3 { background-color: #f39c12; }",
"sandboxCSS": ".flex-container { border: 0.125rem dashed #ccc; padding: 1rem; display: flex; width: 100%; }",
"codePrefix": "/* Lass box2 mehr wachsen und verhindere das Schrumpfen von box3 */\n",
"initialCode": ".box1 {\n flex: 1;\n}\n\n.box2 {\n /* Diese soll doppelt so stark wachsen */\n}\n\n.box3 {\n flex: 1;\n /* Verhindere das Schrumpfen */\n}",
"codeSuffix": "",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": ".box2",
"message": "Style das '.box2' Element",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": ".box3",
"message": "Style das '.box3' Element",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": ".box2\\s*{[^}]*flex:\\s*2",
"message": "Setze flex auf '2' für box2",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": ".box3\\s*{[^}]*flex-shrink:\\s*0",
"message": "Setze flex-shrink auf '0' für box3",
"options": { "caseSensitive": false }
}
]
},
{
"id": "flexbox-6",
"title": "Einzelne Elemente ausrichten",
"description": "Lerne, wie du die Container-Ausrichtung für einzelne Flex-Elemente überschreiben kannst.",
"task": "Setze 'align-self' auf das mittlere Element, um es am Anfang der Querachse auszurichten.",
"previewHTML": "<div class='flex-container'><div class='box'>1</div><div class='box middle'>2</div><div class='box'>3</div></div>",
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1.25rem; } .box { background-color: #3498db; color: white; padding: 1.25rem; margin: 0.5rem; text-align: center; font-weight: bold; width: 3rem; height: 3rem; display: flex; align-items: center; justify-content: center; } .middle { background-color: #2ecc71; }",
"sandboxCSS": ".flex-container { border: 0.125rem dashed #ccc; padding: 1rem; display: flex; height: 15rem; align-items: center; }",
"codePrefix": "/* Richte das mittlere Element am Anfang aus */\n",
"initialCode": "",
"codeSuffix": "",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": ".middle",
"message": "Verwende den '.middle' Klassenselektor",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "align-self",
"message": "Verwende die 'align-self' Eigenschaft",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "align-self", "expected": "flex-start" },
"message": "Setze align-self auf 'flex-start'",
"options": { "exact": true }
}
]
}
]
}

View File

@@ -6,6 +6,12 @@
import basicSelectorsConfig from "../../lessons/de/00-basic-selectors.json";
import advancedSelectorsConfig from "../../lessons/de/01-advanced-selectors.json";
import tailwindConfig from "../../lessons/de/10-tailwind-basics.json";
// CSS lessons
import boxModelConfig from "../../lessons/de/01-box-model.json";
import flexboxConfig from "../../lessons/de/flexbox.json";
import responsiveConfig from "../../lessons/de/08-responsive.json";
import unitsVariablesConfig from "../../lessons/de/05-units-variables.json";
import transitionsAnimationsConfig from "../../lessons/de/06-transitions-animations.json";
// HTML lessons
import htmlElementsConfig from "../../lessons/de/20-html-elements.json";
import htmlFormsBasicConfig from "../../lessons/de/21-html-forms-basic.json";
@@ -36,6 +42,11 @@ const moduleStore = [
htmlTablesConfig,
htmlMarqueeConfig,
htmlSvgConfig,
boxModelConfig,
flexboxConfig,
responsiveConfig,
unitsVariablesConfig,
transitionsAnimationsConfig,
basicSelectorsConfig,
advancedSelectorsConfig,
tailwindConfig

View File

@@ -6,6 +6,12 @@
import basicSelectorsConfig from "../../lessons/00-basic-selectors.json";
import advancedSelectorsConfig from "../../lessons/01-advanced-selectors.json";
import tailwindConfig from "../../lessons/10-tailwind-basics.json";
// CSS lessons
import boxModelConfig from "../../lessons/01-box-model.json";
import flexboxConfig from "../../lessons/flexbox.json";
import responsiveConfig from "../../lessons/08-responsive.json";
import unitsVariablesConfig from "../../lessons/05-units-variables.json";
import transitionsAnimationsConfig from "../../lessons/06-transitions-animations.json";
// HTML lessons
import htmlElementsConfig from "../../lessons/20-html-elements.json";
import htmlFormsBasicConfig from "../../lessons/21-html-forms-basic.json";
@@ -36,6 +42,11 @@ const moduleStore = [
htmlTablesConfig,
htmlMarqueeConfig,
htmlSvgConfig,
boxModelConfig,
flexboxConfig,
responsiveConfig,
unitsVariablesConfig,
transitionsAnimationsConfig,
basicSelectorsConfig,
advancedSelectorsConfig,
tailwindConfig

View File

@@ -7,17 +7,19 @@ describe("Lessons Config Module", () => {
const modules = await loadModules();
expect(Array.isArray(modules)).toBe(true);
expect(modules.length).toBe(6);
expect(modules.length).toBeGreaterThanOrEqual(6);
// Check if modules have the right structure
const moduleIds = modules.map((m) => m.id);
// HTML modules (first)
// HTML modules
expect(moduleIds).toContain("html-elements");
expect(moduleIds).toContain("html-forms-basic");
expect(moduleIds).toContain("html-forms-validation");
// CSS modules
expect(moduleIds).toContain("css-basic-selectors");
expect(moduleIds).toContain("css-advanced-selectors");
expect(moduleIds).toContain("box-model");
expect(moduleIds).toContain("flexbox");
// Tailwind
expect(moduleIds).toContain("tailwind-basics");
});