Files
code-crispies/lessons/05-units-variables.json
Michael Czechowski 9dc06012f1 auto-claude: 4.3 - Explain relative vs absolute units, why rem is pre
Add conceptual explanations to all 4 lessons in 05-units-variables.json:
- Lesson 1 (Absolute vs Relative Units): Explains fixed px vs scalable rem/%, why rem is preferred for accessibility, and how units calculate
- Lesson 2 (CSS Custom Properties): Explains variable definition/reference, inheritance cascade, scoping, and live updates vs preprocessor variables
- Lesson 3 (calc): Explains runtime calculation, mixing units, syntax requirements for operators
- Lesson 4 (Viewport Units): Explains vw/vh/vmin/vmax relative to viewport, auto-resize behavior, and difference from percentage units

All concepts include beginner-friendly explanations (2-4 sentences) and detailed ASCII diagrams showing calculations and visual representations.
2026-01-11 05:35:12 +01:00

133 lines
16 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": "units-variables",
"title": "CSS Units & Variables",
"description": "Understand the variety of CSS measurement units and how to define and use custom properties for maintainable styles.",
"difficulty": "beginner",
"lessons": [
{
"id": "units-1",
"title": "Absolute vs. Relative Units",
"description": "Learn the difference between px, rem, em, %, and vw/vh for flexible, responsive layouts.<br><br><pre>width: 80%; /* relative to parent */\nmax-width: 40rem; /* relative to root font */\npadding: 16px; /* fixed pixels */</pre>",
"task": "Set the <kbd>width</kbd> of <kbd>.box</kbd> to <kbd>80%</kbd> and <kbd>max-width</kbd> to <kbd>40rem</kbd>.",
"previewHTML": "<div class=\"box\">Resize me!</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { background: #f5f5f5; padding: 1rem; }",
"sandboxCSS": "",
"codePrefix": "/* Set flexible sizing */\n.box {",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: 80%;\n max-width: 40rem;",
"previewContainer": "preview-area",
"concept": {
"explanation": "CSS units fall into two categories: absolute units (px) with fixed sizes, and relative units (%, rem, em, vw, vh) that scale based on context. Absolute units like 16px always render at the same physical size regardless of user settings, while relative units adapt to user preferences and screen sizes. Rem (root em) is preferred for most spacing because 1rem equals the root font size (usually 16px), so if a user increases their browser's font size for accessibility, all rem-based spacing scales proportionally. Percentage units (%) are relative to the parent element's size, making them perfect for fluid layouts that adapt to container width.",
"diagram": "Unit Types & How They Calculate\n\nAbsolute Units (Fixed Size):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━\npx (pixels) → 16px always = 16 pixels\n (ignores user font settings)\n\nRelative Units (Scale with Context):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nrem (root em) → 1rem = root font-size\n If html { font-size: 16px }\n then 2rem = 32px\n Scales with user settings ✓\n\n% (percent) → 80% = 80% of parent width\n Parent: 500px → 80% = 400px\n Parent: 800px → 80% = 640px\n Fluid layouts ✓\n\nem (element) → 1em = current font-size\n .box { font-size: 20px }\n padding: 1em = 20px\n Compounds in nested elements ⚠️\n\nvw/vh → 50vw = 50% viewport width\n 1vh = 1% viewport height\n\nWhy rem is preferred:\n┌──────────────────────────────────┐\n│ User increases browser font size │\n│ (Settings → Appearance → Text) │\n└────────────┬─────────────────────┘\n ↓\n┌──────────────────────────────────┐\n│ rem-based spacing scales up ✓ │\n│ px-based spacing stays fixed ✗ │\n└──────────────────────────────────┘\nAccessibility & responsive design!"
},
"validations": [
{ "type": "contains", "value": "width", "message": "Use <kbd>width</kbd> property", "options": { "caseSensitive": false } },
{ "type": "property_value", "value": { "property": "width", "expected": "80%" }, "message": "Set width to <kbd>80%</kbd>" },
{ "type": "contains", "value": "max-width", "message": "Use <kbd>max-width</kbd> property", "options": { "caseSensitive": false } },
{
"type": "property_value",
"value": { "property": "max-width", "expected": "40rem" },
"message": "Set max-width to <kbd>40rem</kbd>"
}
]
},
{
"id": "units-2",
"title": "CSS Custom Properties",
"description": "Define and reuse variables (--custom properties) to centralize your theme values.<br><br><pre>:root {\n --main-color: mediumpurple;\n}\n.themed {\n border-color: var(--main-color);\n}</pre>",
"task": "Create a <kbd>--main-color</kbd> variable in <kbd>:root</kbd> with <kbd>mediumpurple</kbd> and apply it as the border color on <kbd>.themed</kbd>.",
"previewHTML": "<div class=\"themed\">Variable Box</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .themed { padding: 1rem; border: 0.125rem solid #ddd; }",
"sandboxCSS": "",
"codePrefix": "/* Define and use a CSS variable */\n:root {",
"initialCode": "",
"codeSuffix": "}\n.themed { }",
"solution": " --main-color: mediumpurple;\n}\n.themed {\n border-color: var(--main-color);",
"previewContainer": "preview-area",
"concept": {
"explanation": "CSS custom properties (variables) are values you define once and reference throughout your stylesheet using the var() function. They follow the CSS cascade, meaning variables defined on :root (the html element) are inherited by all descendants, while variables defined on specific elements are scoped to those elements and their children. When you change a custom property value, all references to that variable automatically update, making theme management and design systems much easier to maintain. Unlike preprocessor variables (Sass/Less), CSS custom properties are live in the browser and can be updated dynamically with JavaScript or media queries.",
"diagram": "CSS Custom Properties & Inheritance\n\nDefinition & Reference:\n\n:root {\n --main-color: mediumpurple; ← Define\n --spacing: 1rem;\n}\n\n.themed {\n border-color: var(--main-color); ← Reference\n padding: var(--spacing);\n}\n\nInheritance cascade:\n\n┌─────────────────────────────┐\n│ :root (html element) │\n│ --primary: blue │ ← Defined here\n└──────────┬──────────────────┘\n │ Inherits ↓\n ┌──────┴──────┐\n │ body │\n │ (inherits) │\n └──────┬──────┘\n │ Inherits ↓\n ┌──────┴──────────┐\n │ .card │\n │ color: var(--primary) │ ← Can use it!\n └─────────────────┘\n\nScoping example:\n\n:root { --theme: light; }\n\n.dark-mode {\n --theme: dark; ← Overrides for this\n} subtree only\n\nDynamic updates:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nCSS: :root { --size: 16px; }\n ↓\nJS: document.documentElement\n .style.setProperty('--size', '20px');\n ↓\nAll var(--size) references update! ✓\n\nVs Preprocessor Variables:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nSass: $color: blue; ← Compile-time\nCSS: --color: blue; ← Runtime (live)"
},
"validations": [
{
"type": "contains",
"value": "--main-color",
"message": "Define <kbd>--main-color</kbd> in :root",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "var(--main-color)",
"message": "Use <kbd>var(--main-color)</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "border", "expected": "var(--main-color)" },
"message": "Apply variable to border color",
"options": { "exact": false }
}
]
},
{
"id": "units-3",
"title": "Unit Calculations (calc)",
"description": "Use the <kbd>calc()</kbd> function to combine different units in one expression.<br><br><pre>width: calc(100% - 2rem);\nmin-height: calc(10vh + 1rem);</pre>",
"task": "Set the <kbd>width</kbd> of <kbd>.sized</kbd> to <kbd>calc(100% - 2rem)</kbd> and <kbd>min-height</kbd> to <kbd>calc(10vh + 1rem)</kbd>.",
"previewHTML": "<div class=\"sized\">Calc Demo</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sized { background: #e8f5e9; padding: 1rem; }",
"sandboxCSS": "",
"codePrefix": "/* Use calc for dynamic sizing */\n.sized {",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: calc(100% - 2rem);\n min-height: calc(10vh + 1rem);",
"previewContainer": "preview-area",
"concept": {
"explanation": "The calc() function performs mathematical calculations at runtime, allowing you to mix different unit types (px, %, rem, vw, etc.) in a single expression. The browser evaluates calc() expressions during layout calculation, after all relative units have been resolved to their pixel values, then performs the arithmetic operation. This is powerful for responsive layouts because you can combine fluid units (%) with fixed spacing (rem/px), like calc(100% - 2rem) for \"full width minus some padding.\" Spaces around + and - operators are required because calc(10vh+1rem) would be parsed as a single invalid unit, while calc(10vh + 1rem) correctly separates the operands.",
"diagram": "How calc() Works at Runtime\n\nExpression: width: calc(100% - 2rem);\n\nStep 1: Resolve relative units\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nParent width: 500px\n100% → 500px\n\nRoot font-size: 16px\n2rem → 32px (2 × 16)\n\nStep 2: Perform calculation\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━\ncalc(100% - 2rem)\n → calc(500px - 32px)\n → 468px ✓\n\nVisual representation:\n\n┌──────────────────────────────┐\n│ Parent container (500px) │\n│ ┌──────────────────────────┐ │\n│ │ .sized (468px) │ │ ← calc(100% - 2rem)\n│ │ │ │\n│ └──────────────────────────┘ │\n│ ◀── 16px gap (1rem) on each │\n│ side = 32px total │\n└──────────────────────────────┘\n\nMixing units:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\ncalc(100% - 2rem) Fluid - Fixed\ncalc(50vw + 100px) Viewport + Fixed\ncalc(2rem * 3) Multiplication\ncalc(100% / 3) Division\n\nImportant syntax rules:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\ncalc(10vh + 1rem) ✓ Spaces around +/-\ncalc(10vh+1rem) ✗ Treated as one unit\ncalc(2rem * 3) ✓ No space needed for */\ncalc(100%/3) ✓ Division works too"
},
"validations": [
{ "type": "contains", "value": "calc", "message": "Use <kbd>calc()</kbd> function", "options": { "caseSensitive": false } },
{
"type": "regex",
"value": "width:\\s*calc\\(100% - 2rem\\)",
"message": "Width should be <kbd>calc(100% - 2rem)</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "min-height:\\s*calc\\(10vh \\+ 1rem\\)",
"message": "Min-height should be <kbd>calc(10vh + 1rem)</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "units-4",
"title": "Viewport & Responsive Units",
"description": "Control layouts relative to viewport size with vw, vh, and vmin/vmax units.<br><br><pre>width: 50vw; /* 50% of viewport width */\nheight: 20vh; /* 20% of viewport height */</pre>",
"task": "Give <kbd>.view</kbd> a <kbd>width</kbd> of <kbd>50vw</kbd> and <kbd>height</kbd> of <kbd>20vh</kbd>.",
"previewHTML": "<div class=\"view\">Viewport Box</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .view { background: #ffe0b2; }",
"sandboxCSS": "",
"codePrefix": "/* Use viewport units */\n.view {",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: 50vw;\n height: 20vh;",
"previewContainer": "preview-area",
"concept": {
"explanation": "Viewport units (vw, vh, vmin, vmax) are relative to the browser window dimensions, not the parent element. 1vw equals 1% of the viewport width, and 1vh equals 1% of the viewport height, so 50vw is always half the screen width regardless of element nesting. These units are perfect for full-screen hero sections, responsive typography, and layouts that scale with screen size. The browser recalculates viewport unit values when the window is resized, making elements automatically adapt without media queries. Vmin uses the smaller dimension (min of width/height) while vmax uses the larger, useful for ensuring elements fit on both portrait and landscape orientations.",
"diagram": "Viewport Units & How They Calculate\n\nViewport Dimensions:\n┌────────────────────────────────┐ ↕\n│ │ 800px\n│ Browser Window │ viewport\n│ (Viewport) │ height\n│ │ ↕\n│ │\n└────────────────────────────────┘\n◀────── 1400px viewport width ──▶\n\nUnit calculations:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n1vw = 1400px ÷ 100 = 14px\n1vh = 800px ÷ 100 = 8px\n\n50vw = 50 × 14px = 700px (half width)\n20vh = 20 × 8px = 160px (1/5 height)\n\nvmin = min(1vw, 1vh) = min(14px, 8px) = 8px\nvmax = max(1vw, 1vh) = max(14px, 8px) = 14px\n\nViewport vs Percentage:\n\n% units (relative to parent):\n┌─────────────────────────────┐\n│ Parent (600px) │\n│ ┌─────────────────────┐ │\n│ │ width: 50% │ │ ← 300px\n│ │ (50% of parent) │ │ (50% of 600px)\n│ └─────────────────────┘ │\n└─────────────────────────────┘\n\nvw units (relative to viewport):\n┌────────────────────────────────┐ Viewport (1400px)\n│ ┌──────────────────────┐ │\n│ │ Parent (600px) │ │\n│ │ ┌──────────────────┐ │ │\n│ │ │ width: 50vw │ │ │ ← 700px\n│ │ │ (50% of viewport)│ │ │ (50% of 1400px)\n│ │ └──────────────────┘ │ │ Escapes parent!\n│ └──────────────────────┘ │\n└────────────────────────────────┘\n\nCommon use cases:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nFull-screen: width: 100vw; height: 100vh;\nHero section: min-height: 80vh;\nResponsive: font-size: calc(1rem + 1vw);\nSquare ratio: width: 50vmin; height: 50vmin;"
},
"validations": [
{ "type": "contains", "value": "vw", "message": "Use <kbd>vw</kbd> unit", "options": { "caseSensitive": false } },
{ "type": "contains", "value": "vh", "message": "Use <kbd>vh</kbd> unit", "options": { "caseSensitive": false } },
{ "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Set width to <kbd>50vw</kbd>" },
{ "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Set height to <kbd>20vh</kbd>" }
]
}
]
}