refactor: rewrite CSS lessons with realistic real-world examples

- Box Model: profile cards, alerts, buttons instead of generic boxes
- Flexbox: navigation bars, headers, toolbars, card layouts
- Grid: photo gallery with SVG images, product cards, dashboard layout
- Colors: notification alerts, buttons, badges with visible changes
- Units/Variables: article width, brand variables, sidebar calc, hero vh
- Responsive: feature cards grid instead of numbered divs
- Added missing solutions to enable "Show Expected" feature
- Fixed barely visible border color change in colors lesson

🤖 Generated with [Claude Code](https://claude.com/claude-code)
This commit is contained in:
2026-01-13 21:11:41 +01:00
parent fb33930328
commit 98d4362706
9 changed files with 348 additions and 481 deletions

View File

@@ -129,6 +129,7 @@
"initialCode": "", "initialCode": "",
"codeSuffix": "", "codeSuffix": "",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"solution": ".highlight {\n background-color: yellow;\n font-weight: bold;\n}",
"validations": [ "validations": [
{ {
"type": "regex", "type": "regex",
@@ -249,6 +250,7 @@
"initialCode": "", "initialCode": "",
"codeSuffix": "", "codeSuffix": "",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"solution": "span.highlight {\n background-color: orange;\n}",
"validations": [ "validations": [
{ {
"type": "regex", "type": "regex",
@@ -293,6 +295,7 @@
"initialCode": "", "initialCode": "",
"codeSuffix": "", "codeSuffix": "",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"solution": "#main-title {\n color: purple;\n text-decoration: underline;\n}",
"validations": [ "validations": [
{ {
"type": "regex", "type": "regex",
@@ -350,6 +353,7 @@
"initialCode": "", "initialCode": "",
"codeSuffix": "", "codeSuffix": "",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"solution": "p#special {\n font-style: italic;\n}",
"validations": [ "validations": [
{ {
"type": "regex", "type": "regex",
@@ -481,6 +485,7 @@
"initialCode": "", "initialCode": "",
"codeSuffix": "", "codeSuffix": "",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"solution": "div.container * {\n margin: 0;\n}",
"validations": [ "validations": [
{ {
"type": "regex", "type": "regex",
@@ -525,6 +530,7 @@
"initialCode": "", "initialCode": "",
"codeSuffix": "", "codeSuffix": "",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"solution": ".content p {\n color: green;\n}",
"validations": [ "validations": [
{ {
"type": "regex", "type": "regex",

View File

@@ -17,7 +17,7 @@
"initialCode": "", "initialCode": "",
"codeSuffix": "", "codeSuffix": "",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"solution": "input[type=\"text\"] {\n background-color: lightblue;\n border: 2px solid blue\n}", "solution": "input[type=\"text\"] {\n background-color: lightblue;\n border: 2px solid blue;\n}",
"validations": [ "validations": [
{ {
"type": "regex", "type": "regex",

View File

@@ -7,13 +7,13 @@
"lessons": [ "lessons": [
{ {
"id": "box-model-1", "id": "box-model-1",
"title": "Box Model Components", "title": "Padding",
"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.", "description": "Every element in CSS is a box with four layers: content, padding, border, and margin. <strong>Padding</strong> creates breathing room between your content and the box's edge.<br><br>Without padding, text presses against borders awkwardly. Padding makes content readable and visually balanced.<br><br><pre>.card {\n padding: 1rem;\n}</pre>",
"task": "Set <kbd>padding</kbd> to <kbd>1rem</kbd> to create space between the content and border.", "task": "This profile card looks cramped. Add <kbd>padding: 1rem</kbd> to <kbd>.card</kbd> so the text has room to breathe.",
"previewHTML": "<div class=\"box\">Box Model Components</div>", "previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: lavender; border: 2px dashed slategray; }", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
"sandboxCSS": "", "sandboxCSS": "",
"codePrefix": ".box {\n ", "codePrefix": ".card {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "\n}", "codeSuffix": "\n}",
"solution": "padding: 1rem;", "solution": "padding: 1rem;",
@@ -28,56 +28,56 @@
}, },
{ {
"id": "box-model-2", "id": "box-model-2",
"title": "Adding Borders", "title": "Borders",
"description": "Borders outline an element, creating visual separation from surrounding content. The border shorthand accepts three values: width, style, and color.", "description": "Borders create visual boundaries around elements. The <kbd>border</kbd> shorthand takes three values: width, style, and color.<br><br>Common styles: <kbd>solid</kbd>, <kbd>dashed</kbd>, <kbd>dotted</kbd>, <kbd>none</kbd>",
"task": "Set <kbd>border</kbd> to <kbd>2px solid darkslategray</kbd>.", "task": "Add a subtle left accent to the card with <kbd>border-left: 4px solid steelblue</kbd>.",
"previewHTML": "<div class=\"box\">This box needs a border</div>", "previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: mintcream; padding: 1rem; }", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
"sandboxCSS": "", "sandboxCSS": "",
"codePrefix": ".box {\n ", "codePrefix": ".card {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "\n}", "codeSuffix": "\n}",
"solution": "border: 2px solid darkslategray;", "solution": "border-left: 4px solid steelblue;",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"validations": [ "validations": [
{ {
"type": "regex", "type": "regex",
"value": "border:\\s*2px\\s+solid\\s+darkslategray", "value": "border-left:\\s*4px\\s+solid\\s+steelblue",
"message": "Set <kbd>border: 2px solid darkslategray</kbd>", "message": "Set <kbd>border-left: 4px solid steelblue</kbd>",
"options": { "caseSensitive": false } "options": { "caseSensitive": false }
} }
] ]
}, },
{ {
"id": "box-model-3", "id": "box-model-3",
"title": "Adding Margins", "title": "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.", "description": "Margins create space <em>outside</em> the element, separating it from neighbors. While padding pushes content inward, margins push other elements away.",
"task": "Set <kbd>margin</kbd> to <kbd>1rem</kbd> to create space between this element and its neighbors.", "task": "Add space between these two profile cards with <kbd>margin-bottom: 1rem</kbd> on <kbd>.card</kbd>.",
"previewHTML": "<div class=\"container\"><div class=\"outer\">This box needs margins</div><div class=\"neighbor\">Adjacent element</div></div>", "previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article><article class=\"card\"><h3>Alex Rivera</h3><p>UX Designer</p></article>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .outer { background-color: plum; padding: 1rem; border: 2px solid orchid; } .neighbor { background-color: lightblue; padding: 1rem; border: 2px solid steelblue; }", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; border-left: 4px solid steelblue; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
"sandboxCSS": "", "sandboxCSS": "",
"codePrefix": ".outer {\n ", "codePrefix": ".card {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "\n}", "codeSuffix": "\n}",
"solution": "margin: 1rem;", "solution": "margin-bottom: 1rem;",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"validations": [ "validations": [
{ {
"type": "property_value", "type": "property_value",
"value": { "property": "margin", "expected": "1rem" }, "value": { "property": "margin-bottom", "expected": "1rem" },
"message": "Set <kbd>margin: 1rem</kbd>" "message": "Set <kbd>margin-bottom: 1rem</kbd>"
} }
] ]
}, },
{ {
"id": "box-model-4", "id": "box-model-4",
"title": "Box Sizing: Border-Box", "title": "Box Sizing",
"description": "The <kbd>box-sizing</kbd> property determines how element dimensions are calculated. The default <kbd>content-box</kbd> excludes padding and border from width/height, while <kbd>border-box</kbd> includes them, making layout calculations more intuitive.", "description": "By default, <kbd>width</kbd> only sets the content width. Padding and borders add to the total. This causes layout headaches.<br><br><kbd>box-sizing: border-box</kbd> includes padding and border in the width, making sizing predictable. Most developers apply this to all elements.",
"task": "Set <kbd>box-sizing</kbd> to <kbd>border-box</kbd> so padding and border are included in the width.", "task": "Both cards have <kbd>width: 200px</kbd>. The left uses default sizing (content-box), making it wider than expected. Fix the right card with <kbd>box-sizing: border-box</kbd>.",
"previewHTML": "<div class=\"sizing-demo\"><div class=\"box default\">Content-box (default)</div><div class=\"box sized\">Border-box</div></div>", "previewHTML": "<div class=\"demo\"><article class=\"card\">Content-box</article><article class=\"card fix\">Border-box</article></div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .sizing-demo { display: flex; gap: 1rem; } .box { width: 200px; padding: 1rem; border: 4px solid teal; background: lightcyan; } .default { box-sizing: content-box; }", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .demo { display: flex; gap: 1rem; } .card { width: 200px; padding: 1rem; border: 4px solid steelblue; background: white; border-radius: 8px; }",
"sandboxCSS": "", "sandboxCSS": "",
"codePrefix": ".sized {\n ", "codePrefix": ".fix {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "\n}", "codeSuffix": "\n}",
"solution": "box-sizing: border-box;", "solution": "box-sizing: border-box;",
@@ -92,87 +92,98 @@
}, },
{ {
"id": "box-model-5", "id": "box-model-5",
"title": "Margin Collapse", "title": "Padding Shorthand",
"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.", "description": "Padding accepts 1-4 values:<br>• 1 value: all sides<br>• 2 values: vertical | horizontal<br>• 4 values: top | right | bottom | left",
"task": "Set <kbd>margin-bottom</kbd> to <kbd>2rem</kbd>. Notice the space between paragraphs equals 2rem (not 3rem) due to margin collapse.", "task": "This button needs more horizontal space than vertical. Set <kbd>padding: 8px 1rem</kbd> (8px top/bottom, 1rem left/right).",
"previewHTML": "<div class=\"collapse-demo\"><p class=\"first\">This paragraph has a bottom margin.</p><p class=\"second\">This paragraph has a top margin of 1rem.</p></div>", "previewHTML": "<button class=\"btn\">Follow</button>",
"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; }", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .btn { background: steelblue; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem; }",
"sandboxCSS": "", "sandboxCSS": "",
"codePrefix": ".first {\n ", "codePrefix": ".btn {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "\n}", "codeSuffix": "\n}",
"solution": "margin-bottom: 2rem;", "solution": "padding: 8px 1rem;",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"validations": [ "validations": [
{ {
"type": "property_value", "type": "regex",
"value": { "property": "margin-bottom", "expected": "2rem" }, "value": "padding:\\s*8px\\s+1rem",
"message": "Set <kbd>margin-bottom: 2rem</kbd>" "message": "Set <kbd>padding: 8px 1rem</kbd>",
"options": { "caseSensitive": false }
} }
] ]
}, },
{ {
"id": "box-model-6", "id": "box-model-6",
"title": "Margin Shorthand Notation", "title": "Margin Shorthand",
"description": "The margin shorthand can set all four sides at once. Two values set vertical (top/bottom) and horizontal (left/right) margins respectively.", "description": "Margin uses the same shorthand pattern as padding. A common pattern is centering block elements horizontally with <kbd>margin: 0 auto</kbd>.",
"task": "Set <kbd>margin</kbd> to <kbd>1rem 2rem</kbd> for 1rem top/bottom and 2rem left/right.", "task": "Center this card horizontally. Set <kbd>margin: 0 auto</kbd> to auto-calculate equal left/right margins.",
"previewHTML": "<div class=\"container\"><div class=\"spaced\">This box needs margins: 1rem top/bottom, 2rem left/right</div></div>", "previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .spaced { background-color: honeydew; border: 2px solid mediumseagreen; padding: 1rem; }", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { width: 250px; background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; border-left: 4px solid steelblue; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
"sandboxCSS": "", "sandboxCSS": "",
"codePrefix": ".spaced {\n ", "codePrefix": ".card {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "\n}", "codeSuffix": "\n}",
"solution": "margin: 1rem 2rem;", "solution": "margin: 0 auto;",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"validations": [ "validations": [
{ {
"type": "regex", "type": "regex",
"value": "margin:\\s*1rem\\s+2rem", "value": "margin:\\s*0\\s+auto",
"message": "Set <kbd>margin: 1rem 2rem</kbd>", "message": "Set <kbd>margin: 0 auto</kbd>",
"options": { "caseSensitive": false } "options": { "caseSensitive": false }
} }
] ]
}, },
{ {
"id": "box-model-7", "id": "box-model-7",
"title": "Padding Shorthand Notation", "title": "Border Radius",
"description": "Like margin, padding shorthand allows setting all sides at once. A single value applies to all four sides equally.", "description": "While not part of the classic box model, <kbd>border-radius</kbd> rounds the corners of an element's border box. Use <kbd>50%</kbd> on a square element to create a circle.",
"task": "Set <kbd>padding</kbd> to <kbd>2rem</kbd> to add equal padding on all sides.", "task": "Make the avatar image circular with <kbd>border-radius: 50%</kbd>.",
"previewHTML": "<div class=\"padded\">This box needs equal padding on all sides</div>", "previewHTML": "<article class=\"card\"><img class=\"avatar\" src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='35' r='25' fill='%23666'/%3E%3Ccircle cx='50' cy='90' r='40' fill='%23666'/%3E%3C/svg%3E\" alt=\"Avatar\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .padded { background-color: papayawhip; border: 2px solid orange; }", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; text-align: center; } .avatar { width: 80px; height: 80px; background: #ddd; margin-bottom: 8px; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
"sandboxCSS": "", "sandboxCSS": "",
"codePrefix": ".padded {\n ", "codePrefix": ".avatar {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "\n}", "codeSuffix": "\n}",
"solution": "padding: 2rem;", "solution": "border-radius: 50%;",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"validations": [ "validations": [
{ {
"type": "property_value", "type": "property_value",
"value": { "property": "padding", "expected": "2rem" }, "value": { "property": "border-radius", "expected": "50%" },
"message": "Set <kbd>padding: 2rem</kbd>" "message": "Set <kbd>border-radius: 50%</kbd>"
} }
] ]
}, },
{ {
"id": "box-model-8", "id": "box-model-8",
"title": "Border on Specific Sides", "title": "Complete Card",
"description": "For granular control, you can target specific sides with <kbd>border-top</kbd>, <kbd>border-right</kbd>, <kbd>border-bottom</kbd>, or <kbd>border-left</kbd>.", "description": "Let's combine everything. This notification card needs styling to look polished.",
"task": "Set <kbd>border-bottom</kbd> to <kbd>4px solid dodgerblue</kbd>.", "task": "Style the notification: add <kbd>padding: 1rem</kbd>, <kbd>border-left: 4px solid coral</kbd>, and <kbd>border-radius: 4px</kbd>.",
"previewHTML": "<div class=\"line\">This element needs only a bottom border</div>", "previewHTML": "<div class=\"alert\"><strong>New message</strong><p>You have 3 unread notifications</p></div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .line { padding: 1rem; background-color: aliceblue; }", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .alert { background: seashell; } .alert strong { color: coral; } .alert p { margin: 4px 0 0; color: #666; }",
"sandboxCSS": "", "sandboxCSS": "",
"codePrefix": ".line {\n ", "codePrefix": ".alert {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "\n}", "codeSuffix": "\n}",
"solution": "border-bottom: 4px solid dodgerblue;", "solution": "padding: 1rem;\n border-left: 4px solid coral;\n border-radius: 4px;",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"validations": [ "validations": [
{
"type": "property_value",
"value": { "property": "padding", "expected": "1rem" },
"message": "Set <kbd>padding: 1rem</kbd>"
},
{ {
"type": "regex", "type": "regex",
"value": "border-bottom:\\s*4px\\s+solid\\s+dodgerblue", "value": "border-left:\\s*4px\\s+solid\\s+coral",
"message": "Set <kbd>border-bottom: 4px solid dodgerblue</kbd>", "message": "Set <kbd>border-left: 4px solid coral</kbd>",
"options": { "caseSensitive": false } "options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "border-radius", "expected": "4px" },
"message": "Set <kbd>border-radius: 4px</kbd>"
} }
] ]
} }

View File

@@ -8,54 +8,57 @@
{ {
"id": "colors-1", "id": "colors-1",
"title": "Background Color", "title": "Background Color",
"description": "Color is one of the most powerful tools in web design. It creates visual hierarchy, conveys meaning, and establishes brand identity. CSS provides multiple ways to specify colors.<br><br><strong>CSS named colors:</strong> CSS includes 147 named colors like <kbd>steelblue</kbd>, <kbd>coral</kbd>, <kbd>gold</kbd>, and <kbd>tomato</kbd>. These are easy to remember and read.<br><br><strong>The background-color property:</strong> Sets the fill color behind an element's content and padding areas. The color extends to the edge of the element's border.<br><br><pre>.box {\n background-color: lightblue;\n}</pre>", "description": "Color is one of the most powerful tools in web design. It creates visual hierarchy, conveys meaning, and establishes brand identity. CSS provides multiple ways to specify colors.<br><br><strong>CSS named colors:</strong> CSS includes 147 named colors like <kbd>steelblue</kbd>, <kbd>coral</kbd>, <kbd>gold</kbd>, and <kbd>tomato</kbd>. These are easy to remember and read.<br><br><strong>The background-color property:</strong> Sets the fill color behind an element's content and padding areas.<br><br><pre>.card {\n background-color: lightblue;\n}</pre>",
"task": "Set <kbd>background-color</kbd> to <kbd>lightcyan</kbd> on <kbd>.box</kbd>.", "task": "This notification card needs a subtle background. Set <kbd>background-color: seashell</kbd> on <kbd>.alert</kbd>.",
"previewHTML": "<div class=\"box\">Background Demo</div>", "previewHTML": "<div class=\"alert\"><strong>New message</strong><p>You have 3 unread notifications</p></div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { padding: 1rem; border: 2px solid steelblue; }", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .alert { padding: 1rem; border-left: 4px solid coral; border-radius: 4px; } .alert strong { display: block; margin-bottom: 4px; } .alert p { margin: 0; color: #666; font-size: 0.9rem; }",
"sandboxCSS": "", "sandboxCSS": "",
"codePrefix": ".box {\n ", "codePrefix": ".alert {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "\n}", "codeSuffix": "\n}",
"solution": "background-color: seashell;",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"validations": [ "validations": [
{ {
"type": "property_value", "type": "property_value",
"value": { "property": "background-color", "expected": "lightcyan" }, "value": { "property": "background-color", "expected": "seashell" },
"message": "Set <kbd>background-color: lightcyan</kbd>" "message": "Set <kbd>background-color: seashell</kbd>"
} }
] ]
}, },
{ {
"id": "colors-2", "id": "colors-2",
"title": "Text Color", "title": "Text Color",
"description": "The <kbd>color</kbd> property sets the color of text content. Good contrast between text and background is essential for readability.", "description": "The <kbd>color</kbd> property sets the color of text content. Good contrast between text and background is essential for readability and accessibility.",
"task": "Set <kbd>color</kbd> to <kbd>darkslategray</kbd> on <kbd>.box</kbd>.", "task": "Make the alert title stand out by setting <kbd>color: coral</kbd> on <kbd>.title</kbd>.",
"previewHTML": "<div class=\"box\">Color & Contrast</div>", "previewHTML": "<div class=\"alert\"><strong class=\"title\">Warning</strong><p>Your session will expire in 5 minutes</p></div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { padding: 1rem; background-color: lightcyan; border: 2px solid steelblue; }", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .alert { padding: 1rem; background-color: seashell; border-left: 4px solid coral; border-radius: 4px; } .alert .title { display: block; margin-bottom: 4px; } .alert p { margin: 0; color: #666; font-size: 0.9rem; }",
"sandboxCSS": "", "sandboxCSS": "",
"codePrefix": ".box {\n ", "codePrefix": ".title {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "\n}", "codeSuffix": "\n}",
"solution": "color: coral;",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"validations": [ "validations": [
{ {
"type": "property_value", "type": "property_value",
"value": { "property": "color", "expected": "darkslategray" }, "value": { "property": "color", "expected": "coral" },
"message": "Set <kbd>color: darkslategray</kbd>" "message": "Set <kbd>color: coral</kbd>"
} }
] ]
}, },
{ {
"id": "colors-3", "id": "colors-3",
"title": "Border Color", "title": "Border Color",
"description": "Borders can have their own color using <kbd>border-color</kbd>, or you can specify color in the border shorthand.", "description": "Borders can have their own color using <kbd>border-color</kbd>. This is useful when you want to change just the color without redefining the entire border.",
"task": "Set <kbd>border-color</kbd> to <kbd>coral</kbd> on <kbd>.box</kbd>.", "task": "This card needs an accent border. Set <kbd>border-color: coral</kbd> on <kbd>.card</kbd>.",
"previewHTML": "<div class=\"box\">Border Color</div>", "previewHTML": "<article class=\"card\"><h3>Premium Plan</h3><p>Unlimited access to all features</p></article>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { padding: 1rem; background-color: seashell; border: 4px solid gray; }", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { padding: 1rem; background: white; border: 4px solid #ddd; border-radius: 8px; } .card h3 { margin: 0 0 8px; } .card p { margin: 0; color: #666; }",
"sandboxCSS": "", "sandboxCSS": "",
"codePrefix": ".box {\n ", "codePrefix": ".card {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "\n}", "codeSuffix": "\n}",
"solution": "border-color: coral;",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"validations": [ "validations": [
{ {
@@ -67,21 +70,22 @@
}, },
{ {
"id": "colors-4", "id": "colors-4",
"title": "Color Formats", "title": "Hex Colors",
"description": "Besides named colors, CSS supports hex codes (<kbd>#ff6347</kbd>), RGB (<kbd>rgb(255, 99, 71)</kbd>), and HSL (<kbd>hsl(9, 100%, 64%)</kbd>) formats.", "description": "Beyond named colors, CSS supports hex codes (<kbd>#ff6347</kbd>), RGB (<kbd>rgb(255, 99, 71)</kbd>), and HSL (<kbd>hsl(9, 100%, 64%)</kbd>). Hex codes are the most common format in professional projects.",
"task": "Set <kbd>background-color</kbd> to <kbd>#f0e68c</kbd> (khaki in hex) on <kbd>.box</kbd>.", "task": "Set the badge background to gold using its hex code: <kbd>background-color: #ffd700</kbd>.",
"previewHTML": "<div class=\"box\">Hex Color</div>", "previewHTML": "<span class=\"badge\">NEW</span>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { padding: 1rem; border: 2px solid olive; }", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .badge { display: inline-block; padding: 4px 12px; border-radius: 999px; font-size: 0.75rem; font-weight: bold; text-transform: uppercase; color: #333; }",
"sandboxCSS": "", "sandboxCSS": "",
"codePrefix": ".box {\n ", "codePrefix": ".badge {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "\n}", "codeSuffix": "\n}",
"solution": "background-color: #ffd700;",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"validations": [ "validations": [
{ {
"type": "property_value", "type": "property_value",
"value": { "property": "background-color", "expected": "#f0e68c" }, "value": { "property": "background-color", "expected": "#ffd700" },
"message": "Set <kbd>background-color: #f0e68c</kbd>" "message": "Set <kbd>background-color: #ffd700</kbd>"
} }
] ]
} }

View File

@@ -13,10 +13,11 @@
"previewHTML": "<p class=\"text\">This text shows the chosen font family.</p>", "previewHTML": "<p class=\"text\">This text shows the chosen font family.</p>",
"previewBaseCSS": "body { padding: 1rem; }", "previewBaseCSS": "body { padding: 1rem; }",
"sandboxCSS": "", "sandboxCSS": "",
"codePrefix": "/* Set font family */\n.text {", "codePrefix": ".text {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "}", "codeSuffix": "\n}",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"solution": "font-family: Georgia, serif;",
"validations": [ "validations": [
{ {
"type": "contains", "type": "contains",
@@ -40,10 +41,11 @@
"previewHTML": "<h2 class=\"heading\">Readable Heading</h2>", "previewHTML": "<h2 class=\"heading\">Readable Heading</h2>",
"previewBaseCSS": "body { padding: 1rem; }", "previewBaseCSS": "body { padding: 1rem; }",
"sandboxCSS": "", "sandboxCSS": "",
"codePrefix": "/* Set size and line height */\n.heading {", "codePrefix": ".heading {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "}", "codeSuffix": "\n}",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"solution": "font-size: 1.5rem;\n line-height: 1.5;",
"validations": [ "validations": [
{ "type": "contains", "value": "font-size", "message": "Use <kbd>font-size</kbd> property", "options": { "caseSensitive": false } }, { "type": "contains", "value": "font-size", "message": "Use <kbd>font-size</kbd> property", "options": { "caseSensitive": false } },
{ {
@@ -72,10 +74,11 @@
"previewHTML": "<p class=\"emphasis\">This text should stand out.</p>", "previewHTML": "<p class=\"emphasis\">This text should stand out.</p>",
"previewBaseCSS": "body { padding: 1rem; }", "previewBaseCSS": "body { padding: 1rem; }",
"sandboxCSS": "", "sandboxCSS": "",
"codePrefix": "/* Emphasize text */\n.emphasis {", "codePrefix": ".emphasis {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "}", "codeSuffix": "\n}",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"solution": "font-style: italic;\n font-weight: bold;",
"validations": [ "validations": [
{ "type": "contains", "value": "font-style", "message": "Use <kbd>font-style</kbd> property", "options": { "caseSensitive": false } }, { "type": "contains", "value": "font-style", "message": "Use <kbd>font-style</kbd> property", "options": { "caseSensitive": false } },
{ {
@@ -104,10 +107,11 @@
"previewHTML": "<p class=\"fancy\">Fancy text effect!</p>", "previewHTML": "<p class=\"fancy\">Fancy text effect!</p>",
"previewBaseCSS": "body { padding: 1rem; } .fancy { font-size: 1.25rem; }", "previewBaseCSS": "body { padding: 1rem; } .fancy { font-size: 1.25rem; }",
"sandboxCSS": "", "sandboxCSS": "",
"codePrefix": "/* Decorate text */\n.fancy {", "codePrefix": ".fancy {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "}", "codeSuffix": "\n}",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"solution": "text-decoration: underline;\n text-shadow: 1px 1px 2px gray;",
"validations": [ "validations": [
{ {
"type": "contains", "type": "contains",

View File

@@ -7,109 +7,94 @@
"lessons": [ "lessons": [
{ {
"id": "units-1", "id": "units-1",
"title": "Absolute vs. Relative Units", "title": "Relative Units",
"description": "Learn the difference between px, rem, em, %, and vw/vh for flexible, responsive layouts.<br><br><pre>width: 80%; /* relative to parent */\nmax-width: 40rem; /* relative to root font */\npadding: 16px; /* fixed pixels */</pre>", "description": "CSS offers two types of units: <em>absolute</em> (like <kbd>px</kbd>) and <em>relative</em> (like <kbd>%</kbd> and <kbd>rem</kbd>). Relative units adapt to their context, making layouts flexible and accessible.<br><br><strong>Common relative units:</strong><br>• <kbd>%</kbd> Relative to parent element<br>• <kbd>rem</kbd> Relative to root font size (typically 16px)<br>• <kbd>em</kbd> Relative to element's font size<br><br>A common pattern for readable content: set <kbd>width: 100%</kbd> so it fills available space, then <kbd>max-width: 40rem</kbd> to cap line length for readability.",
"task": "Set the <kbd>width</kbd> of <kbd>.box</kbd> to <kbd>80%</kbd> and <kbd>max-width</kbd> to <kbd>40rem</kbd>.", "task": "This article text runs too wide on large screens. Add <kbd>max-width: 40rem</kbd> to <kbd>.article</kbd> for optimal reading width.",
"previewHTML": "<div class=\"box\">Resize me!</div>", "previewHTML": "<article class=\"article\"><h2>The Art of Typography</h2><p>Good typography is invisible. When text is set well, readers absorb information without noticing the design decisions that make it comfortable to read. Line length is crucial—too wide and eyes get lost, too narrow and reading becomes choppy.</p><p>The ideal line length is 45-75 characters per line. At typical font sizes, this works out to roughly 40rem maximum width.</p></article>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { background: #f5f5f5; padding: 1rem; }", "previewBaseCSS": "body { font-family: Georgia, serif; padding: 1rem; background: #f9f9f9; } .article { background: white; padding: 2rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .article h2 { margin: 0 0 1rem; color: #333; } .article p { margin: 0 0 1rem; line-height: 1.6; color: #444; } .article p:last-child { margin-bottom: 0; }",
"sandboxCSS": "", "sandboxCSS": "",
"codePrefix": "/* Set flexible sizing */\n.box {", "codePrefix": ".article {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "}", "codeSuffix": "\n}",
"solution": " width: 80%;\n max-width: 40rem;", "solution": "max-width: 40rem;",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"validations": [ "validations": [
{ "type": "contains", "value": "width", "message": "Use <kbd>width</kbd> property", "options": { "caseSensitive": false } },
{ "type": "property_value", "value": { "property": "width", "expected": "80%" }, "message": "Set width to <kbd>80%</kbd>" },
{ "type": "contains", "value": "max-width", "message": "Use <kbd>max-width</kbd> property", "options": { "caseSensitive": false } },
{ {
"type": "property_value", "type": "property_value",
"value": { "property": "max-width", "expected": "40rem" }, "value": { "property": "max-width", "expected": "40rem" },
"message": "Set max-width to <kbd>40rem</kbd>" "message": "Set <kbd>max-width: 40rem</kbd>"
} }
] ]
}, },
{ {
"id": "units-2", "id": "units-2",
"title": "CSS Custom Properties", "title": "CSS Variables",
"description": "Define and reuse variables (--custom properties) to centralize your theme values.<br><br><pre>:root {\n --main-color: mediumpurple;\n}\n.themed {\n border-color: var(--main-color);\n}</pre>", "description": "CSS custom properties (variables) let you define reusable values. Define them with <kbd>--name</kbd> and use them with <kbd>var(--name)</kbd>. Variables defined on <kbd>:root</kbd> are available everywhere.",
"task": "Create a <kbd>--main-color</kbd> variable in <kbd>:root</kbd> with <kbd>mediumpurple</kbd> and apply it as the border color on <kbd>.themed</kbd>.", "task": "Define <kbd>--brand: steelblue</kbd> in <kbd>:root</kbd>, then use it as the <kbd>background</kbd> color for <kbd>.btn</kbd>.",
"previewHTML": "<div class=\"themed\">Variable Box</div>", "previewHTML": "<div class=\"actions\"><button class=\"btn\">Subscribe</button><button class=\"btn\">Learn More</button></div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .themed { padding: 1rem; border: 0.125rem solid #ddd; }", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .actions { display: flex; gap: 1rem; } .btn { color: white; border: none; padding: 12px 24px; border-radius: 6px; font-size: 1rem; cursor: pointer; background: #ccc; }",
"sandboxCSS": "", "sandboxCSS": "",
"codePrefix": "/* Define and use a CSS variable */\n:root {", "codePrefix": ":root {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "}\n.themed { }", "codeSuffix": "\n}\n\n.btn {\n background: var(--brand);\n}",
"solution": " --main-color: mediumpurple;\n}\n.themed {\n border-color: var(--main-color);", "solution": "--brand: steelblue;",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"validations": [ "validations": [
{ {
"type": "contains", "type": "contains",
"value": "--main-color", "value": "--brand",
"message": "Define <kbd>--main-color</kbd> in :root", "message": "Define <kbd>--brand</kbd> variable",
"options": { "caseSensitive": false } "options": { "caseSensitive": false }
}, },
{ {
"type": "contains", "type": "contains",
"value": "var(--main-color)", "value": "steelblue",
"message": "Use <kbd>var(--main-color)</kbd>", "message": "Set the value to <kbd>steelblue</kbd>",
"options": { "caseSensitive": false } "options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "border", "expected": "var(--main-color)" },
"message": "Apply variable to border color",
"options": { "exact": false }
} }
] ]
}, },
{ {
"id": "units-3", "id": "units-3",
"title": "Unit Calculations (calc)", "title": "calc() Function",
"description": "Use the <kbd>calc()</kbd> function to combine different units in one expression.<br><br><pre>width: calc(100% - 2rem);\nmin-height: calc(10vh + 1rem);</pre>", "description": "The <kbd>calc()</kbd> function lets you mix different units in calculations. This is essential for layouts that combine fixed and flexible sizing, like a sidebar layout.",
"task": "Set the <kbd>width</kbd> of <kbd>.sized</kbd> to <kbd>calc(100% - 2rem)</kbd> and <kbd>min-height</kbd> to <kbd>calc(10vh + 1rem)</kbd>.", "task": "The main content should fill the remaining space after the 200px sidebar. Set <kbd>width: calc(100% - 200px)</kbd> on <kbd>.main</kbd>.",
"previewHTML": "<div class=\"sized\">Calc Demo</div>", "previewHTML": "<div class=\"layout\"><aside class=\"sidebar\">Sidebar<br>Navigation</aside><main class=\"main\"><h2>Main Content</h2><p>This area should fill the remaining width after accounting for the fixed-width sidebar.</p></main></div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sized { background: #e8f5e9; padding: 1rem; }", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .layout { display: flex; gap: 1rem; } .sidebar { width: 200px; background: #1a1a2e; color: white; padding: 1rem; border-radius: 8px; flex-shrink: 0; } .main { background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .main h2 { margin: 0 0 8px; } .main p { margin: 0; color: #666; }",
"sandboxCSS": "", "sandboxCSS": "",
"codePrefix": "/* Use calc for dynamic sizing */\n.sized {", "codePrefix": ".main {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "}", "codeSuffix": "\n}",
"solution": " width: calc(100% - 2rem);\n min-height: calc(10vh + 1rem);", "solution": "width: calc(100% - 200px);",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"validations": [ "validations": [
{ "type": "contains", "value": "calc", "message": "Use <kbd>calc()</kbd> function", "options": { "caseSensitive": false } },
{ {
"type": "regex", "type": "regex",
"value": "width:\\s*calc\\(100% - 2rem\\)", "value": "width:\\s*calc\\(\\s*100%\\s*-\\s*200px\\s*\\)",
"message": "Width should be <kbd>calc(100% - 2rem)</kbd>", "message": "Set <kbd>width: calc(100% - 200px)</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "min-height:\\s*calc\\(10vh \\+ 1rem\\)",
"message": "Min-height should be <kbd>calc(10vh + 1rem)</kbd>",
"options": { "caseSensitive": false } "options": { "caseSensitive": false }
} }
] ]
}, },
{ {
"id": "units-4", "id": "units-4",
"title": "Viewport & Responsive Units", "title": "Viewport Units",
"description": "Control layouts relative to viewport size with vw, vh, and vmin/vmax units.<br><br><pre>width: 50vw; /* 50% of viewport width */\nheight: 20vh; /* 20% of viewport height */</pre>", "description": "Viewport units size elements relative to the browser window:<br>• <kbd>vw</kbd> 1% of viewport width<br>• <kbd>vh</kbd> 1% of viewport height<br><br>These are perfect for full-screen sections like hero banners.",
"task": "Give <kbd>.view</kbd> a <kbd>width</kbd> of <kbd>50vw</kbd> and <kbd>height</kbd> of <kbd>20vh</kbd>.", "task": "Make this hero section fill the viewport height by setting <kbd>min-height: 100vh</kbd> on <kbd>.hero</kbd>.",
"previewHTML": "<div class=\"view\">Viewport Box</div>", "previewHTML": "<section class=\"hero\"><h1>Welcome</h1><p>Scroll down to explore</p></section>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .view { background: #ffe0b2; }", "previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .hero { background: linear-gradient(135deg, #1a1a2e 0%, steelblue 100%); color: white; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 2rem; } .hero h1 { margin: 0 0 1rem; font-size: 2.5rem; } .hero p { margin: 0; opacity: 0.8; }",
"sandboxCSS": "", "sandboxCSS": "",
"codePrefix": "/* Use viewport units */\n.view {", "codePrefix": ".hero {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "}", "codeSuffix": "\n}",
"solution": " width: 50vw;\n height: 20vh;", "solution": "min-height: 100vh;",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"validations": [ "validations": [
{ "type": "contains", "value": "vw", "message": "Use <kbd>vw</kbd> unit", "options": { "caseSensitive": false } }, {
{ "type": "contains", "value": "vh", "message": "Use <kbd>vh</kbd> unit", "options": { "caseSensitive": false } }, "type": "property_value",
{ "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Set width to <kbd>50vw</kbd>" }, "value": { "property": "min-height", "expected": "100vh" },
{ "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Set height to <kbd>20vh</kbd>" } "message": "Set <kbd>min-height: 100vh</kbd>"
}
] ]
} }
] ]

View File

@@ -59,15 +59,15 @@
{ {
"id": "responsive-3", "id": "responsive-3",
"title": "Responsive Grid", "title": "Responsive Grid",
"description": "Combine CSS Grid with <kbd>auto-fit</kbd> or <kbd>auto-fill</kbd> for responsive column layouts.", "description": "Combine CSS Grid with <kbd>auto-fit</kbd> or <kbd>auto-fill</kbd> for responsive column layouts that automatically adjust the number of columns based on available space.",
"task": "Add <kbd>display: grid</kbd>, <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd>, and <kbd>gap: 1rem</kbd> to <kbd>.cards</kbd>.", "task": "Add <kbd>display: grid</kbd>, <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd>, and <kbd>gap: 1rem</kbd> to <kbd>.features</kbd>.",
"previewHTML": "<div class=\"cards\"><div>1</div><div>2</div><div>3</div><div>4</div></div>", "previewHTML": "<section class=\"features\"><article class=\"feature\"><h3>Fast</h3><p>Lightning quick load times</p></article><article class=\"feature\"><h3>Secure</h3><p>Enterprise-grade security</p></article><article class=\"feature\"><h3>Reliable</h3><p>99.9% uptime guaranteed</p></article><article class=\"feature\"><h3>Support</h3><p>24/7 customer service</p></article></section>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .cards > div { background: #d1c4e9; padding: 1rem; }", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .feature { background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .feature h3 { margin: 0 0 8px; color: steelblue; } .feature p { margin: 0; color: #666; font-size: 0.9rem; }",
"sandboxCSS": "", "sandboxCSS": "",
"codePrefix": "/* Create a responsive grid */\n.cards {", "codePrefix": ".features {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "}", "codeSuffix": "\n}",
"solution": " display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;", "solution": "display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"validations": [ "validations": [
{ {

View File

@@ -8,12 +8,12 @@
{ {
"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>.container {\n display: flex;\n /* Items now flow horizontally */\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": "Add <kbd>display: flex</kbd> to <kbd>.wrap</kbd> to arrange the boxes horizontally.", "task": "This navigation menu stacks vertically. Add <kbd>display: flex</kbd> to <kbd>.nav</kbd> to arrange the links horizontally.",
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box'>2</div><div class='box'>3</div></div>", "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; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; }", "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": ".wrap { border: 2px dashed #aaa; padding: 1rem; }", "sandboxCSS": "",
"codePrefix": ".wrap {\n ", "codePrefix": ".nav {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "\n}", "codeSuffix": "\n}",
"solution": "display: flex;", "solution": "display: flex;",
@@ -21,61 +21,41 @@
"validations": [ "validations": [
{ {
"type": "property_value", "type": "property_value",
"value": { "value": { "property": "display", "expected": "flex" },
"property": "display",
"expected": "flex"
},
"message": "Set <kbd>display: flex</kbd>" "message": "Set <kbd>display: flex</kbd>"
} }
] ]
}, },
{ {
"id": "flexbox-2", "id": "flexbox-2",
"title": "Direction & Wrap", "title": "Gap",
"description": "Control the direction and wrapping of flex items within a container.", "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>flex-direction: column</kbd> and <kbd>flex-wrap: wrap</kbd> to <kbd>.wrap</kbd>.", "task": "Add <kbd>gap: 1rem</kbd> to space out the navigation links evenly.",
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box'>2</div><div class='box'>3</div><div class='box'>4</div><div class='box'>5</div></div>", "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; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; width: 3rem; height: 3rem; display: flex; align-items: center; justify-content: center; }", "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": ".wrap { border: 2px dashed #aaa; padding: 1rem; height: 16rem; display: flex; }", "sandboxCSS": "",
"codePrefix": ".wrap {\n ", "codePrefix": ".nav {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "\n}", "codeSuffix": "\n}",
"solution": "flex-direction: column;\n flex-wrap: wrap;", "solution": "gap: 1rem;",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"validations": [ "validations": [
{ {
"type": "property_value", "type": "property_value",
"value": { "value": { "property": "gap", "expected": "1rem" },
"property": "flex-direction", "message": "Set <kbd>gap: 1rem</kbd>"
"expected": "column"
},
"message": "Set <kbd>flex-direction: column</kbd>",
"options": {
"exact": true
}
},
{
"type": "property_value",
"value": {
"property": "flex-wrap",
"expected": "wrap"
},
"message": "Set <kbd>flex-wrap: wrap</kbd>",
"options": {
"exact": true
}
} }
] ]
}, },
{ {
"id": "flexbox-3", "id": "flexbox-3",
"title": "Justify Content", "title": "Justify Content",
"description": "Learn how to align flex items along the main axis of the flex container.", "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": "Add <kbd>justify-content: space-between</kbd> to <kbd>.wrap</kbd> to distribute the boxes evenly.", "task": "Push the \"Login\" button to the right by setting <kbd>justify-content: space-between</kbd> on the nav.",
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box'>2</div><div class='box'>3</div></div>", "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; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; text-align: center; font-weight: bold; width: 3rem; height: 3rem; display: flex; align-items: center; justify-content: center; }", "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": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; }", "sandboxCSS": "",
"codePrefix": ".wrap {\n ", "codePrefix": ".nav {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "\n}", "codeSuffix": "\n}",
"solution": "justify-content: space-between;", "solution": "justify-content: space-between;",
@@ -83,26 +63,20 @@
"validations": [ "validations": [
{ {
"type": "property_value", "type": "property_value",
"value": { "value": { "property": "justify-content", "expected": "space-between" },
"property": "justify-content", "message": "Set <kbd>justify-content: space-between</kbd>"
"expected": "space-between"
},
"message": "Set <kbd>justify-content: space-between</kbd>",
"options": {
"exact": true
}
} }
] ]
}, },
{ {
"id": "flexbox-4", "id": "flexbox-4",
"title": "Align Items", "title": "Align Items",
"description": "Control how flex items are aligned along the cross axis of the flex container.", "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": "Add <kbd>align-items: center</kbd> to <kbd>.wrap</kbd> to vertically center the boxes.", "task": "The logo and nav links have different heights. Center them vertically with <kbd>align-items: center</kbd>.",
"previewHTML": "<div class='wrap'><div class='box tall'>1</div><div class='box'>2</div><div class='box short'>3</div></div>", "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; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; width: 3rem; display: flex; justify-content: center; } .tall { height: 6rem; } .short { height: 3rem; }", "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": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; height: 10rem; }", "sandboxCSS": "",
"codePrefix": ".wrap {\n ", "codePrefix": ".header {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "\n}", "codeSuffix": "\n}",
"solution": "align-items: center;", "solution": "align-items: center;",
@@ -110,62 +84,50 @@
"validations": [ "validations": [
{ {
"type": "property_value", "type": "property_value",
"value": { "value": { "property": "align-items", "expected": "center" },
"property": "align-items", "message": "Set <kbd>align-items: center</kbd>"
"expected": "center"
},
"message": "Set <kbd>align-items: center</kbd>",
"options": {
"exact": true
}
} }
] ]
}, },
{ {
"id": "flexbox-5", "id": "flexbox-5",
"title": "Flex Grow", "title": "Flex Wrap",
"description": "The <kbd>flex</kbd> property controls how much an item grows relative to others.", "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": "Add <kbd>flex: 2</kbd> to <kbd>.box2</kbd> to make it grow twice as wide.", "task": "These cards overflow the container. Add <kbd>flex-wrap: wrap</kbd> to allow them to wrap to new rows.",
"previewHTML": "<div class='wrap'><div class='box box1'>1</div><div class='box box2'>2</div><div class='box box3'>3</div></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; } .box { color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; display: flex; align-items: center; justify-content: center; } .box1 { background: coral; flex: 1; } .box2 { background: mediumseagreen; } .box3 { background: gold; flex: 1; }", "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": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; }", "sandboxCSS": "",
"codePrefix": ".box2 {\n ", "codePrefix": ".cards {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "\n}", "codeSuffix": "\n}",
"solution": "flex: 2;", "solution": "flex-wrap: wrap;",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"validations": [ "validations": [
{ {
"type": "property_value", "type": "property_value",
"value": { "value": { "property": "flex-wrap", "expected": "wrap" },
"property": "flex", "message": "Set <kbd>flex-wrap: wrap</kbd>"
"expected": "2"
},
"message": "Set <kbd>flex: 2</kbd>"
} }
] ]
}, },
{ {
"id": "flexbox-6", "id": "flexbox-6",
"title": "Align Self", "title": "Flex Grow",
"description": "Use <kbd>align-self</kbd> to override alignment for a single flex item.", "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": "Add <kbd>align-self: flex-start</kbd> to <kbd>.middle</kbd> to move it to the top.", "task": "Make the search input expand to fill available space by setting <kbd>flex: 1</kbd> on <kbd>.search</kbd>.",
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box middle'>2</div><div class='box'>3</div></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; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; width: 3rem; height: 3rem; display: flex; align-items: center; justify-content: center; } .middle { background: mediumseagreen; }", "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": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; height: 12rem; align-items: center; }", "sandboxCSS": "",
"codePrefix": ".middle {\n ", "codePrefix": ".search {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "\n}", "codeSuffix": "\n}",
"solution": "align-self: flex-start;", "solution": "flex: 1;",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"validations": [ "validations": [
{ {
"type": "property_value", "type": "property_value",
"value": { "value": { "property": "flex", "expected": "1" },
"property": "align-self", "message": "Set <kbd>flex: 1</kbd>"
"expected": "flex-start"
},
"message": "Set <kbd>align-self: flex-start</kbd>"
} }
] ]
} }

View File

@@ -8,253 +8,148 @@
{ {
"id": "grid-1", "id": "grid-1",
"title": "Grid Container", "title": "Grid Container",
"description": "CSS Grid is a two-dimensional layout system, meaning it can handle both columns AND rows simultaneously. While Flexbox excels at one-dimensional layouts (a single row or column), Grid shines when you need precise control over both dimensions.<br><br><strong>How it works:</strong> Set <kbd>display: grid</kbd> on a container. Then define your column structure with <kbd>grid-template-columns</kbd>. The <kbd>fr</kbd> unit represents a fraction of available space.<br><br><strong>Key properties:</strong><br>• <kbd>grid-template-columns</kbd> Defines column sizes<br>• <kbd>repeat(3, 1fr)</kbd> Creates 3 equal columns<br>• <kbd>gap</kbd> Adds spacing between grid cells<br><br><pre>.grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1rem;\n}</pre>", "description": "CSS Grid is a two-dimensional layout system, meaning it can handle both columns AND rows simultaneously. While Flexbox excels at one-dimensional layouts (a single row or column), Grid shines when you need precise control over both dimensions—like photo galleries, dashboards, or page layouts.<br><br><strong>How it works:</strong> Set <kbd>display: grid</kbd> on a container, then define columns with <kbd>grid-template-columns</kbd>. The <kbd>fr</kbd> unit represents a fraction of available space—<kbd>1fr 1fr 1fr</kbd> creates three equal columns.<br><br><strong>Key properties:</strong><br>• <kbd>grid-template-columns</kbd> Defines column sizes<br>• <kbd>repeat(3, 1fr)</kbd> Shorthand for 3 equal columns<br>• <kbd>gap</kbd> Adds spacing between grid cells<br><br><pre>.gallery {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1rem;\n}</pre>",
"task": "Add <kbd>display: grid</kbd>, <kbd>grid-template-columns: repeat(3, 1fr)</kbd>, and <kbd>gap: 1rem</kbd> to <kbd>.grid</kbd>.", "task": "This photo gallery displays images in a single column. Add <kbd>display: grid</kbd> to <kbd>.gallery</kbd> to enable grid layout.",
"previewHTML": "<div class='grid'><div class='item'>1</div><div class='item'>2</div><div class='item'>3</div><div class='item'>4</div><div class='item'>5</div><div class='item'>6</div></div>", "previewHTML": "<section class=\"gallery\"><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%234a90a4' width='200' height='150'/%3E%3Cpath d='M60 100 L100 60 L140 100 L160 80 L200 120 L200 150 L0 150 L0 120 Z' fill='%232d5a4a'/%3E%3Ccircle cx='50' cy='40' r='15' fill='%23f4d03f'/%3E%3C/svg%3E\" alt=\"Mountains\"><figcaption>Mountain View</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%23e8c170' width='200' height='150'/%3E%3Crect fill='%2396ceb4' y='100' width='200' height='50'/%3E%3Ccircle cx='160' cy='35' r='20' fill='%23f4d03f'/%3E%3C/svg%3E\" alt=\"Beach\"><figcaption>Sunny Beach</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%232c3e50' width='200' height='150'/%3E%3Crect fill='%2334495e' x='20' y='60' width='40' height='90'/%3E%3Crect fill='%2334495e' x='80' y='40' width='50' height='110'/%3E%3Crect fill='%2334495e' x='150' y='70' width='35' height='80'/%3E%3C/svg%3E\" alt=\"City\"><figcaption>City Skyline</figcaption></figure></section>",
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1.25rem; } .item { background-color: #9b59b6; color: white; padding: 1.25rem; text-align: center; font-weight: bold; }", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .photo { margin: 0; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .photo img { width: 100%; display: block; } .photo figcaption { padding: 8px; text-align: center; font-size: 0.9rem; color: #666; }",
"sandboxCSS": ".grid { border: 0.125rem dashed #ccc; padding: 1rem; }", "sandboxCSS": "",
"codePrefix": "/* Create a grid with 3 equal columns and gap */\n", "codePrefix": ".gallery {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "", "codeSuffix": "\n}",
"solution": ".grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1rem;\n}", "solution": "display: grid;",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"validations": [ "validations": [
{
"type": "contains",
"value": ".grid",
"message": "Use the <kbd>.grid</kbd> class selector",
"options": {
"caseSensitive": false
}
},
{ {
"type": "property_value", "type": "property_value",
"value": { "value": { "property": "display", "expected": "grid" },
"property": "display", "message": "Set <kbd>display: grid</kbd>"
"expected": "grid"
},
"message": "Set <kbd>display: grid</kbd>",
"options": {
"exact": true
}
},
{
"type": "regex",
"value": "grid-template-columns:\\s*repeat\\(\\s*3\\s*,\\s*1fr\\s*\\)",
"message": "Set <kbd>grid-template-columns: repeat(3, 1fr)</kbd>",
"options": {
"caseSensitive": false
}
},
{
"type": "property_value",
"value": {
"property": "gap",
"expected": "1rem"
},
"message": "Set <kbd>gap: 1rem</kbd>",
"options": {
"exact": true
}
} }
] ]
}, },
{ {
"id": "grid-2", "id": "grid-2",
"title": "Grid Template Areas", "title": "Grid Columns",
"description": "Use named grid areas to create visual layouts that are easy to understand.", "description": "The <kbd>grid-template-columns</kbd> property defines how many columns your grid has and how wide each one should be. The <kbd>fr</kbd> unit divides available space into fractions—<kbd>1fr 1fr 1fr</kbd> creates three equal columns.",
"task": "Add <kbd>grid-template-areas</kbd> to create a layout with <kbd>header</kbd> spanning full width, <kbd>sidebar</kbd> and <kbd>content</kbd> in middle, and <kbd>footer</kbd> spanning full width.", "task": "Add <kbd>grid-template-columns: repeat(3, 1fr)</kbd> to display the photos in three equal columns.",
"previewHTML": "<div class='page'><div class='header'>Header</div><div class='sidebar'>Sidebar</div><div class='content'>Main Content</div><div class='footer'>Footer</div></div>", "previewHTML": "<section class=\"gallery\"><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%234a90a4' width='200' height='150'/%3E%3Cpath d='M60 100 L100 60 L140 100 L160 80 L200 120 L200 150 L0 150 L0 120 Z' fill='%232d5a4a'/%3E%3Ccircle cx='50' cy='40' r='15' fill='%23f4d03f'/%3E%3C/svg%3E\" alt=\"Mountains\"><figcaption>Mountain View</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%23e8c170' width='200' height='150'/%3E%3Crect fill='%2396ceb4' y='100' width='200' height='50'/%3E%3Ccircle cx='160' cy='35' r='20' fill='%23f4d03f'/%3E%3C/svg%3E\" alt=\"Beach\"><figcaption>Sunny Beach</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%232c3e50' width='200' height='150'/%3E%3Crect fill='%2334495e' x='20' y='60' width='40' height='90'/%3E%3Crect fill='%2334495e' x='80' y='40' width='50' height='110'/%3E%3Crect fill='%2334495e' x='150' y='70' width='35' height='80'/%3E%3C/svg%3E\" alt=\"City\"><figcaption>City Skyline</figcaption></figure></section>",
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1.25rem; } .page > div { padding: 1.25rem; color: white; text-align: center; font-weight: bold; } .header { background-color: #e74c3c; } .sidebar { background-color: #3498db; } .content { background-color: #2ecc71; } .footer { background-color: #f39c12; }", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .gallery { display: grid; } .photo { margin: 0; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .photo img { width: 100%; display: block; } .photo figcaption { padding: 8px; text-align: center; font-size: 0.9rem; color: #666; }",
"sandboxCSS": ".page { border: 0.125rem dashed #ccc; padding: 1rem; height: 25rem; }", "sandboxCSS": "",
"codePrefix": "/* Create a layout using grid-template-areas */\n.page {\n display: grid;\n grid-template-columns: 12rem 1fr;\n grid-template-rows: auto 1fr auto;\n gap: 1rem;\n \n /* Add your grid-template-areas code below */\n", "codePrefix": ".gallery {\n ",
"initialCode": "", "initialCode": "",
"codeSuffix": "\n}\n\n/* Define which element goes in which grid area */\n.header {\n grid-area: header;\n}\n\n.sidebar {\n grid-area: sidebar;\n}\n\n.content {\n grid-area: content;\n}\n\n.footer {\n grid-area: footer;\n}", "codeSuffix": "\n}",
"solution": "grid-template-areas:\n \"header header\"\n \"sidebar content\"\n \"footer footer\";", "solution": "grid-template-columns: repeat(3, 1fr);",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "grid-template-columns:\\s*repeat\\(\\s*3\\s*,\\s*1fr\\s*\\)",
"message": "Set <kbd>grid-template-columns: repeat(3, 1fr)</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "grid-3",
"title": "Grid Gap",
"description": "Just like in Flexbox, the <kbd>gap</kbd> property adds consistent spacing between grid cells. It creates gutters between columns and rows without adding space around the edges.",
"task": "Add <kbd>gap: 1rem</kbd> to create breathing room between the photos.",
"previewHTML": "<section class=\"gallery\"><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%234a90a4' width='200' height='150'/%3E%3Cpath d='M60 100 L100 60 L140 100 L160 80 L200 120 L200 150 L0 150 L0 120 Z' fill='%232d5a4a'/%3E%3Ccircle cx='50' cy='40' r='15' fill='%23f4d03f'/%3E%3C/svg%3E\" alt=\"Mountains\"><figcaption>Mountain View</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%23e8c170' width='200' height='150'/%3E%3Crect fill='%2396ceb4' y='100' width='200' height='50'/%3E%3Ccircle cx='160' cy='35' r='20' fill='%23f4d03f'/%3E%3C/svg%3E\" alt=\"Beach\"><figcaption>Sunny Beach</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%232c3e50' width='200' height='150'/%3E%3Crect fill='%2334495e' x='20' y='60' width='40' height='90'/%3E%3Crect fill='%2334495e' x='80' y='40' width='50' height='110'/%3E%3Crect fill='%2334495e' x='150' y='70' width='35' height='80'/%3E%3C/svg%3E\" alt=\"City\"><figcaption>City Skyline</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%23a8d8ea' width='200' height='150'/%3E%3Cellipse cx='100' cy='130' rx='80' ry='30' fill='%2396ceb4'/%3E%3Crect fill='%238b4513' x='95' y='80' width='10' height='50'/%3E%3Ccircle cx='100' cy='70' r='30' fill='%232d5a4a'/%3E%3C/svg%3E\" alt=\"Tree\"><figcaption>Lone Tree</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%23ff6b6b' width='200' height='150'/%3E%3Ccircle cx='100' cy='75' r='40' fill='%23f4d03f'/%3E%3C/svg%3E\" alt=\"Sunset\"><figcaption>Golden Sunset</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%23dfe6e9' width='200' height='150'/%3E%3Cpath d='M0 100 Q50 60 100 100 T200 100 L200 150 L0 150 Z' fill='%23b2bec3'/%3E%3C/svg%3E\" alt=\"Clouds\"><figcaption>Cloudy Sky</figcaption></figure></section>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .gallery { display: grid; grid-template-columns: repeat(3, 1fr); } .photo { margin: 0; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .photo img { width: 100%; display: block; } .photo figcaption { padding: 8px; text-align: center; font-size: 0.9rem; color: #666; }",
"sandboxCSS": "",
"codePrefix": ".gallery {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "gap: 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "gap", "expected": "1rem" },
"message": "Set <kbd>gap: 1rem</kbd>"
}
]
},
{
"id": "grid-4",
"title": "Spanning Columns",
"description": "Grid items can span multiple columns using <kbd>grid-column: span N</kbd>. This is perfect for featured content that needs more visual prominence, like a hero image in a gallery.",
"task": "Make the featured photo span two columns by adding <kbd>grid-column: span 2</kbd> to <kbd>.featured</kbd>.",
"previewHTML": "<section class=\"gallery\"><figure class=\"photo featured\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 200'%3E%3Crect fill='%234a90a4' width='400' height='200'/%3E%3Cpath d='M100 140 L200 80 L300 140 L350 100 L400 150 L400 200 L0 200 L0 160 Z' fill='%232d5a4a'/%3E%3Ccircle cx='80' cy='50' r='25' fill='%23f4d03f'/%3E%3Ctext x='200' y='180' text-anchor='middle' fill='white' font-size='16' font-weight='bold'>FEATURED</text>%3C/svg%3E\" alt=\"Featured\"><figcaption>Featured Landscape</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%23e8c170' width='200' height='150'/%3E%3Crect fill='%2396ceb4' y='100' width='200' height='50'/%3E%3Ccircle cx='160' cy='35' r='20' fill='%23f4d03f'/%3E%3C/svg%3E\" alt=\"Beach\"><figcaption>Beach</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%232c3e50' width='200' height='150'/%3E%3Crect fill='%2334495e' x='20' y='60' width='40' height='90'/%3E%3Crect fill='%2334495e' x='80' y='40' width='50' height='110'/%3E%3Crect fill='%2334495e' x='150' y='70' width='35' height='80'/%3E%3C/svg%3E\" alt=\"City\"><figcaption>City</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%23a8d8ea' width='200' height='150'/%3E%3Cellipse cx='100' cy='130' rx='80' ry='30' fill='%2396ceb4'/%3E%3Crect fill='%238b4513' x='95' y='80' width='10' height='50'/%3E%3Ccircle cx='100' cy='70' r='30' fill='%232d5a4a'/%3E%3C/svg%3E\" alt=\"Tree\"><figcaption>Tree</figcaption></figure></section>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .gallery { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; } .photo { margin: 0; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .photo img { width: 100%; display: block; } .photo figcaption { padding: 8px; text-align: center; font-size: 0.9rem; color: #666; }",
"sandboxCSS": "",
"codePrefix": ".featured {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "grid-column: span 2;",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "grid-column:\\s*span\\s+2",
"message": "Set <kbd>grid-column: span 2</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "grid-5",
"title": "Auto-Fit Columns",
"description": "For truly responsive grids, use <kbd>auto-fit</kbd> with <kbd>minmax()</kbd>. This automatically creates as many columns as will fit, with each being at least a minimum width but growing to fill space.",
"task": "Add <kbd>grid-template-columns: repeat(auto-fit, minmax(150px, 1fr))</kbd> to make the product grid responsive.",
"previewHTML": "<section class=\"products\"><article class=\"product\"><div class=\"img\" style=\"background: steelblue;\"></div><h3>Wireless Headphones</h3><p>$79</p></article><article class=\"product\"><div class=\"img\" style=\"background: coral;\"></div><h3>Smart Watch</h3><p>$199</p></article><article class=\"product\"><div class=\"img\" style=\"background: mediumseagreen;\"></div><h3>Portable Speaker</h3><p>$49</p></article><article class=\"product\"><div class=\"img\" style=\"background: gold;\"></div><h3>USB-C Hub</h3><p>$35</p></article><article class=\"product\"><div class=\"img\" style=\"background: orchid;\"></div><h3>Webcam HD</h3><p>$89</p></article><article class=\"product\"><div class=\"img\" style=\"background: tomato;\"></div><h3>Keyboard</h3><p>$129</p></article></section>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .products { display: grid; gap: 1rem; } .product { background: white; border-radius: 8px; padding: 1rem; box-shadow: 0 2px 4px rgba(0,0,0,0.1); text-align: center; } .product .img { height: 80px; border-radius: 4px; margin-bottom: 8px; } .product h3 { margin: 0 0 4px; font-size: 0.95rem; } .product p { margin: 0; color: steelblue; font-weight: bold; }",
"sandboxCSS": "",
"codePrefix": ".products {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "grid-template-columns:\\s*repeat\\(\\s*auto-fit\\s*,\\s*minmax\\(\\s*150px\\s*,\\s*1fr\\s*\\)\\s*\\)",
"message": "Set <kbd>grid-template-columns: repeat(auto-fit, minmax(150px, 1fr))</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "grid-6",
"title": "Grid Template Areas",
"description": "For complex page layouts, <kbd>grid-template-areas</kbd> lets you name regions and place items visually. It's like drawing your layout with ASCII art—incredibly readable and maintainable.",
"task": "Complete the dashboard layout by adding the grid-template-areas. The header should span full width, then nav and main side by side, then footer spanning full width.",
"previewHTML": "<div class=\"dashboard\"><header class=\"header\">Dashboard Header</header><nav class=\"nav\">Navigation</nav><main class=\"main\">Main Content Area</main><footer class=\"footer\">Footer</footer></div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .dashboard { display: grid; grid-template-columns: 180px 1fr; grid-template-rows: auto 1fr auto; gap: 1rem; min-height: 300px; } .header { grid-area: header; background: #1a1a2e; color: white; padding: 1rem; border-radius: 8px; } .nav { grid-area: nav; background: steelblue; color: white; padding: 1rem; border-radius: 8px; } .main { grid-area: main; background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .footer { grid-area: footer; background: #333; color: white; padding: 1rem; border-radius: 8px; text-align: center; }",
"sandboxCSS": "",
"codePrefix": ".dashboard {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "grid-template-areas:\n \"header header\"\n \"nav main\"\n \"footer footer\";",
"previewContainer": "preview-area", "previewContainer": "preview-area",
"validations": [ "validations": [
{ {
"type": "contains", "type": "contains",
"value": "grid-template-areas", "value": "grid-template-areas",
"message": "Use the <kbd>grid-template-areas</kbd> property", "message": "Use the <kbd>grid-template-areas</kbd> property",
"options": { "options": { "caseSensitive": false }
"caseSensitive": false
}
}, },
{ {
"type": "regex", "type": "regex",
"value": "grid-template-areas:\\s*['\"]header\\s+header['\"]\\s*['\"]sidebar\\s+content['\"]\\s*['\"]footer\\s+footer['\"]", "value": "['\"]header\\s+header['\"]",
"message": "Create areas: <kbd>\"header header\" \"sidebar content\" \"footer footer\"</kbd>", "message": "Header should span both columns: <kbd>\"header header\"</kbd>",
"options": { "options": { "caseSensitive": false }
"caseSensitive": false
}
}
]
},
{
"id": "grid-3",
"title": "Spanning Grid Cells",
"description": "Make grid items span multiple grid cells horizontally or vertically.",
"task": "Add <kbd>grid-column: span 2</kbd> and <kbd>grid-row: span 2</kbd> to <kbd>.featured</kbd> to span 2x2 cells.",
"previewHTML": "<div class='grid'><div class='item'>1</div><div class='item'>2</div><div class='item featured'>Featured</div><div class='item'>4</div><div class='item'>5</div><div class='item'>6</div><div class='item'>7</div><div class='item'>8</div><div class='item'>9</div></div>",
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1.25rem; } .item { background-color: #9b59b6; color: white; padding: 1.25rem; text-align: center; font-weight: bold; } .featured { background-color: #e74c3c; }",
"sandboxCSS": ".grid { border: 0.125rem dashed #ccc; padding: 1rem; display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; }",
"codePrefix": "/* Make the featured item span 2x2 cells */\n",
"initialCode": "",
"codeSuffix": "",
"solution": ".featured {\n grid-column: span 2;\n grid-row: span 2;\n}",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": ".featured",
"message": "Use the <kbd>.featured</kbd> class selector",
"options": {
"caseSensitive": false
}
}, },
{ {
"type": "regex", "type": "regex",
"value": "grid-column:\\s*span\\s+2", "value": "['\"]nav\\s+main['\"]",
"message": "Set <kbd>grid-column: span 2</kbd>", "message": "Nav and main should be side by side: <kbd>\"nav main\"</kbd>",
"options": { "options": { "caseSensitive": false }
"caseSensitive": false
}
}, },
{ {
"type": "regex", "type": "regex",
"value": "grid-row:\\s*span\\s+2", "value": "['\"]footer\\s+footer['\"]",
"message": "Set <kbd>grid-row: span 2</kbd>", "message": "Footer should span both columns: <kbd>\"footer footer\"</kbd>",
"options": { "options": { "caseSensitive": false }
"caseSensitive": false
}
}
]
},
{
"id": "grid-4",
"title": "Automatic Grid Placement",
"description": "Learn how to use auto-placement and <kbd>auto-fit</kbd>/<kbd>auto-fill</kbd> for responsive grid layouts.",
"task": "Add <kbd>display: grid</kbd> and <kbd>grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr))</kbd> to <kbd>.cards</kbd>.",
"previewHTML": "<div class='cards'><div class='card'>Card 1</div><div class='card'>Card 2</div><div class='card'>Card 3</div><div class='card'>Card 4</div><div class='card'>Card 5</div><div class='card'>Card 6</div></div>",
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1.25rem; } .card { background-color: #3498db; color: white; padding: 1.25rem; text-align: center; font-weight: bold; height: 6rem; display: flex; align-items: center; justify-content: center; }",
"sandboxCSS": ".cards { border: 0.125rem dashed #ccc; padding: 1rem; }",
"codePrefix": "/* Create a responsive grid with auto-fit columns */\n",
"initialCode": "",
"codeSuffix": "",
"solution": ".cards {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));\n}",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": ".cards",
"message": "Use the <kbd>.cards</kbd> class selector",
"options": {
"caseSensitive": false
}
},
{
"type": "property_value",
"value": {
"property": "display",
"expected": "grid"
},
"message": "Set <kbd>display: grid</kbd>",
"options": {
"exact": true
}
},
{
"type": "regex",
"value": "grid-template-columns:\\s*repeat\\(\\s*auto-fit\\s*,\\s*minmax\\(\\s*10rem\\s*,\\s*1fr\\s*\\)\\s*\\)",
"message": "Set <kbd>grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr))</kbd>",
"options": {
"caseSensitive": false
}
}
]
},
{
"id": "grid-5",
"title": "Grid Alignment",
"description": "Control the alignment of grid items within their cells on both axes.",
"task": "Add <kbd>justify-items: center</kbd> and <kbd>align-items: center</kbd> to center items within their cells.",
"previewHTML": "<div class='cells'><div class='item'>1</div><div class='item tall'>2</div><div class='item'>3</div><div class='item'>4</div><div class='item'>5</div><div class='item wide'>6</div></div>",
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1.25rem; } .item { background-color: #9b59b6; color: white; padding: 1.25rem; text-align: center; font-weight: bold; width: 3rem; height: 3rem; } .tall { height: 6rem; } .wide { width: 6rem; }",
"sandboxCSS": ".cells { border: 0.125rem dashed #ccc; padding: 1rem; display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; height: 25rem; }",
"codePrefix": "/* Center grid items both horizontally and vertically */\n.cells {\n /* Add alignment properties below */\n",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "justify-items: center;\n align-items: center;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": {
"property": "justify-items",
"expected": "center"
},
"message": "Set <kbd>justify-items: center</kbd>",
"options": {
"exact": true
}
},
{
"type": "property_value",
"value": {
"property": "align-items",
"expected": "center"
},
"message": "Set <kbd>align-items: center</kbd>",
"options": {
"exact": true
}
}
]
},
{
"id": "grid-6",
"title": "Overlapping Grid Items",
"description": "Learn how to create overlapping layouts by using grid positioning and <kbd>z-index</kbd>.",
"task": "Add <kbd>grid-column: 1</kbd>, <kbd>grid-row: 1</kbd>, and <kbd>z-index: 1</kbd> to <kbd>.overlay</kbd> to position it above the base.",
"previewHTML": "<div class='stack'><div class='base'>Base Content</div><div class='overlay'>Overlay</div></div>",
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1.25rem; } .stack { position: relative; height: 15rem; } .base { background-color: #3498db; color: white; padding: 1.25rem; display: flex; align-items: center; justify-content: center; font-weight: bold; } .overlay { background-color: rgba(231, 76, 60, 0.7); color: white; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 1.5rem; }",
"sandboxCSS": ".stack { border: 0.125rem dashed #ccc; padding: 1rem; display: grid; grid-template-columns: 1fr; grid-template-rows: 1fr; }",
"codePrefix": "/* Position the overlay to cover the entire grid */\n.base {\n grid-column: 1;\n grid-row: 1;\n}\n\n.overlay {\n /* Add your code below to position the overlay */\n",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "grid-column: 1;\n grid-row: 1;\n z-index: 1;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": {
"property": "grid-column",
"expected": "1"
},
"message": "Set <kbd>grid-column: 1</kbd>",
"options": {
"exact": true
}
},
{
"type": "property_value",
"value": {
"property": "grid-row",
"expected": "1"
},
"message": "Set <kbd>grid-row: 1</kbd>",
"options": {
"exact": true
}
},
{
"type": "regex",
"value": "z-index:\\s*[1-9]\\d*",
"message": "Set <kbd>z-index</kbd> to a positive number",
"options": {
"caseSensitive": false
}
} }
] ]
} }