diff --git a/lessons/de/01-box-model.json b/lessons/de/01-box-model.json new file mode 100644 index 0000000..3e1a7cc --- /dev/null +++ b/lessons/de/01-box-model.json @@ -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": "
Box-Modell Komponenten
", + "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": "
Diese Box braucht einen Rahmen
", + "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": "
Diese Box braucht Margins
Benachbartes Element
", + "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": "
Content-box (Standard)
Border-box
", + "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": "

Dieser Absatz hat einen Bottom-Margin.

Dieser Absatz hat einen Top-Margin von 1rem.

", + "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": "
Diese Box braucht Margins: 1rem oben/unten, 2rem links/rechts
", + "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": "
Diese Box braucht gleichmäßiges Padding
", + "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": "
Dieses Element braucht nur einen unteren Rahmen
", + "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 } + } + ] + } + ] +} diff --git a/lessons/de/05-units-variables.json b/lessons/de/05-units-variables.json new file mode 100644 index 0000000..488a03d --- /dev/null +++ b/lessons/de/05-units-variables.json @@ -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": "
Ändere meine Größe!
", + "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 --main-color Variable in :root mit #6200ee und wende sie als Rahmenfarbe auf '.var-box' an.", + "previewHTML": "
Variablen-Box
", + "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 calc() 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": "
Calc Demo
", + "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": "
Viewport-Box
", + "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'" } + ] + } + ] +} diff --git a/lessons/de/06-transitions-animations.json b/lessons/de/06-transitions-animations.json new file mode 100644 index 0000000..b613e6a --- /dev/null +++ b/lessons/de/06-transitions-animations.json @@ -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 transition 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": "", + "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": "", + "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 @keyframes und wende sie mit der animation 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": "
", + "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": "
Fade Demo
", + "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" + } + ] + } + ] +} diff --git a/lessons/de/08-responsive.json b/lessons/de/08-responsive.json new file mode 100644 index 0000000..ff579a2 --- /dev/null +++ b/lessons/de/08-responsive.json @@ -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": "
Ändere die Fenstergröße
", + "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": "

Flüssige Typografie

", + "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": "
1
2
3
4
", + "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": "", + "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 } + } + ] + } + ] +} diff --git a/lessons/de/flexbox.json b/lessons/de/flexbox.json new file mode 100644 index 0000000..6af438a --- /dev/null +++ b/lessons/de/flexbox.json @@ -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": "
1
2
3
", + "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": "
1
2
3
4
5
", + "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": "
1
2
3
", + "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": "
1
2
3
", + "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": "
1
2
3
", + "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": "
1
2
3
", + "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 } + } + ] + } + ] +} diff --git a/src/config/lessons.de.js b/src/config/lessons.de.js index b19e720..6a65896 100644 --- a/src/config/lessons.de.js +++ b/src/config/lessons.de.js @@ -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 diff --git a/src/config/lessons.js b/src/config/lessons.js index 8ffce56..397bd96 100644 --- a/src/config/lessons.js +++ b/src/config/lessons.js @@ -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 diff --git a/tests/unit/lessons.test.js b/tests/unit/lessons.test.js index ea2acd1..aa7ffa8 100644 --- a/tests/unit/lessons.test.js +++ b/tests/unit/lessons.test.js @@ -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"); });