{ "$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 <progress> element shows task completion. Use value for current progress and max for the total.

Note: This is not a self-closing tag! Write <progress>...</progress> with fallback text inside for older browsers.", "task": "Create a progress bar showing 70% completion:
1. Add a <label> saying Download:
2. Add a <progress> with value=\"70\" and max=\"100\"", "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": "\n70%", "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\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\n 70% ← Shown in old browsers\n" }, "validations": [ { "type": "element_exists", "value": "progress", "message": "Add a <progress> element" }, { "type": "attribute_value", "value": { "selector": "progress", "attr": "value", "value": "70" }, "message": "Set value=\"70\" on the progress element" }, { "type": "attribute_value", "value": { "selector": "progress", "attr": "max", "value": "100" }, "message": "Set max=\"100\" on the progress element" }, { "type": "element_exists", "value": "label", "message": "Add a <label> for the progress bar" } ] }, { "id": "progress-indeterminate", "title": "Indeterminate Progress", "description": "When progress is unknown (like loading), omit the value attribute. This creates an animated indeterminate state.

Useful for network requests or processes with unknown duration.", "task": "Create a loading indicator:
1. Add a <p> saying Loading...
2. Add a <progress> 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": "

Loading...

\n", "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\n┌─────────────────────────────┐\n│ ████████████████░░░░░░░░░░ │ 70%\n└─────────────────────────────┘\n ↑ Known progress\n\nIndeterminate (no value):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\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 <progress> element" }, { "type": "element_exists", "value": "p", "message": "Add a <p> with loading text" } ] }, { "id": "meter-gauge", "title": "Meter Gauges", "description": "The <meter> element displays a scalar value within a range. Use it for measurements like disk space, battery, or ratings.

Set low, high, and optimum to define good/bad ranges - the browser colors it accordingly!", "task": "Create a battery level meter:
1. Add a <label> saying Battery:
2. Add a <meter> with:
- value=\"0.8\"
- min=\"0\" and max=\"1\"
- low=\"0.2\" and high=\"0.8\"
- optimum=\"1\"", "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": "\n80%", "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 <meter> element" }, { "type": "attribute_value", "value": { "selector": "meter", "attr": "value", "value": "0.8" }, "message": "Set value=\"0.8\" on the meter" }, { "type": "attribute_value", "value": { "selector": "meter", "attr": "min", "value": "0" }, "message": "Set min=\"0\" on the meter" }, { "type": "attribute_value", "value": { "selector": "meter", "attr": "max", "value": "1" }, "message": "Set max=\"1\" on the meter" }, { "type": "attribute_value", "value": { "selector": "meter", "attr": "low", "value": "0.2" }, "message": "Set low=\"0.2\" to define the low threshold" }, { "type": "attribute_value", "value": { "selector": "meter", "attr": "high", "value": "0.8" }, "message": "Set high=\"0.8\" to define the high threshold" }, { "type": "attribute_value", "value": { "selector": "meter", "attr": "optimum", "value": "1" }, "message": "Set optimum=\"1\" to indicate the optimal value" }, { "type": "element_exists", "value": "label", "message": "Add a <label> for the meter" } ] } ] }