Merge pull request 'fix: rewrite CSS Filters tasks to describe visual outcomes' (#13) from 012-filters-tasks into main
This commit is contained in:
@@ -9,7 +9,7 @@
|
|||||||
"id": "filters-1",
|
"id": "filters-1",
|
||||||
"title": "Blur Filter",
|
"title": "Blur Filter",
|
||||||
"description": "The <kbd>filter</kbd> property applies visual effects to elements. The <kbd>blur()</kbd> function creates a Gaussian blur effect.<br><br><pre>filter: blur(4px);</pre><br>Higher values create more blur. This is great for backgrounds or creating depth.",
|
"description": "The <kbd>filter</kbd> property applies visual effects to elements. The <kbd>blur()</kbd> function creates a Gaussian blur effect.<br><br><pre>filter: blur(4px);</pre><br>Higher values create more blur. This is great for backgrounds or creating depth.",
|
||||||
"task": "Blur the background image using <kbd>filter: blur(4px)</kbd>.",
|
"task": "Blur the background image to create a frosted-glass effect. Use a blur radius between 2px and 8px.",
|
||||||
"previewHTML": "<div class=\"bg\"></div><div class=\"content\"><h2>Welcome</h2></div>",
|
"previewHTML": "<div class=\"bg\"></div><div class=\"content\"><h2>Welcome</h2></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; height: 200px; position: relative; overflow: hidden; } .bg { position: absolute; inset: 0; background: linear-gradient(45deg, coral, gold, steelblue); } .content { position: relative; z-index: 1; display: flex; align-items: center; justify-content: center; height: 100%; } .content h2 { color: white; text-shadow: 0 2px 8px rgba(0,0,0,0.3); margin: 0; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; height: 200px; position: relative; overflow: hidden; } .bg { position: absolute; inset: 0; background: linear-gradient(45deg, coral, gold, steelblue); } .content { position: relative; z-index: 1; display: flex; align-items: center; justify-content: center; height: 100%; } .content h2 { color: white; text-shadow: 0 2px 8px rgba(0,0,0,0.3); margin: 0; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -20,9 +20,10 @@
|
|||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "regex",
|
||||||
"value": { "property": "filter", "expected": "blur(4px)" },
|
"value": "filter:\\s*blur\\((2|3|4|5|6|7|8)px\\)",
|
||||||
"message": "Set <kbd>filter: blur(4px)</kbd>"
|
"message": "Use the <kbd>filter</kbd> property with the <kbd>blur()</kbd> function. Try a value between 2px and 8px",
|
||||||
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -30,7 +31,7 @@
|
|||||||
"id": "filters-2",
|
"id": "filters-2",
|
||||||
"title": "Grayscale Filter",
|
"title": "Grayscale Filter",
|
||||||
"description": "The <kbd>grayscale()</kbd> function removes color from an element. Use values from <kbd>0%</kbd> (full color) to <kbd>100%</kbd> (fully grayscale).<br><br><pre>filter: grayscale(100%);</pre><br>Great for hover effects or disabled states.",
|
"description": "The <kbd>grayscale()</kbd> function removes color from an element. Use values from <kbd>0%</kbd> (full color) to <kbd>100%</kbd> (fully grayscale).<br><br><pre>filter: grayscale(100%);</pre><br>Great for hover effects or disabled states.",
|
||||||
"task": "Make the image grayscale with <kbd>filter: grayscale(100%)</kbd>.",
|
"task": "Remove all color from the image to create a black-and-white effect.",
|
||||||
"previewHTML": "<div class=\"photo\"></div>",
|
"previewHTML": "<div class=\"photo\"></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .photo { width: 200px; height: 150px; background: linear-gradient(135deg, coral 0%, gold 50%, steelblue 100%); border-radius: 8px; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .photo { width: 200px; height: 150px; background: linear-gradient(135deg, coral 0%, gold 50%, steelblue 100%); border-radius: 8px; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -41,14 +42,10 @@
|
|||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "regex",
|
||||||
"value": "grayscale",
|
"value": "filter:\\s*grayscale\\(100%\\)",
|
||||||
"message": "Use <kbd>grayscale()</kbd> filter"
|
"message": "Which filter function removes color from an element? Set it to full strength",
|
||||||
},
|
"options": { "caseSensitive": false }
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "100%",
|
|
||||||
"message": "Set to <kbd>100%</kbd> for full grayscale"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -56,7 +53,7 @@
|
|||||||
"id": "filters-3",
|
"id": "filters-3",
|
||||||
"title": "Brightness Filter",
|
"title": "Brightness Filter",
|
||||||
"description": "The <kbd>brightness()</kbd> function adjusts how bright an element appears. Values below <kbd>100%</kbd> darken, above <kbd>100%</kbd> brighten.<br><br><pre>filter: brightness(150%);</pre>",
|
"description": "The <kbd>brightness()</kbd> function adjusts how bright an element appears. Values below <kbd>100%</kbd> darken, above <kbd>100%</kbd> brighten.<br><br><pre>filter: brightness(150%);</pre>",
|
||||||
"task": "Brighten the card with <kbd>filter: brightness(120%)</kbd>.",
|
"task": "Make the card appear brighter and more vivid. Use a brightness value between 110% and 150%.",
|
||||||
"previewHTML": "<div class=\"card\"><span>Featured</span></div>",
|
"previewHTML": "<div class=\"card\"><span>Featured</span></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #1a1a2e; } .card { padding: 2rem; background: linear-gradient(135deg, #4a4a6a, #2a2a4a); border-radius: 12px; text-align: center; } .card span { color: gold; font-weight: 600; text-transform: uppercase; letter-spacing: 2px; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #1a1a2e; } .card { padding: 2rem; background: linear-gradient(135deg, #4a4a6a, #2a2a4a); border-radius: 12px; text-align: center; } .card span { color: gold; font-weight: 600; text-transform: uppercase; letter-spacing: 2px; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -67,14 +64,10 @@
|
|||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "regex",
|
||||||
"value": "brightness",
|
"value": "filter:\\s*brightness\\(1[1-5]0%\\)",
|
||||||
"message": "Use <kbd>brightness()</kbd> filter"
|
"message": "Use the <kbd>filter</kbd> property with the <kbd>brightness()</kbd> function. Values above 100% make things brighter",
|
||||||
},
|
"options": { "caseSensitive": false }
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "120%",
|
|
||||||
"message": "Set to <kbd>120%</kbd>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -82,7 +75,7 @@
|
|||||||
"id": "filters-4",
|
"id": "filters-4",
|
||||||
"title": "Drop Shadow",
|
"title": "Drop Shadow",
|
||||||
"description": "The <kbd>drop-shadow()</kbd> filter creates a shadow that follows the shape of the element, including transparency. Unlike <kbd>box-shadow</kbd>, it works on images with transparent backgrounds.<br><br><pre>filter: drop-shadow(2px 4px 6px black);</pre>",
|
"description": "The <kbd>drop-shadow()</kbd> filter creates a shadow that follows the shape of the element, including transparency. Unlike <kbd>box-shadow</kbd>, it works on images with transparent backgrounds.<br><br><pre>filter: drop-shadow(2px 4px 6px black);</pre>",
|
||||||
"task": "Add a drop shadow with <kbd>filter: drop-shadow(4px 4px 8px gray)</kbd>.",
|
"task": "Add a soft shadow behind the star to give it depth. Use the drop-shadow filter with offset, blur, and a color.",
|
||||||
"previewHTML": "<div class=\"icon\">★</div>",
|
"previewHTML": "<div class=\"icon\">★</div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 2rem; display: flex; justify-content: center; } .icon { font-size: 4rem; color: gold; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 2rem; display: flex; justify-content: center; } .icon { font-size: 4rem; color: gold; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -93,14 +86,10 @@
|
|||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "regex",
|
||||||
"value": "drop-shadow",
|
"value": "filter:\\s*drop-shadow\\(\\d+px\\s+\\d+px\\s+\\d+px\\s+\\w+\\)",
|
||||||
"message": "Use <kbd>drop-shadow()</kbd> filter"
|
"message": "Use the <kbd>filter</kbd> property with <kbd>drop-shadow()</kbd>. It needs horizontal offset, vertical offset, blur radius, and a color",
|
||||||
},
|
"options": { "caseSensitive": false }
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "4px 4px 8px",
|
|
||||||
"message": "Set shadow offset and blur"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
65
specs/012-filters-tasks/plan.md
Normal file
65
specs/012-filters-tasks/plan.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Implementation Plan
|
||||||
|
|
||||||
|
## Objective
|
||||||
|
|
||||||
|
Rewrite the 4 CSS Filters lessons in `lessons/11-filters.json` so tasks describe visual outcomes instead of giving exact CSS code, and validations accept multiple valid answers.
|
||||||
|
|
||||||
|
## Approach
|
||||||
|
|
||||||
|
Follow the same pattern established in issue #9 (colors/box-model rewrite):
|
||||||
|
1. Rewrite each `task` field to describe the desired visual effect
|
||||||
|
2. Replace exact-match validations (`property_value`, `contains`) with `regex` validations that accept a range of values
|
||||||
|
3. Update validation `message` fields to give pedagogical hints without revealing answers
|
||||||
|
4. Keep `description`, `previewHTML`, `previewBaseCSS`, `codePrefix`, `codeSuffix`, and `solution` unchanged
|
||||||
|
|
||||||
|
## File Mapping
|
||||||
|
|
||||||
|
| File | Action | Description |
|
||||||
|
|------|--------|-------------|
|
||||||
|
| `lessons/11-filters.json` | modify | Rewrite tasks and validations for all 4 lessons |
|
||||||
|
|
||||||
|
## Lesson-by-Lesson Plan
|
||||||
|
|
||||||
|
### filters-1: Blur Filter
|
||||||
|
- **Current task:** "Blur the background image using `filter: blur(4px)`."
|
||||||
|
- **New task:** Describe frosted-glass effect, mention blur radius range (2px-8px)
|
||||||
|
- **Validation:** `regex` matching `filter:\s*blur\((2|3|4|5|6|7|8)px\)` — accepts 2px through 8px
|
||||||
|
- **Message:** Hint about the `filter` property and `blur()` function
|
||||||
|
|
||||||
|
### filters-2: Grayscale Filter
|
||||||
|
- **Current task:** "Make the image grayscale with `filter: grayscale(100%)`."
|
||||||
|
- **New task:** Describe removing all color to create a black-and-white effect
|
||||||
|
- **Validation:** `regex` matching `filter:\s*grayscale\(100%\)` — grayscale only makes sense at 100% for "fully desaturated"
|
||||||
|
- **Message:** Hint about which filter function removes color
|
||||||
|
|
||||||
|
### filters-3: Brightness Filter
|
||||||
|
- **Current task:** "Brighten the card with `filter: brightness(120%)`."
|
||||||
|
- **New task:** Describe making the card appear brighter/more vivid, accept range 110%-150%
|
||||||
|
- **Validation:** `regex` matching `filter:\s*brightness\(1[1-5]0%\)` — accepts 110% through 150%
|
||||||
|
- **Message:** Hint about the brightness function and values above 100%
|
||||||
|
|
||||||
|
### filters-4: Drop Shadow
|
||||||
|
- **Current task:** "Add a drop shadow with `filter: drop-shadow(4px 4px 8px gray)`."
|
||||||
|
- **New task:** Describe adding a soft shadow behind the star to give it depth
|
||||||
|
- **Validation:** `regex` matching `filter:\s*drop-shadow\(` with reasonable offset/blur values
|
||||||
|
- **Message:** Hint about the drop-shadow filter function
|
||||||
|
|
||||||
|
## Architecture Decisions
|
||||||
|
|
||||||
|
- Use `regex` validation type (not `property_value`) to allow multiple acceptable values, consistent with the colors/box-model rewrite
|
||||||
|
- Use `options: { "caseSensitive": false }` on all regex validations for consistency
|
||||||
|
- Keep solution fields unchanged as reference answers
|
||||||
|
|
||||||
|
## Risks
|
||||||
|
|
||||||
|
| Risk | Mitigation |
|
||||||
|
|------|------------|
|
||||||
|
| Regex too permissive | Test edge cases; only accept pedagogically reasonable values |
|
||||||
|
| Regex too restrictive | Allow generous ranges; err on the side of accepting creative answers |
|
||||||
|
| Breaking existing progress | localStorage keys are based on lesson IDs which are unchanged |
|
||||||
|
|
||||||
|
## Testing Strategy
|
||||||
|
|
||||||
|
- Run existing test suite (`npm test`) to verify no regressions
|
||||||
|
- Validate JSON file against schema
|
||||||
|
- Manual review of regex patterns for correctness
|
||||||
30
specs/012-filters-tasks/spec.md
Normal file
30
specs/012-filters-tasks/spec.md
Normal file
@@ -0,0 +1,30 @@
|
|||||||
|
# fix: rewrite CSS Filters tasks to describe visual outcomes instead of exact code
|
||||||
|
|
||||||
|
**Issue:** [#12](https://git.librete.ch/libretech/code-crispies/issues/12)
|
||||||
|
**Repository:** libretech/code-crispies
|
||||||
|
**Author:** libretech
|
||||||
|
**State:** open
|
||||||
|
**Labels:** none
|
||||||
|
|
||||||
|
## Problem
|
||||||
|
|
||||||
|
Pedagogy audit: the Filters module has a 100% copy-paste score. Every exercise gives the exact CSS declaration in the task text, so students can complete lessons without understanding the concepts.
|
||||||
|
|
||||||
|
## Requirements
|
||||||
|
|
||||||
|
- Rewrite ONLY the filters module (`lessons/11-filters.json`) task descriptions to describe the desired visual effect instead of the exact code
|
||||||
|
- Accept multiple valid values in validations (e.g., accept blur values between 2px and 8px instead of only 4px)
|
||||||
|
- Do NOT change any other module
|
||||||
|
|
||||||
|
## Example
|
||||||
|
|
||||||
|
- **Before:** "Add `filter: blur(4px)`"
|
||||||
|
- **After:** "Blur the background image to create a frosted-glass effect. Use a blur radius between 2px and 8px."
|
||||||
|
|
||||||
|
## Acceptance Criteria
|
||||||
|
|
||||||
|
1. All 4 filter lesson tasks describe visual outcomes, not exact CSS
|
||||||
|
2. Validations accept a range of valid values using regex patterns
|
||||||
|
3. Validation messages provide pedagogical hints without revealing answers
|
||||||
|
4. No changes to any file outside `lessons/11-filters.json`
|
||||||
|
5. Existing tests continue to pass
|
||||||
12
specs/012-filters-tasks/tasks.md
Normal file
12
specs/012-filters-tasks/tasks.md
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
# Tasks
|
||||||
|
|
||||||
|
## Phase 1: Core Implementation
|
||||||
|
- [X] Task 1.1: Rewrite filters-1 (Blur) task and validations [P]
|
||||||
|
- [X] Task 1.2: Rewrite filters-2 (Grayscale) task and validations [P]
|
||||||
|
- [X] Task 1.3: Rewrite filters-3 (Brightness) task and validations [P]
|
||||||
|
- [X] Task 1.4: Rewrite filters-4 (Drop Shadow) task and validations [P]
|
||||||
|
|
||||||
|
## Phase 2: Validation
|
||||||
|
- [X] Task 2.1: Validate JSON against schema
|
||||||
|
- [X] Task 2.2: Run existing test suite
|
||||||
|
- [X] Task 2.3: Format lesson file with Prettier
|
||||||
Reference in New Issue
Block a user