diff --git a/lessons/01-box-model.json b/lessons/01-box-model.json
index 1856165..231ada1 100644
--- a/lessons/01-box-model.json
+++ b/lessons/01-box-model.json
@@ -1,7 +1,7 @@
{
"$schema": "../schemas/code-crispies-module-schema.json",
"id": "box-model",
- "title": "WIP: Padding, Borders, and Margins",
+ "title": "Box Model",
"description": "Master the fundamental principles of space management in web design through the CSS box model. This module explores how content, padding, borders, and margins combine to create layout structures that are both visually appealing and structurally sound.",
"difficulty": "beginner",
"lessons": [
@@ -9,19 +9,20 @@
"id": "box-model-1",
"title": "Box Model Components",
"description": "The CSS box model consists of four concentric layers: content area (innermost), padding, border, and margin (outermost). Understanding how these components interact is essential for precise layout control.",
- "task": "Add padding: 1rem to .box to create space between its content and border.",
+ "task": "Add padding: 1rem to .box to create space between its content and border.",
"previewHTML": "
",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: mintcream; padding: 1rem; }",
"sandboxCSS": "",
"codePrefix": ".box {\n ",
"initialCode": "",
"codeSuffix": "\n}",
+ "solution": "border: 2px solid darkslategray;",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "border:\\s*2px\\s+solid\\s+darkslategray",
- "message": "Set border to '2px solid darkslategray'",
+ "message": "Set border: 2px solid darkslategray",
"options": { "caseSensitive": false }
}
]
@@ -50,39 +52,41 @@
"id": "box-model-3",
"title": "Adding Margins",
"description": "Margins create space between elements, controlling how they relate to one another within a layout. Unlike padding (which affects internal spacing), margins exist outside the element's border.",
- "task": "Add margin: 1rem to .margin-box to create space between it and the adjacent element.",
+ "task": "Add margin: 1rem to .margin-box to create space between it and the adjacent element.",
"previewHTML": "
This box needs margins
Adjacent element
",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .margin-box { background-color: plum; padding: 1rem; border: 2px solid orchid; } .neighbor { background-color: lightblue; padding: 1rem; border: 2px solid steelblue; }",
"sandboxCSS": "",
"codePrefix": ".margin-box {\n ",
"initialCode": "",
"codeSuffix": "\n}",
+ "solution": "margin: 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "margin", "expected": "1rem" },
- "message": "Set margin to '1rem'"
+ "message": "Set margin: 1rem"
}
]
},
{
"id": "box-model-4",
"title": "Box Sizing: Border-Box",
- "description": "The box-sizing property determines how element dimensions are calculated. The default 'content-box' excludes padding and border from width/height, while 'border-box' includes them, making layout calculations more intuitive.",
- "task": "Add box-sizing: border-box to .border-box so padding and border are included in its width.",
+ "description": "The box-sizing property determines how element dimensions are calculated. The default content-box excludes padding and border from width/height, while border-box includes them, making layout calculations more intuitive.",
+ "task": "Add box-sizing: border-box to .border-box so padding and border are included in its width.",
"previewHTML": "
Content-box (default)
Border-box
",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .sizing-demo { display: flex; gap: 1rem; } .box { width: 200px; padding: 1rem; border: 4px solid teal; background: lightcyan; } .content-box { box-sizing: content-box; }",
"sandboxCSS": "",
"codePrefix": ".border-box {\n ",
"initialCode": "",
"codeSuffix": "\n}",
+ "solution": "box-sizing: border-box;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "box-sizing", "expected": "border-box" },
- "message": "Set box-sizing to 'border-box'"
+ "message": "Set box-sizing: border-box"
}
]
},
@@ -90,19 +94,20 @@
"id": "box-model-5",
"title": "Margin Collapse",
"description": "When two vertical margins meet, they collapse to the larger value instead of adding up. Understanding this behavior is crucial for consistent vertical spacing.",
- "task": "Add margin-bottom: 2rem to .first. Notice the space between paragraphs equals 2rem (not 3rem) due to margin collapse.",
+ "task": "Add margin-bottom: 2rem to .first. Notice the space between paragraphs equals 2rem (not 3rem) due to margin collapse.",
"previewHTML": "
This paragraph has a bottom margin.
This paragraph has a top margin of 1rem.
",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .collapse-demo { border: 1px solid silver; padding: 8px; background: ghostwhite; } .second { margin-top: 1rem; background: linen; }",
"sandboxCSS": "",
"codePrefix": ".first {\n ",
"initialCode": "",
"codeSuffix": "\n}",
+ "solution": "margin-bottom: 2rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "margin-bottom", "expected": "2rem" },
- "message": "Set margin-bottom to '2rem'"
+ "message": "Set margin-bottom: 2rem"
}
]
},
@@ -110,19 +115,20 @@
"id": "box-model-6",
"title": "Margin Shorthand Notation",
"description": "The margin shorthand can set all four sides at once. Two values set vertical (top/bottom) and horizontal (left/right) margins respectively.",
- "task": "Add margin: 1rem 2rem to .shorthand-box for 1rem top/bottom and 2rem left/right.",
+ "task": "Add margin: 1rem 2rem to .shorthand-box for 1rem top/bottom and 2rem left/right.",
"previewHTML": "
This box needs margins: 1rem top/bottom, 2rem left/right
",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .shorthand-box { background-color: honeydew; border: 2px solid mediumseagreen; padding: 1rem; }",
"sandboxCSS": "",
"codePrefix": ".shorthand-box {\n ",
"initialCode": "",
"codeSuffix": "\n}",
+ "solution": "margin: 1rem 2rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "margin:\\s*1rem\\s+2rem",
- "message": "Set margin to '1rem 2rem'",
+ "message": "Set margin: 1rem 2rem",
"options": { "caseSensitive": false }
}
]
@@ -131,39 +137,41 @@
"id": "box-model-7",
"title": "Padding Shorthand Notation",
"description": "Like margin, padding shorthand allows setting all sides at once. A single value applies to all four sides equally.",
- "task": "Add padding: 1.5rem to .padding-box to add equal padding on all sides.",
+ "task": "Add padding: 1.5rem to .padding-box to add equal padding on all sides.",
"previewHTML": "
This box needs equal padding on all sides
",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .padding-box { background-color: papayawhip; border: 2px solid orange; }",
"sandboxCSS": "",
"codePrefix": ".padding-box {\n ",
"initialCode": "",
"codeSuffix": "\n}",
+ "solution": "padding: 1.5rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "padding", "expected": "1.5rem" },
- "message": "Set padding to '1.5rem'"
+ "message": "Set padding: 1.5rem"
}
]
},
{
"id": "box-model-8",
"title": "Border on Specific Sides",
- "description": "For granular control, you can target specific sides with border-top, border-right, border-bottom, or border-left.",
- "task": "Add border-bottom: 4px solid dodgerblue to .border-demo.",
+ "description": "For granular control, you can target specific sides with border-top, border-right, border-bottom, or border-left.",
+ "task": "Add border-bottom: 4px solid dodgerblue to .border-demo.",
"previewHTML": "
This element needs only a bottom border
",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .border-demo { padding: 1rem; background-color: aliceblue; }",
"sandboxCSS": "",
"codePrefix": ".border-demo {\n ",
"initialCode": "",
"codeSuffix": "\n}",
+ "solution": "border-bottom: 4px solid dodgerblue;",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "border-bottom:\\s*4px\\s+solid\\s+dodgerblue",
- "message": "Set border-bottom to '4px solid dodgerblue'",
+ "message": "Set border-bottom: 4px solid dodgerblue",
"options": { "caseSensitive": false }
}
]
diff --git a/lessons/05-units-variables.json b/lessons/05-units-variables.json
index 82afc57..682c4b2 100644
--- a/lessons/05-units-variables.json
+++ b/lessons/05-units-variables.json
@@ -1,7 +1,7 @@
{
"$schema": "../schemas/code-crispies-module-schema.json",
"id": "units-variables",
- "title": "Units, var() and calc()",
+ "title": "Units & Vars",
"description": "Understand the variety of CSS measurement units and how to define and use custom properties for maintainable styles.",
"difficulty": "beginner",
"lessons": [
@@ -9,36 +9,38 @@
"id": "units-1",
"title": "Absolute vs. Relative Units",
"description": "Learn the difference between px, rem, em, %, and vw/vh for flexible, responsive layouts.",
- "task": "Set the width of '.unit-box' to 80% and max-width to 37.5rem.",
+ "task": "Set the width of .unit-box to 80% and max-width to 37.5rem.",
"previewHTML": "
Resize me!
",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .unit-box { background: #f5f5f5; padding: 1rem; }",
"sandboxCSS": "",
"codePrefix": "/* Set flexible sizing */\n.unit-box {",
"initialCode": "",
"codeSuffix": "}",
+ "solution": " width: 80%;\n max-width: 37.5rem;",
"previewContainer": "preview-area",
"validations": [
- { "type": "contains", "value": "width", "message": "Use 'width' property", "options": { "caseSensitive": false } },
- { "type": "property_value", "value": { "property": "width", "expected": "80%" }, "message": "Set width to '80%'" },
- { "type": "contains", "value": "max-width", "message": "Use 'max-width' property", "options": { "caseSensitive": false } },
- { "type": "property_value", "value": { "property": "max-width", "expected": "37.5rem" }, "message": "Set max-width to '37.5rem'" }
+ { "type": "contains", "value": "width", "message": "Use width property", "options": { "caseSensitive": false } },
+ { "type": "property_value", "value": { "property": "width", "expected": "80%" }, "message": "Set width to 80%" },
+ { "type": "contains", "value": "max-width", "message": "Use max-width property", "options": { "caseSensitive": false } },
+ { "type": "property_value", "value": { "property": "max-width", "expected": "37.5rem" }, "message": "Set max-width to 37.5rem" }
]
},
{
"id": "units-2",
"title": "CSS Custom Properties",
"description": "Define and reuse variables (--custom properties) to centralize your theme values.",
- "task": "Create a --main-color variable in :root with #6200ee and apply it as the border color on '.var-box'.",
+ "task": "Create a --main-color variable in :root with #6200ee and apply it as the border color on .var-box.",
"previewHTML": "
Variable Box
",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .var-box { padding: 1rem; border: 0.125rem solid #ddd; }",
"sandboxCSS": "",
"codePrefix": "/* Define and use a CSS variable */\n:root {",
"initialCode": "",
"codeSuffix": "}\n.var-box { }",
+ "solution": " --main-color: #6200ee;\n}\n.var-box {\n border-color: var(--main-color);",
"previewContainer": "preview-area",
"validations": [
- { "type": "contains", "value": "--main-color", "message": "Define '--main-color' in :root", "options": { "caseSensitive": false } },
- { "type": "contains", "value": "var(--main-color)", "message": "Use var(--main-color)", "options": { "caseSensitive": false } },
+ { "type": "contains", "value": "--main-color", "message": "Define --main-color in :root", "options": { "caseSensitive": false } },
+ { "type": "contains", "value": "var(--main-color)", "message": "Use var(--main-color)", "options": { "caseSensitive": false } },
{
"type": "property_value",
"value": { "property": "border", "expected": "var(--main-color)" },
@@ -50,27 +52,28 @@
{
"id": "units-3",
"title": "Unit Calculations (calc)",
- "description": "Use the calc() function to combine different units in one expression.",
- "task": "Set the width of '.calc-box' to calc(100% - 2rem) and min-height to calc(10vh + 1rem).",
+ "description": "Use the calc() function to combine different units in one expression.",
+ "task": "Set the width of .calc-box to calc(100% - 2rem) and min-height to calc(10vh + 1rem).",
"previewHTML": "
Calc Demo
",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .calc-box { background: #e8f5e9; padding: 1rem; }",
"sandboxCSS": "",
"codePrefix": "/* Use calc for dynamic sizing */\n.calc-box {",
"initialCode": "",
"codeSuffix": "}",
+ "solution": " width: calc(100% - 2rem);\n min-height: calc(10vh + 1rem);",
"previewContainer": "preview-area",
"validations": [
- { "type": "contains", "value": "calc", "message": "Use 'calc()' function", "options": { "caseSensitive": false } },
+ { "type": "contains", "value": "calc", "message": "Use calc() function", "options": { "caseSensitive": false } },
{
"type": "regex",
"value": "width:\\s*calc\\(100% - 2rem\\)",
- "message": "Width should be calc(100% - 2rem)",
+ "message": "Width should be calc(100% - 2rem)",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "min-height:\\s*calc\\(10vh \\+ 1rem\\)",
- "message": "Min-height should be calc(10vh + 1rem)",
+ "message": "Min-height should be calc(10vh + 1rem)",
"options": { "caseSensitive": false }
}
]
@@ -79,19 +82,20 @@
"id": "units-4",
"title": "Viewport & Responsive Units",
"description": "Control layouts relative to viewport size with vw, vh, and vmin/vmax units.",
- "task": "Give '.viewport-box' a width of 50vw and height of 20vh.",
+ "task": "Give .viewport-box a width of 50vw and height of 20vh.",
"previewHTML": "
Viewport Box
",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .viewport-box { background: #ffe0b2; }",
"sandboxCSS": "",
"codePrefix": "/* Use viewport units */\n.viewport-box {",
"initialCode": "",
"codeSuffix": "}",
+ "solution": " width: 50vw;\n height: 20vh;",
"previewContainer": "preview-area",
"validations": [
- { "type": "contains", "value": "vw", "message": "Use 'vw' unit", "options": { "caseSensitive": false } },
- { "type": "contains", "value": "vh", "message": "Use 'vh' unit", "options": { "caseSensitive": false } },
- { "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Set width to '50vw'" },
- { "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Set height to '20vh'" }
+ { "type": "contains", "value": "vw", "message": "Use vw unit", "options": { "caseSensitive": false } },
+ { "type": "contains", "value": "vh", "message": "Use vh unit", "options": { "caseSensitive": false } },
+ { "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Set width to 50vw" },
+ { "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Set height to 20vh" }
]
}
]
diff --git a/lessons/06-transitions-animations.json b/lessons/06-transitions-animations.json
index bdb7a17..1d5b942 100644
--- a/lessons/06-transitions-animations.json
+++ b/lessons/06-transitions-animations.json
@@ -1,129 +1,128 @@
{
"$schema": "../schemas/code-crispies-module-schema.json",
"id": "transitions-animations",
- "title": "Transitions & Animations",
+ "title": "Animations",
"description": "Bring interactivity to your UI by smoothly transitioning properties and creating keyframe-driven animations.",
"difficulty": "beginner",
"lessons": [
{
"id": "transitions-1",
- "title": "Simple Transitions",
- "description": "Learn how to apply transition to properties for smooth changes on state changes.",
- "task": "Add a hover transition on a button so its background-color fades over 0.3s.",
+ "title": "Transitions",
+ "description": "Learn how to apply transition to properties for smooth changes on state changes.",
+ "task": "Add transition: background-color 0.3s to .btn so the color fades smoothly on hover.",
"previewHTML": "",
- "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: #6200ee; color: white; padding: 0.5rem 1rem; border: none; } .btn:hover { background: #3700b3; }",
+ "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: black; color: white; padding: 0.5rem 1rem; border: none; cursor: pointer; } .btn:hover { background: white; color: black; }",
"sandboxCSS": "",
"codePrefix": "/* Add transition */\n.btn {",
"initialCode": "",
"codeSuffix": "}",
+ "solution": " transition: background-color 0.3s;",
"previewContainer": "preview-area",
"validations": [
- { "type": "contains", "value": "transition", "message": "Use the 'transition' property", "options": { "caseSensitive": false } },
+ { "type": "contains", "value": "transition", "message": "Use the transition property", "options": { "caseSensitive": false } },
{
"type": "regex",
"value": "transition:\\s*background-color\\s*0\\.3s",
- "message": "Transition background-color over 0.3s",
+ "message": "Set transition: background-color 0.3s",
"options": { "caseSensitive": false }
}
]
},
{
"id": "transitions-2",
- "title": "Transition Timing Functions",
- "description": "Explore easing functions like ease, linear, ease-in, ease-out to control animation pacing.",
- "task": "Modify the button to use 'ease-in-out' timing for its transition.",
+ "title": "Timing Funcs",
+ "description": "Explore easing functions like ease, linear, ease-in, ease-out to control animation pacing.",
+ "task": "Set transition-timing-function to ease-in-out on .btn.",
"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; }",
+ "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: navy; color: gold; padding: 0.5rem 1rem; border: none; cursor: pointer; transition: all 0.5s; } .btn:hover { background: gold; color: navy; transform: scale(1.1); }",
"sandboxCSS": "",
"codePrefix": "/* Set timing function */\n.btn {",
"initialCode": "",
"codeSuffix": "}",
+ "solution": " transition-timing-function: ease-in-out;",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "transition-timing-function",
- "message": "Use 'transition-timing-function'",
+ "message": "Use transition-timing-function",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "transition-timing-function", "expected": "ease-in-out" },
- "message": "Set timing to 'ease-in-out'"
+ "message": "Set timing to ease-in-out"
}
]
},
{
"id": "transitions-3",
- "title": "Keyframe Animations Basics",
- "description": "Create named animations using @keyframes and apply them via the animation shorthand.",
- "task": "Define a keyframe named 'bounce' that moves an element up 20px at 50% and apply it to '.ball' over 1s infinite.",
+ "title": "Keyframes",
+ "description": "Create named animations using @keyframes and apply them via the animation shorthand.",
+ "task": "Define a keyframe at 50% with transform: translateY(-20px) and apply animation: bounce 1s infinite to .ball.",
"previewHTML": "",
- "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: #ff0266; border-radius: 50%; margin: 2rem auto; }",
+ "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: crimson; border-radius: 50%; margin: 2rem auto; }",
"sandboxCSS": "",
"codePrefix": "/* Define keyframes and apply animation */\n@keyframes bounce {",
"initialCode": "",
"codeSuffix": "}\n.ball { }",
+ "solution": " 50% { transform: translateY(-20px); }\n}\n.ball {\n animation: bounce 1s infinite;",
"previewContainer": "preview-area",
"validations": [
- { "type": "contains", "value": "@keyframes bounce", "message": "Define '@keyframes bounce'", "options": { "caseSensitive": false } },
+ { "type": "contains", "value": "@keyframes bounce", "message": "Define @keyframes bounce", "options": { "caseSensitive": false } },
{
"type": "regex",
"value": "50%.*transform: translateY\\(-20px\\)",
- "message": "At 50%, move up 20px",
+ "message": "At 50%, use transform: translateY(-20px)",
"options": { "caseSensitive": false }
},
- { "type": "contains", "value": "animation", "message": "Use 'animation' property on .ball", "options": { "caseSensitive": false } },
+ { "type": "contains", "value": "animation", "message": "Use animation property on .ball", "options": { "caseSensitive": false } },
{
"type": "regex",
"value": "animation:.*bounce.*1s.*infinite",
- "message": "Apply 'bounce 1s infinite'",
+ "message": "Apply animation: bounce 1s infinite",
"options": { "caseSensitive": false }
}
]
},
{
"id": "transitions-4",
- "title": "Animation Properties Deep Dive",
- "description": "Fine-tune animations with delay, iteration-count, direction, and fill-mode.",
- "task": "Animate '.box' using a 'fade' keyframe over 2s, delay 1s, run twice, and remain visible after.",
- "previewHTML": "
Fade Demo
",
- "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { width: 100px; height: 100px; background: #4caf50; margin: 2rem auto; }",
+ "title": "Anim Properties",
+ "description": "Fine-tune animations with animation-delay, animation-iteration-count, animation-direction, and animation-fill-mode.",
+ "task": "Apply the pulse animation to .box with animation-name: pulse, animation-duration: 2s, animation-delay: 1s, animation-iteration-count: 2, and animation-fill-mode: forwards.",
+ "previewHTML": "
Pulse
",
+ "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { width: 100px; height: 100px; background: black; color: white; display: flex; align-items: center; justify-content: center; margin: 2rem auto; } @keyframes pulse { 0% { background: black; color: white; transform: scale(1); } 50% { background: white; color: black; transform: scale(1.2); } 100% { background: limegreen; color: black; transform: scale(1); } }",
"sandboxCSS": "",
- "codePrefix": "/* Define fade and set properties */\n@keyframes fade { from { opacity: 0; } to { opacity: 1; } }\n.box {",
+ "codePrefix": "/* Apply animation properties */\n.box {",
"initialCode": "",
"codeSuffix": "}",
+ "solution": " animation-name: pulse;\n animation-duration: 2s;\n animation-delay: 1s;\n animation-iteration-count: 2;\n animation-fill-mode: forwards;",
"previewContainer": "preview-area",
"validations": [
{
- "type": "contains",
- "value": "animation-delay",
- "message": "Use 'animation-delay' property",
- "options": { "caseSensitive": false }
+ "type": "property_value",
+ "value": { "property": "animation-name", "expected": "pulse" },
+ "message": "Set animation-name: pulse"
},
{
- "type": "contains",
- "value": "animation-iteration-count",
- "message": "Use 'animation-iteration-count' property",
- "options": { "caseSensitive": false }
+ "type": "property_value",
+ "value": { "property": "animation-duration", "expected": "2s" },
+ "message": "Set animation-duration: 2s"
},
{
- "type": "contains",
- "value": "animation-fill-mode",
- "message": "Use 'animation-fill-mode' property",
- "options": { "caseSensitive": false }
+ "type": "property_value",
+ "value": { "property": "animation-delay", "expected": "1s" },
+ "message": "Set animation-delay: 1s"
},
- { "type": "property_value", "value": { "property": "animation-duration", "expected": "2s" }, "message": "Duration should be 2s" },
- { "type": "property_value", "value": { "property": "animation-delay", "expected": "1s" }, "message": "Delay should be 1s" },
{
"type": "property_value",
"value": { "property": "animation-iteration-count", "expected": "2" },
- "message": "Iteration count should be 2"
+ "message": "Set animation-iteration-count: 2"
},
{
"type": "property_value",
"value": { "property": "animation-fill-mode", "expected": "forwards" },
- "message": "Fill mode should be forwards"
+ "message": "Set animation-fill-mode: forwards"
}
]
}
diff --git a/lessons/08-responsive.json b/lessons/08-responsive.json
index 089a03f..718fbb1 100644
--- a/lessons/08-responsive.json
+++ b/lessons/08-responsive.json
@@ -1,114 +1,118 @@
{
"$schema": "../schemas/code-crispies-module-schema.json",
"id": "responsive-design",
- "title": "Responsive Design & Media Queries",
+ "title": "Responsive",
"description": "Make your layouts adapt to different screen sizes using media queries and fluid design techniques.",
"difficulty": "intermediate",
"lessons": [
{
"id": "responsive-1",
- "title": "Introduction to Media Queries",
+ "title": "Media Queries",
"description": "Understand the syntax and use cases for CSS media queries to apply styles conditionally based on viewport characteristics.",
- "task": "Write a media query that applies when the viewport is at most 600px wide and changes the background of '.responsive-box' to lightcoral.",
+ "task": "Write a media query with @media (max-width: 600px) that changes .responsive-box background to lightcoral.",
"previewHTML": "
Resize the window
",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .responsive-box { padding: 1rem; background: lightblue; }",
"sandboxCSS": "",
"codePrefix": "/* Add your media query below */\n",
"initialCode": "",
"codeSuffix": "",
+ "solution": "@media (max-width: 600px) {\n .responsive-box {\n background: lightcoral;\n }\n}",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "@media\\s*\\(max-width:\\s*600px\\)",
- "message": "Use a media query for max-width: 600px",
+ "message": "Use @media (max-width: 600px)",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": ".responsive-box",
- "message": "Target '.responsive-box' inside the media query",
+ "message": "Target .responsive-box inside the media query",
"options": { "caseSensitive": false }
},
- { "type": "contains", "value": "background", "message": "Change the 'background' property", "options": { "caseSensitive": false } },
{
"type": "property_value",
"value": { "property": "background", "expected": "lightcoral" },
- "message": "Set background to 'lightcoral'",
+ "message": "Set background: lightcoral",
"options": { "exact": false }
}
]
},
{
"id": "responsive-2",
- "title": "Fluid Typography",
- "description": "Use relative units like vw to make font sizes scale with the viewport width.",
- "task": "Set the font-size of '.fluid-text' to 5vw so it scales as the viewport changes.",
+ "title": "Fluid Type",
+ "description": "Use relative units like vw to make font sizes scale with the viewport width.",
+ "task": "Set font-size: 5vw on .fluid-text so it scales as the viewport changes.",
"previewHTML": "
Fluid Typography
",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; }",
"sandboxCSS": "",
"codePrefix": "/* Apply fluid font sizing */\n.fluid-text {",
"initialCode": "",
"codeSuffix": "}",
+ "solution": " font-size: 5vw;",
"previewContainer": "preview-area",
"validations": [
- { "type": "contains", "value": "font-size", "message": "Use 'font-size' property", "options": { "caseSensitive": false } },
- { "type": "contains", "value": "vw", "message": "Use 'vw' unit for fluid sizing", "options": { "caseSensitive": false } },
- { "type": "property_value", "value": { "property": "font-size", "expected": "5vw" }, "message": "Set font-size to '5vw'" }
+ { "type": "property_value", "value": { "property": "font-size", "expected": "5vw" }, "message": "Set font-size: 5vw" }
]
},
{
"id": "responsive-3",
- "title": "Flexible Grids",
- "description": "Combine CSS Grid with auto-fit or auto-fill for responsive column layouts.",
- "task": "Define '.grid-responsive' with grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)); and a gap of 1rem.",
+ "title": "Flex Grids",
+ "description": "Combine CSS Grid with auto-fit or auto-fill for responsive column layouts.",
+ "task": "Add display: grid, grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)), and gap: 1rem to .grid-responsive.",
"previewHTML": "
1
2
3
4
",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .grid-responsive > div { background: #d1c4e9; padding: 1rem; }",
"sandboxCSS": "",
"codePrefix": "/* Create a responsive grid */\n.grid-responsive {",
"initialCode": "",
"codeSuffix": "}",
+ "solution": " display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
"previewContainer": "preview-area",
"validations": [
{
- "type": "contains",
- "value": "grid-template-columns",
- "message": "Define 'grid-template-columns'",
- "options": { "caseSensitive": false }
+ "type": "property_value",
+ "value": { "property": "display", "expected": "grid" },
+ "message": "Set display: grid"
},
{
"type": "regex",
"value": "repeat\\(auto-fit,\\s*minmax\\(200px,\\s*1fr\\)\\)",
- "message": "Use repeat(auto-fit, minmax(200px, 1fr))",
+ "message": "Use repeat(auto-fit, minmax(200px, 1fr))",
"options": { "caseSensitive": false }
},
- { "type": "contains", "value": "gap", "message": "Use 'gap' property", "options": { "caseSensitive": false } }
+ {
+ "type": "property_value",
+ "value": { "property": "gap", "expected": "1rem" },
+ "message": "Set gap: 1rem"
+ }
]
},
{
"id": "responsive-4",
- "title": "Mobile-First Media Queries",
+ "title": "Mobile-First",
"description": "Adopt a mobile-first approach by writing base styles for small screens and enhancing for larger viewports.",
- "task": "Write a media query for min-width 768px that sets '.sidebar' width to 250px.",
+ "task": "Write a media query with @media (min-width: 768px) that sets .sidebar width to 250px.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sidebar { background: #c8e6c9; padding: 1rem; }",
"sandboxCSS": "",
"codePrefix": "/* Add mobile-first enhancement */\n",
"initialCode": "",
"codeSuffix": "",
+ "solution": "@media (min-width: 768px) {\n .sidebar {\n width: 250px;\n }\n}",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "@media\\s*\\(min-width:\\s*768px\\)",
- "message": "Use a media query for min-width: 768px",
+ "message": "Use @media (min-width: 768px)",
"options": { "caseSensitive": false }
},
- { "type": "contains", "value": ".sidebar", "message": "Target '.sidebar' inside media query", "options": { "caseSensitive": false } },
+ { "type": "contains", "value": ".sidebar", "message": "Target .sidebar inside media query", "options": { "caseSensitive": false } },
{
"type": "property_value",
"value": { "property": "width", "expected": "250px" },
- "message": "Set width to '250px'",
+ "message": "Set width: 250px",
"options": { "exact": false }
}
]
diff --git a/lessons/flexbox.json b/lessons/flexbox.json
index 807dec0..f98606a 100644
--- a/lessons/flexbox.json
+++ b/lessons/flexbox.json
@@ -1,21 +1,22 @@
{
"$schema": "../schemas/code-crispies-module-schema.json",
"id": "flexbox",
- "title": "CSS Flexbox",
+ "title": "Flexbox",
"description": "Master the flexible box layout model for modern responsive designs",
"difficulty": "intermediate",
"lessons": [
{
"id": "flexbox-1",
- "title": "Flexbox Container Basics",
+ "title": "Container",
"description": "Learn how to create a flex container and understand the main and cross axes.",
- "task": "Add display: flex to .flex-container to create a flexbox layout.",
+ "task": "Add display: flex to .flex-container to create a flexbox layout.",
"previewHTML": "
1
2
3
",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; }",
"sandboxCSS": ".flex-container { border: 2px dashed #aaa; padding: 1rem; }",
"codePrefix": ".flex-container {\n ",
"initialCode": "",
"codeSuffix": "\n}",
+ "solution": "display: flex;",
"previewContainer": "preview-area",
"validations": [
{
@@ -24,46 +25,31 @@
"property": "display",
"expected": "flex"
},
- "message": "Set display to 'flex'"
+ "message": "Set display: flex"
}
]
},
{
"id": "flexbox-2",
- "title": "Flex Direction and Wrap",
+ "title": "Direction & Wrap",
"description": "Control the direction and wrapping of flex items within a container.",
- "task": "Add flex-direction: column and flex-wrap: wrap to .flex-container.",
+ "task": "Add flex-direction: column and flex-wrap: wrap to .flex-container.",
"previewHTML": "