Compare commits
4 Commits
004-pedago
...
004-valida
| Author | SHA1 | Date | |
|---|---|---|---|
| 8d567390e5 | |||
| 372320b807 | |||
| 61acd692f4 | |||
| 672a2d28cb |
@@ -22,7 +22,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "1rem" },
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"message": "Set <kbd>padding: 1rem</kbd>"
|
"message": "Which property adds space between content and the element's edge?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
|
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
|
||||||
"message": "Set <kbd>border-left: 4px solid steelblue</kbd>",
|
"message": "Use the shorthand that sets a border on just one side",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "margin-bottom", "expected": "1rem" },
|
"value": { "property": "margin-bottom", "expected": "1rem" },
|
||||||
"message": "Set <kbd>margin-bottom: 1rem</kbd>"
|
"message": "Which property pushes neighboring elements away from the bottom?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "box-sizing", "expected": "border-box" },
|
"value": { "property": "box-sizing", "expected": "border-box" },
|
||||||
"message": "Set <kbd>box-sizing: border-box</kbd>"
|
"message": "Which sizing mode includes padding and border in the element's width?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "padding:\\s*8px\\s+1rem",
|
"value": "padding:\\s*8px\\s+1rem",
|
||||||
"message": "Set <kbd>padding: 8px 1rem</kbd>",
|
"message": "Use the two-value shorthand: vertical first, then horizontal",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "margin:\\s*0\\s+auto",
|
"value": "margin:\\s*0\\s+auto",
|
||||||
"message": "Set <kbd>margin: 0 auto</kbd>",
|
"message": "Use the shorthand that auto-calculates equal horizontal margins",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -151,7 +151,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "border-radius", "expected": "50%" },
|
"value": { "property": "border-radius", "expected": "50%" },
|
||||||
"message": "Set <kbd>border-radius: 50%</kbd>"
|
"message": "Which property rounds corners? Think about what percentage makes a circle"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -172,18 +172,18 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "1rem" },
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"message": "Set <kbd>padding: 1rem</kbd>"
|
"message": "Add inner spacing to the notification"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border-left:\\s*4px\\s+solid\\s+coral",
|
"value": "border-left:\\s*4px\\s+solid\\s+coral",
|
||||||
"message": "Set <kbd>border-left: 4px solid coral</kbd>",
|
"message": "Add a colored accent on the left edge",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "border-radius", "expected": "4px" },
|
"value": { "property": "border-radius", "expected": "4px" },
|
||||||
"message": "Set <kbd>border-radius: 4px</kbd>"
|
"message": "Soften the corners of the notification"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "background-color", "expected": "seashell" },
|
"value": { "property": "background-color", "expected": "seashell" },
|
||||||
"message": "Set <kbd>background-color: seashell</kbd>"
|
"message": "Which property fills the area behind the content?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "color", "expected": "coral" },
|
"value": { "property": "color", "expected": "coral" },
|
||||||
"message": "Set <kbd>color: coral</kbd>"
|
"message": "Which property changes the text color?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "border-color", "expected": "coral" },
|
"value": { "property": "border-color", "expected": "coral" },
|
||||||
"message": "Set <kbd>border-color: coral</kbd>"
|
"message": "Which property changes just the border's color without redefining the whole border?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "background-color", "expected": "#ffd700" },
|
"value": { "property": "background-color", "expected": "#ffd700" },
|
||||||
"message": "Set <kbd>background-color: #ffd700</kbd>"
|
"message": "Use the same background property, but with a hex code this time"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "1rem" },
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"message": "اضبط <kbd>padding: 1rem</kbd>"
|
"message": "أي خاصية تضيف مساحة بين المحتوى وحافة العنصر؟"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
|
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
|
||||||
"message": "اضبط <kbd>border-left: 4px solid steelblue</kbd>",
|
"message": "استخدم الاختصار الذي يحدد حداً على جانب واحد فقط",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "margin-bottom", "expected": "1rem" },
|
"value": { "property": "margin-bottom", "expected": "1rem" },
|
||||||
"message": "اضبط <kbd>margin-bottom: 1rem</kbd>"
|
"message": "أي خاصية تدفع العناصر المجاورة بعيداً من الأسفل؟"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "box-sizing", "expected": "border-box" },
|
"value": { "property": "box-sizing", "expected": "border-box" },
|
||||||
"message": "اضبط <kbd>box-sizing: border-box</kbd>"
|
"message": "أي وضع تحجيم يشمل padding والحدود في عرض العنصر؟"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "padding:\\s*8px\\s+1rem",
|
"value": "padding:\\s*8px\\s+1rem",
|
||||||
"message": "اضبط <kbd>padding: 8px 1rem</kbd>",
|
"message": "استخدم اختصار القيمتين: العمودي أولاً، ثم الأفقي",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "margin:\\s*0\\s+auto",
|
"value": "margin:\\s*0\\s+auto",
|
||||||
"message": "اضبط <kbd>margin: 0 auto</kbd>",
|
"message": "استخدم الاختصار الذي يحسب هوامش أفقية متساوية تلقائياً",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -151,7 +151,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "border-radius", "expected": "50%" },
|
"value": { "property": "border-radius", "expected": "50%" },
|
||||||
"message": "اضبط <kbd>border-radius: 50%</kbd>"
|
"message": "أي خاصية تدوّر الزوايا؟ فكر في النسبة المئوية التي تصنع دائرة"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -172,18 +172,18 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "1rem" },
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"message": "اضبط <kbd>padding: 1rem</kbd>"
|
"message": "أضف مساحة داخلية للإشعار"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border-left:\\s*4px\\s+solid\\s+coral",
|
"value": "border-left:\\s*4px\\s+solid\\s+coral",
|
||||||
"message": "اضبط <kbd>border-left: 4px solid coral</kbd>",
|
"message": "أضف لمسة ملونة على الحافة اليسرى",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "border-radius", "expected": "4px" },
|
"value": { "property": "border-radius", "expected": "4px" },
|
||||||
"message": "اضبط <kbd>border-radius: 4px</kbd>"
|
"message": "نعّم زوايا الإشعار"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "1rem" },
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"message": "Setze <kbd>padding: 1rem</kbd>"
|
"message": "Welche Eigenschaft fügt Abstand zwischen Inhalt und Elementrand hinzu?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
|
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
|
||||||
"message": "Setze <kbd>border-left: 4px solid steelblue</kbd>",
|
"message": "Verwende die Kurzschreibweise, die einen Rahmen auf nur einer Seite setzt",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "margin-bottom", "expected": "1rem" },
|
"value": { "property": "margin-bottom", "expected": "1rem" },
|
||||||
"message": "Setze <kbd>margin-bottom: 1rem</kbd>"
|
"message": "Welche Eigenschaft schiebt benachbarte Elemente nach unten weg?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "box-sizing", "expected": "border-box" },
|
"value": { "property": "box-sizing", "expected": "border-box" },
|
||||||
"message": "Setze <kbd>box-sizing: border-box</kbd>"
|
"message": "Welcher Größenmodus bezieht Padding und Rahmen in die Breite des Elements ein?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "padding:\\s*8px\\s+1rem",
|
"value": "padding:\\s*8px\\s+1rem",
|
||||||
"message": "Setze <kbd>padding: 8px 1rem</kbd>",
|
"message": "Verwende die Zwei-Werte-Kurzschreibweise: vertikal zuerst, dann horizontal",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "margin:\\s*0\\s+auto",
|
"value": "margin:\\s*0\\s+auto",
|
||||||
"message": "Setze <kbd>margin: 0 auto</kbd>",
|
"message": "Verwende die Kurzschreibweise, die gleiche horizontale Abstände automatisch berechnet",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -151,7 +151,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "border-radius", "expected": "50%" },
|
"value": { "property": "border-radius", "expected": "50%" },
|
||||||
"message": "Setze <kbd>border-radius: 50%</kbd>"
|
"message": "Welche Eigenschaft rundet Ecken? Denke daran, welcher Prozentwert einen Kreis ergibt"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -172,18 +172,18 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "1rem" },
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"message": "Setze <kbd>padding: 1rem</kbd>"
|
"message": "Füge inneren Abstand zur Benachrichtigung hinzu"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border-left:\\s*4px\\s+solid\\s+coral",
|
"value": "border-left:\\s*4px\\s+solid\\s+coral",
|
||||||
"message": "Setze <kbd>border-left: 4px solid coral</kbd>",
|
"message": "Füge einen farbigen Akzent an der linken Kante hinzu",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "border-radius", "expected": "4px" },
|
"value": { "property": "border-radius", "expected": "4px" },
|
||||||
"message": "Setze <kbd>border-radius: 4px</kbd>"
|
"message": "Runde die Ecken der Benachrichtigung ab"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "1rem" },
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"message": "Establece <kbd>padding: 1rem</kbd>"
|
"message": "¿Qué propiedad añade espacio entre el contenido y el borde del elemento?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
|
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
|
||||||
"message": "Establece <kbd>border-left: 4px solid steelblue</kbd>",
|
"message": "Usa el atajo que define un borde en un solo lado",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "margin-bottom", "expected": "1rem" },
|
"value": { "property": "margin-bottom", "expected": "1rem" },
|
||||||
"message": "Establece <kbd>margin-bottom: 1rem</kbd>"
|
"message": "¿Qué propiedad empuja los elementos vecinos hacia abajo?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "box-sizing", "expected": "border-box" },
|
"value": { "property": "box-sizing", "expected": "border-box" },
|
||||||
"message": "Establece <kbd>box-sizing: border-box</kbd>"
|
"message": "¿Qué modo de tamaño incluye padding y borde en el ancho del elemento?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "padding:\\s*8px\\s+1rem",
|
"value": "padding:\\s*8px\\s+1rem",
|
||||||
"message": "Establece <kbd>padding: 8px 1rem</kbd>",
|
"message": "Usa el atajo de dos valores: vertical primero, luego horizontal",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "margin:\\s*0\\s+auto",
|
"value": "margin:\\s*0\\s+auto",
|
||||||
"message": "Establece <kbd>margin: 0 auto</kbd>",
|
"message": "Usa el atajo que calcula márgenes horizontales iguales automáticamente",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -151,7 +151,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "border-radius", "expected": "50%" },
|
"value": { "property": "border-radius", "expected": "50%" },
|
||||||
"message": "Establece <kbd>border-radius: 50%</kbd>"
|
"message": "¿Qué propiedad redondea las esquinas? Piensa en qué porcentaje crea un círculo"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -172,18 +172,18 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "1rem" },
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"message": "Establece <kbd>padding: 1rem</kbd>"
|
"message": "Añade espacio interior a la notificación"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border-left:\\s*4px\\s+solid\\s+coral",
|
"value": "border-left:\\s*4px\\s+solid\\s+coral",
|
||||||
"message": "Establece <kbd>border-left: 4px solid coral</kbd>",
|
"message": "Añade un acento de color en el borde izquierdo",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "border-radius", "expected": "4px" },
|
"value": { "property": "border-radius", "expected": "4px" },
|
||||||
"message": "Establece <kbd>border-radius: 4px</kbd>"
|
"message": "Suaviza las esquinas de la notificación"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@
|
|||||||
"id": "flexbox-1",
|
"id": "flexbox-1",
|
||||||
"title": "Container",
|
"title": "Container",
|
||||||
"description": "Before flexbox, creating even simple layouts required floats, positioning hacks, or table-based layouts. Flexbox (Flexible Box Layout) revolutionized CSS by providing a one-dimensional layout system designed specifically for distributing space and aligning content.<br><br><strong>How it works:</strong> When you set <kbd>display: flex</kbd> on an element, it becomes a <em>flex container</em>. Its direct children automatically become <em>flex items</em> that flow along a main axis (horizontal by default). This single property transforms stacked block elements into a horizontal row.<br><br><strong>The two axes:</strong><br>• <em>Main axis</em> – The primary direction items flow (row = left→right)<br>• <em>Cross axis</em> – Perpendicular to main (row = top→bottom)<br><br><pre>.nav {\n display: flex;\n}</pre>",
|
"description": "Before flexbox, creating even simple layouts required floats, positioning hacks, or table-based layouts. Flexbox (Flexible Box Layout) revolutionized CSS by providing a one-dimensional layout system designed specifically for distributing space and aligning content.<br><br><strong>How it works:</strong> When you set <kbd>display: flex</kbd> on an element, it becomes a <em>flex container</em>. Its direct children automatically become <em>flex items</em> that flow along a main axis (horizontal by default). This single property transforms stacked block elements into a horizontal row.<br><br><strong>The two axes:</strong><br>• <em>Main axis</em> – The primary direction items flow (row = left→right)<br>• <em>Cross axis</em> – Perpendicular to main (row = top→bottom)<br><br><pre>.nav {\n display: flex;\n}</pre>",
|
||||||
"task": "This navigation menu stacks vertically. Add <kbd>display: flex</kbd> to <kbd>.nav</kbd> to arrange the links horizontally.",
|
"task": "The navigation links are stacking vertically. Make them display side by side in a horizontal row.",
|
||||||
"previewHTML": "<nav class=\"nav\"><a href=\"#\">Home</a><a href=\"#\">Products</a><a href=\"#\">About</a><a href=\"#\">Contact</a></nav>",
|
"previewHTML": "<nav class=\"nav\"><a href=\"#\">Home</a><a href=\"#\">Products</a><a href=\"#\">About</a><a href=\"#\">Contact</a></nav>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .nav { background: #1a1a2e; padding: 1rem; } .nav a { color: white; text-decoration: none; padding: 8px 1rem; border-radius: 4px; } .nav a:hover { background: rgba(255,255,255,0.1); }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .nav { background: #1a1a2e; padding: 1rem; } .nav a { color: white; text-decoration: none; padding: 8px 1rem; border-radius: 4px; } .nav a:hover { background: rgba(255,255,255,0.1); }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "display", "expected": "flex" },
|
"value": { "property": "display", "expected": "flex" },
|
||||||
"message": "Set <kbd>display: flex</kbd>"
|
"message": "Try changing the display mode to create a flex container"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -30,7 +30,7 @@
|
|||||||
"id": "flexbox-2",
|
"id": "flexbox-2",
|
||||||
"title": "Gap",
|
"title": "Gap",
|
||||||
"description": "The <kbd>gap</kbd> property adds consistent spacing between flex items without needing margins. It only creates space between items, not around the edges.",
|
"description": "The <kbd>gap</kbd> property adds consistent spacing between flex items without needing margins. It only creates space between items, not around the edges.",
|
||||||
"task": "Add <kbd>gap: 1rem</kbd> to space out the navigation links evenly.",
|
"task": "The navigation links are crammed together with no breathing room. Add 1rem of spacing between them.",
|
||||||
"previewHTML": "<nav class=\"nav\"><a href=\"#\">Home</a><a href=\"#\">Products</a><a href=\"#\">About</a><a href=\"#\">Contact</a></nav>",
|
"previewHTML": "<nav class=\"nav\"><a href=\"#\">Home</a><a href=\"#\">Products</a><a href=\"#\">About</a><a href=\"#\">Contact</a></nav>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .nav { background: #1a1a2e; padding: 1rem; display: flex; } .nav a { color: white; text-decoration: none; padding: 8px 1rem; border-radius: 4px; background: rgba(255,255,255,0.1); }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .nav { background: #1a1a2e; padding: 1rem; display: flex; } .nav a { color: white; text-decoration: none; padding: 8px 1rem; border-radius: 4px; background: rgba(255,255,255,0.1); }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "gap", "expected": "1rem" },
|
"value": { "property": "gap", "expected": "1rem" },
|
||||||
"message": "Set <kbd>gap: 1rem</kbd>"
|
"message": "Use the property that adds spacing between flex items"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -51,7 +51,7 @@
|
|||||||
"id": "flexbox-3",
|
"id": "flexbox-3",
|
||||||
"title": "Justify Content",
|
"title": "Justify Content",
|
||||||
"description": "<kbd>justify-content</kbd> distributes items along the main axis. Common values:<br>• <kbd>flex-start</kbd> – pack items at the start<br>• <kbd>flex-end</kbd> – pack at the end<br>• <kbd>center</kbd> – center items<br>• <kbd>space-between</kbd> – equal space between items<br>• <kbd>space-around</kbd> – equal space around items",
|
"description": "<kbd>justify-content</kbd> distributes items along the main axis. Common values:<br>• <kbd>flex-start</kbd> – pack items at the start<br>• <kbd>flex-end</kbd> – pack at the end<br>• <kbd>center</kbd> – center items<br>• <kbd>space-between</kbd> – equal space between items<br>• <kbd>space-around</kbd> – equal space around items",
|
||||||
"task": "Push the \"Login\" button to the right by setting <kbd>justify-content: space-between</kbd> on the nav.",
|
"task": "The Login button should sit on the far right, with the other links staying on the left. Distribute the space between them.",
|
||||||
"previewHTML": "<nav class=\"nav\"><div class=\"links\"><a href=\"#\">Home</a><a href=\"#\">Products</a><a href=\"#\">About</a></div><a href=\"#\" class=\"login\">Login</a></nav>",
|
"previewHTML": "<nav class=\"nav\"><div class=\"links\"><a href=\"#\">Home</a><a href=\"#\">Products</a><a href=\"#\">About</a></div><a href=\"#\" class=\"login\">Login</a></nav>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .nav { background: #1a1a2e; padding: 1rem; display: flex; } .links { display: flex; gap: 8px; } .nav a { color: white; text-decoration: none; padding: 8px 1rem; border-radius: 4px; } .nav a:hover { background: rgba(255,255,255,0.1); } .login { background: steelblue; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .nav { background: #1a1a2e; padding: 1rem; display: flex; } .links { display: flex; gap: 8px; } .nav a { color: white; text-decoration: none; padding: 8px 1rem; border-radius: 4px; } .nav a:hover { background: rgba(255,255,255,0.1); } .login { background: steelblue; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -64,7 +64,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "justify-content", "expected": "space-between" },
|
"value": { "property": "justify-content", "expected": "space-between" },
|
||||||
"message": "Set <kbd>justify-content: space-between</kbd>"
|
"message": "Use the property that distributes items along the main axis"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -72,7 +72,7 @@
|
|||||||
"id": "flexbox-4",
|
"id": "flexbox-4",
|
||||||
"title": "Align Items",
|
"title": "Align Items",
|
||||||
"description": "<kbd>align-items</kbd> controls alignment on the cross axis (vertical when flex-direction is row). Values include:<br>• <kbd>stretch</kbd> – stretch to fill (default)<br>• <kbd>flex-start</kbd> – align to top<br>• <kbd>flex-end</kbd> – align to bottom<br>• <kbd>center</kbd> – center vertically",
|
"description": "<kbd>align-items</kbd> controls alignment on the cross axis (vertical when flex-direction is row). Values include:<br>• <kbd>stretch</kbd> – stretch to fill (default)<br>• <kbd>flex-start</kbd> – align to top<br>• <kbd>flex-end</kbd> – align to bottom<br>• <kbd>center</kbd> – center vertically",
|
||||||
"task": "The logo and nav links have different heights. Center them vertically with <kbd>align-items: center</kbd>.",
|
"task": "The logo and nav links sit at different heights. Center them vertically so they line up.",
|
||||||
"previewHTML": "<header class=\"header\"><div class=\"logo\">ACME</div><nav><a href=\"#\">Products</a><a href=\"#\">Pricing</a><a href=\"#\">Docs</a></nav></header>",
|
"previewHTML": "<header class=\"header\"><div class=\"logo\">ACME</div><nav><a href=\"#\">Products</a><a href=\"#\">Pricing</a><a href=\"#\">Docs</a></nav></header>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .header { background: white; padding: 1rem 2rem; display: flex; justify-content: space-between; border-bottom: 1px solid #eee; } .logo { font-size: 1.5rem; font-weight: bold; color: steelblue; } nav { display: flex; gap: 1rem; } nav a { color: #333; text-decoration: none; font-size: 0.9rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .header { background: white; padding: 1rem 2rem; display: flex; justify-content: space-between; border-bottom: 1px solid #eee; } .logo { font-size: 1.5rem; font-weight: bold; color: steelblue; } nav { display: flex; gap: 1rem; } nav a { color: #333; text-decoration: none; font-size: 0.9rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -85,7 +85,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "align-items", "expected": "center" },
|
"value": { "property": "align-items", "expected": "center" },
|
||||||
"message": "Set <kbd>align-items: center</kbd>"
|
"message": "Use the property that controls cross-axis alignment"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -93,7 +93,7 @@
|
|||||||
"id": "flexbox-5",
|
"id": "flexbox-5",
|
||||||
"title": "Flex Wrap",
|
"title": "Flex Wrap",
|
||||||
"description": "By default, flex items squeeze onto one line. <kbd>flex-wrap: wrap</kbd> allows items to flow onto multiple lines when they run out of space.",
|
"description": "By default, flex items squeeze onto one line. <kbd>flex-wrap: wrap</kbd> allows items to flow onto multiple lines when they run out of space.",
|
||||||
"task": "These cards overflow the container. Add <kbd>flex-wrap: wrap</kbd> to allow them to wrap to new rows.",
|
"task": "The cards overflow the container instead of fitting within it. Allow the items to flow onto new rows when they run out of space.",
|
||||||
"previewHTML": "<div class=\"cards\"><article class=\"card\">Card 1</article><article class=\"card\">Card 2</article><article class=\"card\">Card 3</article><article class=\"card\">Card 4</article><article class=\"card\">Card 5</article><article class=\"card\">Card 6</article></div>",
|
"previewHTML": "<div class=\"cards\"><article class=\"card\">Card 1</article><article class=\"card\">Card 2</article><article class=\"card\">Card 3</article><article class=\"card\">Card 4</article><article class=\"card\">Card 5</article><article class=\"card\">Card 6</article></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .cards { display: flex; gap: 1rem; } .card { background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); min-width: 120px; text-align: center; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .cards { display: flex; gap: 1rem; } .card { background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); min-width: 120px; text-align: center; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -106,7 +106,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "flex-wrap", "expected": "wrap" },
|
"value": { "property": "flex-wrap", "expected": "wrap" },
|
||||||
"message": "Set <kbd>flex-wrap: wrap</kbd>"
|
"message": "Use the property that allows flex items to wrap onto new lines"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -114,7 +114,7 @@
|
|||||||
"id": "flexbox-6",
|
"id": "flexbox-6",
|
||||||
"title": "Flex Grow",
|
"title": "Flex Grow",
|
||||||
"description": "The <kbd>flex</kbd> property on items controls how they grow and shrink. <kbd>flex: 1</kbd> makes an item grow to fill available space. Multiple items with <kbd>flex: 1</kbd> share space equally.",
|
"description": "The <kbd>flex</kbd> property on items controls how they grow and shrink. <kbd>flex: 1</kbd> makes an item grow to fill available space. Multiple items with <kbd>flex: 1</kbd> share space equally.",
|
||||||
"task": "Make the search input expand to fill available space by setting <kbd>flex: 1</kbd> on <kbd>.search</kbd>.",
|
"task": "The search input is too narrow. Make it stretch to fill all the remaining space in the toolbar.",
|
||||||
"previewHTML": "<div class=\"toolbar\"><input class=\"search\" type=\"text\" placeholder=\"Search...\"><button class=\"btn\">Search</button><button class=\"btn\">Filters</button></div>",
|
"previewHTML": "<div class=\"toolbar\"><input class=\"search\" type=\"text\" placeholder=\"Search...\"><button class=\"btn\">Search</button><button class=\"btn\">Filters</button></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .toolbar { display: flex; gap: 8px; padding: 1rem; background: #f5f5f5; border-radius: 8px; } .search { padding: 8px 1rem; border: 1px solid #ddd; border-radius: 4px; font-size: 1rem; } .btn { padding: 8px 1rem; background: steelblue; color: white; border: none; border-radius: 4px; cursor: pointer; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .toolbar { display: flex; gap: 8px; padding: 1rem; background: #f5f5f5; border-radius: 8px; } .search { padding: 8px 1rem; border: 1px solid #ddd; border-radius: 4px; font-size: 1rem; } .btn { padding: 8px 1rem; background: steelblue; color: white; border: none; border-radius: 4px; cursor: pointer; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -125,9 +125,9 @@
|
|||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "regex",
|
||||||
"value": { "property": "flex", "expected": "1" },
|
"value": "(flex\\s*:\\s*1|flex-grow\\s*:\\s*1)",
|
||||||
"message": "Set <kbd>flex: 1</kbd>"
|
"message": "Use the property that makes a flex item grow to fill available space"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "1rem" },
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"message": "Ustaw <kbd>padding: 1rem</kbd>"
|
"message": "Która właściwość dodaje przestrzeń między treścią a krawędzią elementu?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
|
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
|
||||||
"message": "Ustaw <kbd>border-left: 4px solid steelblue</kbd>",
|
"message": "Użyj skrótu, który ustawia ramkę tylko po jednej stronie",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "margin-bottom", "expected": "1rem" },
|
"value": { "property": "margin-bottom", "expected": "1rem" },
|
||||||
"message": "Ustaw <kbd>margin-bottom: 1rem</kbd>"
|
"message": "Która właściwość odpycha sąsiednie elementy w dół?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "box-sizing", "expected": "border-box" },
|
"value": { "property": "box-sizing", "expected": "border-box" },
|
||||||
"message": "Ustaw <kbd>box-sizing: border-box</kbd>"
|
"message": "Który tryb rozmiaru uwzględnia padding i ramkę w szerokości elementu?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "padding:\\s*8px\\s+1rem",
|
"value": "padding:\\s*8px\\s+1rem",
|
||||||
"message": "Ustaw <kbd>padding: 8px 1rem</kbd>",
|
"message": "Użyj skrótu dwuwartościowego: najpierw pionowo, potem poziomo",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "margin:\\s*0\\s+auto",
|
"value": "margin:\\s*0\\s+auto",
|
||||||
"message": "Ustaw <kbd>margin: 0 auto</kbd>",
|
"message": "Użyj skrótu, który automatycznie oblicza równe marginesy poziome",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -151,7 +151,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "border-radius", "expected": "50%" },
|
"value": { "property": "border-radius", "expected": "50%" },
|
||||||
"message": "Ustaw <kbd>border-radius: 50%</kbd>"
|
"message": "Która właściwość zaokrągla rogi? Pomyśl, jaki procent tworzy koło"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -172,18 +172,18 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "1rem" },
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"message": "Ustaw <kbd>padding: 1rem</kbd>"
|
"message": "Dodaj wewnętrzny odstęp do powiadomienia"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border-left:\\s*4px\\s+solid\\s+coral",
|
"value": "border-left:\\s*4px\\s+solid\\s+coral",
|
||||||
"message": "Ustaw <kbd>border-left: 4px solid coral</kbd>",
|
"message": "Dodaj kolorowy akcent na lewej krawędzi",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "border-radius", "expected": "4px" },
|
"value": { "property": "border-radius", "expected": "4px" },
|
||||||
"message": "Ustaw <kbd>border-radius: 4px</kbd>"
|
"message": "Wygładź rogi powiadomienia"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -22,7 +22,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "1rem" },
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"message": "Встановіть <kbd>padding: 1rem</kbd>"
|
"message": "Яка властивість додає простір між контентом і краєм елемента?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -43,7 +43,7 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
|
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
|
||||||
"message": "Встановіть <kbd>border-left: 4px solid steelblue</kbd>",
|
"message": "Використайте скорочення, яке встановлює межу лише з одного боку",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -65,7 +65,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "margin-bottom", "expected": "1rem" },
|
"value": { "property": "margin-bottom", "expected": "1rem" },
|
||||||
"message": "Встановіть <kbd>margin-bottom: 1rem</kbd>"
|
"message": "Яка властивість відштовхує сусідні елементи знизу?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -86,7 +86,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "box-sizing", "expected": "border-box" },
|
"value": { "property": "box-sizing", "expected": "border-box" },
|
||||||
"message": "Встановіть <kbd>box-sizing: border-box</kbd>"
|
"message": "Який режим розміру включає padding і межу в ширину елемента?"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -107,7 +107,7 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "padding:\\s*8px\\s+1rem",
|
"value": "padding:\\s*8px\\s+1rem",
|
||||||
"message": "Встановіть <kbd>padding: 8px 1rem</kbd>",
|
"message": "Використайте скорочення з двома значеннями: спочатку вертикальне, потім горизонтальне",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -129,7 +129,7 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "margin:\\s*0\\s+auto",
|
"value": "margin:\\s*0\\s+auto",
|
||||||
"message": "Встановіть <kbd>margin: 0 auto</kbd>",
|
"message": "Використайте скорочення, яке автоматично розраховує рівні горизонтальні поля",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -151,7 +151,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "border-radius", "expected": "50%" },
|
"value": { "property": "border-radius", "expected": "50%" },
|
||||||
"message": "Встановіть <kbd>border-radius: 50%</kbd>"
|
"message": "Яка властивість заокруглює кути? Подумайте, який відсоток створює коло"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -172,18 +172,18 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "1rem" },
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"message": "Встановіть <kbd>padding: 1rem</kbd>"
|
"message": "Додайте внутрішній відступ до сповіщення"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border-left:\\s*4px\\s+solid\\s+coral",
|
"value": "border-left:\\s*4px\\s+solid\\s+coral",
|
||||||
"message": "Встановіть <kbd>border-left: 4px solid coral</kbd>",
|
"message": "Додайте кольоровий акцент на лівому краю",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "border-radius", "expected": "4px" },
|
"value": { "property": "border-radius", "expected": "4px" },
|
||||||
"message": "Встановіть <kbd>border-radius: 4px</kbd>"
|
"message": "Згладьте кути сповіщення"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
76
specs/003-flexbox-task-wording/plan.md
Normal file
76
specs/003-flexbox-task-wording/plan.md
Normal file
@@ -0,0 +1,76 @@
|
|||||||
|
# Implementation Plan
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Rewrite all 6 flexbox lesson task descriptions to describe the desired visual outcome instead of giving the exact CSS declaration. Update validation messages to hint without revealing answers, and accept alternative valid solutions where applicable.
|
||||||
|
|
||||||
|
## Approach
|
||||||
|
|
||||||
|
This is a content-only change to a single JSON file (`lessons/flexbox.json`). Each lesson needs three edits:
|
||||||
|
|
||||||
|
1. **Task text**: Replace copy-pasteable CSS declarations with outcome-oriented descriptions
|
||||||
|
2. **Validation messages**: Replace answer-revealing messages with pedagogical hints
|
||||||
|
3. **Validations array**: Add alternative accepted solutions where multiple CSS approaches achieve the same visual result
|
||||||
|
|
||||||
|
The lesson `description` fields (which teach concepts with code examples) remain unchanged — they are the learning material, not the exercise prompt.
|
||||||
|
|
||||||
|
## File Mapping
|
||||||
|
|
||||||
|
| File | Action | Description |
|
||||||
|
|------|--------|-------------|
|
||||||
|
| `lessons/flexbox.json` | modify | Rewrite `task` and validation `message` fields for all 6 lessons; add alternative validations for flexbox-6 |
|
||||||
|
|
||||||
|
No new files need to be created. No validator code changes needed — the existing `property_value` and `regex` validation types already support everything required.
|
||||||
|
|
||||||
|
## Detailed Changes Per Lesson
|
||||||
|
|
||||||
|
### flexbox-1 (Container)
|
||||||
|
- **Task**: Describe that nav links stack vertically and should display side by side
|
||||||
|
- **Validation msg**: Hint at display property for flex layout
|
||||||
|
- **Alt solutions**: None — `display: flex` is the only correct answer (inline-flex changes block behavior)
|
||||||
|
|
||||||
|
### flexbox-2 (Gap)
|
||||||
|
- **Task**: Describe that links are crammed together and need 1rem of spacing between them
|
||||||
|
- **Validation msg**: Hint at the gap property
|
||||||
|
- **Alt solutions**: None — `gap: 1rem` is the specific expected value
|
||||||
|
|
||||||
|
### flexbox-3 (Justify Content)
|
||||||
|
- **Task**: Describe that Login button should be pushed to the far right, with nav links on the left
|
||||||
|
- **Validation msg**: Hint at main-axis distribution property
|
||||||
|
- **Alt solutions**: None — `justify-content: space-between` is the only property that works when targeting `.nav`
|
||||||
|
|
||||||
|
### flexbox-4 (Align Items)
|
||||||
|
- **Task**: Describe the visual misalignment and ask for vertical centering
|
||||||
|
- **Validation msg**: Hint at cross-axis alignment property
|
||||||
|
- **Alt solutions**: None — `align-items: center` is the correct answer
|
||||||
|
|
||||||
|
### flexbox-5 (Flex Wrap)
|
||||||
|
- **Task**: Describe cards overflowing and needing to flow onto new rows
|
||||||
|
- **Validation msg**: Hint at wrapping property
|
||||||
|
- **Alt solutions**: None — `flex-wrap: wrap` is the only answer
|
||||||
|
|
||||||
|
### flexbox-6 (Flex Grow)
|
||||||
|
- **Task**: Describe that the search input should stretch to fill remaining space
|
||||||
|
- **Validation msg**: Hint at flex growth property
|
||||||
|
- **Alt solutions**: Accept both `flex: 1` and `flex-grow: 1` via regex validation
|
||||||
|
|
||||||
|
## Architecture Decisions
|
||||||
|
|
||||||
|
1. **No validator code changes**: The existing `regex` validation type can handle alternative solutions for flexbox-6. No need to add a new validation type.
|
||||||
|
2. **Keep values in tasks where needed**: Some tasks mention target values like "1rem" since the validator checks exact values and students need to know the amount. The key change is removing the *property name* from the task.
|
||||||
|
3. **Solution field unchanged**: The `solution` field is used for the "show solution" feature and should remain as the canonical answer.
|
||||||
|
4. **codePrefix unchanged**: The existing codePrefix already shows the selector context (e.g., `.nav {`), which is enough guidance for students.
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
| Risk | Likelihood | Mitigation |
|
||||||
|
|------|-----------|------------|
|
||||||
|
| Tasks become too vague for beginners | Low | Descriptions still teach the property; tasks describe specific visual outcomes |
|
||||||
|
| Alternative regex validation too permissive | Low | Regex will be specific to `flex:\s*1` and `flex-grow:\s*1` patterns |
|
||||||
|
| Validation messages too cryptic | Low | Messages will hint at the property category without giving the exact declaration |
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
1. **Run existing test suite**: `npm run test` — all tests should pass since no code or module structure changes
|
||||||
|
2. **Manual verification**: Validate that each rewritten task accurately describes the visual outcome shown in the preview
|
||||||
|
3. **JSON schema validation**: Ensure `lessons/flexbox.json` still conforms to the module schema
|
||||||
35
specs/003-flexbox-task-wording/spec.md
Normal file
35
specs/003-flexbox-task-wording/spec.md
Normal file
@@ -0,0 +1,35 @@
|
|||||||
|
# fix: remove answers from flexbox task descriptions (copy-paste score 95%)
|
||||||
|
|
||||||
|
**Issue**: [libretech/code-crispies#3](https://git.librete.ch/libretech/code-crispies/issues/3)
|
||||||
|
**State**: open
|
||||||
|
**Author**: libretech
|
||||||
|
**Labels**: none
|
||||||
|
**Complexity**: simple
|
||||||
|
|
||||||
|
## Issue Body
|
||||||
|
|
||||||
|
Pedagogy audit: All 6 flexbox exercises give the exact CSS declaration in the task text. Students type without understanding. Rewrite tasks to describe the DESIRED OUTCOME instead of the exact code. Example: 'Add display: flex' → 'The navigation links stack vertically. Make them display side by side.' Accept multiple valid solutions in validations.
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
All 6 lessons in `lessons/flexbox.json` have task descriptions that include the exact CSS declaration students need to type:
|
||||||
|
|
||||||
|
| Lesson | Current Task (gives away answer) |
|
||||||
|
|--------|----------------------------------|
|
||||||
|
| flexbox-1 | "Add `display: flex` to `.nav`" |
|
||||||
|
| flexbox-2 | "Add `gap: 1rem` to space out..." |
|
||||||
|
| flexbox-3 | "setting `justify-content: space-between` on the nav" |
|
||||||
|
| flexbox-4 | "Center them vertically with `align-items: center`" |
|
||||||
|
| flexbox-5 | "Add `flex-wrap: wrap` to allow them to wrap" |
|
||||||
|
| flexbox-6 | "setting `flex: 1` on `.search`" |
|
||||||
|
|
||||||
|
Validation error messages also give away answers (e.g., "Set `display: flex`").
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
1. All 6 flexbox task descriptions rewritten to describe the desired visual outcome, not the exact CSS code
|
||||||
|
2. Students cannot copy-paste from the task into the editor to pass
|
||||||
|
3. Validation error messages updated to provide hints without revealing the exact declaration
|
||||||
|
4. Where applicable, validations accept multiple valid CSS solutions (e.g., `flex: 1` and `flex-grow: 1`)
|
||||||
|
5. Existing tests continue to pass
|
||||||
|
6. Lesson descriptions (which teach the concepts) remain unchanged
|
||||||
13
specs/003-flexbox-task-wording/tasks.md
Normal file
13
specs/003-flexbox-task-wording/tasks.md
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
# Tasks
|
||||||
|
|
||||||
|
## Phase 1: Core Content Changes
|
||||||
|
- [X] Task 1.1: Rewrite task text for all 6 flexbox lessons to describe visual outcomes [P]
|
||||||
|
- [X] Task 1.2: Rewrite validation error messages to hint without revealing answers [P]
|
||||||
|
|
||||||
|
## Phase 2: Alternative Validations
|
||||||
|
- [X] Task 2.1: Add regex validation for flexbox-6 to accept both `flex: 1` and `flex-grow: 1`
|
||||||
|
|
||||||
|
## Phase 3: Validation
|
||||||
|
- [X] Task 3.1: Run existing test suite to confirm no regressions
|
||||||
|
- [X] Task 3.2: Verify flexbox.json still conforms to module schema
|
||||||
|
- [X] Task 3.3: Run lesson format check (`npm run format.lessons`)
|
||||||
77
specs/004-validation-messages/plan.md
Normal file
77
specs/004-validation-messages/plan.md
Normal file
@@ -0,0 +1,77 @@
|
|||||||
|
# Implementation Plan
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Rewrite validation error messages in the box-model and colors lesson modules (and their localizations) so they guide learners toward the answer instead of revealing it. This breaks the "fail-then-copy" loop identified in the pedagogy audit.
|
||||||
|
|
||||||
|
## Approach
|
||||||
|
|
||||||
|
1. Rewrite each validation `message` field in the English box-model and colors JSON files using question/hint phrasing that describes the *concept* without stating the exact property-value pair
|
||||||
|
2. Use the flexbox module's existing messages as the style guide
|
||||||
|
3. Apply equivalent translations to all 5 localized box-model files (ar, de, es, pl, uk)
|
||||||
|
4. Run the format-lessons script and tests to verify nothing breaks
|
||||||
|
5. Commit as a docs/content fix (`fix:` conventional commit)
|
||||||
|
|
||||||
|
## File Mapping
|
||||||
|
|
||||||
|
### Files to Modify
|
||||||
|
|
||||||
|
| File | Action | Changes |
|
||||||
|
|------|--------|---------|
|
||||||
|
| `lessons/01-box-model.json` | modify | Rewrite 11 validation messages |
|
||||||
|
| `lessons/03-colors.json` | modify | Rewrite 4 validation messages |
|
||||||
|
| `lessons/ar/01-box-model.json` | modify | Translate 11 new guiding messages to Arabic |
|
||||||
|
| `lessons/de/01-box-model.json` | modify | Translate 11 new guiding messages to German |
|
||||||
|
| `lessons/es/01-box-model.json` | modify | Translate 11 new guiding messages to Spanish |
|
||||||
|
| `lessons/pl/01-box-model.json` | modify | Translate 11 new guiding messages to Polish |
|
||||||
|
| `lessons/uk/01-box-model.json` | modify | Translate 11 new guiding messages to Ukrainian |
|
||||||
|
|
||||||
|
### Files NOT Changed
|
||||||
|
|
||||||
|
- `lessons/flexbox.json` — already uses guiding messages
|
||||||
|
- All localized flexbox files — already correct
|
||||||
|
- No colors localizations exist
|
||||||
|
|
||||||
|
## Architecture Decisions
|
||||||
|
|
||||||
|
1. **Message style**: Use the same imperative hint style as flexbox ("Use the property that...", "Try the property that...") rather than pure questions. This is consistent with the existing codebase and gives just enough direction without revealing the answer.
|
||||||
|
|
||||||
|
2. **No `<kbd>` tags in new messages**: The current answer-revealing messages use `<kbd>` to format exact code. The new guiding messages should avoid `<kbd>` since they won't contain code literals — they describe concepts.
|
||||||
|
|
||||||
|
3. **Preserve validation logic**: Only the `message` field changes. The `type`, `value`, `options`, and all other fields remain untouched.
|
||||||
|
|
||||||
|
4. **Localization approach**: Translate the English guiding messages into each target language, maintaining the same hint/question style. Keep CSS property names untranslated (they are code).
|
||||||
|
|
||||||
|
## Message Mapping (English)
|
||||||
|
|
||||||
|
| Lesson | Current Message | New Message |
|
||||||
|
|--------|----------------|-------------|
|
||||||
|
| box-model-1 | Set `padding: 1rem` | Which property adds space between content and the element's edge? |
|
||||||
|
| box-model-2 | Set `border-left: 4px solid steelblue` | Use the shorthand that sets a border on just one side |
|
||||||
|
| box-model-3 | Set `margin-bottom: 1rem` | Which property pushes neighboring elements away from the bottom? |
|
||||||
|
| box-model-4 | Set `box-sizing: border-box` | Which sizing mode includes padding and border in the element's width? |
|
||||||
|
| box-model-5 | Set `padding: 8px 1rem` | Use the two-value shorthand: vertical first, then horizontal |
|
||||||
|
| box-model-6 | Set `margin: 0 auto` | Use the shorthand that auto-calculates equal horizontal margins |
|
||||||
|
| box-model-7 | Set `border-radius: 50%` | Which property rounds corners? Think about what percentage makes a circle |
|
||||||
|
| box-model-8 v1 | Set `padding: 1rem` | Add inner spacing to the notification |
|
||||||
|
| box-model-8 v2 | Set `border-left: 4px solid coral` | Add a colored accent on the left edge |
|
||||||
|
| box-model-8 v3 | Set `border-radius: 4px` | Soften the corners of the notification |
|
||||||
|
| colors-1 | Set `background-color: seashell` | Which property fills the area behind the content? |
|
||||||
|
| colors-2 | Set `color: coral` | Which property changes the text color? |
|
||||||
|
| colors-3 | Set `border-color: coral` | Which property changes just the border's color without redefining the whole border? |
|
||||||
|
| colors-4 | Set `background-color: #ffd700` | Use the same background property, but with a hex code this time |
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
| Risk | Likelihood | Mitigation |
|
||||||
|
|------|-----------|------------|
|
||||||
|
| Translation quality for 5 languages | Medium | Use consistent patterns; CSS property names stay in English; keep messages short |
|
||||||
|
| Messages too vague, frustrating learners | Low | Each message still hints at the concept/direction; task descriptions already contain the answer for early lessons |
|
||||||
|
| Schema validation failure | Very Low | Only `message` string changes; no structural changes |
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
1. **Automated**: Run `npm run test` — existing unit tests validate the validator logic, not message content, so they should pass unchanged
|
||||||
|
2. **Automated**: Run `npm run format.lessons` — ensures JSON formatting is correct
|
||||||
|
3. **Manual verification**: Spot-check that each new message conceptually matches its lesson without revealing the answer
|
||||||
|
4. **Schema validation**: JSON files reference the schema; any structural errors would be caught by the editor/tooling
|
||||||
57
specs/004-validation-messages/spec.md
Normal file
57
specs/004-validation-messages/spec.md
Normal file
@@ -0,0 +1,57 @@
|
|||||||
|
# fix: validation error messages reveal the solution instead of guiding learning
|
||||||
|
|
||||||
|
**Issue:** [#4](https://git.librete.ch/libretech/code-crispies/issues/4)
|
||||||
|
**Repository:** libretech/code-crispies
|
||||||
|
**Author:** libretech
|
||||||
|
**State:** open
|
||||||
|
**Labels:** none
|
||||||
|
|
||||||
|
## Issue Body
|
||||||
|
|
||||||
|
Pedagogy audit: 88% of exercises reveal the answer in error messages, creating a fail-then-copy loop. Change validation messages from 'Set padding: 1rem' to 'Which property adds space between content and the element edge?' This applies across all modules — start with flexbox, box-model, and colors (the 3 worst offenders).
|
||||||
|
|
||||||
|
## Scope
|
||||||
|
|
||||||
|
The three priority modules:
|
||||||
|
|
||||||
|
1. **Flexbox** (`lessons/flexbox.json`) — already uses guiding messages (0 messages need changes)
|
||||||
|
2. **Box Model** (`lessons/01-box-model.json`) — 11 validation messages reveal exact answers
|
||||||
|
3. **Colors** (`lessons/03-colors.json`) — 4 validation messages reveal exact answers
|
||||||
|
|
||||||
|
Localized versions that need corresponding updates:
|
||||||
|
- `lessons/ar/01-box-model.json`
|
||||||
|
- `lessons/de/01-box-model.json`
|
||||||
|
- `lessons/es/01-box-model.json`
|
||||||
|
- `lessons/pl/01-box-model.json`
|
||||||
|
- `lessons/uk/01-box-model.json`
|
||||||
|
|
||||||
|
No localized versions exist for colors.
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
- [ ] All validation messages in box-model module guide the learner instead of revealing the answer
|
||||||
|
- [ ] All validation messages in colors module guide the learner instead of revealing the answer
|
||||||
|
- [ ] Messages use question or hint phrasing (e.g., "Which property..." or "Try the property that...")
|
||||||
|
- [ ] Messages never include the exact property-value pair that solves the exercise
|
||||||
|
- [ ] All 5 localized box-model files receive equivalent translated guiding messages
|
||||||
|
- [ ] Existing tests continue to pass (message content is not tested, only validation logic)
|
||||||
|
- [ ] Lesson JSON files remain valid against the module schema
|
||||||
|
|
||||||
|
## Current vs Desired Pattern
|
||||||
|
|
||||||
|
**Current (answer-revealing):**
|
||||||
|
```
|
||||||
|
"message": "Set <kbd>padding: 1rem</kbd>"
|
||||||
|
```
|
||||||
|
|
||||||
|
**Desired (guiding):**
|
||||||
|
```
|
||||||
|
"message": "Which property adds space between the content and the element's edge?"
|
||||||
|
```
|
||||||
|
|
||||||
|
## Prior Art
|
||||||
|
|
||||||
|
The flexbox module already follows the desired pattern. Its messages serve as the style reference:
|
||||||
|
- "Try changing the display mode to create a flex container"
|
||||||
|
- "Use the property that adds spacing between flex items"
|
||||||
|
- "Use the property that distributes items along the main axis"
|
||||||
20
specs/004-validation-messages/tasks.md
Normal file
20
specs/004-validation-messages/tasks.md
Normal file
@@ -0,0 +1,20 @@
|
|||||||
|
# Tasks
|
||||||
|
|
||||||
|
## Phase 1: English Lesson Files
|
||||||
|
- [X] Task 1.1: Rewrite 11 validation messages in `lessons/01-box-model.json`
|
||||||
|
- [X] Task 1.2: Rewrite 4 validation messages in `lessons/03-colors.json`
|
||||||
|
|
||||||
|
## Phase 2: Localized Box-Model Files
|
||||||
|
- [X] Task 2.1: Update validation messages in `lessons/ar/01-box-model.json` (Arabic) [P]
|
||||||
|
- [X] Task 2.2: Update validation messages in `lessons/de/01-box-model.json` (German) [P]
|
||||||
|
- [X] Task 2.3: Update validation messages in `lessons/es/01-box-model.json` (Spanish) [P]
|
||||||
|
- [X] Task 2.4: Update validation messages in `lessons/pl/01-box-model.json` (Polish) [P]
|
||||||
|
- [X] Task 2.5: Update validation messages in `lessons/uk/01-box-model.json` (Ukrainian) [P]
|
||||||
|
|
||||||
|
## Phase 3: Validation
|
||||||
|
- [X] Task 3.1: Run `npm run format.lessons` to normalize JSON formatting
|
||||||
|
- [X] Task 3.2: Run `npm run test` to verify no regressions
|
||||||
|
- [X] Task 3.3: Spot-check that no message reveals the exact answer
|
||||||
|
|
||||||
|
## Phase 4: Commit
|
||||||
|
- [X] Task 4.1: Commit all changes with conventional commit message
|
||||||
Reference in New Issue
Block a user