- Reviewed all 85+ lesson concepts across 20+ modules - Trimmed 7 lessons that exceeded 2-4 sentence limit - Edited 06-transitions-animations.json (all 4 lessons) - Edited 08-responsive.json (all 4 lessons) - Preserved beginner-friendly language and WHY focus - Maintained excellent ASCII diagrams All concepts now comply with 2-4 sentence guideline while maintaining clarity and conceptual depth.
147 lines
21 KiB
JSON
147 lines
21 KiB
JSON
{
|
||
"$schema": "../schemas/code-crispies-module-schema.json",
|
||
"id": "responsive-design",
|
||
"title": "CSS Responsive Design",
|
||
"description": "Make your layouts adapt to different screen sizes using media queries and fluid design techniques.",
|
||
"difficulty": "intermediate",
|
||
"lessons": [
|
||
{
|
||
"id": "responsive-1",
|
||
"title": "Media Queries",
|
||
"description": "Understand the syntax and use cases for CSS media queries to apply styles conditionally based on viewport characteristics.<br><br><pre>@media (max-width: 600px) {\n .panel {\n background: lightcoral;\n }\n}</pre>",
|
||
"task": "Write a media query with <kbd>@media (max-width: 600px)</kbd> that changes <kbd>.panel</kbd> background to <kbd>lightcoral</kbd>.",
|
||
"previewHTML": "<div class=\"panel\">Resize the window</div>",
|
||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .panel { padding: 1rem; background: lightblue; }",
|
||
"sandboxCSS": "",
|
||
"codePrefix": "/* Add your media query below */\n",
|
||
"initialCode": "",
|
||
"codeSuffix": "",
|
||
"solution": "@media (max-width: 600px) {\n .panel {\n background: lightcoral;\n }\n}",
|
||
"previewContainer": "preview-area",
|
||
"concept": {
|
||
"explanation": "Media queries are conditional CSS rules that the browser evaluates continuously as the viewport changes. When you write @media (max-width: 600px), the browser checks if the viewport width is 600 pixels or less—if true, it applies the enclosed styles; if false, it ignores them. The browser re-evaluates this condition on every resize, instantly applying or removing styles based on viewport size, making responsive design possible without JavaScript. Common media features include width, height, orientation (portrait/landscape), and prefers-color-scheme (light/dark mode).",
|
||
"diagram": "Media Query Evaluation Process\n\nHow @media (max-width: 600px) works:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nBrowser checks viewport width continuously:\n\nViewport: 800px wide\n┌─────────────────────────────────┐\n│ @media (max-width: 600px) │\n│ Is 800px ≤ 600px? │\n│ NO → Styles NOT applied │\n│ │\n│ .panel { background: lightblue; }\n└─────────────────────────────────┘\n (default style)\n\nUser resizes window → 500px wide\n┌────────────────────────┐\n│ @media (max-width: 600px) │\n│ Is 500px ≤ 600px? │\n│ YES → Styles applied │\n│ │\n│ .panel { background: lightcoral; }\n└────────────────────────┘\n (media query style wins)\n\nBreakpoint Behavior:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n 0px 600px ∞\n ├──────────────────┼─────────────────►\n lightcoral │ lightblue\n (max-width) │ (default)\n ↑\n breakpoint\n (600px)\n\nCascade with Media Queries:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nCSS source order:\n.panel {\n background: lightblue; /* 1. Base style */\n}\n\n@media (max-width: 600px) {\n .panel {\n background: lightcoral; /* 2. Override when\n } condition matches */\n}\n\nWhen viewport ≤ 600px:\n Both rules have same specificity (0,0,1,0)\n Media query comes later → wins cascade\n Result: lightcoral\n\nWhen viewport > 600px:\n Media query condition false → ignored\n Only base style applies\n Result: lightblue\n\nCommon Media Features:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n@media (max-width: 600px) Viewport ≤ 600px\n@media (min-width: 768px) Viewport ≥ 768px\n@media (orientation: portrait) Height > Width\n@media (prefers-color-scheme: dark) OS dark mode\n@media (hover: hover) Device has hover",
|
||
"containerVsItem": ""
|
||
},
|
||
"validations": [
|
||
{
|
||
"type": "regex",
|
||
"value": "@media\\s*\\(max-width:\\s*600px\\)",
|
||
"message": "Use <kbd>@media (max-width: 600px)</kbd>",
|
||
"options": { "caseSensitive": false }
|
||
},
|
||
{
|
||
"type": "contains",
|
||
"value": ".panel",
|
||
"message": "Target <kbd>.panel</kbd> inside the media query",
|
||
"options": { "caseSensitive": false }
|
||
},
|
||
{
|
||
"type": "property_value",
|
||
"value": { "property": "background", "expected": "lightcoral" },
|
||
"message": "Set <kbd>background: lightcoral</kbd>",
|
||
"options": { "exact": false }
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "responsive-2",
|
||
"title": "Fluid Type",
|
||
"description": "Use relative units like <kbd>vw</kbd> to make font sizes scale with the viewport width.",
|
||
"task": "Set <kbd>font-size: 5vw</kbd> on <kbd>.text</kbd> so it scales as the viewport changes.",
|
||
"previewHTML": "<p class=\"text\">Fluid Typography</p>",
|
||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; }",
|
||
"sandboxCSS": "",
|
||
"codePrefix": "/* Apply fluid font sizing */\n.text {",
|
||
"initialCode": "",
|
||
"codeSuffix": "}",
|
||
"solution": " font-size: 5vw;",
|
||
"previewContainer": "preview-area",
|
||
"concept": {
|
||
"explanation": "Viewport units (vw, vh, vmin, vmax) scale proportionally with the browser window—1vw equals 1% of viewport width, so 5vw on a 1000px screen equals 50px. As the user resizes, the browser recalculates in real-time: 5vw becomes 30px on a 600px screen or 70px on a 1400px screen, creating truly fluid typography without media query breakpoints. However, pure vw units can become too small on mobile or too large on wide screens. Production sites often use clamp(16px, 5vw, 48px) to set minimum and maximum bounds for readability.",
|
||
"diagram": "Viewport Width Units (vw)\n\nHow 5vw calculates across screen sizes:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nMobile (375px wide):\n1vw = 375px / 100 = 3.75px\n5vw = 3.75px × 5 = 18.75px\n┌──────────┐\n│ Text │ 18.75px font\n└──────────┘\n 375px\n\nTablet (768px wide):\n1vw = 768px / 100 = 7.68px\n5vw = 7.68px × 5 = 38.4px\n┌─────────────────────┐\n│ Text │ 38.4px font\n└─────────────────────┘\n 768px\n\nDesktop (1440px wide):\n1vw = 1440px / 100 = 14.4px\n5vw = 14.4px × 5 = 72px\n┌───────────────────────────────────────┐\n│ Text │ 72px font\n└───────────────────────────────────────┘\n 1440px\n\nViewport Unit Reference:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nvw = 1% of viewport width\nvh = 1% of viewport height\nvmin = 1% of viewport's smaller dimension\nvmax = 1% of viewport's larger dimension\n\nExample with 800px × 600px viewport:\n 1vw = 8px (1% of 800px)\n 1vh = 6px (1% of 600px)\n 1vmin = 6px (1% of smaller: 600px)\n 1vmax = 8px (1% of larger: 800px)\n\nProblem: Unbounded Scaling\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nPure vw can be too small or too large:\n\nMobile (320px): font-size: 5vw → 16px ✓ OK\nTablet (768px): font-size: 5vw → 38px ✓ OK\nDesktop (2560px): font-size: 5vw → 128px ✗ TOO BIG!\n\nSolution: Combine with clamp() or calc()\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nBetter approach:\nfont-size: clamp(16px, 5vw, 48px);\n ↓ ↓ ↓\n minimum fluid maximum\n\nResult across viewports:\n320px → 5vw = 16px → clamped to 16px (min)\n768px → 5vw = 38px → 38px (in range)\n2560px → 5vw = 128px → clamped to 48px (max)\n\nWhen to Use Fluid Typography:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nGood: Hero headings, banners, display text\nAvoid: Body text, UI elements (use rem instead)",
|
||
"containerVsItem": ""
|
||
},
|
||
"validations": [
|
||
{ "type": "property_value", "value": { "property": "font-size", "expected": "5vw" }, "message": "Set <kbd>font-size: 5vw</kbd>" }
|
||
]
|
||
},
|
||
{
|
||
"id": "responsive-3",
|
||
"title": "Responsive Grid",
|
||
"description": "Combine CSS Grid with <kbd>auto-fit</kbd> or <kbd>auto-fill</kbd> for responsive column layouts.",
|
||
"task": "Add <kbd>display: grid</kbd>, <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd>, and <kbd>gap: 1rem</kbd> to <kbd>.cards</kbd>.",
|
||
"previewHTML": "<div class=\"cards\"><div>1</div><div>2</div><div>3</div><div>4</div></div>",
|
||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .cards > div { background: #d1c4e9; padding: 1rem; }",
|
||
"sandboxCSS": "",
|
||
"codePrefix": "/* Create a responsive grid */\n.cards {",
|
||
"initialCode": "",
|
||
"codeSuffix": "}",
|
||
"solution": " display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
|
||
"previewContainer": "preview-area",
|
||
"concept": {
|
||
"explanation": "The auto-fit keyword combined with minmax() creates intrinsically responsive grids that adapt without media queries. The pattern repeat(auto-fit, minmax(200px, 1fr)) tells the browser to create as many columns as will fit, where each is at least 200px but can grow to 1fr. The browser calculates how many 200px columns fit, distributes extra space equally, and automatically reflows columns as the viewport shrinks (4 → 3 → 2 → 1)—all without breakpoints. The key difference: auto-fit collapses empty columns to zero width, while auto-fill preserves empty column tracks.",
|
||
"diagram": "Auto-Fit with Minmax: Responsive Grid Without Media Queries\n\nHow repeat(auto-fit, minmax(200px, 1fr)) works:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nContainer: 900px wide (4 items)\n\nStep 1: Calculate how many 200px columns fit\n900px ÷ 200px = 4.5 → fits 4 columns\n\nStep 2: Distribute extra space with 1fr\n900px - (4 × 200px) = 100px extra\n100px ÷ 4 columns = 25px each\nFinal: 225px per column\n\n┌──────┬──────┬──────┬──────┐\n│ 1 │ 2 │ 3 │ 4 │\n└──────┴──────┴──────┴──────┘\n 225px 225px 225px 225px\n\nResponsive Behavior Across Viewports:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nWide (900px): 4 columns\n┌─────┬─────┬─────┬─────┐\n│ 1 │ 2 │ 3 │ 4 │\n└─────┴─────┴─────┴─────┘\n 225px each (200px + extra space)\n\nMedium (650px): 3 columns\n┌──────┬──────┬──────┐\n│ 1 │ 2 │ 3 │\n├──────┴──────┴──────┤\n│ 4 │ │ ← grows to fill\n└──────┴──────────────┘\n 216px each (200px + extra)\n\nNarrow (450px): 2 columns\n┌──────────┬──────────┐\n│ 1 │ 2 │\n├──────────┼──────────┤\n│ 3 │ 4 │\n└──────────┴──────────┘\n 225px each\n\nMobile (250px): 1 column\n┌────────────────────┐\n│ 1 │\n├────────────────────┤\n│ 2 │\n├────────────────────┤\n│ 3 │\n├────────────────────┤\n│ 4 │\n└────────────────────┘\n 250px (fills width)\n\nAuto-Fit vs Auto-Fill:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nWith 3 items in 900px container:\n\nauto-fit: Collapses empty tracks to zero\n┌────────────┬────────────┬────────────┐\n│ 1 │ 2 │ 3 │\n└────────────┴────────────┴────────────┘\n 300px 300px 300px\n ↑ Items expand to fill empty space\n\nauto-fill: Preserves empty tracks\n┌─────┬─────┬─────┬─────┐\n│ 1 │ 2 │ 3 │empty│ ← ghost column\n└─────┴─────┴─────┴─────┘\n 225px 225px 225px 0px (collapsed)\n\nBreakpoint Calculation:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nNatural breakpoints occur when columns reflow:\n4→3 columns: 600px (3 × 200px)\n3→2 columns: 400px (2 × 200px)\n2→1 column: 200px (1 × 200px)\n\nNo media queries needed—grid adapts automatically!",
|
||
"containerVsItem": "display: grid, grid-template-columns, and gap are CONTAINER properties. The auto-fit and minmax() functions define how the grid automatically creates responsive columns without requiring item-level overrides."
|
||
},
|
||
"validations": [
|
||
{
|
||
"type": "property_value",
|
||
"value": { "property": "display", "expected": "grid" },
|
||
"message": "Set <kbd>display: grid</kbd>"
|
||
},
|
||
{
|
||
"type": "regex",
|
||
"value": "repeat\\(auto-fit,\\s*minmax\\(200px,\\s*1fr\\)\\)",
|
||
"message": "Use <kbd>repeat(auto-fit, minmax(200px, 1fr))</kbd>",
|
||
"options": { "caseSensitive": false }
|
||
},
|
||
{
|
||
"type": "property_value",
|
||
"value": { "property": "gap", "expected": "1rem" },
|
||
"message": "Set <kbd>gap: 1rem</kbd>"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "responsive-4",
|
||
"title": "Mobile-First",
|
||
"description": "Adopt a mobile-first approach by writing base styles for small screens and enhancing for larger viewports.",
|
||
"task": "Write a media query with <kbd>@media (min-width: 768px)</kbd> that sets <kbd>.sidebar</kbd> width to <kbd>250px</kbd>.",
|
||
"previewHTML": "<aside class=\"sidebar\">Sidebar</aside>",
|
||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sidebar { background: #c8e6c9; padding: 1rem; }",
|
||
"sandboxCSS": "",
|
||
"codePrefix": "/* Add mobile-first enhancement */\n",
|
||
"initialCode": "",
|
||
"codeSuffix": "",
|
||
"solution": "@media (min-width: 768px) {\n .sidebar {\n width: 250px;\n }\n}",
|
||
"previewContainer": "preview-area",
|
||
"concept": {
|
||
"explanation": "Mobile-first design means writing base CSS for mobile devices first, then using min-width media queries to progressively enhance for larger screens. This approach has key advantages: mobile users download less CSS (desktop styles are in media queries they never trigger), it forces content prioritization for limited mobile screens, and the CSS cascade works in your favor—base styles apply everywhere while larger screens simply add enhancements. Using @media (min-width: 768px) means \"on screens 768px or wider, add these styles\"—the opposite of max-width which removes styles as screens shrink. This progressive enhancement pattern aligns with how users browse: most traffic is mobile, so optimize for that first, then layer on desktop features.",
|
||
"diagram": "Mobile-First vs Desktop-First Design\n\nMobile-First (Recommended):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nBase styles (mobile):\n.sidebar {\n width: 100%; /* Full width on mobile */\n padding: 1rem;\n}\n\nEnhancement for tablet+:\n@media (min-width: 768px) {\n .sidebar {\n width: 250px; /* Fixed sidebar on desktop */\n float: left;\n }\n}\n\nFlow: Mobile → Tablet → Desktop\n (add features as space increases)\n\n 0px 768px 1024px\n ├────────────────┼─────────────────┼──────►\n Base styles + Tablet styles + Desktop\n (mobile) styles\n\nDesktop-First (Not Recommended):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nBase styles (desktop):\n.sidebar {\n width: 250px; /* Desktop default */\n float: left;\n}\n\nOverrides for mobile:\n@media (max-width: 767px) {\n .sidebar {\n width: 100%; /* Undo desktop styles */\n float: none; /* More overrides needed */\n }\n}\n\nFlow: Desktop → Tablet → Mobile\n (remove features as space decreases)\n\n 0px 767px 1024px\n ├────────────────┼─────────────────┼──────►\n Mobile overrides │ Base styles\n (undo desktop) │ (desktop)\n\nPerformance Benefits:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nMobile-First CSS (320px phone):\n✓ Base styles: 2KB (downloaded)\n✗ @min-width 768: 1KB (ignored, not parsed)\n✗ @min-width 1024: 1KB (ignored)\n Total parsed: 2KB\n\nDesktop-First CSS (320px phone):\n✓ Base styles: 3KB (downloaded)\n✓ @max-width 767: 1KB (downloaded & parsed)\n Total parsed: 4KB (2x more!)\n\nMobile users save bandwidth and parsing time.\n\nCommon Mobile-First Breakpoints:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n/* Base: Mobile (0-767px) */\n/* Full-width, stacked layout */\n\n@media (min-width: 768px) {\n /* Tablet: Side-by-side for some elements */\n}\n\n@media (min-width: 1024px) {\n /* Desktop: Multi-column layouts */\n}\n\n@media (min-width: 1280px) {\n /* Large desktop: Max widths, more spacing */\n}\n\nContent Prioritization:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nMobile forces you to answer:\n\"What's ESSENTIAL for this page?\"\n\nMobile (320px): Desktop (1280px):\n┌──────────────┐ ┌─────┬──────────┬─────┐\n│ Header │ │ Ad │ Header │ User│\n├──────────────┤ ├─────┴──────────┴─────┤\n│ Content │ │ Side │ Content │ Side│\n│ (core) │ │ bar │ (core) │ bar │\n├──────────────┤ │ ├──────────┤ │\n│ Footer │ │ │ Related │ │\n└──────────────┘ └──────┴──────────┴─────┘\n ↑ Extra features added\n Core only when space allows\n\nWhy Min-Width Is Better:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n✓ Less CSS for mobile (faster page loads)\n✓ Progressive enhancement (build up, not tear down)\n✓ Aligns with CSS cascade (adds, not overrides)\n✓ Encourages content-first thinking\n✓ Easier to maintain (fewer conflicting rules)",
|
||
"containerVsItem": ""
|
||
},
|
||
"validations": [
|
||
{
|
||
"type": "regex",
|
||
"value": "@media\\s*\\(min-width:\\s*768px\\)",
|
||
"message": "Use <kbd>@media (min-width: 768px)</kbd>",
|
||
"options": { "caseSensitive": false }
|
||
},
|
||
{
|
||
"type": "contains",
|
||
"value": ".sidebar",
|
||
"message": "Target <kbd>.sidebar</kbd> inside media query",
|
||
"options": { "caseSensitive": false }
|
||
},
|
||
{
|
||
"type": "property_value",
|
||
"value": { "property": "width", "expected": "250px" },
|
||
"message": "Set <kbd>width: 250px</kbd>",
|
||
"options": { "exact": false }
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|