feat: add new lesson modules and reach 101 total lessons
New CSS Modules: - Gradients (3 lessons): linear-gradient, radial-gradient, direction - Filters (4 lessons): blur, grayscale, brightness, drop-shadow - Positioning (4 lessons): relative, absolute, offset properties - Pseudo-elements (4 lessons): ::before, ::after, content, decorative New HTML Module: - Semantic HTML (3 lessons): article, section, aside Expanded Existing Modules: - Typography: +2 lessons (text-decoration, text-shadow) - Tables: +2 lessons (thead/tbody/tfoot, colspan) Total lessons: 101 (up from ~66) - Enables full milestone system (1, 5, 10, 20, 30, 50, 75, 100) - All modules added to all 6 language stores with EN fallback
This commit is contained in:
@@ -98,6 +98,53 @@
|
||||
"message": "Set letter-spacing to <kbd>1px</kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "text-decoration",
|
||||
"title": "Text Decoration",
|
||||
"description": "The <kbd>text-decoration</kbd> property adds lines to text. Common values:<br><br>• <kbd>underline</kbd> — line below text<br>• <kbd>line-through</kbd> — strikethrough<br>• <kbd>none</kbd> — removes decoration (useful for links)<br><br>You can also style decorations with <kbd>text-decoration-color</kbd> and <kbd>text-decoration-style</kbd>.",
|
||||
"task": "Show the old price with a strikethrough. Add <kbd>text-decoration: line-through</kbd>.",
|
||||
"previewHTML": "<div class=\"price-box\"><span class=\"old-price\">$49.99</span><span class=\"new-price\">$29.99</span></div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } .price-box { display: flex; gap: 1rem; align-items: center; } .old-price { color: #999; font-size: 1rem; } .new-price { color: coral; font-size: 1.5rem; font-weight: bold; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".old-price {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"previewContainer": "preview-area",
|
||||
"solution": "text-decoration: line-through;",
|
||||
"validations": [
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "text-decoration", "expected": "line-through" },
|
||||
"message": "Set text-decoration to <kbd>line-through</kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "text-shadow",
|
||||
"title": "Text Shadow",
|
||||
"description": "The <kbd>text-shadow</kbd> property adds shadow effects to text. The syntax is:<br><br><pre>text-shadow: x-offset y-offset blur color;</pre><br>Example: <kbd>text-shadow: 2px 2px 4px gray</kbd> creates a soft shadow offset down and right.",
|
||||
"task": "Add depth to the heading with <kbd>text-shadow: 2px 2px 4px gray</kbd>.",
|
||||
"previewHTML": "<h1 class=\"hero-title\">Welcome</h1>",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 2rem; background: linear-gradient(135deg, #667eea, #764ba2); } .hero-title { margin: 0; font-size: 3rem; color: white; text-align: center; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".hero-title {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"previewContainer": "preview-area",
|
||||
"solution": "text-shadow: 2px 2px 4px gray;",
|
||||
"validations": [
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "text-shadow",
|
||||
"message": "Use <kbd>text-shadow</kbd> property"
|
||||
},
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "2px 2px",
|
||||
"message": "Set offset to <kbd>2px 2px</kbd>"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
92
lessons/09-gradients.json
Normal file
92
lessons/09-gradients.json
Normal file
@@ -0,0 +1,92 @@
|
||||
{
|
||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||
"id": "css-gradients",
|
||||
"title": "CSS Gradients",
|
||||
"description": "Create smooth color transitions with CSS gradients.",
|
||||
"difficulty": "intermediate",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "gradients-1",
|
||||
"title": "Linear Gradient",
|
||||
"description": "Gradients create smooth transitions between colors. The <kbd>linear-gradient()</kbd> function creates a gradient along a straight line.<br><br><strong>Basic syntax:</strong><br><pre>background: linear-gradient(color1, color2);</pre><br>By default, gradients flow from top to bottom.",
|
||||
"task": "Add a gradient background from <kbd>coral</kbd> to <kbd>gold</kbd>.",
|
||||
"previewHTML": "<div class=\"card\"><h3>Summer Sale</h3><p>Up to 50% off</p></div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .card { padding: 2rem; border-radius: 12px; color: white; text-shadow: 0 1px 2px rgba(0,0,0,0.2); } .card h3 { margin: 0 0 8px; font-size: 1.5rem; } .card p { margin: 0; opacity: 0.9; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".card {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "background: linear-gradient(coral, gold);",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "linear-gradient",
|
||||
"message": "Use <kbd>linear-gradient()</kbd>"
|
||||
},
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "coral",
|
||||
"message": "Include <kbd>coral</kbd> as the first color"
|
||||
},
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "gold",
|
||||
"message": "Include <kbd>gold</kbd> as the second color"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gradients-2",
|
||||
"title": "Gradient Direction",
|
||||
"description": "Control the gradient direction by adding an angle or keyword before the colors.<br><br><strong>Keywords:</strong> <kbd>to right</kbd>, <kbd>to left</kbd>, <kbd>to bottom right</kbd><br><strong>Angles:</strong> <kbd>45deg</kbd>, <kbd>90deg</kbd>, <kbd>180deg</kbd><br><br><pre>background: linear-gradient(to right, blue, purple);</pre>",
|
||||
"task": "Make the gradient flow from left to right using <kbd>to right</kbd>.",
|
||||
"previewHTML": "<button class=\"btn\">Get Started</button>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 2rem; } .btn { padding: 1rem 2rem; border: none; border-radius: 8px; font-size: 1rem; font-weight: 600; color: white; cursor: pointer; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".btn {\n background: linear-gradient(",
|
||||
"initialCode": "",
|
||||
"codeSuffix": ", steelblue, mediumseagreen);\n}",
|
||||
"solution": "to right",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "to right",
|
||||
"message": "Add <kbd>to right</kbd> to set the direction"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "gradients-3",
|
||||
"title": "Radial Gradient",
|
||||
"description": "The <kbd>radial-gradient()</kbd> function creates a gradient that radiates from a center point outward in a circular or elliptical pattern.<br><br><pre>background: radial-gradient(circle, white, steelblue);</pre><br>Add <kbd>circle</kbd> for a perfect circular gradient.",
|
||||
"task": "Create a radial gradient from <kbd>white</kbd> to <kbd>steelblue</kbd>.",
|
||||
"previewHTML": "<div class=\"orb\"></div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 2rem; display: flex; justify-content: center; } .orb { width: 150px; height: 150px; border-radius: 50%; box-shadow: 0 8px 32px rgba(70, 130, 180, 0.4); }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".orb {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "background: radial-gradient(circle, white, steelblue);",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "radial-gradient",
|
||||
"message": "Use <kbd>radial-gradient()</kbd>"
|
||||
},
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "white",
|
||||
"message": "Start with <kbd>white</kbd>"
|
||||
},
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "steelblue",
|
||||
"message": "End with <kbd>steelblue</kbd>"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
108
lessons/11-filters.json
Normal file
108
lessons/11-filters.json
Normal file
@@ -0,0 +1,108 @@
|
||||
{
|
||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||
"id": "css-filters",
|
||||
"title": "CSS Filters",
|
||||
"description": "Apply visual effects like blur, brightness, and shadows with CSS filters.",
|
||||
"difficulty": "intermediate",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "filters-1",
|
||||
"title": "Blur Filter",
|
||||
"description": "The <kbd>filter</kbd> property applies visual effects to elements. The <kbd>blur()</kbd> function creates a Gaussian blur effect.<br><br><pre>filter: blur(4px);</pre><br>Higher values create more blur. This is great for backgrounds or creating depth.",
|
||||
"task": "Blur the background image using <kbd>filter: blur(4px)</kbd>.",
|
||||
"previewHTML": "<div class=\"bg\"></div><div class=\"content\"><h2>Welcome</h2></div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; height: 200px; position: relative; overflow: hidden; } .bg { position: absolute; inset: 0; background: linear-gradient(45deg, coral, gold, steelblue); } .content { position: relative; z-index: 1; display: flex; align-items: center; justify-content: center; height: 100%; } .content h2 { color: white; text-shadow: 0 2px 8px rgba(0,0,0,0.3); margin: 0; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".bg {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "filter: blur(4px);",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "filter", "expected": "blur(4px)" },
|
||||
"message": "Set <kbd>filter: blur(4px)</kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "filters-2",
|
||||
"title": "Grayscale Filter",
|
||||
"description": "The <kbd>grayscale()</kbd> function removes color from an element. Use values from <kbd>0%</kbd> (full color) to <kbd>100%</kbd> (fully grayscale).<br><br><pre>filter: grayscale(100%);</pre><br>Great for hover effects or disabled states.",
|
||||
"task": "Make the image grayscale with <kbd>filter: grayscale(100%)</kbd>.",
|
||||
"previewHTML": "<div class=\"photo\"></div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .photo { width: 200px; height: 150px; background: linear-gradient(135deg, coral 0%, gold 50%, steelblue 100%); border-radius: 8px; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".photo {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "filter: grayscale(100%);",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "grayscale",
|
||||
"message": "Use <kbd>grayscale()</kbd> filter"
|
||||
},
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "100%",
|
||||
"message": "Set to <kbd>100%</kbd> for full grayscale"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "filters-3",
|
||||
"title": "Brightness Filter",
|
||||
"description": "The <kbd>brightness()</kbd> function adjusts how bright an element appears. Values below <kbd>100%</kbd> darken, above <kbd>100%</kbd> brighten.<br><br><pre>filter: brightness(150%);</pre>",
|
||||
"task": "Brighten the card with <kbd>filter: brightness(120%)</kbd>.",
|
||||
"previewHTML": "<div class=\"card\"><span>Featured</span></div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #1a1a2e; } .card { padding: 2rem; background: linear-gradient(135deg, #4a4a6a, #2a2a4a); border-radius: 12px; text-align: center; } .card span { color: gold; font-weight: 600; text-transform: uppercase; letter-spacing: 2px; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".card {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "filter: brightness(120%);",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "brightness",
|
||||
"message": "Use <kbd>brightness()</kbd> filter"
|
||||
},
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "120%",
|
||||
"message": "Set to <kbd>120%</kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "filters-4",
|
||||
"title": "Drop Shadow",
|
||||
"description": "The <kbd>drop-shadow()</kbd> filter creates a shadow that follows the shape of the element, including transparency. Unlike <kbd>box-shadow</kbd>, it works on images with transparent backgrounds.<br><br><pre>filter: drop-shadow(2px 4px 6px black);</pre>",
|
||||
"task": "Add a drop shadow with <kbd>filter: drop-shadow(4px 4px 8px gray)</kbd>.",
|
||||
"previewHTML": "<div class=\"icon\">★</div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 2rem; display: flex; justify-content: center; } .icon { font-size: 4rem; color: gold; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".icon {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "filter: drop-shadow(4px 4px 8px gray);",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "drop-shadow",
|
||||
"message": "Use <kbd>drop-shadow()</kbd> filter"
|
||||
},
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "4px 4px 8px",
|
||||
"message": "Set shadow offset and blur"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
98
lessons/12-positioning.json
Normal file
98
lessons/12-positioning.json
Normal file
@@ -0,0 +1,98 @@
|
||||
{
|
||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||
"id": "css-positioning",
|
||||
"title": "CSS Positioning",
|
||||
"description": "Control element placement with CSS positioning properties.",
|
||||
"difficulty": "intermediate",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "position-1",
|
||||
"title": "Relative Position",
|
||||
"description": "The <kbd>position</kbd> property controls how elements are placed. <kbd>relative</kbd> keeps the element in normal flow but allows you to offset it with <kbd>top</kbd>, <kbd>right</kbd>, <kbd>bottom</kbd>, <kbd>left</kbd>.<br><br><pre>.box {\n position: relative;\n top: 10px;\n}</pre>",
|
||||
"task": "Make the badge position relative so we can offset it.",
|
||||
"previewHTML": "<div class=\"card\"><span class=\"badge\">NEW</span><h3>Product</h3></div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .card { padding: 1rem; background: white; border: 2px solid #eee; border-radius: 8px; } .card h3 { margin: 0; } .badge { display: inline-block; padding: 2px 8px; background: coral; color: white; font-size: 0.7rem; font-weight: bold; border-radius: 4px; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".badge {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "position: relative;",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "position", "expected": "relative" },
|
||||
"message": "Set <kbd>position: relative</kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "position-2",
|
||||
"title": "Offset Properties",
|
||||
"description": "With <kbd>position: relative</kbd>, use offset properties to nudge the element from its original position:<br><br><kbd>top</kbd> - pushes down from top<br><kbd>left</kbd> - pushes right from left<br><br>Negative values move in the opposite direction.",
|
||||
"task": "Move the badge up with <kbd>top: -8px</kbd>.",
|
||||
"previewHTML": "<div class=\"card\"><span class=\"badge\">NEW</span><h3>Product</h3></div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .card { padding: 1rem; background: white; border: 2px solid #eee; border-radius: 8px; } .card h3 { margin: 0; } .badge { display: inline-block; padding: 2px 8px; background: coral; color: white; font-size: 0.7rem; font-weight: bold; border-radius: 4px; position: relative; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".badge {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "top: -8px;",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "top", "expected": "-8px" },
|
||||
"message": "Set <kbd>top: -8px</kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "position-3",
|
||||
"title": "Absolute Position",
|
||||
"description": "<kbd>position: absolute</kbd> removes the element from normal flow and positions it relative to its nearest positioned ancestor (or the viewport if none exists).<br><br>Always set a parent to <kbd>position: relative</kbd> to contain absolute children.",
|
||||
"task": "Position the close button absolutely.",
|
||||
"previewHTML": "<div class=\"modal\"><button class=\"close\">×</button><h3>Modal</h3><p>Content here</p></div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .modal { position: relative; padding: 2rem; background: white; border-radius: 12px; box-shadow: 0 4px 24px rgba(0,0,0,0.15); max-width: 250px; } .modal h3 { margin: 0 0 8px; } .modal p { margin: 0; color: #666; } .close { width: 32px; height: 32px; border: none; background: #f5f5f5; border-radius: 50%; font-size: 1.2rem; cursor: pointer; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".close {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "position: absolute;",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "position", "expected": "absolute" },
|
||||
"message": "Set <kbd>position: absolute</kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "position-4",
|
||||
"title": "Placing Absolute Elements",
|
||||
"description": "Combine <kbd>position: absolute</kbd> with offset properties to place elements precisely.<br><br><pre>.close {\n position: absolute;\n top: 8px;\n right: 8px;\n}</pre>",
|
||||
"task": "Move the close button to the top right corner with <kbd>top: 8px</kbd> and <kbd>right: 8px</kbd>.",
|
||||
"previewHTML": "<div class=\"modal\"><button class=\"close\">×</button><h3>Modal</h3><p>Content here</p></div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .modal { position: relative; padding: 2rem; background: white; border-radius: 12px; box-shadow: 0 4px 24px rgba(0,0,0,0.15); max-width: 250px; } .modal h3 { margin: 0 0 8px; } .modal p { margin: 0; color: #666; } .close { position: absolute; width: 32px; height: 32px; border: none; background: #f5f5f5; border-radius: 50%; font-size: 1.2rem; cursor: pointer; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".close {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "top: 8px;\n right: 8px;",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "top", "expected": "8px" },
|
||||
"message": "Set <kbd>top: 8px</kbd>"
|
||||
},
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "right", "expected": "8px" },
|
||||
"message": "Set <kbd>right: 8px</kbd>"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
113
lessons/13-pseudo-elements.json
Normal file
113
lessons/13-pseudo-elements.json
Normal file
@@ -0,0 +1,113 @@
|
||||
{
|
||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||
"id": "css-pseudo-elements",
|
||||
"title": "CSS Pseudo-elements",
|
||||
"description": "Create decorative elements and style specific parts of content with pseudo-elements.",
|
||||
"difficulty": "intermediate",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "pseudo-1",
|
||||
"title": "The ::before Element",
|
||||
"description": "Pseudo-elements let you style specific parts of an element. <kbd>::before</kbd> creates a virtual element as the first child.<br><br>It requires the <kbd>content</kbd> property to display anything (even if empty).<br><br><pre>.item::before {\n content: \"→ \";\n}</pre>",
|
||||
"task": "Add a bullet before each list item using <kbd>::before</kbd> with <kbd>content: \"• \"</kbd>.",
|
||||
"previewHTML": "<ul class=\"list\"><li>First item</li><li>Second item</li><li>Third item</li></ul>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .list { list-style: none; padding: 0; margin: 0; } .list li { padding: 8px 0; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".list li::before {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "content: \"• \";",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "content",
|
||||
"message": "Use the <kbd>content</kbd> property"
|
||||
},
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "•",
|
||||
"message": "Add a bullet character <kbd>•</kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "pseudo-2",
|
||||
"title": "Styling ::before",
|
||||
"description": "Pseudo-elements can be styled like any element. Add color, size, margins, and more.<br><br><pre>.item::before {\n content: \"★\";\n color: gold;\n margin-right: 8px;\n}</pre>",
|
||||
"task": "Style the bullet with <kbd>color: coral</kbd>.",
|
||||
"previewHTML": "<ul class=\"list\"><li>First item</li><li>Second item</li><li>Third item</li></ul>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .list { list-style: none; padding: 0; margin: 0; } .list li { padding: 8px 0; } .list li::before { content: \"• \"; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".list li::before {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "color: coral;",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "color", "expected": "coral" },
|
||||
"message": "Set <kbd>color: coral</kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "pseudo-3",
|
||||
"title": "The ::after Element",
|
||||
"description": "<kbd>::after</kbd> works like <kbd>::before</kbd> but inserts content as the last child. Common uses include badges, icons, or decorative elements.<br><br><pre>.new::after {\n content: \" ✓\";\n color: green;\n}</pre>",
|
||||
"task": "Add a checkmark after completed items with <kbd>content: \" ✓\"</kbd>.",
|
||||
"previewHTML": "<ul class=\"list\"><li class=\"done\">Buy groceries</li><li class=\"done\">Walk the dog</li><li>Read a book</li></ul>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .list { list-style: none; padding: 0; margin: 0; } .list li { padding: 8px 0; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".done::after {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "content: \" ✓\";",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "content",
|
||||
"message": "Use the <kbd>content</kbd> property"
|
||||
},
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "✓",
|
||||
"message": "Add a checkmark <kbd>✓</kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "pseudo-4",
|
||||
"title": "Decorative Lines",
|
||||
"description": "Pseudo-elements with <kbd>content: \"\"</kbd> can create decorative shapes when combined with width, height, and background.<br><br><pre>.title::after {\n content: \"\";\n display: block;\n width: 50px;\n height: 3px;\n background: coral;\n}</pre>",
|
||||
"task": "Create an underline decoration with <kbd>width: 40px</kbd>, <kbd>height: 3px</kbd>, and <kbd>background: steelblue</kbd>.",
|
||||
"previewHTML": "<h2 class=\"title\">About Us</h2><p>We build great things.</p>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .title { margin: 0 0 1rem; } .title::after { content: \"\"; display: block; margin-top: 8px; } p { margin: 0; color: #666; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".title::after {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "width: 40px;\n height: 3px;\n background: steelblue;",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "width", "expected": "40px" },
|
||||
"message": "Set <kbd>width: 40px</kbd>"
|
||||
},
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "height", "expected": "3px" },
|
||||
"message": "Set <kbd>height: 3px</kbd>"
|
||||
},
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "background", "expected": "steelblue" },
|
||||
"message": "Set <kbd>background: steelblue</kbd>"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -39,6 +39,54 @@
|
||||
"message": "Add 3 rows (1 header + 2 data rows)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "table-sections",
|
||||
"title": "Table Sections",
|
||||
"description": "Semantic table sections improve accessibility and allow for separate styling:<br><br>• <kbd><thead></kbd> — header section<br>• <kbd><tbody></kbd> — main content<br>• <kbd><tfoot></kbd> — footer (totals, summaries)",
|
||||
"task": "Wrap the header row in <kbd><thead></kbd> and data rows in <kbd><tbody></kbd>.",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } table { border-collapse: collapse; width: 100%; max-width: 350px; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } th, td { padding: 12px 16px; text-align: left; } thead { background: steelblue; color: white; } tbody tr:nth-child(even) { background: #f8f9fa; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<table>\n <tr>\n <th>Name</th>\n <th>Score</th>\n </tr>\n <tr>\n <td>Alice</td>\n <td>95</td>\n </tr>\n <tr>\n <td>Bob</td>\n <td>87</td>\n </tr>\n</table>",
|
||||
"solution": "<table>\n <thead>\n <tr>\n <th>Name</th>\n <th>Score</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Alice</td>\n <td>95</td>\n </tr>\n <tr>\n <td>Bob</td>\n <td>87</td>\n </tr>\n </tbody>\n</table>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "thead",
|
||||
"message": "Add a <kbd><thead></kbd> section for the header"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "tbody",
|
||||
"message": "Add a <kbd><tbody></kbd> section for the data"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "table-colspan",
|
||||
"title": "Spanning Columns",
|
||||
"description": "The <kbd>colspan</kbd> attribute lets a cell span multiple columns. This is useful for headers that group multiple columns or footer totals.<br><br><pre><td colspan=\"2\">...</td></pre>",
|
||||
"task": "Add a footer row that spans both columns using <kbd>colspan=\"2\"</kbd>.",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } table { border-collapse: collapse; width: 100%; max-width: 350px; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } th, td { padding: 12px 16px; text-align: left; border-bottom: 1px solid #eee; } thead { background: steelblue; color: white; } tfoot { background: #f0f0f0; font-weight: 600; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<table>\n <thead>\n <tr>\n <th>Item</th>\n <th>Price</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Coffee</td>\n <td>$4</td>\n </tr>\n <tr>\n <td>Cake</td>\n <td>$6</td>\n </tr>\n </tbody>\n</table>",
|
||||
"solution": "<table>\n <thead>\n <tr>\n <th>Item</th>\n <th>Price</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Coffee</td>\n <td>$4</td>\n </tr>\n <tr>\n <td>Cake</td>\n <td>$6</td>\n </tr>\n </tbody>\n <tfoot>\n <tr>\n <td colspan=\"2\">Total: $10</td>\n </tr>\n </tfoot>\n</table>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "tfoot",
|
||||
"message": "Add a <kbd><tfoot></kbd> section"
|
||||
},
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "colspan",
|
||||
"message": "Use <kbd>colspan</kbd> to span columns"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
88
lessons/33-html-semantic.json
Normal file
88
lessons/33-html-semantic.json
Normal file
@@ -0,0 +1,88 @@
|
||||
{
|
||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-semantic",
|
||||
"title": "Semantic HTML",
|
||||
"mode": "html",
|
||||
"description": "Use meaningful HTML elements to structure content properly.",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "semantic-1",
|
||||
"title": "The <article> Element",
|
||||
"description": "The <kbd><article></kbd> element represents self-contained content that could be distributed independently, like a blog post, news article, or comment.<br><br><pre><article>\n <h2>Article Title</h2>\n <p>Article content...</p>\n</article></pre>",
|
||||
"task": "Wrap the blog post content in an <kbd><article></kbd> element.",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } article { padding: 1rem; background: #f9f9f9; border-left: 4px solid steelblue; border-radius: 4px; } h2 { margin: 0 0 8px; color: steelblue; } p { margin: 0; color: #555; line-height: 1.5; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": "",
|
||||
"initialCode": "<h2>My First Post</h2>\n<p>This is a blog post about learning HTML.</p>",
|
||||
"codeSuffix": "",
|
||||
"solution": "<article>\n<h2>My First Post</h2>\n<p>This is a blog post about learning HTML.</p>\n</article>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "<article>",
|
||||
"message": "Add an opening <kbd><article></kbd> tag"
|
||||
},
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "</article>",
|
||||
"message": "Add a closing <kbd></article></kbd> tag"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "semantic-2",
|
||||
"title": "The <section> Element",
|
||||
"description": "The <kbd><section></kbd> element represents a thematic grouping of content, typically with a heading. Use it to divide a page into logical sections.<br><br><pre><section>\n <h2>Features</h2>\n <p>Our product features...</p>\n</section></pre>",
|
||||
"task": "Wrap the features content in a <kbd><section></kbd> element.",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } section { padding: 1rem; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); border-radius: 8px; } h2 { margin: 0 0 12px; } ul { margin: 0; padding-left: 1.5rem; } li { margin: 4px 0; color: #444; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": "",
|
||||
"initialCode": "<h2>Features</h2>\n<ul>\n <li>Fast performance</li>\n <li>Easy to use</li>\n</ul>",
|
||||
"codeSuffix": "",
|
||||
"solution": "<section>\n<h2>Features</h2>\n<ul>\n <li>Fast performance</li>\n <li>Easy to use</li>\n</ul>\n</section>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "<section>",
|
||||
"message": "Add an opening <kbd><section></kbd> tag"
|
||||
},
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "</section>",
|
||||
"message": "Add a closing <kbd></section></kbd> tag"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "semantic-3",
|
||||
"title": "The <aside> Element",
|
||||
"description": "The <kbd><aside></kbd> element represents content tangentially related to the main content, like sidebars, pull quotes, or related links.<br><br><pre><aside>\n <h3>Related</h3>\n <ul>...</ul>\n</aside></pre>",
|
||||
"task": "Wrap the related links in an <kbd><aside></kbd> element.",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } aside { padding: 1rem; background: #fff8e7; border: 1px solid #ffe0a6; border-radius: 8px; } h3 { margin: 0 0 8px; color: #b8860b; font-size: 0.9rem; text-transform: uppercase; } ul { margin: 0; padding-left: 1.2rem; } li { margin: 4px 0; } a { color: #b8860b; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": "",
|
||||
"initialCode": "<h3>Related Posts</h3>\n<ul>\n <li><a href=\"#\">CSS Basics</a></li>\n <li><a href=\"#\">HTML Tips</a></li>\n</ul>",
|
||||
"codeSuffix": "",
|
||||
"solution": "<aside>\n<h3>Related Posts</h3>\n<ul>\n <li><a href=\"#\">CSS Basics</a></li>\n <li><a href=\"#\">HTML Tips</a></li>\n</ul>\n</aside>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "<aside>",
|
||||
"message": "Add an opening <kbd><aside></kbd> tag"
|
||||
},
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "</aside>",
|
||||
"message": "Add a closing <kbd></aside></kbd> tag"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
}
|
||||
@@ -23,8 +23,13 @@ import htmlFieldsetEN from "../../lessons/28-html-forms-fieldset.json";
|
||||
import htmlFigureEN from "../../lessons/29-html-figure.json";
|
||||
import htmlTablesEN from "../../lessons/30-html-tables.json";
|
||||
import htmlSvgEN from "../../lessons/32-html-svg.json";
|
||||
import htmlSemanticEN from "../../lessons/33-html-semantic.json";
|
||||
import flexboxEN from "../../lessons/flexbox.json";
|
||||
import gridEN from "../../lessons/grid.json";
|
||||
import gradientsEN from "../../lessons/09-gradients.json";
|
||||
import filtersEN from "../../lessons/11-filters.json";
|
||||
import positioningEN from "../../lessons/12-positioning.json";
|
||||
import pseudoElementsEN from "../../lessons/13-pseudo-elements.json";
|
||||
import playgroundEN from "../../lessons/98-playground.json";
|
||||
import goodbyeEN from "../../lessons/99-goodbye.json";
|
||||
|
||||
@@ -130,17 +135,22 @@ const moduleStoreEN = [
|
||||
// CSS Visual (immediate impact)
|
||||
basicSelectorsEN,
|
||||
colorsEN,
|
||||
gradientsEN,
|
||||
typographyEN,
|
||||
boxModelEN,
|
||||
// CSS Layout
|
||||
flexboxEN,
|
||||
gridEN,
|
||||
positioningEN,
|
||||
unitsVariablesEN,
|
||||
responsiveEN,
|
||||
// CSS Polish
|
||||
transitionsAnimationsEN,
|
||||
filtersEN,
|
||||
pseudoElementsEN,
|
||||
// HTML Structure
|
||||
htmlElementsEN,
|
||||
htmlSemanticEN,
|
||||
htmlFigureEN,
|
||||
htmlSvgEN,
|
||||
// HTML Interactive
|
||||
@@ -164,17 +174,22 @@ const moduleStoreDE = [
|
||||
// CSS Visual (immediate impact)
|
||||
basicSelectorsDE,
|
||||
colorsEN, // Using EN fallback until translated
|
||||
gradientsEN, // Using EN fallback until translated
|
||||
typographyEN, // Using EN fallback until translated
|
||||
boxModelDE,
|
||||
// CSS Layout
|
||||
flexboxDE,
|
||||
gridEN, // Using EN fallback until translated
|
||||
positioningEN, // Using EN fallback until translated
|
||||
unitsVariablesDE,
|
||||
responsiveDE,
|
||||
// CSS Polish
|
||||
transitionsAnimationsDE,
|
||||
filtersEN, // Using EN fallback until translated
|
||||
pseudoElementsEN, // Using EN fallback until translated
|
||||
// HTML Structure
|
||||
htmlElementsDE,
|
||||
htmlSemanticEN, // Using EN fallback until translated
|
||||
htmlFigureEN, // Using EN fallback until translated
|
||||
htmlSvgDE,
|
||||
// HTML Interactive
|
||||
@@ -198,17 +213,22 @@ const moduleStorePL = [
|
||||
// CSS Visual (immediate impact)
|
||||
basicSelectorsPL,
|
||||
colorsEN, // Using EN fallback until translated
|
||||
gradientsEN, // Using EN fallback until translated
|
||||
typographyEN, // Using EN fallback until translated
|
||||
boxModelPL,
|
||||
// CSS Layout
|
||||
flexboxPL,
|
||||
gridEN, // Using EN fallback until translated
|
||||
positioningEN, // Using EN fallback until translated
|
||||
unitsVariablesPL,
|
||||
responsivePL,
|
||||
// CSS Polish
|
||||
transitionsAnimationsPL,
|
||||
filtersEN, // Using EN fallback until translated
|
||||
pseudoElementsEN, // Using EN fallback until translated
|
||||
// HTML Structure
|
||||
htmlElementsPL,
|
||||
htmlSemanticEN, // Using EN fallback until translated
|
||||
htmlFigureEN, // Using EN fallback until translated
|
||||
htmlSvgPL,
|
||||
// HTML Interactive
|
||||
@@ -232,17 +252,22 @@ const moduleStoreES = [
|
||||
// CSS Visual (immediate impact)
|
||||
basicSelectorsES,
|
||||
colorsEN, // Using EN fallback until translated
|
||||
gradientsEN, // Using EN fallback until translated
|
||||
typographyEN, // Using EN fallback until translated
|
||||
boxModelES,
|
||||
// CSS Layout
|
||||
flexboxES,
|
||||
gridEN, // Using EN fallback until translated
|
||||
positioningEN, // Using EN fallback until translated
|
||||
unitsVariablesES,
|
||||
responsiveES,
|
||||
// CSS Polish
|
||||
transitionsAnimationsES,
|
||||
filtersEN, // Using EN fallback until translated
|
||||
pseudoElementsEN, // Using EN fallback until translated
|
||||
// HTML Structure
|
||||
htmlElementsES,
|
||||
htmlSemanticEN, // Using EN fallback until translated
|
||||
htmlFigureEN, // Using EN fallback until translated
|
||||
htmlSvgES,
|
||||
// HTML Interactive
|
||||
@@ -266,17 +291,22 @@ const moduleStoreAR = [
|
||||
// CSS Visual (immediate impact)
|
||||
basicSelectorsAR,
|
||||
colorsEN, // Using EN fallback until translated
|
||||
gradientsEN, // Using EN fallback until translated
|
||||
typographyEN, // Using EN fallback until translated
|
||||
boxModelAR,
|
||||
// CSS Layout
|
||||
flexboxAR,
|
||||
gridEN, // Using EN fallback until translated
|
||||
positioningEN, // Using EN fallback until translated
|
||||
unitsVariablesAR,
|
||||
responsiveAR,
|
||||
// CSS Polish
|
||||
transitionsAnimationsAR,
|
||||
filtersEN, // Using EN fallback until translated
|
||||
pseudoElementsEN, // Using EN fallback until translated
|
||||
// HTML Structure
|
||||
htmlElementsAR,
|
||||
htmlSemanticEN, // Using EN fallback until translated
|
||||
htmlFigureEN, // Using EN fallback until translated
|
||||
htmlSvgAR,
|
||||
// HTML Interactive
|
||||
@@ -300,17 +330,22 @@ const moduleStoreUK = [
|
||||
// CSS Visual (immediate impact)
|
||||
basicSelectorsUK,
|
||||
colorsEN, // Using EN fallback until translated
|
||||
gradientsEN, // Using EN fallback until translated
|
||||
typographyEN, // Using EN fallback until translated
|
||||
boxModelUK,
|
||||
// CSS Layout
|
||||
flexboxUK,
|
||||
gridEN, // Using EN fallback until translated
|
||||
positioningEN, // Using EN fallback until translated
|
||||
unitsVariablesUK,
|
||||
responsiveUK,
|
||||
// CSS Polish
|
||||
transitionsAnimationsUK,
|
||||
filtersEN, // Using EN fallback until translated
|
||||
pseudoElementsEN, // Using EN fallback until translated
|
||||
// HTML Structure
|
||||
htmlElementsUK,
|
||||
htmlSemanticEN, // Using EN fallback until translated
|
||||
htmlFigureEN, // Using EN fallback until translated
|
||||
htmlSvgUK,
|
||||
// HTML Interactive
|
||||
|
||||
Reference in New Issue
Block a user