\n → Abuses id attribute\n✓ data-category=\"electronics\"\n → Semantic & maintainable"
+ },
"validations": [
{
"type": "element_count",
@@ -46,6 +50,10 @@
"initialCode": "
",
"solution": "
\n Buy groceries \n Finish homework \n Call mom \n ",
"previewContainer": "preview-area",
+ "concept": {
+ "explanation": "CSS attribute selectors enable state-based styling without adding/removing classes via JavaScript—you just change the attribute value and CSS reactivity handles the rest. The selector [data-status='active'] has the same specificity as a class (0,0,1,0), making it equally powerful but more semantic for data-driven states. This pattern shines in component libraries and SPAs where state changes frequently: updating one attribute triggers CSS transitions and visual changes automatically. Unlike classes that describe presentation (\"button-blue\"), data attributes describe meaning (\"status=active\"), and CSS translates meaning to presentation, keeping your HTML semantic and your styling decoupled from implementation details.",
+ "diagram": "CSS Attribute Selectors\n\nAttribute Selector Syntax:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n[data-status] → Has attribute\n[data-status=\"active\"] → Exact match\n[data-status^=\"act\"] → Starts with\n[data-status$=\"ed\"] → Ends with\n[data-status*=\"iv\"] → Contains\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nState-Based Styling:\n\n
\n ↓ CSS applies\nli[data-status=\"pending\"] {\n background: lightblue;\n}\n\nJS changes state:\nel.dataset.status = \"active\"\n ↓ CSS automatically updates\nli[data-status=\"active\"] {\n background: orange;\n}\n\nvs Class Approach:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\ndata-status=\"active\"\n✓ Semantic (describes state)\n✓ One attribute to update\n✓ Clear data meaning\n\nclass=\"task active pending\"\n✗ Presentational\n✗ Multiple classes to manage\n✗ Unclear which is data\n\nSpecificity:\n[data-status=\"active\"] = .active\nBoth have 0,0,1,0 specificity"
+ },
"validations": [
{
"type": "element_exists",
diff --git a/lessons/27-html-dialog.json b/lessons/27-html-dialog.json
index f31ddd0..22de0da 100644
--- a/lessons/27-html-dialog.json
+++ b/lessons/27-html-dialog.json
@@ -17,6 +17,10 @@
"initialCode": "",
"solution": "\n Welcome! \n This is a native HTML dialog element.
\n \n ",
"previewContainer": "preview-area",
+ "concept": {
+ "explanation": "The dialog element is a native modal/popup that the browser manages entirely—it handles backdrop rendering, focus trapping, Escape key closing, and scroll locking on the body without any JavaScript. When opened with showModal(), the browser creates an ::backdrop pseudo-element (styled by CSS), traps keyboard focus inside the dialog (Tab cycles through dialog elements only), and prevents interaction with background content. The form method=\"dialog\" pattern leverages native form submission to close the dialog: any button inside submits the form, which closes the dialog and returns the button's value via the dialog's returnValue property. This replaces thousands of lines of modal library code with semantic HTML.",
+ "diagram": "Dialog Mechanics\n\nNative Modal Features:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n✓ Backdrop rendering\n✓ Focus trapping (Tab loops)\n✓ Escape key closes\n✓ Body scroll lock\n✓ Top-layer rendering\n✓ Screen reader isolation\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nDialog Opening Methods:\n\nshowModal() → Modal dialog\n ↓\n ┌─────────────────────┐\n │ [Backdrop overlay] │\n │ ┌───────────────┐ │\n │ │ │ │\n │ │ Content │ │\n │ └───────────────┘ │\n └─────────────────────┘\n Focus trapped, Esc closes\n\nshow() → Non-modal dialog\n ↓\n ┌───────────────┐\n │ │\n │ Content │ ← Floats above\n └───────────────┘\n Can interact with background\n\nForm Method=\"dialog\":\n\n ↓ Clicking either button\n 1. Submits form\n 2. Closes dialog\n 3. Sets dialog.returnValue"
+ },
"validations": [
{
"type": "element_exists",
@@ -56,6 +60,10 @@
"initialCode": "",
"solution": "\n Confirm Delete \n Are you sure you want to delete this item?
\n \n ",
"previewContainer": "preview-area",
+ "concept": {
+ "explanation": "The combination of dialog + form method=\"dialog\" creates a confirmation pattern where button values become the dialog's return value, letting you distinguish which button was clicked. When a user clicks a button in a method=\"dialog\" form, three things happen atomically: the form submits (triggering submit event), the dialog closes (triggering close event), and dialog.returnValue is set to the clicked button's value attribute. This pattern is perfect for yes/no confirmations or multi-choice prompts where you need to know the user's decision. Unlike window.confirm() which blocks the entire page and looks dated, dialog provides a customizable, non-blocking, accessible alternative that fits modern design systems.",
+ "diagram": "Dialog Return Values\n\nHTML:\n\n \n \n\nExecution Flow:\n\nUser clicks \"Delete\" button\n ↓\n1. Form submits\n (submit event fires)\n ↓\n2. Dialog closes\n (close event fires)\n ↓\n3. returnValue set\n dialog.returnValue = \"delete\"\n\nJavaScript Usage:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nconst dialog = document.querySelector('#confirm');\ndialog.showModal();\n\ndialog.addEventListener('close', () => {\n if (dialog.returnValue === 'delete') {\n // User confirmed\n deleteItem();\n } else {\n // User cancelled\n }\n});\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nvs window.confirm():\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nwindow.confirm() → Blocks page\n → Ugly native UI\n → No customization\n\n → Non-blocking\n → Styleable\n → Accessible"
+ },
"validations": [
{
"type": "element_exists",
diff --git a/lessons/28-html-forms-fieldset.json b/lessons/28-html-forms-fieldset.json
index 24d528b..c609240 100644
--- a/lessons/28-html-forms-fieldset.json
+++ b/lessons/28-html-forms-fieldset.json
@@ -17,6 +17,10 @@
"initialCode": "",
"solution": "",
"previewContainer": "preview-area",
+ "concept": {
+ "explanation": "Fieldset creates a semantic grouping that browsers and assistive technology understand as related form controls, not just a visual border. When a screen reader enters a fieldset, it announces the legend before each control (\"Personal Info, Name, edit text\"), providing context without repetition in every label. The browser also establishes a form control context: disabling the fieldset (disabled attribute) automatically disables all inputs inside, and form validation can be scoped to fieldsets. This semantic structure is crucial for complex forms—it transforms a flat list of inputs into a hierarchical document with clear relationships, improving both accessibility and maintainability.",
+ "diagram": "Fieldset Semantic Structure\n\nHTML:\n\n\nScreen Reader Experience:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\"Personal Info, group\"\n → Focus first input\n \"Personal Info, Name, edit text\"\n → Tab to next input\n \"Personal Info, Email, edit text\"\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nContext announced once, then reused\n\nDisabled Propagation:\n\n\n ← Automatically disabled\n ← Automatically disabled\n \n\nvs Individual Disabling:\n \n ← Must repeat\n\nForm Structure:\n\nFlat (no grouping):\n✗ Name\n✗ Email\n✗ Street\n✗ City\n\nGrouped (semantic):\n✓ Personal Info\n ✓ Name\n ✓ Email\n✓ Address\n ✓ Street\n ✓ City"
+ },
"validations": [
{
"type": "element_exists",
@@ -56,6 +60,10 @@
"initialCode": "",
"solution": "",
"previewContainer": "preview-area",
+ "concept": {
+ "explanation": "The textarea element is designed for multi-line text input, automatically providing scroll bars when content exceeds its dimensions and preserving line breaks and whitespace on submission. Unlike input elements which ignore Enter key (using it for form submission), textarea captures Enter as a newline character, making it suitable for addresses, comments, messages, or any free-form text. The rows and cols attributes set initial dimensions as character counts (rows for lines, cols for width in characters), but CSS width/height override these. Textarea is a container element (not self-closing), so you must use <textarea>content</textarea> syntax—any text between the tags becomes the initial value, preserving formatting.",
+ "diagram": "Textarea vs Input\n\nInput (single-line):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n \n\n┌─────────────────────────────┐\n│ Hello_ │\n└─────────────────────────────┘\n✗ Enter → Submits form\n✗ No line breaks\n✗ Self-closing tag\n\nTextarea (multi-line):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nHello\nWorld\n \n\n┌─────────────────────────────┐\n│ Hello │\n│ World_ │\n│ │ ← rows=\"3\"\n└─────────────────────────────┘\n✓ Enter → New line\n✓ Preserves line breaks\n✓ Container element\n✓ Auto-scrolls if overflowing\n\nSizing:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nrows=\"4\" → 4 lines tall\ncols=\"40\" → 40 chars wide\nCSS overrides → width/height\n\nValue Syntax:\nInitial text \nvs\n "
+ },
"validations": [
{
"type": "element_exists",
@@ -95,6 +103,10 @@
"initialCode": "",
"solution": "\n \n Account Info \n Username: \n \n Password: \n \n \n \n Preferences \n Bio: \n \n \n Register \n ",
"previewContainer": "preview-area",
+ "concept": {
+ "explanation": "Multiple fieldsets divide long forms into logical sections, improving cognitive load by chunking related fields together—users process \"fill out personal info\" and \"fill out address\" as distinct mental tasks rather than one overwhelming list. This pattern also enables progressive disclosure: you can hide/show fieldsets as wizard steps, disable future sections until current ones validate, or use CSS to style sections differently based on state. Screen readers announce fieldset boundaries (\"entering Personal Info group\", \"leaving Personal Info group\"), helping users maintain their place in complex forms. The semantic structure also aids form analytics: you can track which sections users struggle with or abandon most frequently.",
+ "diagram": "Multi-Fieldset Forms\n\nSingle Long Form:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n┌─────────────────────────────┐\n│ Name: [ ] │\n│ Email: [ ] │\n│ Username: [ ] │ Overwhelming\n│ Password: [ ] │ 8 fields at once\n│ Street: [ ] │\n│ City: [ ] │\n│ State: [ ] │\n│ Bio: [ ] │\n│ [Submit] │\n└─────────────────────────────┘\n\nChunked with Fieldsets:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n┌─────────────────────────────┐\n│ Personal Info │ Section 1\n│ Name: [ ] │ (2 fields)\n│ Email: [ ] │\n├─────────────────────────────┤\n│ Account │ Section 2\n│ Username: [ ] │ (2 fields)\n│ Password: [ ] │\n├─────────────────────────────┤\n│ Address │ Section 3\n│ Street: [ ] │ (3 fields)\n│ City: [ ] │\n│ State: [ ] │\n├─────────────────────────────┤\n│ About You │ Section 4\n│ Bio: [ ] │ (1 field)\n├─────────────────────────────┤\n│ [Submit] │\n└─────────────────────────────┘\n\nBenefits:\n✓ Reduced cognitive load\n✓ Clear visual hierarchy\n✓ Section-level validation\n✓ Progressive disclosure\n✓ Better analytics"
+ },
"validations": [
{
"type": "element_count",
diff --git a/lessons/29-html-figure.json b/lessons/29-html-figure.json
index dcd5cb5..7d5c84e 100644
--- a/lessons/29-html-figure.json
+++ b/lessons/29-html-figure.json
@@ -17,6 +17,10 @@
"initialCode": "",
"solution": "\n \n A beautiful mountain landscape at sunset. \n ",
"previewContainer": "preview-area",
+ "concept": {
+ "explanation": "The figure element semantically marks self-contained content that's referenced from the main flow but could be moved elsewhere (like a sidebar or appendix) without losing meaning. Figcaption provides an accessible label that screen readers announce when encountering the figure, establishing a programmatic relationship between image and caption that's stronger than visual proximity alone. Unlike an img followed by a p, figure+figcaption creates an accessibility API relationship: AT announces \"figure\" when entering, reads the caption, then describes the image, giving users complete context. Search engines also parse this relationship, using figcaption content to understand image meaning for image search results and context-aware rankings.",
+ "diagram": "Figure Semantic Relationship\n\nRegular Image + Text:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n \nA beautiful mountain.
\n\n✗ No semantic link\n✗ SR: \"Mountain image\" then \"A beautiful mountain\"\n✗ Caption could apply to any nearby content\n\nFigure + Figcaption:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n \n A beautiful mountain. \n \n\n✓ Semantic relationship\n✓ SR: \"Figure. Mountain image. A beautiful mountain.\"\n✓ Caption explicitly bound to image\n\nScreen Reader Flow:\n┌─────────────────────────────┐\n│ │ → \"Entering figure\"\n│ │ → \"Mountain, image\"\n│ │\n│ A beautiful mountain │ → \"A beautiful mountain\"\n│ │\n│ │ → \"Leaving figure\"\n└─────────────────────────────┘\n\nSEO Benefits:\n✓ Image-caption binding\n✓ Better image search results\n✓ Context for visually similar images"
+ },
"validations": [
{
"type": "element_exists",
@@ -46,6 +50,10 @@
"initialCode": "",
"solution": "\n function greet(name) {\n return `Hello, ${name}!`;\n} \n A simple greeting function in JavaScript \n ",
"previewContainer": "preview-area",
+ "concept": {
+ "explanation": "Figure isn't limited to images—it's for any self-contained content like code samples, charts, diagrams, poems, or quotes. The semantic meaning is \"referenced content with a caption\", not \"photo with text\". When wrapping code in figure, you establish that this code block is an example being discussed, not inline code to execute. Screen readers announce \"figure\" before the code, signaling to users that this is illustrative content they may want to skip if they're scanning. The figcaption describes what the code does or why it's shown, helping users decide whether to read it in detail—crucial for technical documentation where code blocks can be numerous and lengthy.",
+ "diagram": "Figure Use Cases\n\nImages:\n\n \n Sales 2024 \n \n\nCode Samples:\n\n function add() {} \n Addition function \n \n\nQuotes:\n\n To be or not to be \n — Shakespeare \n \n\nPoems:\n\n Roses are red
\n Violets are blue
\n — Anonymous \n \n\nDiagrams (SVG):\n\n ... \n System architecture \n \n\nCommon Pattern:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n✓ Content that illustrates a point\n✓ Content referenced by main text\n✓ Content with a description/attribution\n✓ Self-contained units\n\n✗ Not for decorative images\n✗ Not for inline content\n✗ Not for UI elements"
+ },
"validations": [
{
"type": "element_exists",
@@ -80,6 +88,10 @@
"initialCode": "",
"solution": "\n \n \n \n \n My vacation photo gallery \n ",
"previewContainer": "preview-area",
+ "concept": {
+ "explanation": "A single figure can contain multiple related elements when they collectively form one logical unit, like a photo gallery, before/after comparison, or multi-angle product shots. The figcaption describes the entire collection rather than individual items, establishing that these pieces should be understood together. This pattern is semantically different from multiple separate figures—it communicates \"these images are facets of one concept\" versus \"here are several independent illustrations\". Screen readers announce one figure containing multiple images, then read the collective caption, helping users understand the grouping. Search engines also interpret this structure, understanding that images within a figure are related for relevance ranking.",
+ "diagram": "Single vs Multiple Figures\n\nMultiple Separate Figures:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n \n Paris \n \n\n \n London \n \n→ Two independent illustrations\n\nSingle Figure Gallery:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n \n \n \n European cities \n \n→ One concept with multiple views\n\nUse Cases:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n✓ Photo gallery (vacation pics)\n✓ Before/after comparison\n✓ Product: front, side, back views\n✓ Step-by-step process images\n✓ Multi-panel comics/diagrams\n\nScreen Reader:\n\"Figure containing 4 images\"\n→ Image 1: \"paris.jpg\"\n→ Image 2: \"london.jpg\"\n→ Image 3: \"rome.jpg\"\n→ Image 4: \"berlin.jpg\"\n\"Caption: European cities\""
+ },
"validations": [
{
"type": "element_exists",
diff --git a/lessons/30-html-tables.json b/lessons/30-html-tables.json
index 2d0c6d0..ee7dec8 100644
--- a/lessons/30-html-tables.json
+++ b/lessons/30-html-tables.json
@@ -17,6 +17,10 @@
"initialCode": "",
"solution": "\n Fruit Prices \n \n Fruit \n Price \n \n \n Apple \n $1.50 \n \n \n Banana \n $0.75 \n \n
",
"previewContainer": "preview-area",
+ "concept": {
+ "explanation": "HTML tables communicate semantic data relationships through row/column structure, enabling screen readers to navigate two-dimensionally (announcing \"row 2, column 1: Apple\" or \"Fruit column, row 2\") instead of linearly reading cells. The th (header cell) vs td (data cell) distinction creates accessibility associations: screen readers remember headers and announce them when navigating data cells, giving context. Caption provides a programmatic table title that screen readers announce before entering the table structure. The table element has implicit ARIA role=\"table\", and browsers expose table semantics through accessibility APIs, allowing AT users to jump between tables, skip table content, or navigate by row/column.",
+ "diagram": "Table Semantic Structure\n\n\n Fruit Prices \n ← Row 1 (header)\n Fruit ← Column 1 header\n Price ← Column 2 header\n \n ← Row 2 (data)\n Apple ← Data cell\n $1.50 \n \n
\n\nScreen Reader Navigation:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\"Table, Fruit Prices\"\n\"2 columns, 2 rows\"\n\nEnter table:\n\"Row 1, Column 1: Fruit, header\"\n→ Right arrow\n\"Row 1, Column 2: Price, header\"\n→ Down arrow\n\"Row 2, Column 2: $1.50\"\n(Still remembers \"Price\" header)\n\nHeader Association:\n┌─────────┬─────────┐\n│ Fruit │ Price │ ← th elements\n├─────────┼─────────┤\n│ Apple │ $1.50 │\n└─────────┴─────────┘\n ↑ ↑\n └─────────┘\n When SR focuses on\n \"$1.50\", it announces:\n \"Price: $1.50, row 2\"\n\nvs div Table:\n✗ No semantic structure\n✗ Linear reading only\n✗ No header association"
+ },
"validations": [
{
"type": "element_exists",
@@ -51,6 +55,10 @@
"initialCode": "",
"solution": "\n Monthly Sales \n \n \n Month \n Revenue \n \n \n \n \n January \n $12,500 \n \n \n February \n $14,200 \n \n \n
",
"previewContainer": "preview-area",
+ "concept": {
+ "explanation": "The thead/tbody/tfoot elements create logical sections that browsers can optimize for printing (repeating headers on each page), scrolling (sticky headers while tbody scrolls), and accessibility (screen readers announce section boundaries). This grouping also enables CSS to style sections differently without classes—tbody tr:hover works naturally. Some browsers display thead/tfoot with distinct styling by default. Screen readers announce section transitions (\"entering table header\", \"entering table body\") helping users understand where they are in large tables. For very long tables, browsers may keep thead fixed while scrolling tbody, and when printing multi-page tables, browsers repeat thead at the top of each printed page automatically.",
+ "diagram": "Table Section Structure\n\n\n Sales Data \n ← Header section\n Month \n \n ← Data section\n Jan \n Feb \n ...(many rows)\n \n ← Footer section\n Total \n \n
\n\nPrinting Long Tables:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nPage 1:\n┌─────────────┐\n│ Month │ ← thead (repeated)\n├─────────────┤\n│ Jan │\n│ Feb │ tbody continues...\n\nPage 2:\n┌─────────────┐\n│ Month │ ← thead (repeated)\n├─────────────┤\n│ Mar │\n│ Apr │ tbody continues...\n\nScrolling Long Tables:\n┌─────────────────────────────┐\n│ Month │ Revenue │ Fixed │ ← thead sticky\n├───────────┴─────────┴───────┤\n│ Jan │ $10,000 │ ↕\n│ Feb │ $12,000 │ Scrolls\n│ Mar │ $11,500 │ ↕\n│ ... │\n└─────────────────────────────┘\n\nScreen Reader:\n\"Entering table header\"\n→ Reads headers\n\"Entering table body\"\n→ Reads data rows\n\"Entering table footer\"\n→ Reads totals"
+ },
"validations": [
{
"type": "element_exists",
@@ -90,6 +98,10 @@
"initialCode": "",
"solution": "\n Order Summary \n \n \n Item \n Price \n \n \n \n \n Widget \n $25.00 \n \n \n Gadget \n $35.00 \n \n \n \n \n Total \n $60.00 \n \n \n
",
"previewContainer": "preview-area",
+ "concept": {
+ "explanation": "The tfoot element defines summary or calculation rows that semantically belong at the table's end, even though in HTML source order it can appear before tbody (browsers render it at the bottom regardless). This location flexibility is historical—placing tfoot before tbody in source allows browsers to render footers before receiving all body data, useful for streaming large datasets. Screen readers announce tfoot as \"table footer\" when entering, signaling that this row contains aggregate data rather than individual records. Tfoot is ideal for totals, averages, counts, or any row that summarizes the data above—it gives these special rows semantic meaning that plain tbody rows lack.",
+ "diagram": "Tfoot Source Order Flexibility\n\nHTML Source Order (optional):\n\n ... \n ... ← Before tbody\n ... \n
\n\nBrowser Renders:\n┌─────────────────────────────┐\n│ thead (headers) │\n├─────────────────────────────┤\n│ tbody (data rows) │\n│ ... │\n├─────────────────────────────┤\n│ tfoot (totals) │ ← Rendered last\n└─────────────────────────────┘\n\nWhy Allow tfoot Before tbody?\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nStreaming Large Datasets:\n1. Send \n2. Send \n3. Stream (may take time)\n→ Footer renders before all data\n\nSemantic Meaning:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n → Individual records\n → Aggregate summary\n\nScreen Reader:\n\"Table footer, row 1\"\n\"Total, $60.00\"\n\nCommon tfoot Content:\n✓ Totals/Subtotals\n✓ Averages\n✓ Record counts\n✓ Summary calculations\n\n✗ Not for regular data rows\n✗ Not for pagination controls"
+ },
"validations": [
{
"type": "element_exists",
diff --git a/lessons/31-html-marquee.json b/lessons/31-html-marquee.json
index 29c0d99..f88513f 100644
--- a/lessons/31-html-marquee.json
+++ b/lessons/31-html-marquee.json
@@ -17,6 +17,10 @@
"initialCode": "",
"solution": "Welcome to my website! ",
"previewContainer": "preview-area",
+ "concept": {
+ "explanation": "Marquee is a non-standard HTML element introduced by Netscape in the 1990s that created auto-scrolling text without JavaScript—browsers handled animation entirely natively. While deprecated by W3C standards (never officially standardized), browsers still support it for backward compatibility with legacy websites. The element teaches an important web history lesson: browser vendors sometimes implement features unilaterally, and if popular enough, those features persist even after standards bodies reject them. Modern development uses CSS animations (`@keyframes` + `animation`) or JavaScript for scrolling effects, giving developers more control and adhering to web standards, but marquee demonstrates how declarative HTML can embed behavior.",
+ "diagram": "Marquee: A Web History Lesson\n\nTimeline:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n1995 → Netscape adds \n1996 → IE copies it (vendor wars)\n2000s → W3C: \"This is non-standard\"\n2024 → Still works! (legacy compat)\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nHow It Works:\nText \n ↓\nBrowser sees marquee element\n ↓\nNative animation engine starts\n ↓\nText scrolls without JS/CSS\n\nModern Equivalent:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nText
\n\n.scroll {\n animation: scroll 10s linear infinite;\n}\n\n@keyframes scroll {\n from { transform: translateX(100%); }\n to { transform: translateX(-100%); }\n}\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nWhy Deprecated?\n✗ Non-standard (vendor-specific)\n✗ Accessibility issues (motion)\n✗ Limited control\n✗ No standard API\n\n✓ Use CSS animations instead\n✓ Add prefers-reduced-motion\n✓ Full control over timing/easing"
+ },
"validations": [
{
"type": "element_exists",
@@ -36,6 +40,10 @@
"initialCode": "",
"solution": "Bounce! Bounce! Bounce! ",
"previewContainer": "preview-area",
+ "concept": {
+ "explanation": "Marquee attributes control animation parameters through HTML rather than CSS or JavaScript—this declarative approach was innovative for the 1990s but inflexible by modern standards. The behavior attribute changes motion physics: 'scroll' creates continuous looping (exits one side, enters opposite), 'slide' stops when reaching the edge (one-time animation), and 'alternate' bounces back and forth (ping-pong effect). Direction controls axis (left/right for horizontal, up/down for vertical), and scrollamount sets pixels moved per frame (higher = faster). These attributes demonstrate early attempts at animation control before CSS animations existed, showing how HTML sometimes blurred the line between structure and presentation.",
+ "diagram": "Marquee Behaviors\n\nbehavior=\"scroll\" (default):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n→ Text → → → → → (loops)\n\nbehavior=\"slide\":\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n→ Text → → STOP\n(animates once, stops at edge)\n\nbehavior=\"alternate\":\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n→ Text → → ← ← Text ← ←\n(bounces back and forth)\n\nDirection:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\ndirection=\"left\" → Text moves ←\ndirection=\"right\" → Text moves →\ndirection=\"up\" → Text moves ↑\ndirection=\"down\" → Text moves ↓\n\nSpeed Control:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nscrollamount=\"1\" → Slow (1px/frame)\nscrollamount=\"6\" → Default\nscrollamount=\"20\" → Fast (20px/frame)\n\nCombinations:\n\n Bounces horizontally at 10px/frame\n "
+ },
"validations": [
{
"type": "element_exists",
@@ -60,6 +68,10 @@
"initialCode": "",
"solution": "BREAKING NEWS: Marquee element still works in browsers! ",
"previewContainer": "preview-area",
+ "concept": {
+ "explanation": "Marquee represents a cautionary tale about web standards versus implementation reality: despite being deprecated for over a decade and never formally standardized, browsers maintain support because removing it would break thousands of legacy websites. This teaches the web's core principle of \"don't break the web\"—backward compatibility trumps clean standards. Modern developers should avoid marquee for accessibility reasons (motion can trigger vestibular disorders, and it ignores prefers-reduced-motion), lack of control (can't pause on hover, sync with other animations, or adjust timing curves), and semantic incorrectness (mixing behavior into structure). Instead, CSS animations provide the same visual effects with full control, accessibility hooks, and standards compliance.",
+ "diagram": "Legacy Compat vs Modern Standards\n\nThe \"Don't Break the Web\" Principle:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n1995 → Site uses \n2024 → Site still online (unchanged)\n → Browser must still support it\n → Can't remove deprecated feature\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nAccessibility Issues:\n✗ Triggers motion sickness\n✗ Ignores prefers-reduced-motion\n✗ Can't pause on hover\n✗ Distracts from content\n✗ Not keyboard-accessible\n\nModern Replacement:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nHTML:\nBREAKING NEWS
\n\nCSS:\n@media (prefers-reduced-motion: no-preference) {\n .ticker {\n animation: scroll 10s linear infinite;\n }\n}\n\n@keyframes scroll {\n from { transform: translateX(100%); }\n to { transform: translateX(-100%); }\n}\n\n.ticker:hover {\n animation-play-state: paused;\n}\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nLesson Learned:\n✓ Standards matter\n✓ Accessibility first\n✓ Use CSS for presentation\n✓ Don't mix behavior into HTML\n✓ Respect user preferences"
+ },
"validations": [
{
"type": "element_exists",
diff --git a/lessons/32-html-svg.json b/lessons/32-html-svg.json
index 37bd7a6..b888354 100644
--- a/lessons/32-html-svg.json
+++ b/lessons/32-html-svg.json
@@ -17,6 +17,10 @@
"initialCode": "",
"solution": "\n \n ",
"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\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",
@@ -66,6 +70,10 @@
"initialCode": "",
"solution": "\n \n \n ",
"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 ← Solid interior\n\n ↑ interior ↑ outline\n\n ← Only visible\n ← 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: \nCSS: rect { fill: red; }\nCSS wins if both present!"
+ },
"validations": [
{
"type": "element_exists",
@@ -150,6 +158,10 @@
"initialCode": "",
"solution": "\n \n \n \n \n ",
"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 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\n ← Painted first (back)\n ← Painted second\n ← Painted third\n ← Painted last (front)\n \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 :\n\n \n \n \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 += ' ';\n→ Add shapes dynamically\n\nvs Canvas:\nSVG = Retained mode (DOM)\nCanvas = Immediate mode (pixels)"
+ },
"validations": [
{
"type": "element_exists",