Files
code-crispies/lessons/24-html-progress-meter.json

135 lines
12 KiB
JSON
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

{
"$schema": "../schemas/code-crispies-module-schema.json",
"id": "html-progress-meter",
"title": "HTML Progress & Meter",
"description": "Display completion status and scalar measurements natively",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "progress-basic",
"title": "Progress Bars",
"description": "The <kbd>&lt;progress&gt;</kbd> element shows task completion. Use <kbd>value</kbd> for current progress and <kbd>max</kbd> for the total.<br><br><b>Note:</b> This is not a self-closing tag! Write <kbd>&lt;progress&gt;...&lt;/progress&gt;</kbd> with fallback text inside for older browsers.",
"task": "Create a progress bar showing 70% completion:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Download:</code><br>2. Add a <kbd>&lt;progress&gt;</kbd> with <kbd>value=\"70\"</kbd> and <kbd>max=\"100\"</kbd>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } progress { width: 100%; height: 20px; border-radius: 10px; } progress::-webkit-progress-bar { background: #e0e0e0; border-radius: 10px; } progress::-webkit-progress-value { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; } progress::-moz-progress-bar { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; }",
"sandboxCSS": "",
"initialCode": "",
"solution": "<label for=\"download\">Download:</label>\n<progress id=\"download\" value=\"70\" max=\"100\">70%</progress>",
"previewContainer": "preview-area",
"concept": {
"explanation": "The progress element represents completion of a task with a known duration or endpoint—the browser calculates the fill percentage by dividing value by max. Browsers render progress bars with native OS styling by default, which means they look different on Windows, macOS, iOS, and Android, giving each platform's users a familiar appearance. Screen readers announce the completion percentage automatically (\"70 percent\"), and the element has an implicit ARIA role of 'progressbar'. JavaScript can update the value attribute dynamically to reflect real-time progress, and the text content inside serves as fallback for browsers that don't support progress.",
"diagram": "Progress Element Calculation\n\nFormula:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nFill % = (value / max) × 100\n\nExample:\n<progress value=\"70\" max=\"100\">\n\n 70 ÷ 100 = 0.7 → 70% filled\n\n┌─────────────────────────────┐\n│ Download: │\n├─────────────────────────────┤\n│ ████████████████░░░░░░░░░░ │ 70%\n└─────────────────────────────┘\n ← 70 units → ← 30 units →\n\nUpdating Progress:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nJS: element.value = 50 → 50%\nJS: element.value = 100 → 100%\n\nAccessibility:\nScreen reader announces:\n\"Download, progress bar, 70 percent\"\n\nFallback Content:\n<progress value=\"70\" max=\"100\">\n 70% ← Shown in old browsers\n</progress>"
},
"validations": [
{
"type": "element_exists",
"value": "progress",
"message": "Add a <kbd>&lt;progress&gt;</kbd> element"
},
{
"type": "attribute_value",
"value": { "selector": "progress", "attr": "value", "value": "70" },
"message": "Set <kbd>value=</kbd>\"70\" on the progress element"
},
{
"type": "attribute_value",
"value": { "selector": "progress", "attr": "max", "value": "100" },
"message": "Set <kbd>max=</kbd>\"100\" on the progress element"
},
{
"type": "element_exists",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for the progress bar"
}
]
},
{
"id": "progress-indeterminate",
"title": "Indeterminate Progress",
"description": "When progress is unknown (like loading), omit the <kbd>value</kbd> attribute. This creates an animated indeterminate state.<br><br>Useful for network requests or processes with unknown duration.",
"task": "Create a loading indicator:<br>1. Add a <kbd>&lt;p&gt;</kbd> saying <code>Loading...</code><br>2. Add a <kbd>&lt;progress&gt;</kbd> without a value attribute",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } p { margin-bottom: 10px; color: #666; } progress { width: 100%; height: 8px; border-radius: 4px; } progress::-webkit-progress-bar { background: #e0e0e0; border-radius: 4px; }",
"sandboxCSS": "",
"initialCode": "",
"solution": "<p>Loading...</p>\n<progress></progress>",
"previewContainer": "preview-area",
"concept": {
"explanation": "Indeterminate state communicates \"something is happening, but we don't know when it will finish\"—browsers render this with an animated pattern that moves continuously to show activity without implying a specific completion percentage. The animation style is platform-native: macOS uses a barber-pole stripe pattern, Windows uses a pulsing dot animation, and browsers may customize the appearance. This semantic distinction matters because it sets correct user expectations—a filled bar implies \"almost done\" while an animated loop implies \"still working\". Screen readers announce indeterminate progress as \"progress bar, busy\" rather than announcing a percentage.",
"diagram": "Determinate vs Indeterminate\n\nDeterminate (value present):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n<progress value=\"70\" max=\"100\">\n┌─────────────────────────────┐\n│ ████████████████░░░░░░░░░░ │ 70%\n└─────────────────────────────┘\n ↑ Known progress\n\nIndeterminate (no value):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n<progress></progress>\n┌─────────────────────────────┐\n│ ░░▓▓▓░░░░░░░░░░░░░░░░░░░░░ │ Animating\n└─────────────────────────────┘\n ↑ Unknown duration\n\nBrowser Animations:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nmacOS → Barber-pole stripes\nWindows → Pulsing dots\nAndroid → Circular spinner\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nUse Cases:\n✓ Network requests\n✓ File processing\n✓ Background sync\n✓ Unknown wait time\n\nScreen Reader:\n\"Progress bar, busy\""
},
"validations": [
{
"type": "element_exists",
"value": "progress",
"message": "Add a <kbd>&lt;progress&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "p",
"message": "Add a <kbd>&lt;p&gt;</kbd> with loading text"
}
]
},
{
"id": "meter-gauge",
"title": "Meter Gauges",
"description": "The <kbd>&lt;meter&gt;</kbd> element displays a scalar value within a range. Use it for measurements like disk space, battery, or ratings.<br><br>Set <kbd>low</kbd>, <kbd>high</kbd>, and <kbd>optimum</kbd> to define good/bad ranges - the browser colors it accordingly!",
"task": "Create a battery level meter:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Battery:</code><br>2. Add a <kbd>&lt;meter&gt;</kbd> with:<br> - <kbd>value=\"0.8\"</kbd><br> - <kbd>min=\"0\"</kbd> and <kbd>max=\"1\"</kbd><br> - <kbd>low=\"0.2\"</kbd> and <kbd>high=\"0.8\"</kbd><br> - <kbd>optimum=\"1\"</kbd>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
"sandboxCSS": "",
"initialCode": "",
"solution": "<label for=\"battery\">Battery:</label>\n<meter id=\"battery\" value=\"0.8\" min=\"0\" max=\"1\" low=\"0.2\" high=\"0.8\" optimum=\"1\">80%</meter>",
"previewContainer": "preview-area",
"concept": {
"explanation": "Unlike progress (which shows task completion moving toward 100%), meter represents a measurement at a point in time that can be good or bad depending on context. The browser uses low/high/optimum thresholds to automatically color-code the gauge: green when value is near optimum, yellow in the middle range, and red when critically low or high. For example, battery at 80% is green (good), 40% is yellow (warning), and 10% is red (critical). This semantic intelligence means you don't need CSS—the browser applies appropriate colors based on your threshold values and whether higher or lower is better.",
"diagram": "Meter Threshold Logic\n\nAttributes:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nmin=\"0\" → Range start\nmax=\"1\" → Range end\nlow=\"0.2\" → Below this = bad\nhigh=\"0.8\" → Above this = depends\noptimum=\"1\" → Ideal value\nvalue=\"0.8\" → Current measurement\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nAutomatic Color Zones:\n\nBattery (optimum=high):\n┌─────────────────────────────┐\n│ 0.0 ────────────────── 1.0 │\n│ RED YELLOW GREEN │\n│ 0─0.2 0.2─0.8 0.8─1.0 │\n└─────────────────────────────┘\n ↑ value=0.8 (green)\n\nDisk Usage (optimum=low):\n┌─────────────────────────────┐\n│ 0% ─────────────────── 100% │\n│ GREEN YELLOW RED │\n│ 0─20 20─80 80─100 │\n└─────────────────────────────┘\n ↑ 90% (red)\n\nvs Progress:\nmeter → Snapshot measurement\nprogress → Task moving to 100%"
},
"validations": [
{
"type": "element_exists",
"value": "meter",
"message": "Add a <kbd>&lt;meter&gt;</kbd> element"
},
{
"type": "attribute_value",
"value": { "selector": "meter", "attr": "value", "value": "0.8" },
"message": "Set <kbd>value=</kbd>\"0.8\" on the meter"
},
{
"type": "attribute_value",
"value": { "selector": "meter", "attr": "min", "value": "0" },
"message": "Set <kbd>min=</kbd>\"0\" on the meter"
},
{
"type": "attribute_value",
"value": { "selector": "meter", "attr": "max", "value": "1" },
"message": "Set <kbd>max=</kbd>\"1\" on the meter"
},
{
"type": "attribute_value",
"value": { "selector": "meter", "attr": "low", "value": "0.2" },
"message": "Set <kbd>low=</kbd>\"0.2\" to define the low threshold"
},
{
"type": "attribute_value",
"value": { "selector": "meter", "attr": "high", "value": "0.8" },
"message": "Set <kbd>high=</kbd>\"0.8\" to define the high threshold"
},
{
"type": "attribute_value",
"value": { "selector": "meter", "attr": "optimum", "value": "1" },
"message": "Set <kbd>optimum=</kbd>\"1\" to indicate the optimal value"
},
{
"type": "element_exists",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for the meter"
}
]
}
]
}