Files
code-crispies/lessons/32-html-svg.json

185 lines
13 KiB
JSON

{
"$schema": "../schemas/code-crispies-module-schema.json",
"id": "html-svg",
"title": "HTML SVG",
"description": "Draw scalable vector graphics directly in HTML",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "svg-circle",
"title": "Drawing Circles",
"description": "SVG (Scalable Vector Graphics) lets you draw shapes directly in HTML. The <kbd>&lt;svg&gt;</kbd> element is the container, with <kbd>width</kbd> and <kbd>height</kbd> attributes.<br><br>Use <kbd>&lt;circle&gt;</kbd> with <kbd>cx</kbd>, <kbd>cy</kbd> (center) and <kbd>r</kbd> (radius) to draw circles.",
"task": "Create an SVG with a circle:<br>1. An <kbd>&lt;svg&gt;</kbd> with width=\"200\" and height=\"200\"<br>2. A <kbd>&lt;circle&gt;</kbd> centered at (100,100) with radius 50<br>3. Add a <kbd>fill</kbd> color",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; display: flex; justify-content: center; } svg { background: white; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }",
"sandboxCSS": "",
"initialCode": "",
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"50\" fill=\"steelblue\" />\n</svg>",
"previewContainer": "preview-area",
"concept": {
"explanation": "SVG (Scalable Vector Graphics) defines graphics mathematically rather than as pixels, meaning shapes stay crisp at any zoom level or screen resolution—unlike raster images (PNG, JPG) that pixelate when scaled. The browser renders SVG by calculating shape geometry at display time: a circle at (100,100) with radius 50 is recomputed for each pixel density (1x, 2x, 3x displays). This makes SVG perfect for icons, logos, charts, and responsive graphics. SVG elements are DOM nodes like HTML elements, so you can style them with CSS (fill, stroke), animate them with CSS animations or JavaScript, and attach event listeners. The coordinate system starts at top-left (0,0), with x increasing rightward and y increasing downward.",
"diagram": "Vector vs Raster Graphics\n\nRaster (PNG/JPG):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nStores pixels:\n100x100 image = 10,000 pixels\n\n1x display: ■■■■ (crisp)\n2x display: ▪▪▪▪ (pixelated)\n\nVector (SVG):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nStores math:\n<circle cx=\"100\" cy=\"100\" r=\"50\"/>\n\n1x display: ● (crisp)\n2x display: ● (still crisp!)\n\nSVG Coordinate System:\n(0,0) ─────────→ x\n │\n │ (100,100)\n │ ● ← center\n │\n ↓\n y\n\nCircle Attributes:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\ncx=\"100\" → Center X coordinate\ncy=\"100\" → Center Y coordinate\nr=\"50\" → Radius (pixels)\nfill=\"steelblue\" → Interior color\n\nBenefits:\n✓ Resolution-independent\n✓ Small file size\n✓ CSS styleable\n✓ Animatable\n✓ Accessible (text labels)\n✓ DOM-scriptable"
},
"validations": [
{
"type": "element_exists",
"value": "svg",
"message": "Add an <kbd>&lt;svg&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "circle",
"message": "Add a <kbd>&lt;circle&gt;</kbd> element inside the SVG"
},
{
"type": "attribute_value",
"value": { "selector": "svg", "attr": "width", "value": "200" },
"message": "Set <kbd>width=</kbd>\"200\" on the SVG"
},
{
"type": "attribute_value",
"value": { "selector": "svg", "attr": "height", "value": "200" },
"message": "Set <kbd>height=</kbd>\"200\" on the SVG"
},
{
"type": "attribute_value",
"value": { "selector": "circle", "attr": "cx", "value": "100" },
"message": "Set <kbd>cx=</kbd>\"100\" for the circle's horizontal center"
},
{
"type": "attribute_value",
"value": { "selector": "circle", "attr": "cy", "value": "100" },
"message": "Set <kbd>cy=</kbd>\"100\" for the circle's vertical center"
},
{
"type": "attribute_value",
"value": { "selector": "circle", "attr": "r", "value": "50" },
"message": "Set <kbd>r=</kbd>\"50\" for the circle's radius"
}
]
},
{
"id": "svg-rect-line",
"title": "Rectangles & Lines",
"description": "Draw rectangles with <kbd>&lt;rect&gt;</kbd> using <kbd>x</kbd>, <kbd>y</kbd>, <kbd>width</kbd>, <kbd>height</kbd>.<br><br>Draw lines with <kbd>&lt;line&gt;</kbd> using <kbd>x1</kbd>, <kbd>y1</kbd> (start) and <kbd>x2</kbd>, <kbd>y2</kbd> (end). Lines need a <kbd>stroke</kbd> color!",
"task": "Create an SVG with:<br>1. An <kbd>&lt;svg&gt;</kbd> (200x150)<br>2. A <kbd>&lt;rect&gt;</kbd> at position (20,20) with size 80x60<br>3. A <kbd>&lt;line&gt;</kbd> from (120,30) to (180,90) with a stroke color",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 200px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); }",
"sandboxCSS": "",
"initialCode": "",
"solution": "<svg width=\"200\" height=\"150\">\n <rect x=\"20\" y=\"20\" width=\"80\" height=\"60\" fill=\"tomato\" />\n <line x1=\"120\" y1=\"30\" x2=\"180\" y2=\"90\" stroke=\"slategray\" stroke-width=\"3\" />\n</svg>",
"previewContainer": "preview-area",
"concept": {
"explanation": "SVG distinguishes between filled shapes (solid interior) and stroked shapes (outline only) through fill and stroke attributes. Rectangles use top-left corner positioning (x, y) plus dimensions (width, height), while lines use start point (x1, y1) and end point (x2, y2) coordinates—no implicit fill for lines since they're one-dimensional. Lines require an explicit stroke to be visible because fill doesn't apply to one-dimensional paths. Stroke-width controls line thickness in user units (typically pixels), and additional stroke properties like stroke-linecap (round, square, butt) and stroke-linejoin (round, bevel, miter) control how stroke endpoints and corners render. SVG's presentation attributes (fill, stroke) can be overridden by CSS for dynamic styling.",
"diagram": "SVG Shape Positioning\n\nRectangle Coordinates:\n(0,0)\n ┌─────────────────→ x\n │ (20,20)\n │ ┌────────┐ ← x=\"20\" y=\"20\"\n │ │ │ width=\"80\"\n │ │ rect │ height=\"60\"\n │ └────────┘\n ↓\n y\n\nLine Coordinates:\n(x1,y1) (x2,y2)\n ●───────────────●\n(120,30) (180,90)\n ↑ start ↑ end\n\nFill vs Stroke:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n<rect fill=\"red\"> ← Solid interior\n<rect fill=\"red\" stroke=\"black\" stroke-width=\"2\">\n ↑ interior ↑ outline\n\n<line stroke=\"blue\"> ← Only visible\n<line fill=\"red\"> ← Ignored!\n\nStroke Properties:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nstroke=\"color\"\nstroke-width=\"3\" → Thickness\nstroke-linecap=\"round\" → End shape\nstroke-dasharray=\"5,5\" → Dashed line\nstroke-opacity=\"0.5\" → Transparency\n\nPresentation Attributes:\nHTML: <rect fill=\"red\">\nCSS: rect { fill: red; }\nCSS wins if both present!"
},
"validations": [
{
"type": "element_exists",
"value": "svg",
"message": "Add an <kbd>&lt;svg&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "rect",
"message": "Add a <kbd>&lt;rect&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "line",
"message": "Add a <kbd>&lt;line&gt;</kbd> element"
},
{
"type": "attribute_value",
"value": { "selector": "svg", "attr": "width", "value": "200" },
"message": "Set <kbd>width=</kbd>\"200\" on the SVG"
},
{
"type": "attribute_value",
"value": { "selector": "svg", "attr": "height", "value": "150" },
"message": "Set <kbd>height=</kbd>\"150\" on the SVG"
},
{
"type": "attribute_value",
"value": { "selector": "rect", "attr": "x", "value": "20" },
"message": "Set <kbd>x=</kbd>\"20\" on the rect"
},
{
"type": "attribute_value",
"value": { "selector": "rect", "attr": "y", "value": "20" },
"message": "Set <kbd>y=</kbd>\"20\" on the rect"
},
{
"type": "attribute_value",
"value": { "selector": "rect", "attr": "width", "value": "80" },
"message": "Set <kbd>width=</kbd>\"80\" on the rect"
},
{
"type": "attribute_value",
"value": { "selector": "rect", "attr": "height", "value": "60" },
"message": "Set <kbd>height=</kbd>\"60\" on the rect"
},
{
"type": "attribute_value",
"value": { "selector": "line", "attr": "x1", "value": "120" },
"message": "Set <kbd>x1=</kbd>\"120\" on the line"
},
{
"type": "attribute_value",
"value": { "selector": "line", "attr": "y1", "value": "30" },
"message": "Set <kbd>y1=</kbd>\"30\" on the line"
},
{
"type": "attribute_value",
"value": { "selector": "line", "attr": "x2", "value": "180" },
"message": "Set <kbd>x2=</kbd>\"180\" on the line"
},
{
"type": "attribute_value",
"value": { "selector": "line", "attr": "y2", "value": "90" },
"message": "Set <kbd>y2=</kbd>\"90\" on the line"
},
{
"type": "contains",
"value": "stroke",
"message": "Add a <kbd>stroke</kbd> color to the line"
}
]
},
{
"id": "svg-shapes",
"title": "Multiple Shapes",
"description": "Combine shapes to create simple graphics! Add <kbd>stroke</kbd> for outlines and <kbd>stroke-width</kbd> for thickness.<br><br>Use <kbd>fill=\"none\"</kbd> for hollow shapes. Shapes stack in order - later elements appear on top.",
"task": "Create a simple face:<br>1. An <kbd>&lt;svg&gt;</kbd> (200x200)<br>2. A large <kbd>&lt;circle&gt;</kbd> for the face<br>3. Two small <kbd>&lt;circle&gt;</kbd> elements for eyes<br>4. A <kbd>&lt;line&gt;</kbd> for the smile",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); min-height: 250px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); }",
"sandboxCSS": "",
"initialCode": "",
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"80\" fill=\"gold\" stroke=\"orange\" stroke-width=\"4\" />\n <circle cx=\"70\" cy=\"80\" r=\"10\" fill=\"darkslategray\" />\n <circle cx=\"130\" cy=\"80\" r=\"10\" fill=\"darkslategray\" />\n <line x1=\"60\" y1=\"130\" x2=\"140\" y2=\"130\" stroke=\"darkslategray\" stroke-width=\"4\" stroke-linecap=\"round\" />\n</svg>",
"previewContainer": "preview-area",
"concept": {
"explanation": "SVG elements stack in source order like HTML—later elements render on top of earlier ones, creating a painter's algorithm where each shape is \"painted\" over previous shapes. This z-order control means shape placement in your markup defines layering: the face circle must come before the eye circles for eyes to appear on top. Unlike CSS z-index (which requires positioning context), SVG stacking is purely document-order based. You can group related shapes with <g> elements for organizational purposes and apply transformations or styles to the entire group. SVG's declarative nature makes it ideal for programmatic generation: you can template SVG markup or use JavaScript to create/manipulate shapes dynamically, and the browser automatically handles rendering updates.",
"diagram": "SVG Stacking Order\n\nSource Order = Paint Order:\n<svg>\n <circle ... /> ← Painted first (back)\n <circle ... /> ← Painted second\n <circle ... /> ← Painted third\n <line ... /> ← Painted last (front)\n</svg>\n\nVisual Result:\n Layer 4 (front)\n │\n Layer 3\n │\n Layer 2\n │\n Layer 1 (back)\n\nSmiley Face Example:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n1. Face (large circle)\n2. Left eye (small circle) ─┐\n3. Right eye (small circle) ├→ On top of face\n4. Smile (line) ─┘\n\nGrouping with <g>:\n<g id=\"eyes\">\n <circle cx=\"70\" cy=\"80\" r=\"10\"/>\n <circle cx=\"130\" cy=\"80\" r=\"10\"/>\n</g>\n\nBenefits:\n✓ Apply transform to group\n✓ Style entire group\n✓ Semantic organization\n✓ Easy to show/hide\n\nDynamic SVG:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nJS: circle.setAttribute('r', 60);\n→ Browser re-renders instantly\n\nJS: svg.innerHTML += '<circle.../>';\n→ Add shapes dynamically\n\nvs Canvas:\nSVG = Retained mode (DOM)\nCanvas = Immediate mode (pixels)"
},
"validations": [
{
"type": "element_exists",
"value": "svg",
"message": "Add an <kbd>&lt;svg&gt;</kbd> element"
},
{
"type": "element_count",
"value": { "selector": "circle", "min": 3 },
"message": "Add at least 3 circles (1 face + 2 eyes)"
},
{
"type": "element_exists",
"value": "line",
"message": "Add a <kbd>&lt;line&gt;</kbd> for the smile"
}
]
}
]
}