diff --git a/lessons/04-typography.json b/lessons/04-typography.json
index 490d3d9..9dda4b8 100644
--- a/lessons/04-typography.json
+++ b/lessons/04-typography.json
@@ -98,6 +98,53 @@
"message": "Set letter-spacing to 1px "
}
]
+ },
+ {
+ "id": "text-decoration",
+ "title": "Text Decoration",
+ "description": "The text-decoration property adds lines to text. Common values: • underline — line below text • line-through — strikethrough • none — removes decoration (useful for links) You can also style decorations with text-decoration-color and text-decoration-style .",
+ "task": "Show the old price with a strikethrough. Add text-decoration: line-through .",
+ "previewHTML": "
$49.99 $29.99
",
+ "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 line-through "
+ }
+ ]
+ },
+ {
+ "id": "text-shadow",
+ "title": "Text Shadow",
+ "description": "The text-shadow property adds shadow effects to text. The syntax is:text-shadow: x-offset y-offset blur color; Example: text-shadow: 2px 2px 4px gray creates a soft shadow offset down and right.",
+ "task": "Add depth to the heading with text-shadow: 2px 2px 4px gray .",
+ "previewHTML": "Welcome ",
+ "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 text-shadow property"
+ },
+ {
+ "type": "contains",
+ "value": "2px 2px",
+ "message": "Set offset to 2px 2px "
+ }
+ ]
}
]
}
diff --git a/lessons/09-gradients.json b/lessons/09-gradients.json
new file mode 100644
index 0000000..808c01a
--- /dev/null
+++ b/lessons/09-gradients.json
@@ -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 linear-gradient() function creates a gradient along a straight line.Basic syntax: background: linear-gradient(color1, color2); By default, gradients flow from top to bottom.",
+ "task": "Add a gradient background from coral to gold .",
+ "previewHTML": "",
+ "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 linear-gradient() "
+ },
+ {
+ "type": "contains",
+ "value": "coral",
+ "message": "Include coral as the first color"
+ },
+ {
+ "type": "contains",
+ "value": "gold",
+ "message": "Include gold as the second color"
+ }
+ ]
+ },
+ {
+ "id": "gradients-2",
+ "title": "Gradient Direction",
+ "description": "Control the gradient direction by adding an angle or keyword before the colors.Keywords: to right , to left , to bottom right Angles: 45deg , 90deg , 180deg background: linear-gradient(to right, blue, purple); ",
+ "task": "Make the gradient flow from left to right using to right .",
+ "previewHTML": "Get Started ",
+ "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 to right to set the direction"
+ }
+ ]
+ },
+ {
+ "id": "gradients-3",
+ "title": "Radial Gradient",
+ "description": "The radial-gradient() function creates a gradient that radiates from a center point outward in a circular or elliptical pattern.background: radial-gradient(circle, white, steelblue); Add circle for a perfect circular gradient.",
+ "task": "Create a radial gradient from white to steelblue .",
+ "previewHTML": "
",
+ "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 radial-gradient() "
+ },
+ {
+ "type": "contains",
+ "value": "white",
+ "message": "Start with white "
+ },
+ {
+ "type": "contains",
+ "value": "steelblue",
+ "message": "End with steelblue "
+ }
+ ]
+ }
+ ]
+}
diff --git a/lessons/11-filters.json b/lessons/11-filters.json
new file mode 100644
index 0000000..11fdd7b
--- /dev/null
+++ b/lessons/11-filters.json
@@ -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 filter property applies visual effects to elements. The blur() function creates a Gaussian blur effect.filter: blur(4px); Higher values create more blur. This is great for backgrounds or creating depth.",
+ "task": "Blur the background image using filter: blur(4px) .",
+ "previewHTML": "
Welcome ",
+ "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 filter: blur(4px) "
+ }
+ ]
+ },
+ {
+ "id": "filters-2",
+ "title": "Grayscale Filter",
+ "description": "The grayscale() function removes color from an element. Use values from 0% (full color) to 100% (fully grayscale).filter: grayscale(100%); Great for hover effects or disabled states.",
+ "task": "Make the image grayscale with filter: grayscale(100%) .",
+ "previewHTML": "
",
+ "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 grayscale() filter"
+ },
+ {
+ "type": "contains",
+ "value": "100%",
+ "message": "Set to 100% for full grayscale"
+ }
+ ]
+ },
+ {
+ "id": "filters-3",
+ "title": "Brightness Filter",
+ "description": "The brightness() function adjusts how bright an element appears. Values below 100% darken, above 100% brighten.filter: brightness(150%); ",
+ "task": "Brighten the card with filter: brightness(120%) .",
+ "previewHTML": "Featured
",
+ "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 brightness() filter"
+ },
+ {
+ "type": "contains",
+ "value": "120%",
+ "message": "Set to 120% "
+ }
+ ]
+ },
+ {
+ "id": "filters-4",
+ "title": "Drop Shadow",
+ "description": "The drop-shadow() filter creates a shadow that follows the shape of the element, including transparency. Unlike box-shadow , it works on images with transparent backgrounds.filter: drop-shadow(2px 4px 6px black); ",
+ "task": "Add a drop shadow with filter: drop-shadow(4px 4px 8px gray) .",
+ "previewHTML": "★
",
+ "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 drop-shadow() filter"
+ },
+ {
+ "type": "contains",
+ "value": "4px 4px 8px",
+ "message": "Set shadow offset and blur"
+ }
+ ]
+ }
+ ]
+}
diff --git a/lessons/12-positioning.json b/lessons/12-positioning.json
new file mode 100644
index 0000000..dcfb0af
--- /dev/null
+++ b/lessons/12-positioning.json
@@ -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 position property controls how elements are placed. relative keeps the element in normal flow but allows you to offset it with top , right , bottom , left ..box {\n position: relative;\n top: 10px;\n} ",
+ "task": "Make the badge position relative so we can offset it.",
+ "previewHTML": "NEW
Product ",
+ "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 position: relative "
+ }
+ ]
+ },
+ {
+ "id": "position-2",
+ "title": "Offset Properties",
+ "description": "With position: relative , use offset properties to nudge the element from its original position:top - pushes down from topleft - pushes right from left Negative values move in the opposite direction.",
+ "task": "Move the badge up with top: -8px .",
+ "previewHTML": "NEW
Product ",
+ "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 top: -8px "
+ }
+ ]
+ },
+ {
+ "id": "position-3",
+ "title": "Absolute Position",
+ "description": "position: absolute removes the element from normal flow and positions it relative to its nearest positioned ancestor (or the viewport if none exists). Always set a parent to position: relative to contain absolute children.",
+ "task": "Position the close button absolutely.",
+ "previewHTML": "",
+ "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 position: absolute "
+ }
+ ]
+ },
+ {
+ "id": "position-4",
+ "title": "Placing Absolute Elements",
+ "description": "Combine position: absolute with offset properties to place elements precisely..close {\n position: absolute;\n top: 8px;\n right: 8px;\n} ",
+ "task": "Move the close button to the top right corner with top: 8px and right: 8px .",
+ "previewHTML": "",
+ "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 top: 8px "
+ },
+ {
+ "type": "property_value",
+ "value": { "property": "right", "expected": "8px" },
+ "message": "Set right: 8px "
+ }
+ ]
+ }
+ ]
+}
diff --git a/lessons/13-pseudo-elements.json b/lessons/13-pseudo-elements.json
new file mode 100644
index 0000000..02da42d
--- /dev/null
+++ b/lessons/13-pseudo-elements.json
@@ -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. ::before creates a virtual element as the first child. It requires the content property to display anything (even if empty)..item::before {\n content: \"→ \";\n} ",
+ "task": "Add a bullet before each list item using ::before with content: \"• \" .",
+ "previewHTML": "First item Second item Third item ",
+ "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 content property"
+ },
+ {
+ "type": "contains",
+ "value": "•",
+ "message": "Add a bullet character • "
+ }
+ ]
+ },
+ {
+ "id": "pseudo-2",
+ "title": "Styling ::before",
+ "description": "Pseudo-elements can be styled like any element. Add color, size, margins, and more..item::before {\n content: \"★\";\n color: gold;\n margin-right: 8px;\n} ",
+ "task": "Style the bullet with color: coral .",
+ "previewHTML": "First item Second item Third item ",
+ "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 color: coral "
+ }
+ ]
+ },
+ {
+ "id": "pseudo-3",
+ "title": "The ::after Element",
+ "description": "::after works like ::before but inserts content as the last child. Common uses include badges, icons, or decorative elements..new::after {\n content: \" ✓\";\n color: green;\n} ",
+ "task": "Add a checkmark after completed items with content: \" ✓\" .",
+ "previewHTML": "Buy groceries Walk the dog Read a book ",
+ "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 content property"
+ },
+ {
+ "type": "contains",
+ "value": "✓",
+ "message": "Add a checkmark ✓ "
+ }
+ ]
+ },
+ {
+ "id": "pseudo-4",
+ "title": "Decorative Lines",
+ "description": "Pseudo-elements with content: \"\" can create decorative shapes when combined with width, height, and background..title::after {\n content: \"\";\n display: block;\n width: 50px;\n height: 3px;\n background: coral;\n} ",
+ "task": "Create an underline decoration with width: 40px , height: 3px , and background: steelblue .",
+ "previewHTML": "About Us We build great things.
",
+ "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 width: 40px "
+ },
+ {
+ "type": "property_value",
+ "value": { "property": "height", "expected": "3px" },
+ "message": "Set height: 3px "
+ },
+ {
+ "type": "property_value",
+ "value": { "property": "background", "expected": "steelblue" },
+ "message": "Set background: steelblue "
+ }
+ ]
+ }
+ ]
+}
diff --git a/lessons/30-html-tables.json b/lessons/30-html-tables.json
index 9fd3c63..21f2e2a 100644
--- a/lessons/30-html-tables.json
+++ b/lessons/30-html-tables.json
@@ -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: • <thead> — header section • <tbody> — main content • <tfoot> — footer (totals, summaries)",
+ "task": "Wrap the header row in <thead> and data rows in <tbody> .",
+ "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": "\n \n Name \n Score \n \n \n Alice \n 95 \n \n \n Bob \n 87 \n \n
",
+ "solution": "\n \n \n Name \n Score \n \n \n \n \n Alice \n 95 \n \n \n Bob \n 87 \n \n \n
",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "thead",
+ "message": "Add a <thead> section for the header"
+ },
+ {
+ "type": "element_exists",
+ "value": "tbody",
+ "message": "Add a <tbody> section for the data"
+ }
+ ]
+ },
+ {
+ "id": "table-colspan",
+ "title": "Spanning Columns",
+ "description": "The colspan attribute lets a cell span multiple columns. This is useful for headers that group multiple columns or footer totals.<td colspan=\"2\">...</td> ",
+ "task": "Add a footer row that spans both columns using colspan=\"2\" .",
+ "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": "\n \n \n Item \n Price \n \n \n \n \n Coffee \n $4 \n \n \n Cake \n $6 \n \n \n
",
+ "solution": "\n \n \n Item \n Price \n \n \n \n \n Coffee \n $4 \n \n \n Cake \n $6 \n \n \n \n \n Total: $10 \n \n \n
",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "element_exists",
+ "value": "tfoot",
+ "message": "Add a <tfoot> section"
+ },
+ {
+ "type": "contains",
+ "value": "colspan",
+ "message": "Use colspan to span columns"
+ }
+ ]
}
]
}
diff --git a/lessons/33-html-semantic.json b/lessons/33-html-semantic.json
new file mode 100644
index 0000000..df148f8
--- /dev/null
+++ b/lessons/33-html-semantic.json
@@ -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 Element",
+ "description": "The <article> element represents self-contained content that could be distributed independently, like a blog post, news article, or comment.<article>\n <h2>Article Title</h2>\n <p>Article content...</p>\n</article> ",
+ "task": "Wrap the blog post content in an <article> 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": "My First Post \nThis is a blog post about learning HTML.
",
+ "codeSuffix": "",
+ "solution": "\nMy First Post \nThis is a blog post about learning HTML.
\n ",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "contains",
+ "value": "",
+ "message": "Add an opening <article> tag"
+ },
+ {
+ "type": "contains",
+ "value": " ",
+ "message": "Add a closing </article> tag"
+ }
+ ]
+ },
+ {
+ "id": "semantic-2",
+ "title": "The Element",
+ "description": "The <section> element represents a thematic grouping of content, typically with a heading. Use it to divide a page into logical sections.<section>\n <h2>Features</h2>\n <p>Our product features...</p>\n</section> ",
+ "task": "Wrap the features content in a <section> 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": "Features \n\n Fast performance \n Easy to use \n ",
+ "codeSuffix": "",
+ "solution": "\nFeatures \n\n Fast performance \n Easy to use \n \n ",
+ "previewContainer": "preview-area",
+ "validations": [
+ {
+ "type": "contains",
+ "value": "",
+ "message": "Add an opening <section> tag"
+ },
+ {
+ "type": "contains",
+ "value": " ",
+ "message": "Add a closing </section> tag"
+ }
+ ]
+ },
+ {
+ "id": "semantic-3",
+ "title": "The