- 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.
162 lines
20 KiB
JSON
162 lines
20 KiB
JSON
{
|
||
"$schema": "../schemas/code-crispies-module-schema.json",
|
||
"id": "transitions-animations",
|
||
"title": "CSS Animations",
|
||
"description": "Bring interactivity to your UI by smoothly transitioning properties and creating keyframe-driven animations.",
|
||
"difficulty": "intermediate",
|
||
"lessons": [
|
||
{
|
||
"id": "transitions-1",
|
||
"title": "Transitions",
|
||
"description": "Learn how to apply <kbd>transition</kbd> to properties for smooth changes on state changes.<br><br><pre>transition: property duration;\n/* e.g. transition: background-color 0.3s; */</pre>",
|
||
"task": "Add <kbd>transition: background-color 0.3s</kbd> to <kbd>.btn</kbd> so the color fades smoothly on hover.",
|
||
"previewHTML": "<button class=\"btn\">Hover Me</button>",
|
||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: black; color: white; padding: 0.5rem 1rem; border: none; cursor: pointer; } .btn:hover { background: white; color: black; }",
|
||
"sandboxCSS": "",
|
||
"codePrefix": "/* Add transition */\n.btn {",
|
||
"initialCode": "",
|
||
"codeSuffix": "}",
|
||
"solution": " transition: background-color 0.3s;",
|
||
"previewContainer": "preview-area",
|
||
"concept": {
|
||
"explanation": "CSS transitions interpolate (calculate in-between values) smoothly between a property's start and end values over a specified duration. When you hover the button, the browser detects the background-color change and automatically generates intermediate color values at each frame (typically 60 frames per second). For colors, the browser converts both values to RGB, then calculates proportional changes for each channel—for example, at 50% through a 0.3s transition, the color is halfway between black and white, resulting in gray.",
|
||
"diagram": "How CSS Transitions Interpolate Values\n\nTransition: background-color 0.3s\nStart: black → End: white\n\nTime progression (60fps = 60 frames in 0.3s):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n0.00s (0%) rgb(0, 0, 0) ███████ black\n ↓ interpolate\n0.05s (17%) rgb(43, 43, 43) ███████ dark gray\n ↓ interpolate\n0.15s (50%) rgb(128,128,128) ███████ gray\n ↓ interpolate\n0.25s (83%) rgb(212,212,212) ███████ light gray\n ↓ interpolate\n0.30s (100%) rgb(255,255,255) ███████ white\n\nRGB interpolation formula at time t:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nvalue = start + (end - start) × progress\n\nAt t=0.15s (50% through 0.3s duration):\nprogress = 0.15 / 0.3 = 0.5\n\nRed: 0 + (255 - 0) × 0.5 = 128\nGreen: 0 + (255 - 0) × 0.5 = 128\nBlue: 0 + (255 - 0) × 0.5 = 128\n\nResult: rgb(128, 128, 128) ✓\n\nBrowser rendering process:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n1. Detect property change (hover triggers)\n2. Start transition timer (0.3s duration)\n3. Calculate frame count (0.3s × 60fps = 18 frames)\n4. For each frame:\n - Calculate progress (elapsed / duration)\n - Interpolate RGB values\n - Repaint element\n5. End at final value\n\nTransitionable properties:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nColors: rgb values interpolate\nLengths: px, rem, % interpolate \nTransforms: matrix values interpolate\nOpacity: 0-1 range interpolates\nNOT text: \"foo\" → \"bar\" can't interpolate!"
|
||
},
|
||
"validations": [
|
||
{
|
||
"type": "contains",
|
||
"value": "transition",
|
||
"message": "Use the <kbd>transition</kbd> property",
|
||
"options": { "caseSensitive": false }
|
||
},
|
||
{
|
||
"type": "regex",
|
||
"value": "transition:\\s*background-color\\s*0\\.3s",
|
||
"message": "Set <kbd>transition: background-color 0.3s</kbd>",
|
||
"options": { "caseSensitive": false }
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "transitions-2",
|
||
"title": "Timing Funcs",
|
||
"description": "Explore easing functions like <kbd>ease</kbd>, <kbd>linear</kbd>, <kbd>ease-in</kbd>, <kbd>ease-out</kbd> to control animation pacing.",
|
||
"task": "Set <kbd>transition-timing-function</kbd> to <kbd>ease-in-out</kbd> on <kbd>.btn</kbd>.",
|
||
"previewHTML": "<button class=\"btn\">Timing</button>",
|
||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: navy; color: gold; padding: 0.5rem 1rem; border: none; cursor: pointer; transition: all 0.5s; } .btn:hover { background: gold; color: navy; transform: scale(1.1); }",
|
||
"sandboxCSS": "",
|
||
"codePrefix": "/* Set timing function */\n.btn {",
|
||
"initialCode": "",
|
||
"codeSuffix": "}",
|
||
"solution": " transition-timing-function: ease-in-out;",
|
||
"previewContainer": "preview-area",
|
||
"concept": {
|
||
"explanation": "Timing functions (also called easing functions) control the rate of change during a transition by applying a mathematical curve to the linear progress over time. Instead of changing at a constant speed (linear), timing functions accelerate or decelerate at different points, making animations feel more natural. For example, ease-in-out starts slow, speeds up in the middle, then slows down at the end, mimicking real-world physics where objects don't instantly reach full speed or stop abruptly.",
|
||
"diagram": "Timing Functions & Animation Pacing\n\nLinear progress over time:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nTime: 0% 25% 50% 75% 100%\nProgress: 0% 25% 50% 75% 100%\nSpeed: ═══════════════════════════ constant\n\nEase-in (accelerate):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nTime: 0% 25% 50% 75% 100%\nProgress: 0% 6% 25% 56% 100%\nSpeed: ─────────────────────────▶ speeds up\n slow fast\n\nEase-out (decelerate):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nTime: 0% 25% 50% 75% 100%\nProgress: 0% 44% 75% 94% 100%\nSpeed: ◀───────────────────────── slows down\n fast slow\n\nEase-in-out (accelerate then decelerate):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nTime: 0% 25% 50% 75% 100%\nProgress: 0% 10% 50% 90% 100%\nSpeed: ─────▶━━━━━━◀───── natural motion\n slow fast slow\n\nBézier curve visualization:\n\n 1 ┤ ╭──── ease-out\n │ ╭───╯ (fast start)\n │ ╭────╯\n0.5 ┤ ╭────╯──── ease-in-out\n │ ╭───╯ (smooth)\n │ ╭───╯\n 0 ┤──╯─────────────────── linear\n └──┬────┬────┬────┬──── ease-in\n 0 0.25 0.5 0.75 1 (slow start)\n Time →\n\nCommon timing functions:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nlinear cubic-bezier(0, 0, 1, 1)\nease cubic-bezier(0.25, 0.1, 0.25, 1) [default]\nease-in cubic-bezier(0.42, 0, 1, 1)\nease-out cubic-bezier(0, 0, 0.58, 1)\nease-in-out cubic-bezier(0.42, 0, 0.58, 1)\n\nReal-world analogy:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nCar accelerating from stop sign:\n ease-in → Pressing gas pedal gradually\n\nCar approaching red light:\n ease-out → Braking smoothly to stop\n\nCar between two stop signs:\n ease-in-out → Accelerate, cruise, brake"
|
||
},
|
||
"validations": [
|
||
{
|
||
"type": "contains",
|
||
"value": "transition-timing-function",
|
||
"message": "Use <kbd>transition-timing-function</kbd>",
|
||
"options": { "caseSensitive": false }
|
||
},
|
||
{
|
||
"type": "property_value",
|
||
"value": { "property": "transition-timing-function", "expected": "ease-in-out" },
|
||
"message": "Set timing to <kbd>ease-in-out</kbd>"
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "transitions-3",
|
||
"title": "Keyframes",
|
||
"description": "Create named animations using <kbd>@keyframes</kbd> and apply them via the <kbd>animation</kbd> shorthand.<br><br><pre>@keyframes bounce {\n 50% { transform: translateY(-20px); }\n}\n.ball {\n animation: bounce 1s infinite;\n}</pre>",
|
||
"task": "Define a keyframe at <kbd>50%</kbd> with <kbd>transform: translateY(-20px)</kbd> and apply <kbd>animation: bounce 1s infinite</kbd> to <kbd>.ball</kbd>.",
|
||
"previewHTML": "<div class=\"ball\"></div>",
|
||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: crimson; border-radius: 50%; margin: 2rem auto; }",
|
||
"sandboxCSS": "",
|
||
"codePrefix": "/* Define keyframes and apply animation */\n@keyframes bounce {",
|
||
"initialCode": "",
|
||
"codeSuffix": "}\n.ball { }",
|
||
"solution": " 50% { transform: translateY(-20px); }\n}\n.ball {\n animation: bounce 1s infinite;",
|
||
"previewContainer": "preview-area",
|
||
"concept": {
|
||
"explanation": "Keyframe animations define multiple snapshots (keyframes) of property values at specific percentages (0% is start, 100% is end, 50% is halfway), and the browser interpolates smoothly between them. Unlike transitions which only animate from one state to another, keyframes let you define complex multi-step animations with precise control—the browser automatically calculates timing between each keyframe. The 'infinite' keyword makes the animation loop continuously, restarting from 0% each time it completes.",
|
||
"diagram": "Keyframe Animation Timeline & Interpolation\n\n@keyframes bounce {\n 0% { transform: translateY(0px); } ← implicit start\n 50% { transform: translateY(-20px); } ← explicit midpoint\n 100% { transform: translateY(0px); } ← implicit end\n}\n\nanimation: bounce 1s infinite;\n\nTimeline breakdown (1 second duration):\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n0.0s (0%) ●─────────────────── translateY(0px)\n │ interpolate [starting position]\n │ over 0.5s\n ↓\n0.5s (50%) ●─────────────────── translateY(-20px)\n │ interpolate [peak - 20px up]\n │ over 0.5s\n ↓\n1.0s (100%) ●─────────────────── translateY(0px)\n ↓ infinite loop [back to start]\n0.0s restart ●\n\nVisual representation:\n\n -20px ↑ ● ← 50% keyframe (peak)\n │ ╱ ╲\n │╱ ╲\n 0px ●─────● ← 0% and 100% keyframes\n ↑ ↑\n 0s 1s\n\nInterpolation between keyframes:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nFrom 0% to 50% (0s to 0.5s):\nStart: translateY(0px)\nEnd: translateY(-20px)\n\nAt 0.25s (halfway between 0% and 50%):\nprogress = 0.25 / 0.5 = 0.5\nvalue = 0 + (-20 - 0) × 0.5 = -10px\nResult: translateY(-10px) ✓\n\nFrom 50% to 100% (0.5s to 1s):\nStart: translateY(-20px)\nEnd: translateY(0px)\n\nAt 0.75s (halfway between 50% and 100%):\nprogress = (0.75 - 0.5) / 0.5 = 0.5\nvalue = -20 + (0 - (-20)) × 0.5 = -10px\nResult: translateY(-10px) ✓\n\nKeyframes vs Transitions:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nTransitions:\n A → B (one state change)\n Triggered by hover/focus/class change\n Example: button:hover { color: red; }\n\nKeyframes:\n A → B → C → D... (multiple states)\n Runs automatically when element exists\n Example: loading spinner, bounce effect\n Can loop infinitely\n\nImplicit keyframes:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nIf you don't define 0% or 100%, browser uses\ncurrent computed values:\n\n@keyframes bounce {\n 50% { transform: translateY(-20px); }\n}\n↓ Browser expands to:\n@keyframes bounce {\n 0% { transform: translateY(0px); } ← added\n 50% { transform: translateY(-20px); }\n 100% { transform: translateY(0px); } ← added\n}"
|
||
},
|
||
"validations": [
|
||
{
|
||
"type": "contains",
|
||
"value": "@keyframes bounce",
|
||
"message": "Define <kbd>@keyframes bounce</kbd>",
|
||
"options": { "caseSensitive": false }
|
||
},
|
||
{
|
||
"type": "regex",
|
||
"value": "50%.*transform: translateY\\(-20px\\)",
|
||
"message": "At <kbd>50%</kbd>, use <kbd>transform: translateY(-20px)</kbd>",
|
||
"options": { "caseSensitive": false }
|
||
},
|
||
{
|
||
"type": "contains",
|
||
"value": "animation",
|
||
"message": "Use <kbd>animation</kbd> property on <kbd>.ball</kbd>",
|
||
"options": { "caseSensitive": false }
|
||
},
|
||
{
|
||
"type": "regex",
|
||
"value": "animation:.*bounce.*1s.*infinite",
|
||
"message": "Apply <kbd>animation: bounce 1s infinite</kbd>",
|
||
"options": { "caseSensitive": false }
|
||
}
|
||
]
|
||
},
|
||
{
|
||
"id": "transitions-4",
|
||
"title": "Animation Properties",
|
||
"description": "Fine-tune animations with <kbd>animation-delay</kbd>, <kbd>animation-iteration-count</kbd>, <kbd>animation-direction</kbd>, and <kbd>animation-fill-mode</kbd>.",
|
||
"task": "Apply the <kbd>pulse</kbd> animation to <kbd>.box</kbd> with <kbd>animation-name: pulse</kbd>, <kbd>animation-duration: 2s</kbd>, <kbd>animation-delay: 1s</kbd>, <kbd>animation-iteration-count: 2</kbd>, and <kbd>animation-fill-mode: forwards</kbd>.",
|
||
"previewHTML": "<div class=\"box\">Pulse</div>",
|
||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { width: 100px; height: 100px; background: black; color: white; display: flex; align-items: center; justify-content: center; margin: 2rem auto; } @keyframes pulse { 0% { background: black; color: white; transform: scale(1); } 50% { background: white; color: black; transform: scale(1.2); } 100% { background: limegreen; color: black; transform: scale(1); } }",
|
||
"sandboxCSS": "",
|
||
"codePrefix": "/* Apply animation properties */\n.box {",
|
||
"initialCode": "",
|
||
"codeSuffix": "}",
|
||
"solution": " animation-name: pulse;\n animation-duration: 2s;\n animation-delay: 1s;\n animation-iteration-count: 2;\n animation-fill-mode: forwards;",
|
||
"previewContainer": "preview-area",
|
||
"concept": {
|
||
"explanation": "Animation properties give you precise control over playback timing and behavior. Animation-delay postpones the animation start (useful for choreographing sequences), while animation-iteration-count determines how many times it repeats (1, 2, or 'infinite'). Animation-fill-mode controls styles before/after playback: 'none' removes animation styles when not playing, 'forwards' keeps the final keyframe styles after completion, 'backwards' applies starting styles during delay, and 'both' combines both behaviors. These properties control exactly when animations start, how long they run, and what happens before and after playback.",
|
||
"diagram": "Animation Properties & Timeline Control\n\nanimation-name: pulse;\nanimation-duration: 2s;\nanimation-delay: 1s;\nanimation-iteration-count: 2;\nanimation-fill-mode: forwards;\n\nComplete timeline:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\n0s 1s 3s 5s\n│───────────│───────────│───────────│\n│ DELAY │ ITERATION │ ITERATION │ END\n│ (wait) │ #1 │ #2 │ (hold)\n│ │ │ │\n│ ●●●●●●● │ ▶────────▶│ ▶────────▶│ ████\n│ waiting │ playing │ playing │ frozen\n│ │ (2s) │ (2s) │ at\n│ │ │ │ 100%\n\nElement state at each phase:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nBefore delay (0s - 1s):\n background: black ← original CSS\n (animation hasn't started yet)\n\nDuring iteration 1 (1s - 3s):\n 0%: background: black\n 50%: background: white\n 100%: background: limegreen\n (animating through keyframes)\n\nDuring iteration 2 (3s - 5s):\n Repeats: black → white → limegreen\n (second playthrough)\n\nAfter animation (5s+):\n background: limegreen ← fill-mode: forwards\n (stuck at 100% keyframe)\n\nanimation-fill-mode explained:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n\nnone (default):\n Before: original CSS ●●●●●●●\n During: animation ▶────▶\n After: original CSS ●●●●●●●\n\nforwards:\n Before: original CSS ●●●●●●●\n During: animation ▶────▶\n After: 100% keyframe ████████ ← stays!\n\nbackwards:\n Before: 0% keyframe ████████ ← applies!\n During: animation ▶────▶\n After: original CSS ●●●●●●●\n\nboth:\n Before: 0% keyframe ████████ ← applies!\n During: animation ▶────▶\n After: 100% keyframe ████████ ← stays!\n\nanimation-iteration-count:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n1 ▶────▶ (play once)\n2 ▶────▶ ▶────▶ (play twice)\n3 ▶────▶ ▶────▶ ▶────▶\ninfinite ▶────▶ ▶────▶ ▶────▶... (loop forever)\n2.5 ▶────▶ ▶────▶ ▶── (2.5 times)\n\nanimation-delay use cases:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nStaggered animations (cascade effect):\n .item:nth-child(1) { animation-delay: 0s; }\n .item:nth-child(2) { animation-delay: 0.1s; }\n .item:nth-child(3) { animation-delay: 0.2s; }\n\n Result: items animate one after another ↓\n\nNegative delay (start mid-animation):\n animation-delay: -1s; ← starts 1s into animation\n Useful for randomizing loop positions\n\nShorthand syntax:\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\nanimation: pulse 2s 1s 2 forwards;\n │ │ │ │ └─ fill-mode\n │ │ │ └──── iteration-count\n │ │ └─────── delay\n │ └────────── duration\n └──────────────── name"
|
||
},
|
||
"validations": [
|
||
{
|
||
"type": "property_value",
|
||
"value": { "property": "animation-name", "expected": "pulse" },
|
||
"message": "Set <kbd>animation-name: pulse</kbd>"
|
||
},
|
||
{
|
||
"type": "property_value",
|
||
"value": { "property": "animation-duration", "expected": "2s" },
|
||
"message": "Set <kbd>animation-duration: 2s</kbd>"
|
||
},
|
||
{
|
||
"type": "property_value",
|
||
"value": { "property": "animation-delay", "expected": "1s" },
|
||
"message": "Set <kbd>animation-delay: 1s</kbd>"
|
||
},
|
||
{
|
||
"type": "property_value",
|
||
"value": { "property": "animation-iteration-count", "expected": "2" },
|
||
"message": "Set <kbd>animation-iteration-count: 2</kbd>"
|
||
},
|
||
{
|
||
"type": "property_value",
|
||
"value": { "property": "animation-fill-mode", "expected": "forwards" },
|
||
"message": "Set <kbd>animation-fill-mode: forwards</kbd>"
|
||
}
|
||
]
|
||
}
|
||
]
|
||
}
|