Compare commits
164 Commits
autoclaude
...
003-flexbo
| Author | SHA1 | Date | |
|---|---|---|---|
| 61acd692f4 | |||
| 672a2d28cb | |||
| 433379155b | |||
| 756841f8c2 | |||
| c97fce1f29 | |||
| 8b6a88ad59 | |||
| 4476d26140 | |||
| f28531fb4c | |||
| 7ab095718b | |||
| 5a243f332a | |||
| 739470e045 | |||
| 07aafa0d89 | |||
| eb82eed826 | |||
| 82f6e46d3c | |||
| 847b261f16 | |||
| 2ce88f9cb7 | |||
| a8ef3d3c5c | |||
| 0f5ac81fe8 | |||
| cf0d2cba51 | |||
| d5bd23615f | |||
| fcc6748aae | |||
| 5c16a8a767 | |||
| 17b3d5380d | |||
| f9311d83f7 | |||
| f4ce61ba64 | |||
| 813d669302 | |||
| 9328399dcb | |||
| 857ae9c3ef | |||
| c91e8d6f32 | |||
|
|
9821e014c5 | ||
| e0cee41482 | |||
| 11877e8e7a | |||
| d78f0ac0b4 | |||
| 0b22263a68 | |||
| baaf05dda4 | |||
| 2aa35cea2d | |||
| f0e2072ac7 | |||
| 072859459f | |||
| 062659fa30 | |||
| a7dcb3ec6f | |||
| 28d41344d1 | |||
| fb5fbe4107 | |||
| 7ecc115c55 | |||
| d802172e5b | |||
| 73a0c59722 | |||
| 630a0a6a21 | |||
| be9c753a0e | |||
| a7e765cb80 | |||
| d2fbe0e085 | |||
| b051974957 | |||
| 68407fe12b | |||
| ea57ce6d28 | |||
| 0fb352c027 | |||
| 9f9dc73b11 | |||
| 0748b23d4c | |||
| 1b3c2b42dc | |||
| efbadbfb76 | |||
| 547840c3fd | |||
| 55379c14f0 | |||
| c59736c0e2 | |||
| c0e1dab0d9 | |||
| 469f6a81a5 | |||
| cb87adb249 | |||
| 96b71079d8 | |||
| 8513189efe | |||
| e65fdb0abc | |||
| 30635a9e69 | |||
| d408c49e45 | |||
| 817dc09a58 | |||
| 4c56342cb7 | |||
| 0a03d51e63 | |||
| 5ebb007568 | |||
| f4cdfa1e57 | |||
| 0fe618f551 | |||
| f2a5156d01 | |||
| 848881f243 | |||
| 03212da9af | |||
| af74a7024d | |||
| 8790d3c19e | |||
| f475d84b39 | |||
| 9753c7066c | |||
| cb8852f64b | |||
| 410833e91e | |||
| d428f394c5 | |||
| 072b2ad210 | |||
| 1368f1c079 | |||
| f1496e7232 | |||
| 3a2027e825 | |||
| d0d8c5a2c0 | |||
| b9d13d064f | |||
| 5b6b283470 | |||
| 9733ce4d56 | |||
| f2e1ae17e9 | |||
| 8cbf0b0f26 | |||
| 42c4c5d586 | |||
| 3010b1a902 | |||
| 7093a36694 | |||
| 11a57cb39b | |||
| 85205f836e | |||
| 6639a70070 | |||
| 3b6639b401 | |||
| 357f6dc57c | |||
| 79ff72635e | |||
| b04688734d | |||
| c6f33194e8 | |||
| 0340a4d4bc | |||
| aa5292b653 | |||
| 6f9091ceb4 | |||
| a2f998195c | |||
| 50605e6939 | |||
| 8f4a53f1d9 | |||
| 6d5d727ad6 | |||
| 301200abc2 | |||
| a8410df42a | |||
| 512f716054 | |||
| 7125b7adc9 | |||
| 0f14568d2c | |||
| a4563638a0 | |||
| 99380cb9ce | |||
| aebb3f267b | |||
| 96f2ec1970 | |||
| daf5aa1134 | |||
| ac83bb2faa | |||
| 6da748fe66 | |||
| af2ded5381 | |||
| 468163a2ec | |||
| 64caaeb049 | |||
| be9bc7bd2f | |||
| 2d376f4fe2 | |||
| 6e2fce813f | |||
| 22c49ae713 | |||
| 275f2d75c3 | |||
| 46d8f7d282 | |||
| 249becc756 | |||
| 5986e83237 | |||
| bfd2d6943f | |||
| 4643477bf5 | |||
| f7b4962255 | |||
| 7fa67daac8 | |||
| 1d0c2426dc | |||
| cc1b972132 | |||
| 047de757f7 | |||
| ef8bbe7730 | |||
| 1613175112 | |||
| 1a5c09b750 | |||
| 617906acb9 | |||
| 0c2abe376b | |||
| 74ccf32e76 | |||
| 3fed861a77 | |||
| 23e83f0791 | |||
| 1fec205782 | |||
| 18f6b6d8ab | |||
| 9d786b5cf4 | |||
| ab056c42c3 | |||
| 2cd94caafb | |||
| 2a96ba9d00 | |||
| 8b3b9ddde5 | |||
| 1ec3f34eac | |||
| 83e6c12b9a | |||
| 1507bfc8eb | |||
| 532faecc0a | |||
| 1d299eac43 | |||
| 98d4362706 | |||
| fb33930328 |
@@ -1,15 +1,6 @@
|
|||||||
{
|
{
|
||||||
"permissions": {
|
"permissions": {
|
||||||
"allow": [
|
"allow": [
|
||||||
"Bash(git add:*)",
|
|
||||||
"Bash(git commit:*)",
|
|
||||||
"Bash(git push:*)",
|
|
||||||
"Bash(npm run build:*)",
|
|
||||||
"Bash(grep:*)",
|
|
||||||
"Bash(npm run format.lessons:*)",
|
|
||||||
"Bash(xargs:*)",
|
|
||||||
"Bash(cat:*)",
|
|
||||||
"Bash(prettier --write:*)"
|
|
||||||
],
|
],
|
||||||
"deny": ["Read(./.env)", "Read(./.env.*)", "Read(./secrets/**)"]
|
"deny": ["Read(./.env)", "Read(./.env.*)", "Read(./secrets/**)"]
|
||||||
},
|
},
|
||||||
|
|||||||
13
.gitignore
vendored
13
.gitignore
vendored
@@ -3,6 +3,19 @@
|
|||||||
node_modules
|
node_modules
|
||||||
dist
|
dist
|
||||||
coverage
|
coverage
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
|
||||||
# Claude Code local settings (user-specific)
|
# Claude Code local settings (user-specific)
|
||||||
.claude/settings.local.json
|
.claude/settings.local.json
|
||||||
|
.claude_settings.json
|
||||||
|
|
||||||
|
# Auto-Claude
|
||||||
|
.auto-claude
|
||||||
|
.worktrees
|
||||||
|
|
||||||
|
# Wave ephemeral data
|
||||||
|
.wave/workspaces
|
||||||
|
.wave/traces
|
||||||
|
.wave/artifacts
|
||||||
|
.wave/output
|
||||||
|
|||||||
851
LESSON_EVALUATION.md
Normal file
851
LESSON_EVALUATION.md
Normal file
@@ -0,0 +1,851 @@
|
|||||||
|
# Code Crispies Lesson Evaluation
|
||||||
|
|
||||||
|
This document evaluates all lessons using the 5-question framework to identify gaps, improvements needed, and restructuring recommendations.
|
||||||
|
|
||||||
|
## Evaluation Framework
|
||||||
|
|
||||||
|
### The 5 Questions
|
||||||
|
1. **Q1: PURPOSE** - What concept does this teach and why does a student need it?
|
||||||
|
2. **Q2: PREREQUISITES** - Does it assume knowledge already taught?
|
||||||
|
3. **Q3: TASK CLARITY** - Can students complete it without guessing?
|
||||||
|
4. **Q4: EXPLANATION DEPTH** - Is depth appropriate for position?
|
||||||
|
5. **Q5: PROGRESSION** - Does it prepare for the next lesson?
|
||||||
|
|
||||||
|
### Scoring
|
||||||
|
- **5** = Excellent
|
||||||
|
- **4** = Good
|
||||||
|
- **3** = Adequate
|
||||||
|
- **2** = Needs Improvement
|
||||||
|
- **1** = Requires Rewrite
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module 1: Welcome (00-welcome.json)
|
||||||
|
|
||||||
|
| Lesson | Q1 | Q2 | Q3 | Q4 | Q5 | Total | Action |
|
||||||
|
|--------|----|----|----|----|----|----|--------|
|
||||||
|
| Get Started | 5 | 5 | 5 | 5 | 5 | 25/25 | Keep |
|
||||||
|
| Overview | 5 | 5 | 5 | 5 | 5 | 25/25 | Keep |
|
||||||
|
| Playground | 5 | 5 | 5 | 5 | 5 | 25/25 | Keep |
|
||||||
|
|
||||||
|
**Module Summary:** Excellent onboarding. Comprehensive explanation of platform, clear navigation guidance.
|
||||||
|
|
||||||
|
**Issues:** None
|
||||||
|
|
||||||
|
**Recommendation:** Keep as-is
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module 2: HTML Block & Inline (20-html-elements.json)
|
||||||
|
|
||||||
|
| Lesson | Q1 | Q2 | Q3 | Q4 | Q5 | Total | Action |
|
||||||
|
|--------|----|----|----|----|----|----|--------|
|
||||||
|
| Block vs Inline | 4 | 5 | 5 | 3 | 5 | 22/25 | Enhance |
|
||||||
|
| Semantic Tags | 4 | 5 | 4 | 3 | 5 | 21/25 | Enhance |
|
||||||
|
| div & span | 2 | 5 | 5 | 2 | 4 | 18/25 | **REMOVE** |
|
||||||
|
|
||||||
|
**Module Summary:** Good content but lesson 3 (div & span) is redundant with lesson 1.
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- Q4: First lesson (Block vs Inline) needs more historical context about document flow
|
||||||
|
- Q4: Missing explanation of why block/inline matters for layout
|
||||||
|
- Q1: Lesson 3 (div & span) is redundant - block/inline already covered in lesson 1
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
- **REMOVE lesson 3 (div & span)** - redundant to lesson 1
|
||||||
|
- Enhance lesson 1 with comprehensive explanation (~200 words)
|
||||||
|
- Add "why" context: Why does HTML distinguish block from inline?
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module 3: HTML Forms - Basics (21-html-forms-basic.json)
|
||||||
|
|
||||||
|
| Lesson | Q1 | Q2 | Q3 | Q4 | Q5 | Total | Action |
|
||||||
|
|--------|----|----|----|----|----|----|--------|
|
||||||
|
| Form Structure | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| Input Types | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| Submit Button | 5 | 5 | 5 | 3 | 5 | 23/25 | Minor edit |
|
||||||
|
|
||||||
|
**Module Summary:** Well-structured with good accessibility focus (mentions `for` attribute).
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- Q4: First lesson could have more context about why forms exist, HTTP form submission
|
||||||
|
- Q4: Depth is similar across all lessons instead of progressive reduction
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
- Add comprehensive intro to lesson 1 about forms' role in web applications
|
||||||
|
- Reduce depth in lesson 3 (Submit Button)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module 4: HTML Validation (22-html-forms-validation.json)
|
||||||
|
|
||||||
|
| Lesson | Q1 | Q2 | Q3 | Q4 | Q5 | Total | Action |
|
||||||
|
|--------|----|----|----|----|----|----|--------|
|
||||||
|
| Required Fields | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| Constraints | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| Full Form | 5 | 5 | 4 | 3 | 5 | 22/25 | Minor edit |
|
||||||
|
|
||||||
|
**Module Summary:** Good coverage of native validation attributes.
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- Q3: Full Form lesson task says "Add required attributes, proper input types, and validation constraints" - somewhat vague
|
||||||
|
- Q4: Difficulty marked as "intermediate" but content is beginner-friendly
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
- Clarify task in lesson 3: specify exactly what to add
|
||||||
|
- Change difficulty to "beginner"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module 5: HTML Details & Summary (23-html-details-summary.json)
|
||||||
|
|
||||||
|
| Lesson | Q1 | Q2 | Q3 | Q4 | Q5 | Total | Action |
|
||||||
|
|--------|----|----|----|----|----|----|--------|
|
||||||
|
| First Widget | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| Pre-expanded | 5 | 5 | 5 | 3 | 5 | 23/25 | Keep |
|
||||||
|
| FAQ Accordion | 5 | 5 | 5 | 4 | 4 | 23/25 | Keep |
|
||||||
|
|
||||||
|
**Module Summary:** Excellent demonstration of native HTML interactivity.
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- Q4: First lesson could mention historical context (before details, needed JS)
|
||||||
|
- Q5: FAQ Accordion is a good capstone but doesn't connect to next module
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
- Add "why" context to lesson 1: "Before HTML5, collapsible content required JavaScript"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module 6: HTML Progress & Meter (24-html-progress-meter.json)
|
||||||
|
|
||||||
|
| Lesson | Q1 | Q2 | Q3 | Q4 | Q5 | Total | Action |
|
||||||
|
|--------|----|----|----|----|----|----|--------|
|
||||||
|
| Progress Bars | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| Indeterminate | 5 | 5 | 5 | 3 | 5 | 23/25 | Keep |
|
||||||
|
| Meter Gauges | 5 | 5 | 5 | 4 | 4 | 23/25 | Keep |
|
||||||
|
|
||||||
|
**Module Summary:** Good coverage of native visualization elements.
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- Q4: Meter lesson has many attributes - could be overwhelming
|
||||||
|
|
||||||
|
**Recommendation:** Keep as-is
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module 7: HTML Tables (30-html-tables.json)
|
||||||
|
|
||||||
|
| Lesson | Q1 | Q2 | Q3 | Q4 | Q5 | Total | Action |
|
||||||
|
|--------|----|----|----|----|----|----|--------|
|
||||||
|
| Basic Table | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| Head & Body | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| Complete Table | 5 | 5 | 5 | 3 | 4 | 22/25 | Minor edit |
|
||||||
|
|
||||||
|
**Module Summary:** Good semantic table structure teaching.
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- Q4: First lesson could explain when to use tables (tabular data, not layout!)
|
||||||
|
- Q4: Progressive depth reduction works well
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
- Add context to lesson 1: "Tables are for tabular data, not page layout"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module 8: HTML SVG (32-html-svg.json)
|
||||||
|
|
||||||
|
| Lesson | Q1 | Q2 | Q3 | Q4 | Q5 | Total | Action |
|
||||||
|
|--------|----|----|----|----|----|----|--------|
|
||||||
|
| Drawing Circles | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| Rectangles & Lines | 5 | 5 | 5 | 3 | 5 | 23/25 | Keep |
|
||||||
|
| Multiple Shapes | 5 | 5 | 5 | 3 | 3 | 21/25 | Review |
|
||||||
|
|
||||||
|
**Module Summary:** Good intro to SVG but positioned oddly in HTML section.
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- Q5: Final lesson doesn't connect to CSS section that follows
|
||||||
|
- Position: SVG involves graphics/styling - better fit in CSS section
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
- MOVE to CSS section after Units & Variables
|
||||||
|
- Add comprehensive intro about SVG vs raster images
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module 9: HTML Marquee (31-html-marquee.json) - DEPRECATED
|
||||||
|
|
||||||
|
| Lesson | Q1 | Q2 | Q3 | Q4 | Q5 | Total | Action |
|
||||||
|
|--------|----|----|----|----|----|----|--------|
|
||||||
|
| Scrolling Text | 2 | 5 | 5 | 3 | 3 | 18/25 | REMOVE |
|
||||||
|
| Direction & Behavior | 2 | 5 | 5 | 3 | 3 | 18/25 | REMOVE |
|
||||||
|
| Retro News Ticker | 2 | 5 | 5 | 3 | 2 | 17/25 | REMOVE |
|
||||||
|
|
||||||
|
**Module Summary:** Teaches deprecated HTML element.
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- Q1: Marquee is deprecated and should not be taught as standard practice
|
||||||
|
- Q5: Doesn't lead to anything practical
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
- **REMOVE from learning path**
|
||||||
|
- Could be moved to optional "Web History" section if desired
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module 10: CSS Selectors (00-basic-selectors.json)
|
||||||
|
|
||||||
|
| Lesson | Q1 | Q2 | Q3 | Q4 | Q5 | Total | Action |
|
||||||
|
|--------|----|----|----|----|----|----|--------|
|
||||||
|
| What's a Selector? | 5 | 5 | 5 | 5 | 5 | 25/25 | Keep |
|
||||||
|
| Type Selectors | 5 | 5 | 5 | 5 | 5 | 25/25 | Keep |
|
||||||
|
| Class Selectors | 5 | 5 | 5 | 5 | 5 | 25/25 | Keep |
|
||||||
|
| Multiple Classes | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| Combining Types | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| ID Selectors | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| Type + ID | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| Selector Lists | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| Universal (*) | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| Specificity | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
|
||||||
|
**Module Summary:** EXCELLENT! This is the gold standard for explanation depth. First lesson has comprehensive "why" context with code examples.
|
||||||
|
|
||||||
|
**Issues:** None significant
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
- Keep as-is
|
||||||
|
- Use as template for other modules
|
||||||
|
- Note: Progressive depth reduction could be stronger (lessons 5-10 have similar depth)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module 11: CSS Box Model (01-box-model.json)
|
||||||
|
|
||||||
|
| Lesson | Q1 | Q2 | Q3 | Q4 | Q5 | Total | Action |
|
||||||
|
|--------|----|----|----|----|----|----|--------|
|
||||||
|
| Box Model Components | 5 | 5 | 5 | 4 | 5 | 24/25 | Enhance |
|
||||||
|
| Adding Borders | 5 | 5 | 5 | 3 | 5 | 23/25 | Keep |
|
||||||
|
| Adding Margins | 5 | 5 | 5 | 3 | 5 | 23/25 | Keep |
|
||||||
|
| Box Sizing | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| Margin Collapse | 5 | 5 | 5 | 3 | 5 | 23/25 | Keep |
|
||||||
|
| Margin Shorthand | 5 | 5 | 5 | 3 | 5 | 23/25 | Keep |
|
||||||
|
| Padding Shorthand | 5 | 5 | 5 | 2 | 5 | 22/25 | Keep |
|
||||||
|
| Border on Specific Sides | 5 | 5 | 5 | 2 | 4 | 21/25 | Keep |
|
||||||
|
|
||||||
|
**Module Summary:** Good structure with proper progression.
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- Q4: First lesson could have MORE comprehensive explanation (like CSS Selectors has)
|
||||||
|
- Q4: Last lessons appropriately brief - good depth progression!
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
- Enhance lesson 1 description with "why" context (like CSS Selectors lesson 1)
|
||||||
|
- Visual diagram reference would help but not critical
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module 12: CSS Units & Variables (05-units-variables.json)
|
||||||
|
|
||||||
|
| Lesson | Q1 | Q2 | Q3 | Q4 | Q5 | Total | Action |
|
||||||
|
|--------|----|----|----|----|----|----|--------|
|
||||||
|
| Absolute vs Relative | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| CSS Custom Properties | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| Unit Calculations | 5 | 5 | 5 | 3 | 5 | 23/25 | Keep |
|
||||||
|
| Viewport & Responsive | 5 | 5 | 5 | 3 | 4 | 22/25 | Keep |
|
||||||
|
|
||||||
|
**Module Summary:** Good coverage of units and variables.
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- Q5: Last lesson could better transition to Flexbox module
|
||||||
|
- GAP: Colors and Typography should come BEFORE this module
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
- Add transition text mentioning layout modules next
|
||||||
|
- INSERT Colors and Typography modules before this one
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module 13: CSS Flexbox (flexbox.json)
|
||||||
|
|
||||||
|
| Lesson | Q1 | Q2 | Q3 | Q4 | Q5 | Total | Action |
|
||||||
|
|--------|----|----|----|----|----|----|--------|
|
||||||
|
| Container | 5 | 5 | 5 | 2 | 5 | 22/25 | **ENHANCE** |
|
||||||
|
| Direction & Wrap | 5 | 5 | 5 | 2 | 5 | 22/25 | Keep |
|
||||||
|
| Justify Content | 5 | 5 | 5 | 2 | 5 | 22/25 | Keep |
|
||||||
|
| Align Items | 5 | 5 | 5 | 2 | 5 | 22/25 | Keep |
|
||||||
|
| Flex Grow | 5 | 5 | 5 | 2 | 5 | 22/25 | Keep |
|
||||||
|
| Align Self | 5 | 5 | 5 | 2 | 4 | 21/25 | Keep |
|
||||||
|
|
||||||
|
**Module Summary:** MAJOR ISSUE - First lesson has minimal explanation compared to CSS Selectors!
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- Q4: **CRITICAL** - Lesson 1 description is only 2 sentences + code block
|
||||||
|
- Q4: No "why" context about flexbox history or when to use it
|
||||||
|
- Q4: All lessons have similar minimal depth - no progressive reduction
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
- **REWRITE lesson 1** with comprehensive explanation:
|
||||||
|
- History: Before flexbox, layout was done with floats/positioning
|
||||||
|
- Why: Flexbox solves the "vertical centering problem" and responsive layouts
|
||||||
|
- Main vs cross axis explained
|
||||||
|
- When to use flexbox vs grid
|
||||||
|
- Keep lessons 2-6 brief (appropriate for later lessons)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module 14: CSS Responsive Design (08-responsive.json)
|
||||||
|
|
||||||
|
| Lesson | Q1 | Q2 | Q3 | Q4 | Q5 | Total | Action |
|
||||||
|
|--------|----|----|----|----|----|----|--------|
|
||||||
|
| Media Queries | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| Fluid Type | 5 | 5 | 5 | 3 | 5 | 23/25 | Keep |
|
||||||
|
| Responsive Grid | 5 | 4 | 5 | 3 | 5 | 22/25 | Review |
|
||||||
|
| Mobile-First | 5 | 5 | 5 | 3 | 4 | 22/25 | Keep |
|
||||||
|
|
||||||
|
**Module Summary:** Good coverage but has prerequisite issue.
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- Q2: Lesson 3 (Responsive Grid) uses CSS Grid concepts but Grid hasn't been taught!
|
||||||
|
- GAP: Grid module should come BEFORE Responsive Design
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
- ADD Grid module before Responsive Design
|
||||||
|
- Or remove/rewrite Responsive Grid lesson to use flexbox only
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module 15: CSS Animations (06-transitions-animations.json)
|
||||||
|
|
||||||
|
| Lesson | Q1 | Q2 | Q3 | Q4 | Q5 | Total | Action |
|
||||||
|
|--------|----|----|----|----|----|----|--------|
|
||||||
|
| Transitions | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| Timing Funcs | 5 | 5 | 5 | 3 | 5 | 23/25 | Keep |
|
||||||
|
| Keyframes | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| Animation Properties | 5 | 5 | 5 | 3 | 4 | 22/25 | Keep |
|
||||||
|
|
||||||
|
**Module Summary:** Good structure with appropriate progression.
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- Q4: First lesson could have more context about when to use animations (UX principles)
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
- Add brief UX context to lesson 1: "Animations should enhance UX, not distract"
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Module 16: Goodbye (99-goodbye.json)
|
||||||
|
|
||||||
|
| Lesson | Q1 | Q2 | Q3 | Q4 | Q5 | Total | Action |
|
||||||
|
|--------|----|----|----|----|----|----|--------|
|
||||||
|
| Well Done! | 5 | 5 | 5 | 5 | 5 | 25/25 | Keep |
|
||||||
|
| Contribute | 5 | 5 | 5 | 5 | 5 | 25/25 | Keep |
|
||||||
|
| Keep Learning | 5 | 5 | 5 | 5 | 5 | 25/25 | Keep |
|
||||||
|
|
||||||
|
**Module Summary:** Excellent closing with resources and encouragement.
|
||||||
|
|
||||||
|
**Issues:** None
|
||||||
|
|
||||||
|
**Recommendation:** Keep as-is
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Unused Modules Evaluation
|
||||||
|
|
||||||
|
## Colors (03-colors.json) - NOT IN PATH
|
||||||
|
|
||||||
|
| Lesson | Q1 | Q2 | Q3 | Q4 | Q5 | Total | Action |
|
||||||
|
|--------|----|----|----|----|----|----|--------|
|
||||||
|
| Setting Background Colors | 5 | 5 | 4 | 3 | 5 | 22/25 | Enhance |
|
||||||
|
| Text Color and Contrast | 5 | 5 | 5 | 3 | 5 | 23/25 | Keep |
|
||||||
|
| CSS Gradients | 5 | 5 | 5 | 3 | 5 | 23/25 | Keep |
|
||||||
|
| Background Images | 5 | 5 | 4 | 3 | 4 | 21/25 | Review |
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- Q3: Lesson 1 uses hex code (#e0f7fa) instead of named color - inconsistent with guidelines
|
||||||
|
- Q4: First lesson needs more explanation about color theory, accessibility
|
||||||
|
- Q3: Lesson 4 task is vague: "using a placeholder URL"
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
- **ADD to learning path** after CSS Selectors
|
||||||
|
- Change hex codes to named colors where possible
|
||||||
|
- Enhance lesson 1 with color accessibility context (contrast ratios)
|
||||||
|
- Clarify task in lesson 4
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Typography (04-typography.json) - NOT IN PATH
|
||||||
|
|
||||||
|
| Lesson | Q1 | Q2 | Q3 | Q4 | Q5 | Total | Action |
|
||||||
|
|--------|----|----|----|----|----|----|--------|
|
||||||
|
| Font Family & Fallbacks | 5 | 5 | 5 | 3 | 5 | 23/25 | Enhance |
|
||||||
|
| Font Size & Line Height | 5 | 5 | 5 | 3 | 5 | 23/25 | Keep |
|
||||||
|
| Font Weight & Style | 5 | 5 | 5 | 3 | 5 | 23/25 | Keep |
|
||||||
|
| Text Decoration & Shadow | 5 | 5 | 4 | 3 | 4 | 21/25 | Review |
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- Q4: First lesson needs more context about web fonts, system fonts, why fallbacks matter
|
||||||
|
- Q3: Lesson 4 task is vague: "Apply an underline... and a light shadow"
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
- **ADD to learning path** after Colors
|
||||||
|
- Enhance lesson 1 with font loading context
|
||||||
|
- Specify exact values in lesson 4 task
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Grid (grid.json) - NOT IN PATH
|
||||||
|
|
||||||
|
| Lesson | Q1 | Q2 | Q3 | Q4 | Q5 | Total | Action |
|
||||||
|
|--------|----|----|----|----|----|----|--------|
|
||||||
|
| Grid Container Basics | 5 | 5 | 5 | 4 | 5 | 24/25 | Enhance |
|
||||||
|
| Grid Template Areas | 5 | 5 | 5 | 4 | 5 | 24/25 | Keep |
|
||||||
|
| Spanning Grid Cells | 5 | 5 | 5 | 3 | 5 | 23/25 | Keep |
|
||||||
|
| Automatic Grid Placement | 5 | 5 | 5 | 3 | 5 | 23/25 | Keep |
|
||||||
|
| Grid Alignment | 5 | 5 | 5 | 3 | 5 | 23/25 | Keep |
|
||||||
|
| Overlapping Grid Items | 5 | 5 | 5 | 3 | 4 | 22/25 | Keep |
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- Q4: First lesson needs comprehensive intro about Grid vs Flexbox (when to use which)
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
- **ADD to learning path** after Flexbox
|
||||||
|
- Enhance lesson 1 with Grid vs Flexbox comparison
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Advanced Selectors (01-advanced-selectors.json) - NOT IN PATH
|
||||||
|
|
||||||
|
| Lesson | Q1 | Q2 | Q3 | Q4 | Q5 | Total | Action |
|
||||||
|
|--------|----|----|----|----|----|----|--------|
|
||||||
|
| [attribute] | 5 | 5 | 5 | 5 | 5 | 25/25 | Keep |
|
||||||
|
| Attr Matching | 5 | 5 | 5 | 5 | 5 | 25/25 | Keep |
|
||||||
|
| Child (>) | 5 | 5 | 5 | 5 | 5 | 25/25 | Keep |
|
||||||
|
| Descendant | 5 | 5 | 5 | 5 | 5 | 25/25 | Keep |
|
||||||
|
| Adjacent (+) | 5 | 5 | 5 | 5 | 5 | 25/25 | Keep |
|
||||||
|
| Sibling (~) | 5 | 5 | 5 | 5 | 5 | 25/25 | Keep |
|
||||||
|
| :hover | 5 | 5 | 5 | 5 | 5 | 25/25 | Keep |
|
||||||
|
| :first-child | 5 | 5 | 5 | 5 | 5 | 25/25 | Keep |
|
||||||
|
|
||||||
|
**Issues:** None - this is an excellent module!
|
||||||
|
|
||||||
|
**Recommendation:**
|
||||||
|
- **ADD to learning path** after CSS Basic Selectors
|
||||||
|
- This module has the BEST explanation depth of all modules
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Summary
|
||||||
|
|
||||||
|
## Overall Statistics
|
||||||
|
|
||||||
|
| Category | Count |
|
||||||
|
|----------|-------|
|
||||||
|
| Total Active Lessons | 62 |
|
||||||
|
| Lessons Scoring 24-25 | 35 (56%) |
|
||||||
|
| Lessons Scoring 21-23 | 22 (35%) |
|
||||||
|
| Lessons Scoring <21 | 5 (8%) |
|
||||||
|
| Modules to REMOVE | 1 (Marquee) |
|
||||||
|
| Modules to ADD | 4 (Colors, Typography, Grid, Adv Selectors) |
|
||||||
|
|
||||||
|
## Critical Issues
|
||||||
|
|
||||||
|
### 1. Missing Modules (HIGH PRIORITY)
|
||||||
|
- **Colors** - Students use colors without learning them
|
||||||
|
- **Typography** - Font properties not taught
|
||||||
|
- **Grid** - Essential layout missing
|
||||||
|
- **Advanced Selectors** - Excellent content not being used
|
||||||
|
|
||||||
|
### 2. Explanation Depth Issues (MEDIUM PRIORITY)
|
||||||
|
- **Flexbox Lesson 1** - Needs comprehensive rewrite (currently only 2 sentences)
|
||||||
|
- **HTML Block & Inline Lesson 1** - Needs "why" context
|
||||||
|
- **Box Model Lesson 1** - Could be more comprehensive
|
||||||
|
|
||||||
|
### 3. Module Position Issues (MEDIUM PRIORITY)
|
||||||
|
- **SVG** - Should be in CSS section, not HTML section
|
||||||
|
- **Responsive Design** - Uses Grid concepts before Grid is taught
|
||||||
|
|
||||||
|
### 4. Deprecated Content (HIGH PRIORITY)
|
||||||
|
- **Marquee** - Remove from learning path
|
||||||
|
|
||||||
|
## Recommended New Structure
|
||||||
|
|
||||||
|
```
|
||||||
|
1. Welcome
|
||||||
|
2. HTML Block & Inline
|
||||||
|
3. HTML Forms - Basics
|
||||||
|
4. HTML Forms - Validation
|
||||||
|
5. HTML Details & Summary
|
||||||
|
6. HTML Progress & Meter
|
||||||
|
7. HTML Tables
|
||||||
|
8. CSS Selectors - Basics (existing)
|
||||||
|
9. CSS Selectors - Advanced (ADD - excellent content)
|
||||||
|
10. CSS Colors (ADD)
|
||||||
|
11. CSS Typography (ADD)
|
||||||
|
12. CSS Box Model
|
||||||
|
13. CSS Units & Variables
|
||||||
|
14. CSS SVG (MOVE from HTML)
|
||||||
|
15. CSS Flexbox (ENHANCE lesson 1)
|
||||||
|
16. CSS Grid (ADD)
|
||||||
|
17. CSS Responsive Design
|
||||||
|
18. CSS Animations
|
||||||
|
19. What's Next?
|
||||||
|
|
||||||
|
REMOVED: HTML Marquee
|
||||||
|
```
|
||||||
|
|
||||||
|
## Priority Actions
|
||||||
|
|
||||||
|
### Immediate (Before Next Release)
|
||||||
|
1. Remove HTML Marquee from `src/config/lessons.js`
|
||||||
|
2. Remove "div & span" lesson from HTML Block & Inline (redundant)
|
||||||
|
3. Add Advanced Selectors to path after Basic Selectors
|
||||||
|
4. Enhance Flexbox lesson 1 with comprehensive explanation
|
||||||
|
|
||||||
|
### Short-term
|
||||||
|
5. Add Colors module to path (after Basic Selectors)
|
||||||
|
6. Add Typography module to path (after Colors)
|
||||||
|
7. Add Grid module to path (after Flexbox)
|
||||||
|
8. Move SVG from HTML section to CSS section
|
||||||
|
|
||||||
|
### Medium-term
|
||||||
|
9. Enhance all first lessons with comprehensive explanations
|
||||||
|
10. Fix hex colors in Colors module to use named colors
|
||||||
|
11. Clarify vague tasks (Typography lesson 4, Colors lesson 4)
|
||||||
|
|
||||||
|
### Long-term
|
||||||
|
12. Implement progressive depth reduction across all modules
|
||||||
|
13. Update translations for new module order
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## UI Changes
|
||||||
|
|
||||||
|
### Header Logo
|
||||||
|
- **Current:** "code" has background highlight, "crispies" is plain
|
||||||
|
- **Change:** Swap so "crispies" has background highlight, "code" is plain
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Detailed Lesson-Level Improvements
|
||||||
|
|
||||||
|
## HTML Section Lessons
|
||||||
|
|
||||||
|
### Module: HTML Block & Inline (20-html-elements.json)
|
||||||
|
|
||||||
|
#### Lesson 1: Block vs Inline Elements
|
||||||
|
**Current Description:**
|
||||||
|
> "HTML elements fall into two main categories: Block elements (containers) start on a new line and take full width. Examples: div, p, h1, section. Inline elements flow within text and only take needed width. Examples: span, a, strong, em"
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- No "why" context - why does HTML distinguish between block and inline?
|
||||||
|
- No historical context
|
||||||
|
- No practical application explanation
|
||||||
|
|
||||||
|
**Improved Description:**
|
||||||
|
```
|
||||||
|
<strong>Understanding Document Flow</strong><br><br>
|
||||||
|
|
||||||
|
When browsers render HTML, they follow a concept called <em>document flow</em> - the natural way elements arrange themselves on a page. This fundamental concept, dating back to HTML's origins as a document markup language, divides elements into two categories:<br><br>
|
||||||
|
|
||||||
|
<strong>Block elements</strong> behave like paragraphs in a word processor - they start on a new line and stretch to fill available width. Think of them as "containers" that stack vertically. Examples: <kbd><div></kbd>, <kbd><p></kbd>, <kbd><h1></kbd>, <kbd><section></kbd><br><br>
|
||||||
|
|
||||||
|
<strong>Inline elements</strong> behave like words in a sentence - they flow within text without breaking the line, taking only the width they need. Examples: <kbd><span></kbd>, <kbd><a></kbd>, <kbd><strong></kbd>, <kbd><em></kbd><br><br>
|
||||||
|
|
||||||
|
<strong>Why this matters:</strong> Understanding block vs inline is essential for CSS layout. You cannot set width/height on inline elements, and vertical margins behave differently. This distinction forms the foundation of all web layout.
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Lesson 2: Semantic Tags
|
||||||
|
**Current Description:** Good, but could add brief "why" for accessibility
|
||||||
|
|
||||||
|
**Add to beginning:**
|
||||||
|
```
|
||||||
|
Semantic HTML helps both browsers and assistive technologies (like screen readers) understand your content structure. Using the right semantic elements improves accessibility and SEO.<br><br>
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Lesson 3: div & span
|
||||||
|
**Action:** REMOVE (redundant - block/inline already covered in lesson 1)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Module: HTML Forms - Basics (21-html-forms-basic.json)
|
||||||
|
|
||||||
|
#### Lesson 1: Form Structure
|
||||||
|
**Current Description:**
|
||||||
|
> "Every form needs a form wrapper. Inside, use label to describe inputs and input for user data entry. The for attribute on labels should match the id on inputs for accessibility."
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- No context about what forms ARE or why they exist
|
||||||
|
- No mention of HTTP form submission
|
||||||
|
|
||||||
|
**Improved Description:**
|
||||||
|
```
|
||||||
|
<strong>HTML forms are the primary way users send data to websites</strong> - from login credentials to search queries to payment information. Without forms, the web would be read-only.<br><br>
|
||||||
|
|
||||||
|
Every form needs a <kbd><form></kbd> wrapper that defines where data goes when submitted. Inside, you build the interface with:<br><br>
|
||||||
|
|
||||||
|
• <kbd><label></kbd> - Describes what each input is for<br>
|
||||||
|
• <kbd><input></kbd> - Where users enter data<br><br>
|
||||||
|
|
||||||
|
<strong>Accessibility tip:</strong> The <kbd>for</kbd> attribute on labels should match the <kbd>id</kbd> on inputs. This lets users click the label to focus the input - essential for users with motor impairments.
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Lesson 2: Input Types
|
||||||
|
**Status:** Good as-is (score 24/25)
|
||||||
|
|
||||||
|
#### Lesson 3: Submit Button
|
||||||
|
**Current Description:** Good but could be briefer (this is lesson 3)
|
||||||
|
**Action:** Reduce slightly - appropriate for late-module position
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Module: HTML Forms - Validation (22-html-forms-validation.json)
|
||||||
|
|
||||||
|
#### Lesson 3: Full Form (Complete Registration)
|
||||||
|
**Current Task:**
|
||||||
|
> "Complete the registration form. Add required attributes, proper input types, and validation constraints."
|
||||||
|
|
||||||
|
**Issue:** Task is vague - doesn't specify exactly what to add
|
||||||
|
|
||||||
|
**Improved Task:**
|
||||||
|
```
|
||||||
|
Complete the registration form by adding:<br>
|
||||||
|
1. <kbd>required</kbd> to all fields marked with *<br>
|
||||||
|
2. <kbd>type="email"</kbd> to the email input<br>
|
||||||
|
3. <kbd>type="password"</kbd> to the password input<br>
|
||||||
|
4. <kbd>minlength="8"</kbd> to the password input<br>
|
||||||
|
5. <kbd>required</kbd> to the terms checkbox
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Module: HTML Details & Summary (23-html-details-summary.json)
|
||||||
|
|
||||||
|
#### Lesson 1: First Widget
|
||||||
|
**Add to description beginning:**
|
||||||
|
```
|
||||||
|
Before HTML5, creating collapsible content required JavaScript. The <kbd><details></kbd> element gives you this functionality natively - no scripts needed, fully accessible, works everywhere.<br><br>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Module: HTML Tables (30-html-tables.json)
|
||||||
|
|
||||||
|
#### Lesson 1: Basic Table Structure
|
||||||
|
**Add to description beginning:**
|
||||||
|
```
|
||||||
|
<strong>Important:</strong> Tables are for <em>tabular data</em> only (like spreadsheets, schedules, or comparison charts). Never use tables for page layout - that's what CSS Grid and Flexbox are for.<br><br>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Module: HTML SVG (32-html-svg.json)
|
||||||
|
|
||||||
|
#### Lesson 1: Drawing Circles
|
||||||
|
**Add to description beginning:**
|
||||||
|
```
|
||||||
|
<strong>SVG (Scalable Vector Graphics)</strong> creates images using mathematical descriptions rather than pixels. Unlike JPG or PNG, SVG images stay crisp at any size - perfect for logos, icons, and illustrations.<br><br>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CSS Section Lessons
|
||||||
|
|
||||||
|
### Module: CSS Selectors (00-basic-selectors.json)
|
||||||
|
**Status:** EXCELLENT - Use as template for other modules. No changes needed.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Module: CSS Box Model (01-box-model.json)
|
||||||
|
|
||||||
|
#### Lesson 1: Box Model Components
|
||||||
|
**Current Description:**
|
||||||
|
> "The CSS box model consists of four concentric layers: content area (innermost), padding, border, and margin (outermost). Understanding how these components interact is essential for precise layout control."
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- Good but could be more comprehensive like CSS Selectors lesson 1
|
||||||
|
|
||||||
|
**Improved Description:**
|
||||||
|
```
|
||||||
|
<strong>The CSS box model is the foundation of all web layout.</strong> Every HTML element is rendered as a rectangular box, and understanding this box is crucial for controlling spacing and sizing.<br><br>
|
||||||
|
|
||||||
|
The box model consists of four concentric layers (from inside out):<br>
|
||||||
|
1. <strong>Content</strong> - Your actual text, images, or nested elements<br>
|
||||||
|
2. <strong>Padding</strong> - Space between content and border (inside the element)<br>
|
||||||
|
3. <strong>Border</strong> - The visible edge of the element<br>
|
||||||
|
4. <strong>Margin</strong> - Space between this element and others (outside the element)<br><br>
|
||||||
|
|
||||||
|
<pre>.box {
|
||||||
|
/* Content is determined by width/height */
|
||||||
|
padding: 20px; /* Inside spacing */
|
||||||
|
border: 2px solid black;
|
||||||
|
margin: 10px; /* Outside spacing */
|
||||||
|
}</pre>
|
||||||
|
|
||||||
|
<strong>Key insight:</strong> Padding adds to the element's visual size; margin creates empty space around it.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Module: CSS Flexbox (flexbox.json) - CRITICAL
|
||||||
|
|
||||||
|
#### Lesson 1: Container
|
||||||
|
**Current Description:**
|
||||||
|
> "Learn how to create a flex container and understand the main and cross axes.
|
||||||
|
> [code block]"
|
||||||
|
|
||||||
|
**Issues:**
|
||||||
|
- ONLY 2 sentences before code block!
|
||||||
|
- No "why" context
|
||||||
|
- No comparison to older methods
|
||||||
|
- No explanation of when to use flexbox
|
||||||
|
|
||||||
|
**Improved Description:**
|
||||||
|
```
|
||||||
|
<strong>Flexbox revolutionized CSS layout.</strong> Before flexbox (2012), developers used floats and positioning hacks for layouts - techniques that were never designed for that purpose. Flexbox was created specifically to solve common layout problems.<br><br>
|
||||||
|
|
||||||
|
<strong>When to use Flexbox:</strong><br>
|
||||||
|
• Centering content (finally easy!)<br>
|
||||||
|
• Distributing space between items<br>
|
||||||
|
• Aligning items in a row or column<br>
|
||||||
|
• Creating responsive navigation bars<br>
|
||||||
|
• Building card layouts<br><br>
|
||||||
|
|
||||||
|
<strong>The two axes:</strong><br>
|
||||||
|
Flexbox works along two perpendicular axes:<br>
|
||||||
|
• <strong>Main axis</strong> - The direction items flow (horizontal by default)<br>
|
||||||
|
• <strong>Cross axis</strong> - Perpendicular to main axis (vertical by default)<br><br>
|
||||||
|
|
||||||
|
<pre>.container {
|
||||||
|
display: flex; /* Creates flex container */
|
||||||
|
justify-content: center; /* Align on main axis */
|
||||||
|
align-items: center; /* Align on cross axis */
|
||||||
|
}</pre>
|
||||||
|
|
||||||
|
To start, you only need <kbd>display: flex</kbd> on the parent container. This immediately makes all direct children "flex items" that can be aligned and distributed.
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Lessons 2-6
|
||||||
|
**Status:** Good as-is - appropriately brief for later lessons
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Module: CSS Responsive Design (08-responsive.json)
|
||||||
|
|
||||||
|
#### Lesson 1: Media Queries
|
||||||
|
**Add to description beginning:**
|
||||||
|
```
|
||||||
|
<strong>Responsive design</strong> means your layout adapts to different screen sizes - from phones to desktops. Media queries are CSS rules that only apply when certain conditions (like screen width) are met.<br><br>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Module: CSS Animations (06-transitions-animations.json)
|
||||||
|
|
||||||
|
#### Lesson 1: Transitions
|
||||||
|
**Add to description:**
|
||||||
|
```
|
||||||
|
<strong>UX principle:</strong> Animations should enhance usability, not distract. Use subtle, fast transitions (0.2-0.3s) for interactive feedback. Reserve longer animations for meaningful state changes.<br><br>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Unused Modules to Add
|
||||||
|
|
||||||
|
### Module: CSS Colors (03-colors.json)
|
||||||
|
|
||||||
|
#### Lesson 1: Setting Background Colors
|
||||||
|
**Current Issues:**
|
||||||
|
- Uses hex code (#e0f7fa) instead of named color
|
||||||
|
- No context about color accessibility
|
||||||
|
|
||||||
|
**Improved Description:**
|
||||||
|
```
|
||||||
|
<strong>Color is fundamental to web design</strong>, but it's also one of the most important accessibility considerations. Approximately 8% of men have some form of color blindness.<br><br>
|
||||||
|
|
||||||
|
CSS offers multiple ways to specify colors:<br>
|
||||||
|
• <strong>Named colors:</strong> <kbd>steelblue</kbd>, <kbd>coral</kbd>, <kbd>gold</kbd> (147 names)<br>
|
||||||
|
• <strong>Hex codes:</strong> <kbd>#3498db</kbd> (harder to read)<br>
|
||||||
|
• <strong>RGB:</strong> <kbd>rgb(52, 152, 219)</kbd><br>
|
||||||
|
• <strong>HSL:</strong> <kbd>hsl(204, 70%, 53%)</kbd> (easiest to adjust)<br><br>
|
||||||
|
|
||||||
|
For learning, we'll use named colors - they're memorable and readable.
|
||||||
|
```
|
||||||
|
|
||||||
|
**Fix task:** Change `#e0f7fa` to `lightcyan` (equivalent named color)
|
||||||
|
|
||||||
|
#### Lesson 4: Background Images
|
||||||
|
**Current Task:** "Set a background image on '.bg-img' using a placeholder URL, center it, and prevent tiling."
|
||||||
|
|
||||||
|
**Issue:** Vague - what URL? What values?
|
||||||
|
|
||||||
|
**Improved Task:**
|
||||||
|
```
|
||||||
|
The background image is already set. Add these properties to <kbd>.bg-img</kbd>:<br>
|
||||||
|
1. <kbd>background-position: center</kbd><br>
|
||||||
|
2. <kbd>background-repeat: no-repeat</kbd>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Module: CSS Typography (04-typography.json)
|
||||||
|
|
||||||
|
#### Lesson 1: Font Family & Fallbacks
|
||||||
|
**Add context:**
|
||||||
|
```
|
||||||
|
<strong>Web fonts vs System fonts:</strong> Web fonts (like Google Fonts) load from the internet, adding download time. System fonts (like Georgia, Arial) are already on users' devices - instant and reliable.<br><br>
|
||||||
|
|
||||||
|
<strong>Fallback stacks</strong> ensure text displays even if your preferred font fails to load. Always end with a generic family (<kbd>serif</kbd>, <kbd>sans-serif</kbd>, <kbd>monospace</kbd>).
|
||||||
|
```
|
||||||
|
|
||||||
|
#### Lesson 4: Text Decoration & Shadow
|
||||||
|
**Current Task:** "Apply an underline with text-decoration and a light shadow using text-shadow on '.fancy'."
|
||||||
|
|
||||||
|
**Issue:** Doesn't specify shadow values
|
||||||
|
|
||||||
|
**Improved Task:**
|
||||||
|
```
|
||||||
|
Style <kbd>.fancy</kbd> with:<br>
|
||||||
|
1. <kbd>text-decoration: underline</kbd><br>
|
||||||
|
2. <kbd>text-shadow: 1px 1px 2px gray</kbd>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### Module: CSS Grid (grid.json)
|
||||||
|
|
||||||
|
#### Lesson 1: Grid Container Basics
|
||||||
|
**Add context about Grid vs Flexbox:**
|
||||||
|
```
|
||||||
|
<strong>Grid vs Flexbox - when to use which?</strong><br><br>
|
||||||
|
|
||||||
|
• <strong>Flexbox:</strong> One-dimensional layouts (a row OR a column)<br>
|
||||||
|
• <strong>Grid:</strong> Two-dimensional layouts (rows AND columns together)<br><br>
|
||||||
|
|
||||||
|
Use Grid when you need to align items in both directions simultaneously - like a photo gallery, dashboard, or page layout. Use Flexbox for navigation bars, card rows, or centering content.<br><br>
|
||||||
|
|
||||||
|
Grid was the last major layout feature added to CSS (2017), completing the layout toolkit that web designers had wanted for decades.
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
# Summary: Lessons Requiring Changes
|
||||||
|
|
||||||
|
| Module | Lesson | Change Type | Priority |
|
||||||
|
|--------|--------|-------------|----------|
|
||||||
|
| HTML Block & Inline | 1. Block vs Inline | Rewrite description | HIGH |
|
||||||
|
| HTML Block & Inline | 2. Semantic Tags | Add accessibility context | MEDIUM |
|
||||||
|
| HTML Block & Inline | 3. div & span | **REMOVE** | HIGH |
|
||||||
|
| HTML Forms | 1. Form Structure | Enhance description | MEDIUM |
|
||||||
|
| HTML Forms Validation | 3. Full Form | Clarify task | MEDIUM |
|
||||||
|
| HTML Details | 1. First Widget | Add history context | LOW |
|
||||||
|
| HTML Tables | 1. Basic Table | Add "not for layout" warning | MEDIUM |
|
||||||
|
| HTML SVG | 1. Drawing Circles | Add SVG vs raster context | LOW |
|
||||||
|
| CSS Box Model | 1. Components | Enhance description | MEDIUM |
|
||||||
|
| **CSS Flexbox** | **1. Container** | **REWRITE description** | **CRITICAL** |
|
||||||
|
| CSS Responsive | 1. Media Queries | Add responsive context | LOW |
|
||||||
|
| CSS Animations | 1. Transitions | Add UX principle | LOW |
|
||||||
|
| CSS Colors | 1. Background Colors | Fix hex→named, add a11y | MEDIUM |
|
||||||
|
| CSS Colors | 4. Background Images | Clarify task | MEDIUM |
|
||||||
|
| CSS Typography | 1. Font Family | Add web font context | MEDIUM |
|
||||||
|
| CSS Typography | 4. Decoration | Specify shadow values | MEDIUM |
|
||||||
|
| CSS Grid | 1. Container Basics | Add Grid vs Flexbox | MEDIUM |
|
||||||
24
LICENSE
Normal file
24
LICENSE
Normal file
@@ -0,0 +1,24 @@
|
|||||||
|
This is free and unencumbered software released into the public domain.
|
||||||
|
|
||||||
|
Anyone is free to copy, modify, publish, use, compile, sell, or
|
||||||
|
distribute this software, either in source code form or as a compiled
|
||||||
|
binary, for any purpose, commercial or non-commercial, and by any
|
||||||
|
means.
|
||||||
|
|
||||||
|
In jurisdictions that recognize copyright laws, the author or authors
|
||||||
|
of this software dedicate any and all copyright interest in the
|
||||||
|
software to the public domain. We make this dedication for the benefit
|
||||||
|
of the public at large and to the detriment of our heirs and
|
||||||
|
successors. We intend this dedication to be an overt act of
|
||||||
|
relinquishment in perpetuity of all present and future rights to this
|
||||||
|
software under copyright law.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
|
||||||
|
EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
|
||||||
|
MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT.
|
||||||
|
IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR
|
||||||
|
OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE,
|
||||||
|
ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
|
||||||
|
OTHER DEALINGS IN THE SOFTWARE.
|
||||||
|
|
||||||
|
For more information, please refer to <https://unlicense.org>
|
||||||
10
README.md
10
README.md
@@ -1,16 +1,16 @@
|
|||||||

|

|
||||||
# Code Crispies
|
# Code Crispies
|
||||||
|
|
||||||
An interactive platform for learning CSS and Tailwind CSS through practical challenges.
|
An interactive platform for learning HTML, CSS, and Tailwind CSS through practical challenges.
|
||||||
|
|
||||||
## 📚 Overview
|
## 📚 Overview
|
||||||
|
|
||||||
Code Crispies is a web-based learning platform designed to help users master CSS and Tailwind CSS through hands-on exercises. The application presents a series of progressive challenges organized into themed modules, allowing learners to build their skills step by step while receiving immediate feedback.
|
Code Crispies is a web-based learning platform designed to help users master HTML, CSS, and Tailwind CSS through hands-on exercises. The application presents a series of progressive challenges organized into themed modules, allowing learners to build their skills step by step while receiving immediate feedback.
|
||||||
|
|
||||||
### Key Features
|
### Key Features
|
||||||
|
|
||||||
- **Interactive Lessons**: Learn CSS and Tailwind concepts through practical, hands-on challenges
|
- **Interactive Lessons**: Learn HTML, CSS, and Tailwind concepts through practical, hands-on challenges
|
||||||
- **Dual Mode Support**: Switch between CSS and Tailwind CSS learning modes
|
- **Multiple Learning Paths**: CSS fundamentals, HTML semantics, and Tailwind CSS utilities
|
||||||
- **Progressive Difficulty**: Modules are structured to build skills gradually from basic to advanced
|
- **Progressive Difficulty**: Modules are structured to build skills gradually from basic to advanced
|
||||||
- **Real-Time Feedback**: Get immediate validation on your code solutions with comprehensive feedback
|
- **Real-Time Feedback**: Get immediate validation on your code solutions with comprehensive feedback
|
||||||
- **Progress Tracking**: Your learning progress is automatically saved in the browser
|
- **Progress Tracking**: Your learning progress is automatically saved in the browser
|
||||||
@@ -256,4 +256,4 @@ When adding new lessons:
|
|||||||
|
|
||||||
## 📄 License
|
## 📄 License
|
||||||
|
|
||||||
Copyright (c) 2026 Michael Czechowski. Licensed under the [./LICENSE](WTFPL).
|
This project is released into the public domain under the [Unlicense](./LICENSE). You are free to copy, modify, publish, use, compile, sell, or distribute this software for any purpose.
|
||||||
|
|||||||
297
docs/ROADMAP.md
Normal file
297
docs/ROADMAP.md
Normal file
@@ -0,0 +1,297 @@
|
|||||||
|
# Code Crispies Roadmap
|
||||||
|
|
||||||
|
## Current State (Updated)
|
||||||
|
|
||||||
|
**Total Active Lessons:** 101 (excluding welcome, goodbye, playground)
|
||||||
|
**Target:** 100 lessons for milestone system ✅ ACHIEVED
|
||||||
|
|
||||||
|
### Current Module Breakdown
|
||||||
|
|
||||||
|
| Module | Lessons | Category | Status |
|
||||||
|
|--------|---------|----------|--------|
|
||||||
|
| Basic Selectors | 10 | CSS | ✅ |
|
||||||
|
| Colors | 4 | CSS | ✅ |
|
||||||
|
| **Gradients** | 3 | CSS | ✅ NEW |
|
||||||
|
| Typography | 6 | CSS | ✅ +2 |
|
||||||
|
| Box Model | 8 | CSS | ✅ |
|
||||||
|
| Flexbox | 6 | CSS | ✅ |
|
||||||
|
| Grid | 6 | CSS | ✅ |
|
||||||
|
| **Positioning** | 4 | CSS | ✅ NEW |
|
||||||
|
| Units & Variables | 4 | CSS | ✅ |
|
||||||
|
| Responsive | 4 | CSS | ✅ |
|
||||||
|
| Transitions & Animations | 4 | CSS | ✅ |
|
||||||
|
| **Filters** | 4 | CSS | ✅ NEW |
|
||||||
|
| **Pseudo-elements** | 4 | CSS | ✅ NEW |
|
||||||
|
| HTML Elements | 2 | HTML | ✅ |
|
||||||
|
| **Semantic HTML** | 3 | HTML | ✅ NEW |
|
||||||
|
| Figure | 3 | HTML | ✅ |
|
||||||
|
| SVG | 3 | HTML | ✅ |
|
||||||
|
| Details/Summary | 3 | HTML | ✅ |
|
||||||
|
| Dialog | 2 | HTML | ✅ |
|
||||||
|
| Progress/Meter | 3 | HTML | ✅ |
|
||||||
|
| Forms Basic | 3 | HTML | ✅ |
|
||||||
|
| Forms Validation | 1 | HTML | ✅ |
|
||||||
|
| Fieldset | 3 | HTML | ✅ |
|
||||||
|
| Datalist | 2 | HTML | ✅ |
|
||||||
|
| Tables | 3 | HTML | ✅ +2 |
|
||||||
|
| **Total** | **101** | | ✅ |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 1: Milestone Progress System ✅ COMPLETED
|
||||||
|
|
||||||
|
### Design
|
||||||
|
|
||||||
|
Replace percentage-based progress with milestone markers:
|
||||||
|
|
||||||
|
```
|
||||||
|
[1] [5] [10] [20] [30] [50] [75] [100]
|
||||||
|
● ● ◐ ○ ○ ○ ○ ○
|
||||||
|
```
|
||||||
|
|
||||||
|
**Milestones:**
|
||||||
|
- 1 lesson - First Step
|
||||||
|
- 5 lessons - Getting Started
|
||||||
|
- 10 lessons - Rookie
|
||||||
|
- 20 lessons - Learner
|
||||||
|
- 30 lessons - Intermediate
|
||||||
|
- 50 lessons - Halfway Hero
|
||||||
|
- 75 lessons - Advanced
|
||||||
|
- 100 lessons - Master
|
||||||
|
|
||||||
|
### Implementation ✅
|
||||||
|
|
||||||
|
1. **Update `LessonEngine.getProgressStats()`** ✅
|
||||||
|
- Added `currentMilestone` and `nextMilestone` fields
|
||||||
|
- Added `milestonesReached: number[]`
|
||||||
|
- Added `progressToNext` percentage
|
||||||
|
|
||||||
|
2. **Update Progress UI** ✅
|
||||||
|
- Added milestone dots with visual states (reached, current, next)
|
||||||
|
- Animate milestone completion
|
||||||
|
- Show current milestone badge
|
||||||
|
|
||||||
|
3. **Add Milestone Celebration**
|
||||||
|
- Confetti/animation on reaching milestones
|
||||||
|
- Achievement unlocks in sidebar
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Phase 2: New Lessons (34 needed to reach 100)
|
||||||
|
|
||||||
|
### Priority 1: Expand Existing Modules (+15 lessons)
|
||||||
|
|
||||||
|
#### CSS Colors (+3)
|
||||||
|
- Gradients (linear-gradient)
|
||||||
|
- Color functions (hsl, rgb)
|
||||||
|
- Opacity and transparency
|
||||||
|
|
||||||
|
#### Typography (+3)
|
||||||
|
- Web fonts (@font-face)
|
||||||
|
- Text shadows
|
||||||
|
- Letter/word spacing
|
||||||
|
|
||||||
|
#### Responsive (+3)
|
||||||
|
- Container queries
|
||||||
|
- Aspect ratio
|
||||||
|
- Clamp() for fluid typography
|
||||||
|
|
||||||
|
#### Transitions & Animations (+3)
|
||||||
|
- Keyframe animations
|
||||||
|
- Animation timing functions
|
||||||
|
- Transform origin
|
||||||
|
|
||||||
|
#### Tables (+3)
|
||||||
|
- Table styling (borders, spacing)
|
||||||
|
- Responsive tables
|
||||||
|
- Table accessibility
|
||||||
|
|
||||||
|
### Priority 2: New CSS Modules (+12 lessons)
|
||||||
|
|
||||||
|
#### Filters & Effects (4 lessons)
|
||||||
|
- CSS filters (blur, brightness, contrast)
|
||||||
|
- Backdrop filters
|
||||||
|
- Mix-blend-mode
|
||||||
|
- Box shadows advanced
|
||||||
|
|
||||||
|
#### Positioning (4 lessons)
|
||||||
|
- Relative positioning
|
||||||
|
- Absolute positioning
|
||||||
|
- Fixed/sticky positioning
|
||||||
|
- Z-index stacking
|
||||||
|
|
||||||
|
#### Pseudo-elements (4 lessons)
|
||||||
|
- ::before and ::after
|
||||||
|
- ::first-letter and ::first-line
|
||||||
|
- ::marker for lists
|
||||||
|
- Content property
|
||||||
|
|
||||||
|
### Priority 3: New HTML Modules (+7 lessons)
|
||||||
|
|
||||||
|
#### Semantic Structure (3 lessons)
|
||||||
|
- Article vs Section
|
||||||
|
- Header/Footer/Main
|
||||||
|
- Nav and Aside
|
||||||
|
|
||||||
|
#### Media Elements (2 lessons)
|
||||||
|
- Picture element (responsive images)
|
||||||
|
- Audio/Video basics
|
||||||
|
|
||||||
|
#### Accessibility (2 lessons)
|
||||||
|
- ARIA labels
|
||||||
|
- Skip links
|
||||||
|
- Focus management
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## MDN Topics Reference
|
||||||
|
|
||||||
|
### CSS Topics from MDN (prioritized for interactive lessons)
|
||||||
|
|
||||||
|
**Layout Systems:**
|
||||||
|
- [x] Flexbox (covered)
|
||||||
|
- [x] Grid (covered)
|
||||||
|
- [ ] Multi-column layout
|
||||||
|
- [ ] Positioned layout (z-index, stacking)
|
||||||
|
|
||||||
|
**Visual Effects:**
|
||||||
|
- [x] Colors (partially covered)
|
||||||
|
- [ ] Filters (blur, brightness, contrast, drop-shadow)
|
||||||
|
- [ ] Blend modes (mix-blend-mode, background-blend-mode)
|
||||||
|
- [ ] Masking and clipping
|
||||||
|
- [ ] Shapes (shape-outside)
|
||||||
|
|
||||||
|
**Typography:**
|
||||||
|
- [x] Basic text (covered)
|
||||||
|
- [ ] Web fonts (@font-face)
|
||||||
|
- [ ] Variable fonts
|
||||||
|
- [ ] Text decoration advanced
|
||||||
|
|
||||||
|
**Animations:**
|
||||||
|
- [x] Transitions (covered)
|
||||||
|
- [ ] Keyframe animations
|
||||||
|
- [ ] Scroll-driven animations (experimental)
|
||||||
|
- [ ] View transitions
|
||||||
|
|
||||||
|
**Advanced:**
|
||||||
|
- [x] Custom properties (covered in units-variables)
|
||||||
|
- [ ] Container queries
|
||||||
|
- [ ] Anchor positioning (new)
|
||||||
|
- [ ] Logical properties (for RTL support)
|
||||||
|
|
||||||
|
### HTML Topics from MDN
|
||||||
|
|
||||||
|
**Structural:**
|
||||||
|
- [x] Basic elements (covered)
|
||||||
|
- [x] Figure/figcaption (covered)
|
||||||
|
- [ ] Article vs section semantics
|
||||||
|
- [ ] Template element
|
||||||
|
|
||||||
|
**Interactive:**
|
||||||
|
- [x] Details/Summary (covered)
|
||||||
|
- [x] Dialog (have JSON, not active)
|
||||||
|
- [ ] Datalist (have JSON, not active)
|
||||||
|
- [ ] Progress/Meter (have JSON, not active)
|
||||||
|
|
||||||
|
**Forms:**
|
||||||
|
- [x] Basic forms (covered)
|
||||||
|
- [x] Validation (covered)
|
||||||
|
- [x] Fieldset (have JSON, not active)
|
||||||
|
- [ ] Input types exploration
|
||||||
|
|
||||||
|
**Media:**
|
||||||
|
- [x] SVG basics (covered)
|
||||||
|
- [ ] Picture element
|
||||||
|
- [ ] srcset and sizes
|
||||||
|
- [ ] Audio/Video
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Inactive Lesson Files (Ready to Activate)
|
||||||
|
|
||||||
|
These lesson files exist but aren't in the active module list:
|
||||||
|
|
||||||
|
| File | Lessons | Topic |
|
||||||
|
|------|---------|-------|
|
||||||
|
| 24-html-progress-meter.json | 3 | Progress/Meter elements |
|
||||||
|
| 25-html-datalist.json | 2 | Datalist for autocomplete |
|
||||||
|
| 27-html-dialog.json | 2 | Native dialog element |
|
||||||
|
| 28-html-forms-fieldset.json | 3 | Fieldset/legend grouping |
|
||||||
|
| 31-html-marquee.json | 3 | Marquee (deprecated but fun) |
|
||||||
|
| **Total** | **13** | |
|
||||||
|
|
||||||
|
**Quick Win:** Activating these adds 13 lessons immediately → 79 total
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Implementation Order
|
||||||
|
|
||||||
|
### Week 1: Foundation
|
||||||
|
1. Design milestone UI component
|
||||||
|
2. Implement milestone progress system
|
||||||
|
3. Add milestone celebrations
|
||||||
|
|
||||||
|
### Week 2: Quick Wins
|
||||||
|
4. Activate 5 inactive HTML modules (+13 lessons)
|
||||||
|
5. Test and fix translations
|
||||||
|
|
||||||
|
### Week 3-4: New Content
|
||||||
|
6. Create Filters & Effects module (+4)
|
||||||
|
7. Create Positioning module (+4)
|
||||||
|
8. Expand existing modules (+7)
|
||||||
|
|
||||||
|
### Final Polish
|
||||||
|
9. Reach 100 lessons milestone
|
||||||
|
10. Add milestone achievements to sidebar
|
||||||
|
11. Update landing page messaging
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Technical Notes
|
||||||
|
|
||||||
|
### Milestone Data Structure
|
||||||
|
|
||||||
|
```js
|
||||||
|
const MILESTONES = [1, 5, 10, 20, 30, 50, 75, 100];
|
||||||
|
|
||||||
|
function getMilestoneProgress(completed) {
|
||||||
|
const reached = MILESTONES.filter(m => completed >= m);
|
||||||
|
const current = reached[reached.length - 1] || 0;
|
||||||
|
const next = MILESTONES.find(m => m > completed) || 100;
|
||||||
|
|
||||||
|
return {
|
||||||
|
current,
|
||||||
|
next,
|
||||||
|
reached,
|
||||||
|
percentToNext: ((completed - current) / (next - current)) * 100
|
||||||
|
};
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
### Progress Display Update
|
||||||
|
|
||||||
|
```html
|
||||||
|
<div class="milestone-progress">
|
||||||
|
<div class="milestones">
|
||||||
|
<span class="milestone reached" data-value="1">1</span>
|
||||||
|
<span class="milestone reached" data-value="5">5</span>
|
||||||
|
<span class="milestone current" data-value="10">10</span>
|
||||||
|
<span class="milestone" data-value="20">20</span>
|
||||||
|
<!-- ... -->
|
||||||
|
</div>
|
||||||
|
<div class="progress-bar">
|
||||||
|
<div class="progress-fill" style="width: 35%"></div>
|
||||||
|
</div>
|
||||||
|
<span class="progress-label">12 of 100 lessons</span>
|
||||||
|
</div>
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Success Metrics
|
||||||
|
|
||||||
|
- [ ] 100 total lessons
|
||||||
|
- [ ] Milestone system implemented
|
||||||
|
- [ ] All 6 languages have translations
|
||||||
|
- [ ] Achievement celebrations working
|
||||||
|
- [ ] Mobile responsive milestone UI
|
||||||
61
flake.lock
generated
Normal file
61
flake.lock
generated
Normal file
@@ -0,0 +1,61 @@
|
|||||||
|
{
|
||||||
|
"nodes": {
|
||||||
|
"flake-utils": {
|
||||||
|
"inputs": {
|
||||||
|
"systems": "systems"
|
||||||
|
},
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1731533236,
|
||||||
|
"narHash": "sha256-l0KFg5HjrsfsO/JpG+r7fRrqm12kzFHyUHqHCVpMMbI=",
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"rev": "11707dc2f618dd54ca8739b309ec4fc024de578b",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "numtide",
|
||||||
|
"repo": "flake-utils",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"nixpkgs": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1768564909,
|
||||||
|
"narHash": "sha256-Kell/SpJYVkHWMvnhqJz/8DqQg2b6PguxVWOuadbHCc=",
|
||||||
|
"owner": "NixOS",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"rev": "e4bae1bd10c9c57b2cf517953ab70060a828ee6f",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "NixOS",
|
||||||
|
"ref": "nixos-unstable",
|
||||||
|
"repo": "nixpkgs",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": {
|
||||||
|
"inputs": {
|
||||||
|
"flake-utils": "flake-utils",
|
||||||
|
"nixpkgs": "nixpkgs"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"systems": {
|
||||||
|
"locked": {
|
||||||
|
"lastModified": 1681028828,
|
||||||
|
"narHash": "sha256-Vy1rq5AaRuLzOxct8nz4T6wlgyUR7zLU309k9mBC768=",
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"rev": "da67096a3b9bf56a91d16901293e51ba5b49a27e",
|
||||||
|
"type": "github"
|
||||||
|
},
|
||||||
|
"original": {
|
||||||
|
"owner": "nix-systems",
|
||||||
|
"repo": "default",
|
||||||
|
"type": "github"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"root": "root",
|
||||||
|
"version": 7
|
||||||
|
}
|
||||||
@@ -9,13 +9,14 @@
|
|||||||
outputs = { self, nixpkgs, flake-utils }:
|
outputs = { self, nixpkgs, flake-utils }:
|
||||||
flake-utils.lib.eachDefaultSystem (system:
|
flake-utils.lib.eachDefaultSystem (system:
|
||||||
let
|
let
|
||||||
pkgs = nixpkgs.legacyPackages.${system};
|
pkgs = import nixpkgs { inherit system; config.allowUnfree = true; };
|
||||||
in {
|
in {
|
||||||
devShells.default = pkgs.mkShell {
|
devShells.default = pkgs.mkShell {
|
||||||
buildInputs = with pkgs; [
|
buildInputs = with pkgs; [
|
||||||
nodejs_20
|
nodejs_20
|
||||||
nodePackages.npm
|
nodePackages.npm
|
||||||
gnumake
|
gnumake
|
||||||
|
claude-code
|
||||||
];
|
];
|
||||||
|
|
||||||
shellHook = ''
|
shellHook = ''
|
||||||
|
|||||||
@@ -1,548 +1,257 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||||
"id": "css-basic-selectors",
|
"id": "css-basic-selectors",
|
||||||
"title": "CSS Selectors",
|
"title": "CSS Basics",
|
||||||
"description": "CSS selectors are the foundation of styling web pages, allowing you to target specific HTML elements for styling. This module introduces fundamental selector types including element type selectors, class selectors, ID selectors, and the universal selector.",
|
"description": "Learn the fundamental building blocks of CSS: properties, values, and selectors. This module teaches you the syntax rules that every CSS declaration follows.",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "introduction-to-selectors",
|
"id": "css-properties",
|
||||||
"title": "What's a Selector?",
|
"title": "CSS Properties",
|
||||||
"description": "A CSS selector is the first part of a CSS rule that tells the browser which HTML elements should receive the styles defined in the declaration block. Selectors are essentially patterns that match against elements in your HTML document. Understanding selectors is fundamental because they determine which elements your CSS rules will affect. The element or elements targeted by a selector are referred to as the 'subject of the selector.' When writing a CSS rule, you first specify the selector, followed by curly braces that contain the style declarations.<br/>For example, to change the text color of elements, you can use the <kbd>color</kbd> property within your declaration block.<br><br><pre>/* Element selector */\np {\n color: orangered;\n /* │ └─── Indicates the value of the expression\n │ \n └─────────── Indicates the property of the expression */\n}</pre>",
|
"description": "CSS styles elements using <strong>declarations</strong> - pairs of properties and values. Every declaration follows the same pattern:<br><br><pre>property: value;</pre><br>The <strong>property</strong> is what you want to change (like <kbd>color</kbd> or <kbd>background</kbd>). The <strong>value</strong> is what you're setting it to. A colon separates them, and a semicolon ends the line.<br><br>Values come in different types:<br>• <strong>Keywords:</strong> <kbd>red</kbd>, <kbd>bold</kbd>, <kbd>center</kbd><br>• <strong>Numbers with units:</strong> <kbd>16px</kbd>, <kbd>2rem</kbd>, <kbd>100%</kbd><br>• <strong>Colors:</strong> <kbd>steelblue</kbd>, <kbd>#ff0000</kbd>",
|
||||||
"task": "Write a CSS rule using a type selector that targets all paragraph elements <kbd>p</kbd> in the document. Make the text blue by setting the <kbd>color</kbd> property to <kbd>blue</kbd>.",
|
"task": "Complete the declaration by adding <kbd>color: coral;</kbd> to change the text color.",
|
||||||
"previewHTML": "<h1>Introduction to CSS Selectors</h1>\n<p>This paragraph should turn blue.</p>\n<div>This div element should remain unchanged.</div>\n<p>This second paragraph should also turn blue.</p>",
|
"previewHTML": "<p class=\"text\">This text should turn coral.</p>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } .text { font-size: 1.25rem; }",
|
||||||
"sandboxCSS": "h1, p, div { padding: 8px; margin-bottom: 10px; border: 1px dashed #ccc; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Write a type selector to target all paragraph elements */\n",
|
"codePrefix": ".text {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "\n}",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"solution": "p { color: blue }",
|
"solution": "color: coral;",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "property_value",
|
||||||
"value": "^p\\s*{",
|
"value": { "property": "color", "expected": "coral" },
|
||||||
"message": "Start your rule with <kbd>p { … }</kbd> to select all paragraph elements",
|
"message": "Add <kbd>color: coral;</kbd>"
|
||||||
"options": {
|
}
|
||||||
"caseSensitive": false
|
]
|
||||||
}
|
},
|
||||||
},
|
{
|
||||||
|
"id": "multiple-properties",
|
||||||
|
"title": "Multiple Properties",
|
||||||
|
"description": "A rule can have multiple declarations. Each one goes on its own line, and each needs a semicolon at the end:<br><br><pre>.box {<br> background: gold;<br> color: navy;<br> padding: 1rem;<br>}</pre><br>The order usually doesn't matter - CSS applies them all. When properties conflict, the last one wins.",
|
||||||
|
"task": "Add two declarations: <kbd>background: lavender;</kbd> and <kbd>padding: 1rem;</kbd>",
|
||||||
|
"previewHTML": "<div class=\"card\">A styled card with background and padding.</div>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } .card { border-radius: 8px; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".card {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "background: lavender;\n padding: 1rem;",
|
||||||
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "color:",
|
"value": { "property": "background", "expected": "lavender" },
|
||||||
"message": "Include the <kbd>color:</kbd> property in your CSS rule"
|
"message": "Add <kbd>background: lavender;</kbd>"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "blue",
|
|
||||||
"message": "Set the color value to <kbd>blue</kbd>"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"property": "color",
|
"message": "Add <kbd>padding: 1rem;</kbd>"
|
||||||
"expected": "blue"
|
|
||||||
},
|
|
||||||
"message": "Use <kbd>color: blue</kbd> to set the text color"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "p\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "type-selectors",
|
"id": "type-selectors",
|
||||||
"title": "Type Selectors",
|
"title": "Type Selectors",
|
||||||
"description": "Type selectors (also called tag name selectors or element selectors) target HTML elements based on their tag name. For example, <kbd>p</kbd> selects all paragraph elements, <kbd>h1</kbd> selects all level-one headings, and <kbd>div</kbd> selects all division elements. Type selectors are the most fundamental way to select elements, applying styles consistently to all instances of a particular HTML element throughout your document. You can define a variety of CSS properties with type selectors, such as <kbd>color</kbd> for text color, <kbd>background-color</kbd> for the background, and <kbd>font-weight</kbd> for text emphasis. They provide a broad approach for styling your page and are often the starting point for more specific styling using other selector types.",
|
"description": "A <strong>selector</strong> tells the browser which elements to style. The simplest selector is a <strong>type selector</strong> — just the HTML tag name.<br><br><pre>p {<br> color: steelblue;<br>}</pre><br>This rule targets every <kbd><p></kbd> element on the page. Type selectors are great for setting base styles.",
|
||||||
"task": "Write three separate CSS rules using type selectors to target specific HTML elements: make <kbd>h2</kbd> headings <kbd>purple</kbd>, give <kbd>span</kbd> elements a <kbd>yellow</kbd> background, and make <kbd>strong</kbd> elements <kbd>red</kbd>.",
|
"task": "Style all paragraphs. Write a rule with <kbd>p</kbd> as the selector and set <kbd>color: steelblue</kbd>.",
|
||||||
"previewHTML": "<h2>Type Selectors Example</h2>\n<p>Regular paragraph text <span>with a highlighted span</span> that should have a yellow background.</p>\n<p>Another paragraph with <strong>strong important text</strong> that should be red.</p>\n<h2>Another Heading</h2>",
|
"previewHTML": "<article>\n <h2>Fresh Roasted Coffee</h2>\n <p>Our beans are sourced from small farms in Colombia and Ethiopia.</p>\n <p>Each batch is roasted weekly to ensure peak freshness.</p>\n</article>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } h2 { margin: 0 0 1rem; }",
|
||||||
"sandboxCSS": "h2, p, span, strong { padding: 3px; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Write three separate type selectors below */\n\n",
|
"codePrefix": "",
|
||||||
"initialCode": "/* 1. Make h2 headings purple */\n\n\n/* 2. Give span elements a yellow background */\n\n\n/* 3. Make strong elements red */\n",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"solution": "/* 1. Make h2 headings purple */\nh2 {\n color: purple;\n}\n\n/* 2. Give span elements a yellow background */\nspan {\n background-color: yellow;\n}\n\n/* 3. Make strong elements red */\nstrong {\n color: red;\n}",
|
"solution": "p {\n color: steelblue;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^h2\\s*{",
|
"value": "p\\s*\\{",
|
||||||
"message": "Include an <kbd>h2 { … }</kbd> selector"
|
"message": "Start with <kbd>p {</kbd> to select paragraphs"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "color", "expected": "steelblue" },
|
||||||
"property": "color",
|
"message": "Set <kbd>color: steelblue</kbd>"
|
||||||
"expected": "purple"
|
}
|
||||||
},
|
]
|
||||||
"message": "Set the <kbd>color</kbd> property to <kbd>purple</kbd> for h2 elements"
|
},
|
||||||
},
|
{
|
||||||
|
"id": "styling-links",
|
||||||
|
"title": "Styling Links",
|
||||||
|
"description": "Type selectors work for any HTML element. The <kbd>a</kbd> selector targets all links on a page.<br><br>Links have a default blue color and underline. You can change both with CSS — use <kbd>color</kbd> for the text and <kbd>text-decoration: none</kbd> to remove the underline.",
|
||||||
|
"task": "Style the navigation links. Write a rule with <kbd>a</kbd> as the selector and set <kbd>color: coral</kbd>.",
|
||||||
|
"previewHTML": "<nav>\n <a href=\"#\">Home</a>\n <a href=\"#\">Menu</a>\n <a href=\"#\">About</a>\n <a href=\"#\">Contact</a>\n</nav>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } nav { display: flex; gap: 1.5rem; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "a {\n color: coral;\n}",
|
||||||
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "h2\\s*{[^}]*}",
|
"value": "a\\s*\\{",
|
||||||
"message": "Make sure to close your h2 rule with a closing brace <kbd>}</kbd>"
|
"message": "Start with <kbd>a {</kbd> to select links"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^span\\s*{",
|
|
||||||
"message": "Include a <kbd>span { … }</kbd> selector"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "color", "expected": "coral" },
|
||||||
"property": "background-color",
|
"message": "Set <kbd>color: coral</kbd>"
|
||||||
"expected": "yellow"
|
|
||||||
},
|
|
||||||
"message": "Set a <kbd>background-color: yellow</kbd> for span elements"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "span\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your span rule with a closing brace <kbd>}</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^strong\\s*{",
|
|
||||||
"message": "Include a <kbd>strong { … }</kbd> selector"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "strong\\s*{\\s*color:\\s*red;[^}]*}",
|
|
||||||
"message": "Set the <kbd>color: red</kbd> for strong elements"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "class-selectors",
|
"id": "class-selectors",
|
||||||
"title": "Class Selectors",
|
"title": "Class Selectors",
|
||||||
"description": "Class selectors target elements with a specific class attribute value. They begin with a dot (.) followed by the class name. Classes are powerful because they allow you to apply the same styles to multiple elements regardless of their type. An HTML element can have multiple classes (separated by spaces in the class attribute), and a class can be applied to any number of elements. When using class selectors, you can apply properties like <kbd>background-color</kbd> to set the background color of elements, and <kbd>font-weight</kbd> to control text thickness, making text bold or normal. This flexibility makes class selectors one of the most commonly used methods for applying styles in CSS, allowing for modular and reusable styling across your website.",
|
"description": "Type selectors style <em>all</em> elements of that type. But what if you want to style just some of them?<br><br><strong>Class selectors</strong> target elements with a specific <kbd>class</kbd> attribute. They start with a dot:<br><br><pre>.badge {<br> background: coral;<br>}</pre><br>This styles only elements with <kbd>class=\"badge\"</kbd>.",
|
||||||
"task": "Create a CSS rule using a class selector that targets elements with the class <kbd>highlight</kbd>. Give these elements a <kbd>yellow</kbd> background and <kbd>bold</kbd> text.",
|
"task": "Style the notification badge. Write a rule with <kbd>.badge</kbd> as the selector and set <kbd>background: tomato</kbd>.",
|
||||||
"previewHTML": "<h2>Using Class Selectors</h2>\n<p>This is a regular paragraph, but <span class=\"highlight\">this span has the highlight class</span> applied to it.</p>\n<p class=\"highlight\">This entire paragraph has the highlight class.</p>\n<ul>\n <li>Regular list item</li>\n <li class=\"highlight\">This list item is highlighted</li>\n</ul>",
|
"previewHTML": "<header>\n <h1>Dashboard</h1>\n <span class=\"badge\">3</span>\n</header>\n<p>You have new notifications waiting.</p>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } header { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem; } h1 { margin: 0; font-size: 1.5rem; } .badge { color: white; padding: 0.25rem 0.5rem; border-radius: 999px; font-size: 0.875rem; } p { color: #555; margin: 0; }",
|
||||||
"sandboxCSS": "h2, p, li { padding: 5px; margin-bottom: 10px; }",
|
|
||||||
"codePrefix": "/* Create a class selector for elements with the 'highlight' class */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^\\.highlight\\s*{",
|
|
||||||
"message": "Start your rule with <kbd>.highlight { … }</kbd> to create a class selector",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Include the <kbd>background-color:</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "yellow"
|
|
||||||
},
|
|
||||||
"message": "Set the background color to <kbd>yellow</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "font-weight:",
|
|
||||||
"message": "Include the <kbd>font-weight:</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "font-weight",
|
|
||||||
"expected": "bold"
|
|
||||||
},
|
|
||||||
"message": "Set the font-weight to <kbd>bold</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "\\.highlight\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "multiple-classes",
|
|
||||||
"title": "Multiple Classes",
|
|
||||||
"description": "HTML elements can have multiple classes applied simultaneously, allowing for composable and modular CSS designs. When an element has multiple classes, it will receive styles from all matching class selectors. This approach enables you to build a library of reusable CSS classes that can be combined in different ways. You can also target elements that have a specific combination of classes by chaining class selectors together without spaces (e.g., <kbd>.class1.class2</kbd>). When styling these elements, you might use properties like <kbd>border-color</kbd> to change the color of element borders, and <kbd>background-color</kbd> to set the background color of elements. This technique lets you create conditional styles that only apply when certain classes appear together.",
|
|
||||||
"task": "Complete the CSS rule that targets elements with both <kbd>card</kbd> and <kbd>featured</kbd> classes by chaining the selectors. Set the border-color to gold and the background-color to lemonchiffon to make featured cards stand out.",
|
|
||||||
"previewHTML": "<h2>Multiple Class Combinations</h2>\n<div class=\"card\">Regular Card</div>\n<div class=\"card featured\">Featured Card</div>\n<div class=\"featured\">Just Featured (not a card)</div>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } .card { border: 2px solid gray; padding: 15px; margin-bottom: 10px; border-radius: 5px; }",
|
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* The .card class already has basic styling */\n/* Now target elements with BOTH classes: 'card' AND 'featured' */\n",
|
"codePrefix": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"solution": ".card.featured { border-color: gold; background-color: lemonchiffon; }",
|
"solution": ".badge {\n background: tomato;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^\\.card\\.featured\\s*{",
|
"value": "\\.badge\\s*\\{",
|
||||||
"message": "Chain the selectors as <kbd>.card.featured</kbd> (no space between them)",
|
"message": "Start with <kbd>.badge {</kbd> (don't forget the dot!)"
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "border-color:",
|
|
||||||
"message": "Include the <kbd>border-color</kbd> property"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "background", "expected": "tomato" },
|
||||||
"property": "border-color",
|
"message": "Set <kbd>background: tomato</kbd>"
|
||||||
"expected": "gold"
|
|
||||||
},
|
|
||||||
"message": "Set the border color to <kbd>gold</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "\\.card\\.featured\\s*{[^}]*;",
|
|
||||||
"message": "Make sure to end your CSS rule with a semicolon <kbd>;</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Include the <kbd>background-color</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "lemonchiffon"
|
|
||||||
},
|
|
||||||
"message": "Set the background color to <kbd>lemonchiffon</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "\\.card\\.featured\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "class-with-type",
|
"id": "button-variants",
|
||||||
"title": "Combining Types",
|
"title": "Button Variants",
|
||||||
"description": "You can combine type selectors with class selectors to target specific HTML elements that have a certain class. This creates a more specific selector that only matches when both conditions are true: the element is of the specified type AND it has the specified class. For example, <kbd>p.note</kbd> would select paragraph elements with the class <kbd>note</kbd>, but would not select divs or spans with that same class. You can style these combined selections using properties like <kbd>background-color</kbd> to set a colored background for your elements. This approach allows you to apply different styles to the same class when it appears on different element types.",
|
"description": "Elements can have multiple classes. When you chain class selectors without spaces, you target elements that have <em>all</em> those classes:<br><br><pre>.btn.primary {<br> background: steelblue;<br>}</pre><br>This targets elements with both <kbd>class=\"btn primary\"</kbd>, not just <kbd>.btn</kbd> or just <kbd>.primary</kbd>.",
|
||||||
"task": "Create a CSS rule that specifically targets <kbd><span></kbd> elements with the class <kbd>highlight</kbd>. Make those elements have an orange background, while other elements with the highlight class remain untouched.",
|
"task": "Style the primary button. Write a rule with <kbd>.btn.primary</kbd> as the selector and set <kbd>background: steelblue</kbd>.",
|
||||||
"previewHTML": "<h2>Type and Class Combinations</h2>\n<p>This paragraph has a <span class=\"highlight\">highlighted span</span> that should have an orange background.</p>\n<p class=\"highlight\">This paragraph has the highlight class but should NOT have an orange background.</p>",
|
"previewHTML": "<div class=\"actions\">\n <button class=\"btn\">Cancel</button>\n <button class=\"btn primary\">Save Changes</button>\n</div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } .highlight { font-weight: bold; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } .actions { display: flex; gap: 0.75rem; } .btn { padding: 0.5rem 1rem; border: none; border-radius: 6px; font-size: 1rem; cursor: pointer; background: #e0e0e0; color: #333; }",
|
||||||
"sandboxCSS": "h2, p, span { padding: 5px; }",
|
|
||||||
"codePrefix": "/* The .highlight class already sets font-weight to bold */\n/* Now target ONLY span elements with the highlight class */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^span\\.highlight\\s*{",
|
|
||||||
"message": "Use <kbd>span.highlight</kbd> selector (no space between element and class)",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Include the <kbd>background-color</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "orange"
|
|
||||||
},
|
|
||||||
"message": "Set the background color to <kbd>orange</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "span\\.highlight\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "id-selectors",
|
|
||||||
"title": "ID Selectors",
|
|
||||||
"description": "ID selectors target elements with a specific id attribute. They begin with a hash/pound sign (#) followed by the ID name. Unlike classes, IDs must be unique within a document—each ID value should be used only once per page. ID selectors have higher specificity than class or element selectors, meaning they override those selectors when conflicts arise. When styling with ID selectors, you can use properties like <kbd>color</kbd> to define text color, and <kbd>text-decoration</kbd> to control the appearance of text, such as adding underlines to elements. Because of their uniqueness requirement, IDs are best used for one-of-a-kind elements like page headers, main navigation, or specific unique components that appear only once on a page.",
|
|
||||||
"task": "Create a CSS rule with an ID selector that targets the element with the ID <kbd>main-title</kbd>. Set its color to purple and add an underline with <kbd>text-decoration: underline</kbd>.",
|
|
||||||
"previewHTML": "<h1 id=\"main-title\">Main Page Title</h1>\n<p>Regular paragraph content.</p>\n<h2>Secondary Heading</h2>\n<p id=\"intro\">Introduction paragraph (different ID).</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
|
||||||
"sandboxCSS": "h1, h2, p { padding: 8px; margin-bottom: 10px; border: 1px dashed #ccc; }",
|
|
||||||
"codePrefix": "/* Create an ID selector to target the element with id=\"main-title\" */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^#main-title\\s*{",
|
|
||||||
"message": "Start your rule with <kbd>#main-title</kbd> to create an ID selector",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "color:",
|
|
||||||
"message": "Include the <kbd>color</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "color",
|
|
||||||
"expected": "purple"
|
|
||||||
},
|
|
||||||
"message": "Set the color to <kbd>purple</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "text-decoration:",
|
|
||||||
"message": "Include the <kbd>text-decoration</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "text-decoration",
|
|
||||||
"expected": "underline"
|
|
||||||
},
|
|
||||||
"message": "Set the text-decoration to <kbd>underline</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "#main-title\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "id-with-type",
|
|
||||||
"title": "Type + ID",
|
|
||||||
"description": "Similar to how you can combine type and class selectors, you can also combine type selectors with ID selectors. For example, <kbd>h1#title</kbd> targets an h1 element with the ID 'title'. When using this combined approach, you can apply CSS properties like <kbd>font-style</kbd> to control the slant of the text, making it italic or normal. While this selector combination is more specific than using just the ID selector, it's often unnecessary since IDs should already be unique in a document. However, this technique can be useful for improving code readability or when you want to emphasize that a particular ID should only appear on a specific element type.",
|
|
||||||
"task": "Create a CSS rule that combines a type selector with an ID selector to target specifically a paragraph element with the ID <kbd>special</kbd>. Set its font style to italic.",
|
|
||||||
"previewHTML": "<h2 id=\"special\">Heading with ID \"special\" (should NOT be affected)</h2>\n<p id=\"special\">Paragraph with ID \"special\" (should become italic)</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
|
||||||
"sandboxCSS": "h2, p { padding: 8px; margin-bottom: 10px; border: 1px dashed #ccc; }",
|
|
||||||
"codePrefix": "/* Create a combined type+ID selector for a paragraph with id=\"special\" */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^p#special\\s*{",
|
|
||||||
"message": "Use <kbd>p#special</kbd> to target paragraphs with ID <kbd>special</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "font-style:",
|
|
||||||
"message": "Include the <kbd>font-style</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "font-style",
|
|
||||||
"expected": "italic"
|
|
||||||
},
|
|
||||||
"message": "Set the font-style to <kbd>italic</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "p#special\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "selector-lists",
|
|
||||||
"title": "Selector Lists",
|
|
||||||
"description": "When multiple elements need the same styling, you can group them together using a selector list (also known as grouping selectors). Selector lists are created by separating individual selectors with commas. This approach reduces repetition in your CSS, making it more maintainable and efficient. For example, <kbd>h1, h2, h3 { color: blue; }</kbd> applies the same blue color to all three heading levels. When styling multiple selectors at once, you can apply properties like <kbd>background-color</kbd> to set the background, <kbd>border-left</kbd> to create a left border with a specific thickness, style, and color, and <kbd>padding-left</kbd> to create space between the content and the left border. Whitespace around commas is optional, and each selector in the list can be any valid selector type-elements, classes, IDs, or even more complex selectors.",
|
|
||||||
"task": "Create a selector list that applies the same styles to three different elements: paragraphs with class <kbd>note</kbd>, list items with class <kbd>important</kbd>, and the element with ID <kbd>summary</kbd>. Give them a <kbd>lightyellow</kbd> background, a <kbd>gold</kbd> left border, and some left <kbd>padding</kbd>.",
|
|
||||||
"previewHTML": "<p class=\"note\">This is a note paragraph.</p>\n<ul>\n <li>Regular list item</li>\n <li class=\"important\">Important list item</li>\n</ul>\n<div id=\"summary\">Summary section</div>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
|
||||||
"sandboxCSS": "p, li, div { padding: 8px; margin-bottom: 8px; border: 1px dashed gray; }",
|
|
||||||
"codePrefix": "/* Create a selector list to apply the same styles to multiple different elements */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"solution": "p.note,\nli.important,\n#summary {\n background-color: lightyellow;\n border-left: 3px solid gold;\n padding-left: 10px;\n}",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "p.note",
|
|
||||||
"message": "Include <kbd>p.note</kbd> in your selector list",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "li.important",
|
|
||||||
"message": "Include <kbd>li.important</kbd> in your selector list",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "#summary",
|
|
||||||
"message": "Include <kbd>#summary</kbd> in your selector list",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "(p\\.note|li\\.important|#summary)\\s*,\\s*(p\\.note|li\\.important|#summary)\\s*,\\s*(p\\.note|li\\.important|#summary)",
|
|
||||||
"message": "Create a comma-separated list with all three selectors",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Include the <kbd>background-color</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "lightyellow"
|
|
||||||
},
|
|
||||||
"message": "Set the background color to <kbd>lightyellow</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "border-left:",
|
|
||||||
"message": "Include the <kbd>border-left</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "border-left",
|
|
||||||
"expected": "3px solid gold"
|
|
||||||
},
|
|
||||||
"message": "Use <kbd>border-left: 3px solid gold</kbd> to create a left border"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "padding-left:",
|
|
||||||
"message": "Include the <kbd>padding-left</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "padding-left",
|
|
||||||
"expected": "10px"
|
|
||||||
},
|
|
||||||
"message": "Use <kbd>padding-left: 10px</kbd> to add left padding"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "universal-selector",
|
|
||||||
"title": "Universal (*)",
|
|
||||||
"description": "The universal selector is denoted by an asterisk (*) and matches any element of any type. It selects everything in the document or, when combined with other selectors, everything within a specific context. For example, <kbd>* { margin: 0; }</kbd> removes margins from all elements, while <kbd>article *</kbd> selects all elements inside article elements. When using the universal selector in combination with other selectors, you can apply properties like <kbd>margin</kbd> to control the spacing around elements. The universal selector is powerful but should be used carefully due to its broad impact. It's commonly used in CSS resets, to override default browser styling, or to target all children of a particular element.",
|
|
||||||
"task": "Use the universal selector to remove margins from all elements inside the container div. Create a rule using <kbd>div.container *</kbd> as the selector and set <kbd>margin: 0</kbd>.",
|
|
||||||
"previewHTML": "<div class=\"container\">\n <h2>Inside Container</h2>\n <p>This paragraph is inside the container.</p>\n <ul>\n <li>List item inside container</li>\n </ul>\n</div>\n<p>This paragraph is outside the container and should not be affected.</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } div.container { border: 2px solid navy; padding: 15px; background-color: lavender; } h2, p, ul, li { margin: 15px 0; }",
|
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Use the universal selector to target all elements inside the container */\n",
|
"codePrefix": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
|
"solution": ".btn.primary {\n background: steelblue;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^div\\.container\\s+\\*\\s*{",
|
"value": "\\.btn\\.primary\\s*\\{",
|
||||||
"message": "Use <kbd>div.container *</kbd> selector (with a space between container and *)",
|
"message": "Use <kbd>.btn.primary {</kbd> (no space between classes)"
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "margin:",
|
|
||||||
"message": "Include the <kbd>margin</kbd> property"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "background", "expected": "steelblue" },
|
||||||
"property": "margin",
|
"message": "Set <kbd>background: steelblue</kbd>"
|
||||||
"expected": "0"
|
|
||||||
},
|
|
||||||
"message": "Set margin to <kbd>0</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "div\\.container\\s+\\*\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "specificity-basics",
|
"id": "specific-elements",
|
||||||
"title": "Specificity",
|
"title": "Targeting Specific Elements",
|
||||||
"description": "CSS specificity determines which styles take precedence when multiple conflicting rules target the same element. Specificity follows a hierarchical system: inline styles have the highest specificity, followed by ID selectors, then class/attribute/pseudo-class selectors, and finally element/pseudo-element selectors. This can be conceptualized as a four-part score (inline, ID, class, element). When creating multiple rules that may target the same elements, you can use the <kbd>color</kbd> property to set text colors, and specificity will determine which color is actually applied. Understanding specificity is crucial for predictable styling and debugging CSS conflicts. When two selectors have equal specificity, the one that comes last in the stylesheet wins.",
|
"description": "Sometimes you want a class to look different on different elements. Combine a type selector with a class selector (no space) to be more specific:<br><br><pre>a.btn {<br> text-decoration: none;<br>}</pre><br>This styles only <kbd><a></kbd> elements with the <kbd>btn</kbd> class, not <kbd><button></kbd> elements with that class.",
|
||||||
"task": "Examine the existing CSS rules and add a new rule with higher specificity to override the text color of the paragraph. Create a rule using '.content p' as the selector and set color: green.",
|
"task": "Remove the underline from link buttons. Write a rule with <kbd>a.btn</kbd> as the selector and set <kbd>text-decoration: none</kbd>.",
|
||||||
"previewHTML": "<div class=\"content\">\n <p>What color will this paragraph be? Look at the CSS rules and their specificity.</p>\n</div>",
|
"previewHTML": "<div class=\"actions\">\n <button class=\"btn\">Regular Button</button>\n <a href=\"#\" class=\"btn\">Link Button</a>\n</div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } .actions { display: flex; gap: 0.75rem; align-items: center; } .btn { padding: 0.5rem 1rem; border: none; border-radius: 6px; font-size: 1rem; cursor: pointer; background: steelblue; color: white; }",
|
||||||
"sandboxCSS": "p { border: 1px dashed gray; padding: 10px; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* These CSS rules target the same paragraph but have different specificity */\n\n/* Rule 1: Element selector (lowest specificity) */\np {\n color: red;\n}\n\n/* Rule 2: Descendant selector (higher specificity than just 'p') */\n",
|
"codePrefix": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "a.btn {\n text-decoration: none;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^\\.content\\s+p\\s*{",
|
"value": "a\\.btn\\s*\\{",
|
||||||
"message": "Use <kbd>.content p</kbd> as your selector (note the space between)",
|
"message": "Use <kbd>a.btn {</kbd> (type + class, no space)"
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "color:",
|
"value": { "property": "text-decoration", "expected": "none" },
|
||||||
"message": "Include the <kbd>color</kbd> property"
|
"message": "Set <kbd>text-decoration: none</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "grouping-selectors",
|
||||||
|
"title": "Grouping Selectors",
|
||||||
|
"description": "When multiple elements need the same styles, list them separated by commas. This keeps your CSS clean and maintainable.<br><br><pre>h1, h2, h3 {<br> color: steelblue;<br>}</pre><br>This applies the same color to all three heading levels in one rule.",
|
||||||
|
"task": "Style all headings consistently. Add <kbd>color: steelblue</kbd> to the grouped <kbd>h1, h2, h3</kbd> selector.",
|
||||||
|
"previewHTML": "<article><h1>Main Title</h1><p>Introduction paragraph with some text.</p><h2>Section Heading</h2><p>More content here.</p><h3>Subsection</h3><p>Final paragraph.</p></article>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } article { max-width: 500px; } p { color: #555; line-height: 1.6; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "h1, h2, h3 {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "color: steelblue;",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "color", "expected": "steelblue" },
|
||||||
|
"message": "Set <kbd>color: steelblue</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "descendant-selectors",
|
||||||
|
"title": "Descendant Selectors",
|
||||||
|
"description": "Target elements inside other elements using a space between selectors. This is one of the most useful patterns in CSS.<br><br><pre>.nav a {<br> color: white;<br>}</pre><br>This styles only links inside <kbd>.nav</kbd>, leaving other links unchanged.",
|
||||||
|
"task": "Style navigation links differently. Write a rule with <kbd>.nav a</kbd> as the selector and set <kbd>color: white</kbd>.",
|
||||||
|
"previewHTML": "<nav class=\"nav\"><a href=\"#\">Home</a><a href=\"#\">About</a><a href=\"#\">Contact</a></nav><p>Read more in our <a href=\"#\">documentation</a>.</p>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; margin: 0; } .nav { background: steelblue; padding: 1rem; display: flex; gap: 1rem; border-radius: 8px; margin-bottom: 1rem; } .nav a { text-decoration: none; } p a { color: steelblue; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": ".nav a {\n color: white;\n}",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "\\.nav\\s+a\\s*\\{",
|
||||||
|
"message": "Use <kbd>.nav a {</kbd> (space between .nav and a)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "green",
|
"value": { "property": "color", "expected": "white" },
|
||||||
"message": "Set the color to <kbd>green</kbd>"
|
"message": "Set <kbd>color: white</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nested-styling",
|
||||||
|
"title": "Nested Styling",
|
||||||
|
"description": "Descendant selectors let you create contextual styles. The same element can look different depending on where it appears.<br><br>For example, paragraphs in a <kbd>.card</kbd> might be smaller than paragraphs in an <kbd>article</kbd>.",
|
||||||
|
"task": "Make paragraphs inside the card smaller. Write a rule with <kbd>.card p</kbd> as the selector and set <kbd>font-size: 0.9rem</kbd>.",
|
||||||
|
"previewHTML": "<article><h2>Article Title</h2><p>This is a regular article paragraph with normal-sized text for comfortable reading.</p><div class=\"card\"><strong>Quick Tip</strong><p>Card paragraphs should be slightly smaller to fit the compact design.</p></div><p>Back to regular article text here.</p></article>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } article { max-width: 500px; } h2 { color: steelblue; margin-top: 0; } p { line-height: 1.6; color: #444; } .card { background: #f0f4f8; padding: 1rem; border-radius: 8px; border-left: 4px solid steelblue; } .card strong { color: steelblue; display: block; margin-bottom: 0.5rem; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": ".card p {\n font-size: 0.9rem;\n}",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "\\.card\\s+p\\s*\\{",
|
||||||
|
"message": "Use <kbd>.card p {</kbd> (space between .card and p)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "font-size", "expected": "0.9rem" },
|
||||||
|
"message": "Set <kbd>font-size: 0.9rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||||
"id": "welcome",
|
"id": "welcome",
|
||||||
"title": "Code Crispies",
|
"title": "Welcome",
|
||||||
"description": "Welcome to Code Crispies - your interactive web development learning platform",
|
"description": "Get started with Code Crispies",
|
||||||
"mode": "html",
|
"mode": "css",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
|
"excludeFromProgress": true,
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "get-started",
|
"id": "hello",
|
||||||
"title": "Get Started",
|
"title": "Getting started!",
|
||||||
"description": "<strong>Code Crispies</strong> is a free, open-source platform for learning web development through hands-on exercises. No account required!<br><br><strong>What you'll learn:</strong><br>• <strong>HTML</strong> - Semantic elements, forms, tables, SVG (<em>HTML Block & Inline</em>, <em>HTML Forms</em>, <em>HTML Tables</em>)<br>• <strong>CSS</strong> - Selectors, box model, flexbox, animations (<em>CSS Selectors</em>, <em>CSS Box Model</em>, <em>CSS Flexbox</em>)<br>• <strong>Responsive Design</strong> - Media queries and mobile-first layouts<br><br><strong>How it works:</strong><br>1. Read the task in the left panel<br>2. Write code in the editor<br>3. See live results in the preview<br>4. Get instant feedback with hints<br><br><strong>Keyboard shortcuts:</strong> <kbd>Ctrl+Z</kbd> to undo, <kbd>Ctrl+Shift+Z</kbd> to redo<br><br><strong>More resources:</strong><br>• <a href=\"https://nextlevelshit.github.io/html-over-js/\" target=\"_blank\">HTML over JS</a> - Native HTML vs JavaScript solutions<br>• <a href=\"https://nextlevelshit.github.io/web-engineering-mandala/\" target=\"_blank\">Web Engineering Mandala</a> - JavaScript technology roadmap",
|
"description": "<strong>Welcome to Code Crispies!</strong> Learn CSS and HTML through hands-on exercises.<br><br><strong>How it works:</strong><br>1. Read the task on the left<br>2. Write code in the editor<br>3. See live results in the preview<br><br><strong>Tips:</strong> Use <kbd>Ctrl+Z</kbd> to undo. Open the menu (☰) to browse all modules.",
|
||||||
"task": "Write <code>Hello World</code>",
|
"task": "Write <code>Hello World</code> to get started",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 2rem; color: #6366f1; font-weight: bold; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 2rem; color: #6366f1; font-weight: bold; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -24,39 +25,6 @@
|
|||||||
"message": "Write <code>Hello World</code>"
|
"message": "Write <code>Hello World</code>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "overview",
|
|
||||||
"title": "Overview",
|
|
||||||
"description": "<strong>You're ready!</strong> Open the menu (☰) to explore all modules.<br><br><strong>Recommended learning path:</strong><br>1. <em>HTML Block & Inline</em> - Understand container vs inline elements<br>2. <em>HTML Forms</em> - Build interactive forms with validation<br>3. <em>CSS Selectors</em> - Target elements precisely<br>4. <em>CSS Box Model</em> - Master padding, margin, borders<br>5. <em>CSS Flexbox</em> - Create flexible layouts<br>6. <em>CSS Animations</em> - Add motion and transitions<br><br><strong>Tips:</strong><br>• Use <em>Show Expected</em> to see the target result<br>• Your progress is saved automatically<br>• Try Emmet in HTML mode: <kbd>ul>li*3</kbd> + Tab<br><br><strong>Open Source:</strong><br>• <a href=\"https://git.librete.ch/libretech/code-crispies\" target=\"_blank\">Gitea (Source)</a> · <a href=\"https://github.com/nextlevelshit/code-crispies\" target=\"_blank\">GitHub (Mirror)</a><br>• Made by <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a> · <a href=\"https://www.linkedin.com/in/michael-werner-czechowski\" target=\"_blank\">Michael Czechowski</a>",
|
|
||||||
"task": "Click Next to continue",
|
|
||||||
"previewHTML": "<p>Hello World! 🌍</p><p>Hallo Welt!</p><p>Bonjour le monde!</p><p>¡Hola Mundo!</p><p>Ciao Mondo!</p><p>Olá Mundo!</p><p>こんにちは世界!</p><p>你好世界!</p><p>안녕 세상!</p><p>Привет мир!</p><p dir=\"rtl\">שלום עולם!</p><p dir=\"rtl\">مرحبا بالعالم!</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 12px; } p { margin: 6px 0; padding: 6px 12px; border-radius: 6px; font-weight: 600; font-size: 0.95em; } p:nth-child(1) { background: #fef3c7; color: #92400e; font-size: 1.2em; } p:nth-child(2) { background: #dcfce7; color: #166534; } p:nth-child(3) { background: #dbeafe; color: #1e40af; } p:nth-child(4) { background: #fce7f3; color: #9d174d; } p:nth-child(5) { background: #e0e7ff; color: #4338ca; } p:nth-child(6) { background: #fef9c3; color: #854d0e; } p:nth-child(7) { background: #fee2e2; color: #991b1b; } p:nth-child(8) { background: #f3e8ff; color: #7c3aed; } p:nth-child(9) { background: #ccfbf1; color: #0f766e; } p:nth-child(10) { background: #fae8ff; color: #86198f; } p:nth-child(11) { background: #fef3c7; color: #b45309; } p:nth-child(12) { background: #d1fae5; color: #047857; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<p>Hello World! 🌍</p>\n<p>Hallo Welt!</p>\n<p>Bonjour le monde!</p>\n<p>¡Hola Mundo!</p>\n<p>Ciao Mondo!</p>\n<p>Olá Mundo!</p>\n<p>こんにちは世界!</p>\n<p>你好世界!</p>\n<p>안녕 세상!</p>\n<p>Привет мир!</p>\n<p dir=\"rtl\">שלום עולם!</p>\n<p dir=\"rtl\">مرحبا بالعالم!</p>",
|
|
||||||
"solution": "<p>Hello World! 🌍</p>\n<p>Hallo Welt!</p>\n<p>Bonjour le monde!</p>\n<p>¡Hola Mundo!</p>\n<p>Ciao Mondo!</p>\n<p>Olá Mundo!</p>\n<p>こんにちは世界!</p>\n<p>你好世界!</p>\n<p>안녕 세상!</p>\n<p>Привет мир!</p>\n<p dir=\"rtl\">שלום עולם!</p>\n<p dir=\"rtl\">مرحبا بالعالم!</p>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "Hello",
|
|
||||||
"message": "Click Next to continue"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "playground",
|
|
||||||
"title": "Playground",
|
|
||||||
"mode": "playground",
|
|
||||||
"description": "",
|
|
||||||
"task": "",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<style>\n body {\n font-family: system-ui, sans-serif;\n padding: 20px;\n }\n</style>\n\n<h1>Hello World</h1>\n<p>Start coding!</p>",
|
|
||||||
"solution": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -17,7 +17,7 @@
|
|||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"solution": "input[type=\"text\"] {\n background-color: lightblue;\n border: 2px solid blue\n}",
|
"solution": "input[type=\"text\"] {\n background-color: lightblue;\n border: 2px solid blue;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
|
|||||||
@@ -7,13 +7,13 @@
|
|||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "box-model-1",
|
"id": "box-model-1",
|
||||||
"title": "Box Model Components",
|
"title": "Padding",
|
||||||
"description": "The CSS box model consists of four concentric layers: content area (innermost), padding, border, and margin (outermost). Understanding how these components interact is essential for precise layout control.",
|
"description": "Every element in CSS is a box with four layers: content, padding, border, and margin. <strong>Padding</strong> creates breathing room between your content and the box's edge.<br><br>Without padding, text presses against borders awkwardly. Padding makes content readable and visually balanced.<br><br><pre>.card {\n padding: 1rem;\n}</pre>",
|
||||||
"task": "Set <kbd>padding</kbd> to <kbd>1rem</kbd> to create space between the content and border.",
|
"task": "This profile card looks cramped. Add <kbd>padding: 1rem</kbd> to <kbd>.card</kbd> so the text has room to breathe.",
|
||||||
"previewHTML": "<div class=\"box\">Box Model Components</div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: lavender; border: 2px dashed slategray; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".box {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "padding: 1rem;",
|
"solution": "padding: 1rem;",
|
||||||
@@ -28,56 +28,56 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-2",
|
"id": "box-model-2",
|
||||||
"title": "Adding Borders",
|
"title": "Borders",
|
||||||
"description": "Borders outline an element, creating visual separation from surrounding content. The border shorthand accepts three values: width, style, and color.",
|
"description": "Borders create visual boundaries around elements. The <kbd>border</kbd> shorthand takes three values: width, style, and color.<br><br>Common styles: <kbd>solid</kbd>, <kbd>dashed</kbd>, <kbd>dotted</kbd>, <kbd>none</kbd>",
|
||||||
"task": "Set <kbd>border</kbd> to <kbd>2px solid darkslategray</kbd>.",
|
"task": "Add a subtle left accent to the card with <kbd>border-left: 4px solid steelblue</kbd>.",
|
||||||
"previewHTML": "<div class=\"box\">This box needs a border</div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: mintcream; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".box {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "border: 2px solid darkslategray;",
|
"solution": "border-left: 4px solid steelblue;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border:\\s*2px\\s+solid\\s+darkslategray",
|
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
|
||||||
"message": "Set <kbd>border: 2px solid darkslategray</kbd>",
|
"message": "Set <kbd>border-left: 4px solid steelblue</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-3",
|
"id": "box-model-3",
|
||||||
"title": "Adding Margins",
|
"title": "Margins",
|
||||||
"description": "Margins create space between elements, controlling how they relate to one another within a layout. Unlike padding (which affects internal spacing), margins exist outside the element's border.",
|
"description": "Margins create space <em>outside</em> the element, separating it from neighbors. While padding pushes content inward, margins push other elements away.",
|
||||||
"task": "Set <kbd>margin</kbd> to <kbd>1rem</kbd> to create space between this element and its neighbors.",
|
"task": "Add space between these two profile cards with <kbd>margin-bottom: 1rem</kbd> on <kbd>.card</kbd>.",
|
||||||
"previewHTML": "<div class=\"container\"><div class=\"outer\">This box needs margins</div><div class=\"neighbor\">Adjacent element</div></div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article><article class=\"card\"><h3>Alex Rivera</h3><p>UX Designer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .outer { background-color: plum; padding: 1rem; border: 2px solid orchid; } .neighbor { background-color: lightblue; padding: 1rem; border: 2px solid steelblue; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; border-left: 4px solid steelblue; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".outer {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "margin: 1rem;",
|
"solution": "margin-bottom: 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "margin", "expected": "1rem" },
|
"value": { "property": "margin-bottom", "expected": "1rem" },
|
||||||
"message": "Set <kbd>margin: 1rem</kbd>"
|
"message": "Set <kbd>margin-bottom: 1rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-4",
|
"id": "box-model-4",
|
||||||
"title": "Box Sizing: Border-Box",
|
"title": "Box Sizing",
|
||||||
"description": "The <kbd>box-sizing</kbd> property determines how element dimensions are calculated. The default <kbd>content-box</kbd> excludes padding and border from width/height, while <kbd>border-box</kbd> includes them, making layout calculations more intuitive.",
|
"description": "By default, <kbd>width</kbd> only sets the content width. Padding and borders add to the total. This causes layout headaches.<br><br><kbd>box-sizing: border-box</kbd> includes padding and border in the width, making sizing predictable. Most developers apply this to all elements.",
|
||||||
"task": "Set <kbd>box-sizing</kbd> to <kbd>border-box</kbd> so padding and border are included in the width.",
|
"task": "Both cards have <kbd>width: 200px</kbd>. The left uses default sizing (content-box), making it wider than expected. Fix the right card with <kbd>box-sizing: border-box</kbd>.",
|
||||||
"previewHTML": "<div class=\"sizing-demo\"><div class=\"box default\">Content-box (default)</div><div class=\"box sized\">Border-box</div></div>",
|
"previewHTML": "<div class=\"demo\"><article class=\"card\">Content-box</article><article class=\"card fix\">Border-box</article></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .sizing-demo { display: flex; gap: 1rem; } .box { width: 200px; padding: 1rem; border: 4px solid teal; background: lightcyan; } .default { box-sizing: content-box; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .demo { display: flex; gap: 1rem; } .card { width: 200px; padding: 1rem; border: 4px solid steelblue; background: white; border-radius: 8px; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".sized {\n ",
|
"codePrefix": ".fix {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "box-sizing: border-box;",
|
"solution": "box-sizing: border-box;",
|
||||||
@@ -92,87 +92,98 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-5",
|
"id": "box-model-5",
|
||||||
"title": "Margin Collapse",
|
"title": "Padding Shorthand",
|
||||||
"description": "When two vertical margins meet, they collapse to the larger value instead of adding up. Understanding this behavior is crucial for consistent vertical spacing.",
|
"description": "Padding accepts 1-4 values:<br>• 1 value: all sides<br>• 2 values: vertical | horizontal<br>• 4 values: top | right | bottom | left",
|
||||||
"task": "Set <kbd>margin-bottom</kbd> to <kbd>2rem</kbd>. Notice the space between paragraphs equals 2rem (not 3rem) due to margin collapse.",
|
"task": "This button needs more horizontal space than vertical. Set <kbd>padding: 8px 1rem</kbd> (8px top/bottom, 1rem left/right).",
|
||||||
"previewHTML": "<div class=\"collapse-demo\"><p class=\"first\">This paragraph has a bottom margin.</p><p class=\"second\">This paragraph has a top margin of 1rem.</p></div>",
|
"previewHTML": "<button class=\"btn\">Follow</button>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .collapse-demo { border: 1px solid silver; padding: 8px; background: ghostwhite; } .second { margin-top: 1rem; background: linen; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .btn { background: steelblue; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".first {\n ",
|
"codePrefix": ".btn {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "margin-bottom: 2rem;",
|
"solution": "padding: 8px 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "regex",
|
||||||
"value": { "property": "margin-bottom", "expected": "2rem" },
|
"value": "padding:\\s*8px\\s+1rem",
|
||||||
"message": "Set <kbd>margin-bottom: 2rem</kbd>"
|
"message": "Set <kbd>padding: 8px 1rem</kbd>",
|
||||||
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-6",
|
"id": "box-model-6",
|
||||||
"title": "Margin Shorthand Notation",
|
"title": "Margin Shorthand",
|
||||||
"description": "The margin shorthand can set all four sides at once. Two values set vertical (top/bottom) and horizontal (left/right) margins respectively.",
|
"description": "Margin uses the same shorthand pattern as padding. A common pattern is centering block elements horizontally with <kbd>margin: 0 auto</kbd>.",
|
||||||
"task": "Set <kbd>margin</kbd> to <kbd>1rem 2rem</kbd> for 1rem top/bottom and 2rem left/right.",
|
"task": "Center this card horizontally. Set <kbd>margin: 0 auto</kbd> to auto-calculate equal left/right margins.",
|
||||||
"previewHTML": "<div class=\"container\"><div class=\"spaced\">This box needs margins: 1rem top/bottom, 2rem left/right</div></div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .spaced { background-color: honeydew; border: 2px solid mediumseagreen; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { width: 250px; background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; border-left: 4px solid steelblue; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".spaced {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "margin: 1rem 2rem;",
|
"solution": "margin: 0 auto;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "margin:\\s*1rem\\s+2rem",
|
"value": "margin:\\s*0\\s+auto",
|
||||||
"message": "Set <kbd>margin: 1rem 2rem</kbd>",
|
"message": "Set <kbd>margin: 0 auto</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-7",
|
"id": "box-model-7",
|
||||||
"title": "Padding Shorthand Notation",
|
"title": "Border Radius",
|
||||||
"description": "Like margin, padding shorthand allows setting all sides at once. A single value applies to all four sides equally.",
|
"description": "While not part of the classic box model, <kbd>border-radius</kbd> rounds the corners of an element's border box. Use <kbd>50%</kbd> on a square element to create a circle.",
|
||||||
"task": "Set <kbd>padding</kbd> to <kbd>2rem</kbd> to add equal padding on all sides.",
|
"task": "Make the avatar image circular with <kbd>border-radius: 50%</kbd>.",
|
||||||
"previewHTML": "<div class=\"padded\">This box needs equal padding on all sides</div>",
|
"previewHTML": "<article class=\"card\"><img class=\"avatar\" src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='35' r='25' fill='%23666'/%3E%3Ccircle cx='50' cy='90' r='40' fill='%23666'/%3E%3C/svg%3E\" alt=\"Avatar\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .padded { background-color: papayawhip; border: 2px solid orange; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; text-align: center; } .avatar { width: 80px; height: 80px; background: #ddd; margin-bottom: 8px; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".padded {\n ",
|
"codePrefix": ".avatar {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "padding: 2rem;",
|
"solution": "border-radius: 50%;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "2rem" },
|
"value": { "property": "border-radius", "expected": "50%" },
|
||||||
"message": "Set <kbd>padding: 2rem</kbd>"
|
"message": "Set <kbd>border-radius: 50%</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-8",
|
"id": "box-model-8",
|
||||||
"title": "Border on Specific Sides",
|
"title": "Complete Card",
|
||||||
"description": "For granular control, you can target specific sides with <kbd>border-top</kbd>, <kbd>border-right</kbd>, <kbd>border-bottom</kbd>, or <kbd>border-left</kbd>.",
|
"description": "Let's combine everything. This notification card needs styling to look polished.",
|
||||||
"task": "Set <kbd>border-bottom</kbd> to <kbd>4px solid dodgerblue</kbd>.",
|
"task": "Style the notification: add <kbd>padding: 1rem</kbd>, <kbd>border-left: 4px solid coral</kbd>, and <kbd>border-radius: 4px</kbd>.",
|
||||||
"previewHTML": "<div class=\"line\">This element needs only a bottom border</div>",
|
"previewHTML": "<div class=\"alert\"><strong>New message</strong><p>You have 3 unread notifications</p></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .line { padding: 1rem; background-color: aliceblue; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .alert { background: seashell; } .alert strong { color: coral; } .alert p { margin: 4px 0 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".line {\n ",
|
"codePrefix": ".alert {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "border-bottom: 4px solid dodgerblue;",
|
"solution": "padding: 1rem;\n border-left: 4px solid coral;\n border-radius: 4px;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
|
"message": "Set <kbd>padding: 1rem</kbd>"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border-bottom:\\s*4px\\s+solid\\s+dodgerblue",
|
"value": "border-left:\\s*4px\\s+solid\\s+coral",
|
||||||
"message": "Set <kbd>border-bottom: 4px solid dodgerblue</kbd>",
|
"message": "Set <kbd>border-left: 4px solid coral</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "border-radius", "expected": "4px" },
|
||||||
|
"message": "Set <kbd>border-radius: 4px</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,119 +1,91 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||||
"id": "colors-backgrounds",
|
"id": "colors-backgrounds",
|
||||||
"title": "Colors",
|
"title": "CSS Colors",
|
||||||
"description": "Learn how to apply and manipulate colors, backgrounds, and graphical fills using CSS properties.",
|
"description": "Learn how to apply colors to text and backgrounds using CSS properties.",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "colors-1",
|
"id": "colors-1",
|
||||||
"title": "Setting Background Colors",
|
"title": "Background Color",
|
||||||
"description": "Use the <code>background-color</code> property to fill elements with solid colors.",
|
"description": "Color is one of the most powerful tools in web design. It creates visual hierarchy, conveys meaning, and establishes brand identity. CSS provides multiple ways to specify colors.<br><br><strong>CSS named colors:</strong> CSS includes 147 named colors like <kbd>steelblue</kbd>, <kbd>coral</kbd>, <kbd>gold</kbd>, and <kbd>tomato</kbd>. These are easy to remember and read.<br><br><strong>The background-color property:</strong> Sets the fill color behind an element's content and padding areas.<br><br><pre>.card {\n background-color: lightblue;\n}</pre>",
|
||||||
"task": "Apply a light cyan background (#e0f7fa) to the element with class 'colorbox'.",
|
"task": "This notification card needs a subtle background. Add <kbd>background-color: seashell</kbd>.",
|
||||||
"previewHTML": "<div class=\"colorbox\">Background Demo</div>",
|
"previewHTML": "<div class=\"alert\"><strong>New message</strong><p>You have 3 unread notifications</p></div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .colorbox { padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .alert { padding: 1rem; border-left: 4px solid coral; border-radius: 4px; } .alert strong { display: block; margin-bottom: 4px; } .alert p { margin: 0; color: #666; font-size: 0.9rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Set a background color */\n.colorbox {",
|
"codePrefix": ".alert {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "background-color: seashell;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{ "type": "contains", "value": ".colorbox", "message": "Select <kbd>.colorbox</kbd>", "options": { "caseSensitive": false } },
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color",
|
|
||||||
"message": "Use <kbd>background-color</kbd> property",
|
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "background-color", "expected": "#e0f7fa" },
|
"value": { "property": "background-color", "expected": "seashell" },
|
||||||
"message": "Set background-color to <kbd>#e0f7fa</kbd>",
|
"message": "Set <kbd>background-color: seashell</kbd>"
|
||||||
"options": { "exact": true }
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "colors-2",
|
"id": "colors-2",
|
||||||
"title": "Text Color and Contrast",
|
"title": "Text Color",
|
||||||
"description": "Apply the <code>color</code> property to control text readability against backgrounds.",
|
"description": "The <kbd>color</kbd> property sets the color of text content. Good contrast between text and background is essential for readability and accessibility.",
|
||||||
"task": "Set the text color of '.colorbox' to deep blue (#01579b). Ensure good contrast.",
|
"task": "Make the alert title stand out. Add <kbd>color: coral</kbd>.",
|
||||||
"previewHTML": "<div class=\"colorbox\">Color & Contrast</div>",
|
"previewHTML": "<div class=\"alert\"><strong class=\"title\">Warning</strong><p>Your session will expire in 5 minutes</p></div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .colorbox { padding: 1rem; background: #e0f7fa; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .alert { padding: 1rem; background-color: seashell; border-left: 4px solid coral; border-radius: 4px; } .alert .title { display: block; margin-bottom: 4px; } .alert p { margin: 0; color: #666; font-size: 0.9rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Set text color */\n.colorbox {",
|
"codePrefix": ".title {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "color: coral;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{ "type": "contains", "value": ".colorbox", "message": "Select <kbd>.colorbox</kbd>", "options": { "caseSensitive": false } },
|
|
||||||
{ "type": "contains", "value": "color", "message": "Use the <kbd>color</kbd> property", "options": { "caseSensitive": false } },
|
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "color", "expected": "#01579b" },
|
"value": { "property": "color", "expected": "coral" },
|
||||||
"message": "Set color to <kbd>#01579b</kbd>",
|
"message": "Set <kbd>color: coral</kbd>"
|
||||||
"options": { "exact": true }
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "colors-3",
|
"id": "colors-3",
|
||||||
"title": "CSS Gradients",
|
"title": "Border Color",
|
||||||
"description": "Learn to create smooth transitions between colors using linear and radial gradients.",
|
"description": "Borders can have their own color using <kbd>border-color</kbd>. This is useful when you want to change just the color without redefining the entire border.",
|
||||||
"task": "Apply a linear gradient background from #ff9a9e to #fad0c4 on an element with class 'gradient-box'.",
|
"task": "This card needs an accent border. Add <kbd>border-color: coral</kbd>.",
|
||||||
"previewHTML": "<div class=\"gradient-box\">Gradient Demo</div>",
|
"previewHTML": "<article class=\"card\"><h3>Premium Plan</h3><p>Unlimited access to all features</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .gradient-box { padding: 1rem; color: white; text-align: center; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { padding: 1rem; background: white; border: 4px solid #ddd; border-radius: 8px; } .card h3 { margin: 0 0 8px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Set a linear gradient background */\n.gradient-box {",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "border-color: coral;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{ "type": "contains", "value": ".gradient-box", "message": "Select <kbd>.gradient-box</kbd>", "options": { "caseSensitive": false } },
|
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "background-image",
|
"value": { "property": "border-color", "expected": "coral" },
|
||||||
"message": "Use <kbd>background-image</kbd> property",
|
"message": "Set <kbd>border-color: coral</kbd>"
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "linear-gradient\\(.*#ff9a9e.*,.*#fad0c4.*\\)",
|
|
||||||
"message": "Use <kbd>linear-gradient</kbd> from <kbd>#ff9a9e</kbd> to <kbd>#fad0c4</kbd>",
|
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "colors-4",
|
"id": "colors-4",
|
||||||
"title": "Background Images & Repeat",
|
"title": "Hex Colors",
|
||||||
"description": "Add images as backgrounds and control repetition and positioning.",
|
"description": "Beyond named colors, CSS supports hex codes (<kbd>#ff6347</kbd>), RGB (<kbd>rgb(255, 99, 71)</kbd>), and HSL (<kbd>hsl(9, 100%, 64%)</kbd>). Hex codes are the most common format in professional projects.",
|
||||||
"task": "Set a background image on '.bg-img' using a placeholder URL, center it, and prevent tiling.",
|
"task": "Set the badge background to gold using its hex code. Add <kbd>background-color: #ffd700</kbd>.",
|
||||||
"previewHTML": "<div class=\"bg-img\">Image Background</div>",
|
"previewHTML": "<span class=\"badge\">NEW</span>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .bg-img { height: 150px; display: flex; align-items: center; justify-content: center; color: white; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .badge { display: inline-block; padding: 4px 12px; border-radius: 999px; font-size: 0.75rem; font-weight: bold; text-transform: uppercase; color: #333; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Set background image */\n\n.bg-img {",
|
"codePrefix": ".badge {\n ",
|
||||||
"initialCode": " background-image: url('http://placekitten.com/320/320');\n background-position: center; background-repeat: no-repeat;\n ",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "background-color: #ffd700;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "background-image",
|
"value": { "property": "background-color", "expected": "#ffd700" },
|
||||||
"message": "Use <kbd>background-image</kbd> property",
|
"message": "Set <kbd>background-color: #ffd700</kbd>"
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-position: center",
|
|
||||||
"message": "Center the background image with <kbd>background-position: center</kbd>",
|
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-repeat: no-repeat",
|
|
||||||
"message": "Prevent image tiling with <kbd>background-repeat: no-repeat</kbd>",
|
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,93 +2,70 @@
|
|||||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||||
"id": "typography-fonts",
|
"id": "typography-fonts",
|
||||||
"title": "Typography",
|
"title": "Typography",
|
||||||
"description": "Learn how to control text appearance through font selection, sizing, spacing, and decorative effects.",
|
"description": "Learn how to control text appearance through font selection, sizing, spacing, and styling.",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "typography-1",
|
"id": "monospace-font",
|
||||||
"title": "Font Family & Fallbacks",
|
"title": "Monospace Fonts",
|
||||||
"description": "Specify custom fonts and reliable fallback stacks for consistent typography across devices.",
|
"description": "Different font families create different visual impressions. <kbd>monospace</kbd> fonts have equal-width characters, making them perfect for displaying code, data, or keyboard shortcuts.<br><br>Every computer has a monospace font built in, so <kbd>font-family: monospace</kbd> always works. This makes it a safe choice for inline code in any web project.",
|
||||||
"task": "Set the <code>font-family</code> of the '.text' element to 'Georgia, serif' with serif fallback.",
|
"task": "Style the inline code with a monospace font. Add <kbd>font-family: monospace</kbd>.",
|
||||||
"previewHTML": "<p class=\"text\">This text shows the chosen font family.</p>",
|
"previewHTML": "<article>\n <h3>Quick Tip</h3>\n <p>Press <span class=\"code\">Ctrl + S</span> to save your work, or <span class=\"code\">Ctrl + Z</span> to undo.</p>\n</article>",
|
||||||
"previewBaseCSS": "body { padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } h3 { margin: 0 0 0.5rem; } p { margin: 0; color: #555; line-height: 1.6; } .code { background: #f0f0f0; padding: 0.2rem 0.4rem; border-radius: 4px; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Set font family */\n.text {",
|
"codePrefix": ".code {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "font-family: monospace;",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "font-family",
|
"value": { "property": "font-family", "expected": "monospace" },
|
||||||
"message": "Use the <kbd>font-family</kbd> property",
|
"message": "Set font-family to <kbd>monospace</kbd>"
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "Georgia, serif",
|
|
||||||
"message": "Include <kbd>Georgia, serif</kbd> in the font stack",
|
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "typography-2",
|
"id": "font-size-line-height",
|
||||||
"title": "Font Size & Line Height",
|
"title": "Readable Body Text",
|
||||||
"description": "Control text scale and readability by adjusting size and line heights.",
|
"description": "Good typography is invisible—readers focus on content, not struggling to read it. Two properties work together for readability:<br><br><kbd>font-size</kbd> controls how big the text appears. For body text, <kbd>1rem</kbd> (16px by default) is a comfortable starting point.<br><br><kbd>line-height</kbd> sets the space between lines. A value of <kbd>1.6</kbd> means the line height is 1.6 times the font size—giving text room to breathe.",
|
||||||
"task": "Set the heading '.heading' to 1.5rem font-size and 1.5 line-height.",
|
"task": "Make the text comfortable to read. Add <kbd>font-size: 1rem</kbd> and <kbd>line-height: 1.6</kbd>.",
|
||||||
"previewHTML": "<h2 class=\"heading\">Readable Heading</h2>",
|
"previewHTML": "<article>\n <h2>The Art of Coffee</h2>\n <p>Great coffee starts with quality beans. The roast level affects flavor—light roasts are fruity and acidic, while dark roasts are bold and smoky.</p>\n <p>Water temperature matters too. Brew between 90-96°C for optimal extraction. Too hot burns the coffee; too cold leaves it weak.</p>\n</article>",
|
||||||
"previewBaseCSS": "body { padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } h2 { margin: 0 0 1rem; color: #333; } p { margin: 0 0 1rem; color: #444; } p:last-child { margin-bottom: 0; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Set size and line height */\n.heading {",
|
"codePrefix": "article {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "font-size: 1rem;\n line-height: 1.6;",
|
||||||
"validations": [
|
"validations": [
|
||||||
{ "type": "contains", "value": "font-size", "message": "Use <kbd>font-size</kbd> property", "options": { "caseSensitive": false } },
|
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "font-size", "expected": "1.5rem" },
|
"value": { "property": "font-size", "expected": "1rem" },
|
||||||
"message": "Set font-size to <kbd>1.5rem</kbd>"
|
"message": "Set font-size to <kbd>1rem</kbd>"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "line-height",
|
|
||||||
"message": "Use <kbd>line-height</kbd> property",
|
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "line-height", "expected": "1.5" },
|
"value": { "property": "line-height", "expected": "1.6" },
|
||||||
"message": "Set line-height to <kbd>1.5</kbd>"
|
"message": "Set line-height to <kbd>1.6</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "typography-3",
|
"id": "font-weight",
|
||||||
"title": "Font Weight & Style",
|
"title": "Bold Headings",
|
||||||
"description": "Apply weight and style variations like bold, light, italic to emphasize text.",
|
"description": "Font weight controls how thick or thin text appears. Common values include:<br><br>• <kbd>normal</kbd> (400) — default body text<br>• <kbd>bold</kbd> (700) — strong emphasis<br>• <kbd>lighter</kbd> / <kbd>bolder</kbd> — relative to parent<br><br>Card titles and headings often use bold text to create visual hierarchy and draw attention to key information.",
|
||||||
"task": "Make the paragraph '.emphasis' italic and bold using <code>font-style</code> and <code>font-weight</code>.",
|
"task": "Make the card title stand out. Add <kbd>font-weight: bold</kbd>.",
|
||||||
"previewHTML": "<p class=\"emphasis\">This text should stand out.</p>",
|
"previewHTML": "<article class=\"card\">\n <span class=\"title\">Weekend Special</span>\n <p>Fresh pastries and artisan coffee, 20% off all items this Saturday and Sunday.</p>\n</article>",
|
||||||
"previewBaseCSS": "body { padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } .card { background: #f8f9fa; padding: 1.5rem; border-radius: 8px; max-width: 320px; } .title { display: block; font-size: 1.25rem; color: steelblue; margin-bottom: 0.5rem; font-weight: normal; } p { margin: 0; color: #555; line-height: 1.5; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Emphasize text */\n.emphasis {",
|
"codePrefix": ".title {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "font-weight: bold;",
|
||||||
"validations": [
|
"validations": [
|
||||||
{ "type": "contains", "value": "font-style", "message": "Use <kbd>font-style</kbd> property", "options": { "caseSensitive": false } },
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": { "property": "font-style", "expected": "italic" },
|
|
||||||
"message": "Set font-style to <kbd>italic</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "font-weight",
|
|
||||||
"message": "Use <kbd>font-weight</kbd> property",
|
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "font-weight", "expected": "bold" },
|
"value": { "property": "font-weight", "expected": "bold" },
|
||||||
@@ -97,29 +74,75 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "typography-4",
|
"id": "text-transform",
|
||||||
"title": "Text Decoration & Shadow",
|
"title": "Uppercase Labels",
|
||||||
"description": "Add decorative underlines, overlines, line-throughs and subtle shadows to text.",
|
"description": "The <kbd>text-transform</kbd> property changes text capitalization without editing the HTML. This keeps content semantic while styling it differently:<br><br>• <kbd>uppercase</kbd> — ALL CAPS (great for labels, buttons)<br>• <kbd>lowercase</kbd> — all lowercase<br>• <kbd>capitalize</kbd> — First Letter Of Each Word<br><br>Combined with <kbd>letter-spacing</kbd>, uppercase text creates a clean, modern label style often used in UI design.",
|
||||||
"task": "Apply an underline with <code>text-decoration</code> and a light shadow using <code>text-shadow</code> on '.fancy'.",
|
"task": "Style the tag labels. Add <kbd>text-transform: uppercase</kbd> and <kbd>letter-spacing: 1px</kbd>.",
|
||||||
"previewHTML": "<p class=\"fancy\">Fancy text effect!</p>",
|
"previewHTML": "<article>\n <div class=\"tags\">\n <span class=\"tag\">Design</span>\n <span class=\"tag\">CSS</span>\n <span class=\"tag\">Tutorial</span>\n </div>\n <h3>Getting Started with Typography</h3>\n <p>Learn the fundamentals of web typography in this beginner-friendly guide.</p>\n</article>",
|
||||||
"previewBaseCSS": "body { padding: 1rem; } .fancy { font-size: 1.25rem; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } .tags { display: flex; gap: 0.5rem; margin-bottom: 1rem; } .tag { background: #e8f4f8; color: steelblue; padding: 0.25rem 0.75rem; border-radius: 4px; font-size: 0.75rem; } h3 { margin: 0 0 0.5rem; } p { margin: 0; color: #555; line-height: 1.5; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Decorate text */\n.fancy {",
|
"codePrefix": ".tag {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "text-transform: uppercase;\n letter-spacing: 1px;",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "text-transform", "expected": "uppercase" },
|
||||||
|
"message": "Set text-transform to <kbd>uppercase</kbd>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "letter-spacing", "expected": "1px" },
|
||||||
|
"message": "Set letter-spacing to <kbd>1px</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "text-decoration",
|
||||||
|
"title": "Text Decoration",
|
||||||
|
"description": "The <kbd>text-decoration</kbd> property adds lines to text. Common values:<br><br>• <kbd>underline</kbd> — line below text<br>• <kbd>line-through</kbd> — strikethrough<br>• <kbd>none</kbd> — removes decoration (useful for links)<br><br>You can also style decorations with <kbd>text-decoration-color</kbd> and <kbd>text-decoration-style</kbd>.",
|
||||||
|
"task": "Show the old price with a strikethrough. Add <kbd>text-decoration: line-through</kbd>.",
|
||||||
|
"previewHTML": "<div class=\"price-box\"><span class=\"old-price\">$49.99</span><span class=\"new-price\">$29.99</span></div>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } .price-box { display: flex; gap: 1rem; align-items: center; } .old-price { color: #999; font-size: 1rem; } .new-price { color: coral; font-size: 1.5rem; font-weight: bold; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".old-price {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "text-decoration: line-through;",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "text-decoration", "expected": "line-through" },
|
||||||
|
"message": "Set text-decoration to <kbd>line-through</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "text-shadow",
|
||||||
|
"title": "Text Shadow",
|
||||||
|
"description": "The <kbd>text-shadow</kbd> property adds shadow effects to text. The syntax is:<br><br><pre>text-shadow: x-offset y-offset blur color;</pre><br>Example: <kbd>text-shadow: 2px 2px 4px gray</kbd> creates a soft shadow offset down and right.",
|
||||||
|
"task": "Add depth to the heading with <kbd>text-shadow: 2px 2px 4px gray</kbd>.",
|
||||||
|
"previewHTML": "<h1 class=\"hero-title\">Welcome</h1>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui; padding: 2rem; background: linear-gradient(135deg, #667eea, #764ba2); } .hero-title { margin: 0; font-size: 3rem; color: white; text-align: center; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".hero-title {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "text-shadow: 2px 2px 4px gray;",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "text-decoration",
|
"value": "text-shadow",
|
||||||
"message": "Use <kbd>text-decoration</kbd> property",
|
"message": "Use <kbd>text-shadow</kbd> property"
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "text-shadow",
|
"value": "2px 2px",
|
||||||
"message": "Use <kbd>text-shadow</kbd> property",
|
"message": "Set offset to <kbd>2px 2px</kbd>"
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,109 +7,94 @@
|
|||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "units-1",
|
"id": "units-1",
|
||||||
"title": "Absolute vs. Relative Units",
|
"title": "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>",
|
"description": "CSS offers two types of units: <em>absolute</em> (like <kbd>px</kbd>) and <em>relative</em> (like <kbd>%</kbd> and <kbd>rem</kbd>). Relative units adapt to their context, making layouts flexible and accessible.<br><br><strong>Common relative units:</strong><br>• <kbd>%</kbd> – Relative to parent element<br>• <kbd>rem</kbd> – Relative to root font size (typically 16px)<br>• <kbd>em</kbd> – Relative to element's font size<br><br>A common pattern for readable content: set <kbd>width: 100%</kbd> so it fills available space, then <kbd>max-width: 40rem</kbd> to cap line length for readability.",
|
||||||
"task": "Set the <kbd>width</kbd> of <kbd>.box</kbd> to <kbd>80%</kbd> and <kbd>max-width</kbd> to <kbd>40rem</kbd>.",
|
"task": "This article text runs too wide on large screens. Add <kbd>max-width: 40rem</kbd> to <kbd>.article</kbd> for optimal reading width.",
|
||||||
"previewHTML": "<div class=\"box\">Resize me!</div>",
|
"previewHTML": "<article class=\"article\"><h2>The Art of Typography</h2><p>Good typography is invisible. When text is set well, readers absorb information without noticing the design decisions that make it comfortable to read. Line length is crucial—too wide and eyes get lost, too narrow and reading becomes choppy.</p><p>The ideal line length is 45-75 characters per line. At typical font sizes, this works out to roughly 40rem maximum width.</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { background: #f5f5f5; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: Georgia, serif; padding: 1rem; background: #f9f9f9; } .article { background: white; padding: 2rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .article h2 { margin: 0 0 1rem; color: #333; } .article p { margin: 0 0 1rem; line-height: 1.6; color: #444; } .article p:last-child { margin-bottom: 0; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Set flexible sizing */\n.box {",
|
"codePrefix": ".article {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " width: 80%;\n max-width: 40rem;",
|
"solution": "max-width: 40rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"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",
|
"type": "property_value",
|
||||||
"value": { "property": "max-width", "expected": "40rem" },
|
"value": { "property": "max-width", "expected": "40rem" },
|
||||||
"message": "Set max-width to <kbd>40rem</kbd>"
|
"message": "Set <kbd>max-width: 40rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "units-2",
|
"id": "units-2",
|
||||||
"title": "CSS Custom Properties",
|
"title": "CSS Variables",
|
||||||
"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>",
|
"description": "CSS custom properties (variables) let you define reusable values. Define them with <kbd>--name</kbd> and use them with <kbd>var(--name)</kbd>. Variables defined on <kbd>:root</kbd> are available everywhere.",
|
||||||
"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>.",
|
"task": "Define <kbd>--brand: steelblue</kbd> in <kbd>:root</kbd>, then use it as the <kbd>background</kbd> color for <kbd>.btn</kbd>.",
|
||||||
"previewHTML": "<div class=\"themed\">Variable Box</div>",
|
"previewHTML": "<div class=\"actions\"><button class=\"btn\">Subscribe</button><button class=\"btn\">Learn More</button></div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .themed { padding: 1rem; border: 0.125rem solid #ddd; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .actions { display: flex; gap: 1rem; } .btn { color: white; border: none; padding: 12px 24px; border-radius: 6px; font-size: 1rem; cursor: pointer; background: #ccc; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Define and use a CSS variable */\n:root {",
|
"codePrefix": ":root {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}\n.themed { }",
|
"codeSuffix": "\n}\n\n.btn {\n background: var(--brand);\n}",
|
||||||
"solution": " --main-color: mediumpurple;\n}\n.themed {\n border-color: var(--main-color);",
|
"solution": "--brand: steelblue;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "--main-color",
|
"value": "--brand",
|
||||||
"message": "Define <kbd>--main-color</kbd> in :root",
|
"message": "Define <kbd>--brand</kbd> variable",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "var(--main-color)",
|
"value": "steelblue",
|
||||||
"message": "Use <kbd>var(--main-color)</kbd>",
|
"message": "Set the value to <kbd>steelblue</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"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",
|
"id": "units-3",
|
||||||
"title": "Unit Calculations (calc)",
|
"title": "calc() Function",
|
||||||
"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>",
|
"description": "The <kbd>calc()</kbd> function lets you mix different units in calculations. This is essential for layouts that combine fixed and flexible sizing, like a sidebar layout.",
|
||||||
"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>.",
|
"task": "The main content should fill the remaining space after the 200px sidebar. Set <kbd>width: calc(100% - 200px)</kbd> on <kbd>.main</kbd>.",
|
||||||
"previewHTML": "<div class=\"sized\">Calc Demo</div>",
|
"previewHTML": "<div class=\"layout\"><aside class=\"sidebar\">Sidebar<br>Navigation</aside><main class=\"main\"><h2>Main Content</h2><p>This area should fill the remaining width after accounting for the fixed-width sidebar.</p></main></div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sized { background: #e8f5e9; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .layout { display: flex; gap: 1rem; } .sidebar { width: 200px; background: #1a1a2e; color: white; padding: 1rem; border-radius: 8px; flex-shrink: 0; } .main { background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .main h2 { margin: 0 0 8px; } .main p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Use calc for dynamic sizing */\n.sized {",
|
"codePrefix": ".main {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " width: calc(100% - 2rem);\n min-height: calc(10vh + 1rem);",
|
"solution": "width: calc(100% - 200px);",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{ "type": "contains", "value": "calc", "message": "Use <kbd>calc()</kbd> function", "options": { "caseSensitive": false } },
|
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "width:\\s*calc\\(100% - 2rem\\)",
|
"value": "width:\\s*calc\\(\\s*100%\\s*-\\s*200px\\s*\\)",
|
||||||
"message": "Width should be <kbd>calc(100% - 2rem)</kbd>",
|
"message": "Set <kbd>width: calc(100% - 200px)</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 }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "units-4",
|
"id": "units-4",
|
||||||
"title": "Viewport & Responsive Units",
|
"title": "Viewport 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>",
|
"description": "Viewport units size elements relative to the browser window:<br>• <kbd>vw</kbd> – 1% of viewport width<br>• <kbd>vh</kbd> – 1% of viewport height<br><br>These are perfect for full-screen sections like hero banners.",
|
||||||
"task": "Give <kbd>.view</kbd> a <kbd>width</kbd> of <kbd>50vw</kbd> and <kbd>height</kbd> of <kbd>20vh</kbd>.",
|
"task": "Make this hero section fill the viewport height by setting <kbd>min-height: 100vh</kbd> on <kbd>.hero</kbd>.",
|
||||||
"previewHTML": "<div class=\"view\">Viewport Box</div>",
|
"previewHTML": "<section class=\"hero\"><h1>Welcome</h1><p>Scroll down to explore</p></section>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .view { background: #ffe0b2; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .hero { background: linear-gradient(135deg, #1a1a2e 0%, steelblue 100%); color: white; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 2rem; } .hero h1 { margin: 0 0 1rem; font-size: 2.5rem; } .hero p { margin: 0; opacity: 0.8; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Use viewport units */\n.view {",
|
"codePrefix": ".hero {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " width: 50vw;\n height: 20vh;",
|
"solution": "min-height: 100vh;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"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",
|
||||||
{ "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Set width to <kbd>50vw</kbd>" },
|
"value": { "property": "min-height", "expected": "100vh" },
|
||||||
{ "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Set height to <kbd>20vh</kbd>" }
|
"message": "Set <kbd>min-height: 100vh</kbd>"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -59,15 +59,15 @@
|
|||||||
{
|
{
|
||||||
"id": "responsive-3",
|
"id": "responsive-3",
|
||||||
"title": "Responsive Grid",
|
"title": "Responsive Grid",
|
||||||
"description": "Combine CSS Grid with <kbd>auto-fit</kbd> or <kbd>auto-fill</kbd> for responsive column layouts.",
|
"description": "Combine CSS Grid with <kbd>auto-fit</kbd> or <kbd>auto-fill</kbd> for responsive column layouts that automatically adjust the number of columns based on available space.",
|
||||||
"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>.",
|
"task": "Add <kbd>display: grid</kbd>, <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd>, and <kbd>gap: 1rem</kbd> to <kbd>.features</kbd>.",
|
||||||
"previewHTML": "<div class=\"cards\"><div>1</div><div>2</div><div>3</div><div>4</div></div>",
|
"previewHTML": "<section class=\"features\"><article class=\"feature\"><h3>Fast</h3><p>Lightning quick load times</p></article><article class=\"feature\"><h3>Secure</h3><p>Enterprise-grade security</p></article><article class=\"feature\"><h3>Reliable</h3><p>99.9% uptime guaranteed</p></article><article class=\"feature\"><h3>Support</h3><p>24/7 customer service</p></article></section>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .cards > div { background: #d1c4e9; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .feature { background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .feature h3 { margin: 0 0 8px; color: steelblue; } .feature p { margin: 0; color: #666; font-size: 0.9rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Create a responsive grid */\n.cards {",
|
"codePrefix": ".features {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
|
"solution": "display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
|
|||||||
92
lessons/09-gradients.json
Normal file
92
lessons/09-gradients.json
Normal file
@@ -0,0 +1,92 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||||
|
"id": "css-gradients",
|
||||||
|
"title": "CSS Gradients",
|
||||||
|
"description": "Create smooth color transitions with CSS gradients.",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"lessons": [
|
||||||
|
{
|
||||||
|
"id": "gradients-1",
|
||||||
|
"title": "Linear Gradient",
|
||||||
|
"description": "Gradients create smooth transitions between colors. The <kbd>linear-gradient()</kbd> function creates a gradient along a straight line.<br><br><strong>Basic syntax:</strong><br><pre>background: linear-gradient(color1, color2);</pre><br>By default, gradients flow from top to bottom.",
|
||||||
|
"task": "Add a gradient background from <kbd>coral</kbd> to <kbd>gold</kbd>.",
|
||||||
|
"previewHTML": "<div class=\"card\"><h3>Summer Sale</h3><p>Up to 50% off</p></div>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .card { padding: 2rem; border-radius: 12px; color: white; text-shadow: 0 1px 2px rgba(0,0,0,0.2); } .card h3 { margin: 0 0 8px; font-size: 1.5rem; } .card p { margin: 0; opacity: 0.9; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".card {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "background: linear-gradient(coral, gold);",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "linear-gradient",
|
||||||
|
"message": "Use <kbd>linear-gradient()</kbd>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "coral",
|
||||||
|
"message": "Include <kbd>coral</kbd> as the first color"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "gold",
|
||||||
|
"message": "Include <kbd>gold</kbd> as the second color"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "gradients-2",
|
||||||
|
"title": "Gradient Direction",
|
||||||
|
"description": "Control the gradient direction by adding an angle or keyword before the colors.<br><br><strong>Keywords:</strong> <kbd>to right</kbd>, <kbd>to left</kbd>, <kbd>to bottom right</kbd><br><strong>Angles:</strong> <kbd>45deg</kbd>, <kbd>90deg</kbd>, <kbd>180deg</kbd><br><br><pre>background: linear-gradient(to right, blue, purple);</pre>",
|
||||||
|
"task": "Make the gradient flow from left to right using <kbd>to right</kbd>.",
|
||||||
|
"previewHTML": "<button class=\"btn\">Get Started</button>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 2rem; } .btn { padding: 1rem 2rem; border: none; border-radius: 8px; font-size: 1rem; font-weight: 600; color: white; cursor: pointer; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".btn {\n background: linear-gradient(",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": ", steelblue, mediumseagreen);\n}",
|
||||||
|
"solution": "to right",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "to right",
|
||||||
|
"message": "Add <kbd>to right</kbd> to set the direction"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "gradients-3",
|
||||||
|
"title": "Radial Gradient",
|
||||||
|
"description": "The <kbd>radial-gradient()</kbd> function creates a gradient that radiates from a center point outward in a circular or elliptical pattern.<br><br><pre>background: radial-gradient(circle, white, steelblue);</pre><br>Add <kbd>circle</kbd> for a perfect circular gradient.",
|
||||||
|
"task": "Create a radial gradient from <kbd>white</kbd> to <kbd>steelblue</kbd>.",
|
||||||
|
"previewHTML": "<div class=\"orb\"></div>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 2rem; display: flex; justify-content: center; } .orb { width: 150px; height: 150px; border-radius: 50%; box-shadow: 0 8px 32px rgba(70, 130, 180, 0.4); }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".orb {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "background: radial-gradient(circle, white, steelblue);",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "radial-gradient",
|
||||||
|
"message": "Use <kbd>radial-gradient()</kbd>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "white",
|
||||||
|
"message": "Start with <kbd>white</kbd>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "steelblue",
|
||||||
|
"message": "End with <kbd>steelblue</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
108
lessons/11-filters.json
Normal file
108
lessons/11-filters.json
Normal file
@@ -0,0 +1,108 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||||
|
"id": "css-filters",
|
||||||
|
"title": "CSS Filters",
|
||||||
|
"description": "Apply visual effects like blur, brightness, and shadows with CSS filters.",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"lessons": [
|
||||||
|
{
|
||||||
|
"id": "filters-1",
|
||||||
|
"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.",
|
||||||
|
"task": "Blur the background image using <kbd>filter: blur(4px)</kbd>.",
|
||||||
|
"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; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".bg {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "filter: blur(4px);",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "filter", "expected": "blur(4px)" },
|
||||||
|
"message": "Set <kbd>filter: blur(4px)</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "filters-2",
|
||||||
|
"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.",
|
||||||
|
"task": "Make the image grayscale with <kbd>filter: grayscale(100%)</kbd>.",
|
||||||
|
"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; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".photo {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "filter: grayscale(100%);",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "grayscale",
|
||||||
|
"message": "Use <kbd>grayscale()</kbd> filter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "100%",
|
||||||
|
"message": "Set to <kbd>100%</kbd> for full grayscale"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "filters-3",
|
||||||
|
"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>",
|
||||||
|
"task": "Brighten the card with <kbd>filter: brightness(120%)</kbd>.",
|
||||||
|
"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; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".card {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "filter: brightness(120%);",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "brightness",
|
||||||
|
"message": "Use <kbd>brightness()</kbd> filter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "120%",
|
||||||
|
"message": "Set to <kbd>120%</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "filters-4",
|
||||||
|
"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>",
|
||||||
|
"task": "Add a drop shadow with <kbd>filter: drop-shadow(4px 4px 8px gray)</kbd>.",
|
||||||
|
"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; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".icon {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "filter: drop-shadow(4px 4px 8px gray);",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "drop-shadow",
|
||||||
|
"message": "Use <kbd>drop-shadow()</kbd> filter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "4px 4px 8px",
|
||||||
|
"message": "Set shadow offset and blur"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
98
lessons/12-positioning.json
Normal file
98
lessons/12-positioning.json
Normal file
@@ -0,0 +1,98 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||||
|
"id": "css-positioning",
|
||||||
|
"title": "CSS Positioning",
|
||||||
|
"description": "Control element placement with CSS positioning properties.",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"lessons": [
|
||||||
|
{
|
||||||
|
"id": "position-1",
|
||||||
|
"title": "Relative Position",
|
||||||
|
"description": "The <kbd>position</kbd> property controls how elements are placed. <kbd>relative</kbd> keeps the element in normal flow but allows you to offset it with <kbd>top</kbd>, <kbd>right</kbd>, <kbd>bottom</kbd>, <kbd>left</kbd>.<br><br><pre>.box {\n position: relative;\n top: 10px;\n}</pre>",
|
||||||
|
"task": "Make the badge position relative so we can offset it.",
|
||||||
|
"previewHTML": "<div class=\"card\"><span class=\"badge\">NEW</span><h3>Product</h3></div>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .card { padding: 1rem; background: white; border: 2px solid #eee; border-radius: 8px; } .card h3 { margin: 0; } .badge { display: inline-block; padding: 2px 8px; background: coral; color: white; font-size: 0.7rem; font-weight: bold; border-radius: 4px; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".badge {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "position: relative;",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "position", "expected": "relative" },
|
||||||
|
"message": "Set <kbd>position: relative</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "position-2",
|
||||||
|
"title": "Offset Properties",
|
||||||
|
"description": "With <kbd>position: relative</kbd>, use offset properties to nudge the element from its original position:<br><br><kbd>top</kbd> - pushes down from top<br><kbd>left</kbd> - pushes right from left<br><br>Negative values move in the opposite direction.",
|
||||||
|
"task": "Move the badge up with <kbd>top: -8px</kbd>.",
|
||||||
|
"previewHTML": "<div class=\"card\"><span class=\"badge\">NEW</span><h3>Product</h3></div>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .card { padding: 1rem; background: white; border: 2px solid #eee; border-radius: 8px; } .card h3 { margin: 0; } .badge { display: inline-block; padding: 2px 8px; background: coral; color: white; font-size: 0.7rem; font-weight: bold; border-radius: 4px; position: relative; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".badge {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "top: -8px;",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "top", "expected": "-8px" },
|
||||||
|
"message": "Set <kbd>top: -8px</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "position-3",
|
||||||
|
"title": "Absolute Position",
|
||||||
|
"description": "<kbd>position: absolute</kbd> removes the element from normal flow and positions it relative to its nearest positioned ancestor (or the viewport if none exists).<br><br>Always set a parent to <kbd>position: relative</kbd> to contain absolute children.",
|
||||||
|
"task": "Position the close button absolutely.",
|
||||||
|
"previewHTML": "<div class=\"modal\"><button class=\"close\">×</button><h3>Modal</h3><p>Content here</p></div>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .modal { position: relative; padding: 2rem; background: white; border-radius: 12px; box-shadow: 0 4px 24px rgba(0,0,0,0.15); max-width: 250px; } .modal h3 { margin: 0 0 8px; } .modal p { margin: 0; color: #666; } .close { width: 32px; height: 32px; border: none; background: #f5f5f5; border-radius: 50%; font-size: 1.2rem; cursor: pointer; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".close {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "position: absolute;",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "position", "expected": "absolute" },
|
||||||
|
"message": "Set <kbd>position: absolute</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "position-4",
|
||||||
|
"title": "Placing Absolute Elements",
|
||||||
|
"description": "Combine <kbd>position: absolute</kbd> with offset properties to place elements precisely.<br><br><pre>.close {\n position: absolute;\n top: 8px;\n right: 8px;\n}</pre>",
|
||||||
|
"task": "Move the close button to the top right corner with <kbd>top: 8px</kbd> and <kbd>right: 8px</kbd>.",
|
||||||
|
"previewHTML": "<div class=\"modal\"><button class=\"close\">×</button><h3>Modal</h3><p>Content here</p></div>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .modal { position: relative; padding: 2rem; background: white; border-radius: 12px; box-shadow: 0 4px 24px rgba(0,0,0,0.15); max-width: 250px; } .modal h3 { margin: 0 0 8px; } .modal p { margin: 0; color: #666; } .close { position: absolute; width: 32px; height: 32px; border: none; background: #f5f5f5; border-radius: 50%; font-size: 1.2rem; cursor: pointer; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".close {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "top: 8px;\n right: 8px;",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "top", "expected": "8px" },
|
||||||
|
"message": "Set <kbd>top: 8px</kbd>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "right", "expected": "8px" },
|
||||||
|
"message": "Set <kbd>right: 8px</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
113
lessons/13-pseudo-elements.json
Normal file
113
lessons/13-pseudo-elements.json
Normal file
@@ -0,0 +1,113 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||||
|
"id": "css-pseudo-elements",
|
||||||
|
"title": "CSS Pseudo-elements",
|
||||||
|
"description": "Create decorative elements and style specific parts of content with pseudo-elements.",
|
||||||
|
"difficulty": "intermediate",
|
||||||
|
"lessons": [
|
||||||
|
{
|
||||||
|
"id": "pseudo-1",
|
||||||
|
"title": "The ::before Element",
|
||||||
|
"description": "Pseudo-elements let you style specific parts of an element. <kbd>::before</kbd> creates a virtual element as the first child.<br><br>It requires the <kbd>content</kbd> property to display anything (even if empty).<br><br><pre>.item::before {\n content: \"→ \";\n}</pre>",
|
||||||
|
"task": "Add a bullet before each list item using <kbd>::before</kbd> with <kbd>content: \"• \"</kbd>.",
|
||||||
|
"previewHTML": "<ul class=\"list\"><li>First item</li><li>Second item</li><li>Third item</li></ul>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .list { list-style: none; padding: 0; margin: 0; } .list li { padding: 8px 0; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".list li::before {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "content: \"• \";",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "content",
|
||||||
|
"message": "Use the <kbd>content</kbd> property"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "•",
|
||||||
|
"message": "Add a bullet character <kbd>•</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pseudo-2",
|
||||||
|
"title": "Styling ::before",
|
||||||
|
"description": "Pseudo-elements can be styled like any element. Add color, size, margins, and more.<br><br><pre>.item::before {\n content: \"★\";\n color: gold;\n margin-right: 8px;\n}</pre>",
|
||||||
|
"task": "Style the bullet with <kbd>color: coral</kbd>.",
|
||||||
|
"previewHTML": "<ul class=\"list\"><li>First item</li><li>Second item</li><li>Third item</li></ul>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .list { list-style: none; padding: 0; margin: 0; } .list li { padding: 8px 0; } .list li::before { content: \"• \"; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".list li::before {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "color: coral;",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "color", "expected": "coral" },
|
||||||
|
"message": "Set <kbd>color: coral</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pseudo-3",
|
||||||
|
"title": "The ::after Element",
|
||||||
|
"description": "<kbd>::after</kbd> works like <kbd>::before</kbd> but inserts content as the last child. Common uses include badges, icons, or decorative elements.<br><br><pre>.new::after {\n content: \" ✓\";\n color: green;\n}</pre>",
|
||||||
|
"task": "Add a checkmark after completed items with <kbd>content: \" ✓\"</kbd>.",
|
||||||
|
"previewHTML": "<ul class=\"list\"><li class=\"done\">Buy groceries</li><li class=\"done\">Walk the dog</li><li>Read a book</li></ul>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .list { list-style: none; padding: 0; margin: 0; } .list li { padding: 8px 0; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".done::after {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "content: \" ✓\";",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "content",
|
||||||
|
"message": "Use the <kbd>content</kbd> property"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "✓",
|
||||||
|
"message": "Add a checkmark <kbd>✓</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "pseudo-4",
|
||||||
|
"title": "Decorative Lines",
|
||||||
|
"description": "Pseudo-elements with <kbd>content: \"\"</kbd> can create decorative shapes when combined with width, height, and background.<br><br><pre>.title::after {\n content: \"\";\n display: block;\n width: 50px;\n height: 3px;\n background: coral;\n}</pre>",
|
||||||
|
"task": "Create an underline decoration with <kbd>width: 40px</kbd>, <kbd>height: 3px</kbd>, and <kbd>background: steelblue</kbd>.",
|
||||||
|
"previewHTML": "<h2 class=\"title\">About Us</h2><p>We build great things.</p>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .title { margin: 0 0 1rem; } .title::after { content: \"\"; display: block; margin-top: 8px; } p { margin: 0; color: #666; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".title::after {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "width: 40px;\n height: 3px;\n background: steelblue;",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "width", "expected": "40px" },
|
||||||
|
"message": "Set <kbd>width: 40px</kbd>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "height", "expected": "3px" },
|
||||||
|
"message": "Set <kbd>height: 3px</kbd>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "background", "expected": "steelblue" },
|
||||||
|
"message": "Set <kbd>background: steelblue</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -63,35 +63,6 @@
|
|||||||
"message": "Add an <kbd><h1></kbd> heading inside your header"
|
"message": "Add an <kbd><h1></kbd> heading inside your header"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "div-vs-span",
|
|
||||||
"title": "div & span",
|
|
||||||
"description": "When you need a container without semantic meaning:<br><br><kbd><div></kbd> - Generic block container (for layout/grouping)<br><kbd><span></kbd> - Generic inline container (for styling text portions)<br><br>Use semantic elements when possible, div/span when no semantic element fits.",
|
|
||||||
"task": "Wrap the word 'highlighted' in a <kbd><span></kbd> to style it differently. Wrap the whole quote in a <kbd><div></kbd>.",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: Georgia, serif; padding: 20px; } div { background: #f5f5f5; padding: 15px; border-left: 4px solid #1976d2; } span { background: #fff59d; padding: 2px 4px; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "The most highlighted moment was unforgettable.",
|
|
||||||
"solution": "<div>The most <span>highlighted</span> moment was unforgettable.</div>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "div",
|
|
||||||
"message": "Wrap everything in a <kbd><div></kbd> element"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "span",
|
|
||||||
"message": "Add a <kbd><span></kbd> around the word <kbd>highlighted</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_text",
|
|
||||||
"value": { "selector": "span", "text": "highlighted" },
|
|
||||||
"message": "The <kbd><span></kbd> should contain the word <kbd>highlighted</kbd>"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,110 +1,32 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-forms-validation",
|
"id": "html-forms-validation",
|
||||||
"title": "HTML Validation",
|
"title": "Form Validation",
|
||||||
"description": "Learn HTML5 built-in form validation attributes",
|
"description": "Use HTML5 built-in validation for better user experience",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "intermediate",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "required-fields",
|
"id": "required-fields",
|
||||||
"title": "Required Fields",
|
"title": "Required Fields",
|
||||||
"description": "The <kbd>required</kbd> attribute prevents form submission if the field is empty.<br><br>Add it to any input that must be filled:<br><kbd><input type=\"text\" required></kbd><br><br>The browser shows a validation message automatically.",
|
"description": "The <kbd>required</kbd> attribute prevents form submission if the field is empty. The browser shows a validation message automatically - no JavaScript needed!<br><br>Add it to any input that must be filled:<br><kbd><input type=\"text\" required></kbd>",
|
||||||
"task": "Make both the name and email fields required by adding the <kbd>required</kbd> attribute.",
|
"task": "Make both the name and email fields required by adding the <kbd>required</kbd> attribute to each input.",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 350px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-of-type { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } input:invalid { border-color: #d32f2f; } button { margin-top: 20px; padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 320px; } label { display: block; margin-top: 15px; margin-bottom: 5px; font-weight: 500; } label:first-of-type { margin-top: 0; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; box-sizing: border-box; } input:focus { outline: 2px solid steelblue; border-color: transparent; } button { margin-top: 20px; padding: 10px 20px; background: steelblue; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 500; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "<form>\n <label for=\"name\">Name: *</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n \n <label for=\"email\">Email: *</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <button type=\"submit\">Submit</button>\n</form>",
|
"initialCode": "<form>\n <label for=\"name\">Name *</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n \n <label for=\"email\">Email *</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <button type=\"submit\">Submit</button>\n</form>",
|
||||||
"solution": "<form>\n <label for=\"name\">Name: *</label>\n <input type=\"text\" id=\"name\" name=\"name\" required>\n \n <label for=\"email\">Email: *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <button type=\"submit\">Submit</button>\n</form>",
|
"solution": "<form>\n <label for=\"name\">Name *</label>\n <input type=\"text\" id=\"name\" name=\"name\" required>\n \n <label for=\"email\">Email *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <button type=\"submit\">Submit</button>\n</form>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "input[name='name']", "attr": "required", "value": true },
|
"value": { "selector": "input[name='name']", "attr": "required", "value": true },
|
||||||
"message": "Add the <kbd>required</kbd> attribute to the name input"
|
"message": "Add <kbd>required</kbd> to the name input"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "input[name='email']", "attr": "required", "value": true },
|
"value": { "selector": "input[name='email']", "attr": "required", "value": true },
|
||||||
"message": "Add the <kbd>required</kbd> attribute to the email input"
|
"message": "Add <kbd>required</kbd> to the email input"
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "input-constraints",
|
|
||||||
"title": "Constraints",
|
|
||||||
"description": "Control what users can enter:<br><br><kbd>minlength</kbd> / <kbd>maxlength</kbd> - Text length limits<br><kbd>min</kbd> / <kbd>max</kbd> - Number range<br><kbd>pattern</kbd> - Regex pattern matching<br><kbd>placeholder</kbd> - Hint text (not a label!)",
|
|
||||||
"task": "Add validation to the password input:<br>1. Add <kbd>minlength=\"8\"</kbd> for minimum length<br>2. Add <kbd>maxlength=\"20\"</kbd> for maximum length<br>3. Add <kbd>placeholder=\"Enter password\"</kbd> as a hint",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 350px; } label { display: block; margin-bottom: 5px; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } input:invalid:not(:placeholder-shown) { border-color: #d32f2f; } small { display: block; font-size: 12px; color: #666; margin-top: 4px; } button { margin-top: 20px; padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<form>\n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required aria-describedby=\"password-hint\">\n <small id=\"password-hint\">Must be 8-20 characters</small>\n \n <button type=\"submit\">Create Account</button>\n</form>",
|
|
||||||
"solution": "<form>\n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required minlength=\"8\" maxlength=\"20\" placeholder=\"Enter password\" aria-describedby=\"password-hint\">\n <small id=\"password-hint\">Must be 8-20 characters</small>\n \n <button type=\"submit\">Create Account</button>\n</form>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "input[type='password']", "attr": "minlength", "value": "8" },
|
|
||||||
"message": "Add <kbd>minlength</kbd>=\"8\" to the password input"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "input[type='password']", "attr": "maxlength", "value": "20" },
|
|
||||||
"message": "Add <kbd>maxlength</kbd>=\"20\" to the password input"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "input[type='password']", "attr": "placeholder", "value": null },
|
|
||||||
"message": "Add a <kbd>placeholder</kbd> to hint what to enter"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "complete-registration",
|
|
||||||
"title": "Full Form",
|
|
||||||
"description": "Build a complete registration form with all validation concepts:<br><br>- Required fields marked with *<br>- Email validation (use type=\"email\")<br>- Password with length constraints<br>- Terms checkbox (required)<br>- Submit button",
|
|
||||||
"task": "Complete the registration form. Add required attributes, proper input types, and validation constraints.",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 400px; background: #f5f5f5; padding: 25px; border-radius: 8px; } h2 { margin-top: 0; margin-bottom: 20px; } label { display: block; margin-top: 15px; margin-bottom: 5px; font-weight: 500; } input[type='text'], input[type='email'], input[type='password'] { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; font-size: 14px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; } input[type='checkbox'] { width: auto; margin-right: 8px; vertical-align: middle; } label:has(input[type='checkbox']) { display: flex; align-items: center; font-weight: normal; } button { width: 100%; margin-top: 20px; padding: 12px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: 500; } button:hover { background: #1565c0; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<form>\n <h2>Create Account</h2>\n \n <label for=\"fullname\">Full Name *</label>\n <input type=\"text\" id=\"fullname\" name=\"fullname\">\n \n <label for=\"email\">Email *</label>\n <input id=\"email\" name=\"email\">\n \n <label for=\"password\">Password *</label>\n <input id=\"password\" name=\"password\">\n \n <label>\n <input type=\"checkbox\" id=\"terms\" name=\"terms\">\n I agree to the Terms of Service *\n </label>\n \n <button type=\"submit\">Register</button>\n</form>",
|
|
||||||
"solution": "<form>\n <h2>Create Account</h2>\n \n <label for=\"fullname\">Full Name *</label>\n <input type=\"text\" id=\"fullname\" name=\"fullname\" required>\n \n <label for=\"email\">Email *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <label for=\"password\">Password *</label>\n <input type=\"password\" id=\"password\" name=\"password\" required minlength=\"8\">\n \n <label>\n <input type=\"checkbox\" id=\"terms\" name=\"terms\" required>\n I agree to the Terms of Service *\n </label>\n \n <button type=\"submit\">Register</button>\n</form>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#fullname", "attr": "required", "value": true },
|
|
||||||
"message": "Make the full name field <kbd>required</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#email", "attr": "type", "value": "email" },
|
|
||||||
"message": "Set the email input <kbd>type=\"email\"</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#email", "attr": "required", "value": true },
|
|
||||||
"message": "Make the email field <kbd>required</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#password", "attr": "type", "value": "password" },
|
|
||||||
"message": "Set the password input <kbd>type=\"password\"</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#password", "attr": "required", "value": true },
|
|
||||||
"message": "Make the password field <kbd>required</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#password", "attr": "minlength", "value": "8" },
|
|
||||||
"message": "Add <kbd>minlength</kbd>=\"8\" to password"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#terms", "attr": "required", "value": true },
|
|
||||||
"message": "Make the terms checkbox <kbd>required</kbd>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,20 +2,20 @@
|
|||||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-tables",
|
"id": "html-tables",
|
||||||
"title": "HTML Tables",
|
"title": "HTML Tables",
|
||||||
"description": "Create structured data tables with headers and captions",
|
"description": "Create structured data tables with semantic markup",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "table-basic",
|
"id": "table-basic",
|
||||||
"title": "Basic Table Structure",
|
"title": "Data Tables",
|
||||||
"description": "Tables use <kbd><table></kbd> with <kbd><tr></kbd> for rows. Inside rows, use <kbd><th></kbd> for headers and <kbd><td></kbd> for data cells.<br><br>The <kbd><caption></kbd> element provides an accessible title for the table.",
|
"description": "Tables display structured data in rows and columns. Use <kbd><table></kbd> as the container, <kbd><tr></kbd> for rows, <kbd><th></kbd> for header cells, and <kbd><td></kbd> for data cells.<br><br>Add <kbd><caption></kbd> for an accessible title that describes the table's content.",
|
||||||
"task": "Create a simple table with:<br>1. A <kbd><caption></kbd> saying <code>Fruit Prices</code><br>2. A header row with <code>Fruit</code> and <code>Price</code> columns<br>3. At least 2 data rows",
|
"task": "Create a pricing table:<br>1. A <kbd><caption></kbd> saying <code>Pricing</code><br>2. A header row with <code>Plan</code> and <code>Price</code><br>3. Two data rows for Basic ($9) and Pro ($29)",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px; font-weight: 600; font-size: 1.2rem; color: #333; background: #f8f9fa; } th, td { padding: 12px 20px; text-align: left; border-bottom: 1px solid #eee; } th { background: #3498db; color: white; font-weight: 500; } tr:hover { background: #f8f9fa; } tr:last-child td { border-bottom: none; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; } table { border-collapse: collapse; width: 100%; max-width: 350px; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 16px; font-weight: 600; font-size: 1.1rem; color: #333; background: #f8f9fa; } th, td { padding: 14px 20px; text-align: left; border-bottom: 1px solid #eee; } th { background: steelblue; color: white; font-weight: 500; } tr:last-child td { border-bottom: none; } tr:hover td { background: #f8f9fa; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<table>\n <caption>Fruit Prices</caption>\n <tr>\n <th>Fruit</th>\n <th>Price</th>\n </tr>\n <tr>\n <td>Apple</td>\n <td>$1.50</td>\n </tr>\n <tr>\n <td>Banana</td>\n <td>$0.75</td>\n </tr>\n</table>",
|
"solution": "<table>\n <caption>Pricing</caption>\n <tr>\n <th>Plan</th>\n <th>Price</th>\n </tr>\n <tr>\n <td>Basic</td>\n <td>$9</td>\n </tr>\n <tr>\n <td>Pro</td>\n <td>$29</td>\n </tr>\n</table>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
@@ -31,95 +31,60 @@
|
|||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "th", "min": 2 },
|
"value": { "selector": "th", "min": 2 },
|
||||||
"message": "Add at least 2 header cells (th)"
|
"message": "Add header cells (<kbd><th></kbd>) for Plan and Price"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "tr", "min": 3 },
|
"value": { "selector": "tr", "min": 3 },
|
||||||
"message": "Add at least 3 rows (1 header + 2 data rows)"
|
"message": "Add 3 rows (1 header + 2 data rows)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "table-thead-tbody",
|
"id": "table-sections",
|
||||||
"title": "Table Head & Body",
|
"title": "Table Sections",
|
||||||
"description": "Use <kbd><thead></kbd> to group header rows and <kbd><tbody></kbd> to group data rows. This helps browsers and assistive technology understand the table structure.<br><br>You can also use <kbd><tfoot></kbd> for footer rows like totals.",
|
"description": "Semantic table sections improve accessibility and allow for separate styling:<br><br>• <kbd><thead></kbd> — header section<br>• <kbd><tbody></kbd> — main content<br>• <kbd><tfoot></kbd> — footer (totals, summaries)",
|
||||||
"task": "Create a structured table:<br>1. A <kbd><caption></kbd> with <code>Monthly Sales</code><br>2. A <kbd><thead></kbd> with Month and Revenue headers<br>3. A <kbd><tbody></kbd> with at least 2 data rows",
|
"task": "Wrap the header row in <kbd><thead></kbd> and data rows in <kbd><tbody></kbd>.",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } table { border-collapse: collapse; width: 100%; max-width: 450px; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.2); } caption { padding: 20px; font-weight: 700; font-size: 1.3rem; color: white; background: transparent; text-shadow: 0 2px 4px rgba(0,0,0,0.2); caption-side: top; } thead { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } th { padding: 15px 20px; text-align: left; color: white; font-weight: 500; } tbody tr { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } td { padding: 15px 20px; color: #333; } tbody tr:last-child { border-bottom: none; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } table { border-collapse: collapse; width: 100%; max-width: 350px; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } th, td { padding: 12px 16px; text-align: left; } thead { background: steelblue; color: white; } tbody tr:nth-child(even) { background: #f8f9fa; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "<table>\n <tr>\n <th>Name</th>\n <th>Score</th>\n </tr>\n <tr>\n <td>Alice</td>\n <td>95</td>\n </tr>\n <tr>\n <td>Bob</td>\n <td>87</td>\n </tr>\n</table>",
|
||||||
"solution": "<table>\n <caption>Monthly Sales</caption>\n <thead>\n <tr>\n <th>Month</th>\n <th>Revenue</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>January</td>\n <td>$12,500</td>\n </tr>\n <tr>\n <td>February</td>\n <td>$14,200</td>\n </tr>\n </tbody>\n</table>",
|
"solution": "<table>\n <thead>\n <tr>\n <th>Name</th>\n <th>Score</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Alice</td>\n <td>95</td>\n </tr>\n <tr>\n <td>Bob</td>\n <td>87</td>\n </tr>\n </tbody>\n</table>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "table",
|
|
||||||
"message": "Add a <kbd><table></kbd> element"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "caption",
|
|
||||||
"message": "Add a <kbd><caption></kbd> element"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "thead",
|
"value": "thead",
|
||||||
"message": "Add a <kbd><thead></kbd> for the header section"
|
"message": "Add a <kbd><thead></kbd> section for the header"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "tbody",
|
"value": "tbody",
|
||||||
"message": "Add a <kbd><tbody></kbd> for the data rows"
|
"message": "Add a <kbd><tbody></kbd> section for the data"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_count",
|
|
||||||
"value": { "selector": "tbody tr", "min": 2 },
|
|
||||||
"message": "Add at least 2 data rows in tbody"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "table-complete",
|
"id": "table-colspan",
|
||||||
"title": "Complete Table with Footer",
|
"title": "Spanning Columns",
|
||||||
"description": "Add <kbd><tfoot></kbd> to create a footer section for totals or summary data. The footer stays at the bottom even if tbody has many rows.<br><br>Combine all sections for a fully structured, accessible table.",
|
"description": "The <kbd>colspan</kbd> attribute lets a cell span multiple columns. This is useful for headers that group multiple columns or footer totals.<br><br><pre><td colspan=\"2\">...</td></pre>",
|
||||||
"task": "Create a complete table:<br>1. A <kbd><caption></kbd> with <code>Order Summary</code><br>2. A <kbd><thead></kbd> with Item and Price headers<br>3. A <kbd><tbody></kbd> with 2 items<br>4. A <kbd><tfoot></kbd> with a Total row",
|
"task": "Add a footer row that spans both columns using <kbd>colspan=\"2\"</kbd>.",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px 20px; font-weight: 600; font-size: 1.1rem; color: #333; text-align: left; background: #f8f9fa; border-bottom: 2px solid #eee; } th, td { padding: 12px 20px; text-align: left; } thead th { background: #2c3e50; color: white; } tbody td { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } tfoot { background: #ecf0f1; font-weight: 600; } tfoot td { border-top: 2px solid #2c3e50; color: #2c3e50; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } table { border-collapse: collapse; width: 100%; max-width: 350px; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 8px rgba(0,0,0,0.1); } th, td { padding: 12px 16px; text-align: left; border-bottom: 1px solid #eee; } thead { background: steelblue; color: white; } tfoot { background: #f0f0f0; font-weight: 600; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "<table>\n <thead>\n <tr>\n <th>Item</th>\n <th>Price</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Coffee</td>\n <td>$4</td>\n </tr>\n <tr>\n <td>Cake</td>\n <td>$6</td>\n </tr>\n </tbody>\n</table>",
|
||||||
"solution": "<table>\n <caption>Order Summary</caption>\n <thead>\n <tr>\n <th>Item</th>\n <th>Price</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Widget</td>\n <td>$25.00</td>\n </tr>\n <tr>\n <td>Gadget</td>\n <td>$35.00</td>\n </tr>\n </tbody>\n <tfoot>\n <tr>\n <td>Total</td>\n <td>$60.00</td>\n </tr>\n </tfoot>\n</table>",
|
"solution": "<table>\n <thead>\n <tr>\n <th>Item</th>\n <th>Price</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Coffee</td>\n <td>$4</td>\n </tr>\n <tr>\n <td>Cake</td>\n <td>$6</td>\n </tr>\n </tbody>\n <tfoot>\n <tr>\n <td colspan=\"2\">Total: $10</td>\n </tr>\n </tfoot>\n</table>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "table",
|
|
||||||
"message": "Add a <kbd><table></kbd> element"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "caption",
|
|
||||||
"message": "Add a <kbd><caption></kbd> element"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "thead",
|
|
||||||
"message": "Add a <kbd><thead></kbd> section"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "tbody",
|
|
||||||
"message": "Add a <kbd><tbody></kbd> section"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "tfoot",
|
"value": "tfoot",
|
||||||
"message": "Add a <kbd><tfoot></kbd> section for the total"
|
"message": "Add a <kbd><tfoot></kbd> section"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "contains",
|
||||||
"value": { "selector": "tbody tr", "min": 2 },
|
"value": "colspan",
|
||||||
"message": "Add at least 2 item rows in tbody"
|
"message": "Use <kbd>colspan</kbd> to span columns"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
88
lessons/33-html-semantic.json
Normal file
88
lessons/33-html-semantic.json
Normal file
@@ -0,0 +1,88 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||||
|
"id": "html-semantic",
|
||||||
|
"title": "Semantic HTML",
|
||||||
|
"mode": "html",
|
||||||
|
"description": "Use meaningful HTML elements to structure content properly.",
|
||||||
|
"difficulty": "beginner",
|
||||||
|
"lessons": [
|
||||||
|
{
|
||||||
|
"id": "semantic-1",
|
||||||
|
"title": "The <article> Element",
|
||||||
|
"description": "The <kbd><article></kbd> element represents self-contained content that could be distributed independently, like a blog post, news article, or comment.<br><br><pre><article>\n <h2>Article Title</h2>\n <p>Article content...</p>\n</article></pre>",
|
||||||
|
"task": "Wrap the blog post content in an <kbd><article></kbd> element.",
|
||||||
|
"previewHTML": "",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } article { padding: 1rem; background: #f9f9f9; border-left: 4px solid steelblue; border-radius: 4px; } h2 { margin: 0 0 8px; color: steelblue; } p { margin: 0; color: #555; line-height: 1.5; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "",
|
||||||
|
"initialCode": "<h2>My First Post</h2>\n<p>This is a blog post about learning HTML.</p>",
|
||||||
|
"codeSuffix": "",
|
||||||
|
"solution": "<article>\n<h2>My First Post</h2>\n<p>This is a blog post about learning HTML.</p>\n</article>",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "<article>",
|
||||||
|
"message": "Add an opening <kbd><article></kbd> tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "</article>",
|
||||||
|
"message": "Add a closing <kbd></article></kbd> tag"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "semantic-2",
|
||||||
|
"title": "The <section> Element",
|
||||||
|
"description": "The <kbd><section></kbd> element represents a thematic grouping of content, typically with a heading. Use it to divide a page into logical sections.<br><br><pre><section>\n <h2>Features</h2>\n <p>Our product features...</p>\n</section></pre>",
|
||||||
|
"task": "Wrap the features content in a <kbd><section></kbd> element.",
|
||||||
|
"previewHTML": "",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } section { padding: 1rem; background: linear-gradient(135deg, #f5f7fa 0%, #c3cfe2 100%); border-radius: 8px; } h2 { margin: 0 0 12px; } ul { margin: 0; padding-left: 1.5rem; } li { margin: 4px 0; color: #444; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "",
|
||||||
|
"initialCode": "<h2>Features</h2>\n<ul>\n <li>Fast performance</li>\n <li>Easy to use</li>\n</ul>",
|
||||||
|
"codeSuffix": "",
|
||||||
|
"solution": "<section>\n<h2>Features</h2>\n<ul>\n <li>Fast performance</li>\n <li>Easy to use</li>\n</ul>\n</section>",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "<section>",
|
||||||
|
"message": "Add an opening <kbd><section></kbd> tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "</section>",
|
||||||
|
"message": "Add a closing <kbd></section></kbd> tag"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "semantic-3",
|
||||||
|
"title": "The <aside> Element",
|
||||||
|
"description": "The <kbd><aside></kbd> element represents content tangentially related to the main content, like sidebars, pull quotes, or related links.<br><br><pre><aside>\n <h3>Related</h3>\n <ul>...</ul>\n</aside></pre>",
|
||||||
|
"task": "Wrap the related links in an <kbd><aside></kbd> element.",
|
||||||
|
"previewHTML": "",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } aside { padding: 1rem; background: #fff8e7; border: 1px solid #ffe0a6; border-radius: 8px; } h3 { margin: 0 0 8px; color: #b8860b; font-size: 0.9rem; text-transform: uppercase; } ul { margin: 0; padding-left: 1.2rem; } li { margin: 4px 0; } a { color: #b8860b; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "",
|
||||||
|
"initialCode": "<h3>Related Posts</h3>\n<ul>\n <li><a href=\"#\">CSS Basics</a></li>\n <li><a href=\"#\">HTML Tips</a></li>\n</ul>",
|
||||||
|
"codeSuffix": "",
|
||||||
|
"solution": "<aside>\n<h3>Related Posts</h3>\n<ul>\n <li><a href=\"#\">CSS Basics</a></li>\n <li><a href=\"#\">HTML Tips</a></li>\n</ul>\n</aside>",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "<aside>",
|
||||||
|
"message": "Add an opening <kbd><aside></kbd> tag"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "</aside>",
|
||||||
|
"message": "Add a closing <kbd></aside></kbd> tag"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
197
lessons/40-markdown-basics.json
Normal file
197
lessons/40-markdown-basics.json
Normal file
@@ -0,0 +1,197 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||||
|
"id": "markdown-basics",
|
||||||
|
"title": "Markdown Basics",
|
||||||
|
"description": "Learn to format text documents with Markdown, a simple and readable markup language used everywhere from GitHub to note-taking apps.",
|
||||||
|
"mode": "markdown",
|
||||||
|
"difficulty": "beginner",
|
||||||
|
"lessons": [
|
||||||
|
{
|
||||||
|
"id": "md-headings",
|
||||||
|
"title": "Headings",
|
||||||
|
"description": "Markdown uses hash symbols <kbd>#</kbd> to create headings. One <kbd>#</kbd> creates the largest heading (h1), two <kbd>##</kbd> creates a smaller heading (h2), and so on up to six levels.<br><br><pre># Main Title\n## Section\n### Subsection</pre>",
|
||||||
|
"task": "Create a main heading by typing <kbd># Hello</kbd>",
|
||||||
|
"previewHTML": "",
|
||||||
|
"previewBaseCSS": "",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"solution": "# Hello",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "^#\\s+.+",
|
||||||
|
"message": "Start with <kbd>#</kbd> followed by a space and your heading text"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "Hello",
|
||||||
|
"message": "Your heading should contain <kbd>Hello</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "md-heading-levels",
|
||||||
|
"title": "Heading Levels",
|
||||||
|
"description": "Use more <kbd>#</kbd> symbols for smaller headings. <kbd>##</kbd> creates an h2, <kbd>###</kbd> an h3. This creates a clear document structure with visual hierarchy.",
|
||||||
|
"task": "Create an h2 heading with <kbd>## About</kbd> followed by an h3 heading with <kbd>### Details</kbd>",
|
||||||
|
"previewHTML": "",
|
||||||
|
"previewBaseCSS": "",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"solution": "## About\n\n### Details",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "^##\\s+About",
|
||||||
|
"message": "Start with <kbd>## About</kbd>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "###\\s+Details",
|
||||||
|
"message": "Add <kbd>### Details</kbd> for the h3 heading"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "md-bold",
|
||||||
|
"title": "Bold Text",
|
||||||
|
"description": "Wrap text in double asterisks <kbd>**</kbd> or double underscores <kbd>__</kbd> to make it <strong>bold</strong>. This emphasizes important words or phrases.",
|
||||||
|
"task": "Make the word <kbd>important</kbd> bold by wrapping it with <kbd>**</kbd>",
|
||||||
|
"previewHTML": "",
|
||||||
|
"previewBaseCSS": "",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"initialCode": "This is important text.",
|
||||||
|
"solution": "This is **important** text.",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "\\*\\*important\\*\\*",
|
||||||
|
"message": "Wrap <kbd>important</kbd> with double asterisks: <kbd>**important**</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "md-italic",
|
||||||
|
"title": "Italic Text",
|
||||||
|
"description": "Wrap text in single asterisks <kbd>*</kbd> or single underscores <kbd>_</kbd> to make it <em>italic</em>. Use this for subtle emphasis or titles of works.",
|
||||||
|
"task": "Make the word <kbd>elegant</kbd> italic by wrapping it with <kbd>*</kbd>",
|
||||||
|
"previewHTML": "",
|
||||||
|
"previewBaseCSS": "",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"initialCode": "A simple and elegant solution.",
|
||||||
|
"solution": "A simple and *elegant* solution.",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "\\*elegant\\*",
|
||||||
|
"message": "Wrap <kbd>elegant</kbd> with single asterisks: <kbd>*elegant*</kbd>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "not_contains",
|
||||||
|
"value": "**elegant**",
|
||||||
|
"message": "Use single asterisks for italic, not double"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "md-unordered-list",
|
||||||
|
"title": "Bullet Lists",
|
||||||
|
"description": "Create bullet lists using <kbd>-</kbd>, <kbd>*</kbd>, or <kbd>+</kbd> at the start of each line. Each item goes on its own line.",
|
||||||
|
"task": "Create a bullet list with three items: <kbd>Apple</kbd>, <kbd>Banana</kbd>, <kbd>Cherry</kbd>",
|
||||||
|
"previewHTML": "",
|
||||||
|
"previewBaseCSS": "",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"solution": "- Apple\n- Banana\n- Cherry",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "^[-*+]\\s+Apple",
|
||||||
|
"message": "Start with a dash, asterisk, or plus followed by <kbd>Apple</kbd>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "[-*+]\\s+Banana",
|
||||||
|
"message": "Add <kbd>Banana</kbd> as a list item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "[-*+]\\s+Cherry",
|
||||||
|
"message": "Add <kbd>Cherry</kbd> as a list item"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "md-ordered-list",
|
||||||
|
"title": "Numbered Lists",
|
||||||
|
"description": "Create numbered lists by starting lines with <kbd>1.</kbd>, <kbd>2.</kbd>, etc. Markdown automatically numbers them in sequence.",
|
||||||
|
"task": "Create a numbered list: <kbd>Wake up</kbd>, <kbd>Eat breakfast</kbd>, <kbd>Start coding</kbd>",
|
||||||
|
"previewHTML": "",
|
||||||
|
"previewBaseCSS": "",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"solution": "1. Wake up\n2. Eat breakfast\n3. Start coding",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "\\d+\\.\\s+Wake up",
|
||||||
|
"message": "Start with a number and period: <kbd>1. Wake up</kbd>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "\\d+\\.\\s+Eat breakfast",
|
||||||
|
"message": "Add <kbd>Eat breakfast</kbd> as a numbered item"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "\\d+\\.\\s+Start coding",
|
||||||
|
"message": "Add <kbd>Start coding</kbd> as a numbered item"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "md-links",
|
||||||
|
"title": "Links",
|
||||||
|
"description": "Create links with <kbd>[text](url)</kbd>. The text in brackets is what readers see; the URL in parentheses is where they go when clicked.",
|
||||||
|
"task": "Create a link that shows <kbd>Google</kbd> and goes to <kbd>https://google.com</kbd>",
|
||||||
|
"previewHTML": "",
|
||||||
|
"previewBaseCSS": "",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"solution": "[Google](https://google.com)",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "\\[Google\\]\\(https?://google\\.com\\)",
|
||||||
|
"message": "Use the format <kbd>[Google](https://google.com)</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "md-inline-code",
|
||||||
|
"title": "Inline Code",
|
||||||
|
"description": "Wrap text in backticks <kbd>`</kbd> to format it as code. This is useful for variable names, commands, or short code snippets in your text.",
|
||||||
|
"task": "Format <kbd>npm install</kbd> as inline code using backticks",
|
||||||
|
"previewHTML": "",
|
||||||
|
"previewBaseCSS": "",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"initialCode": "Run npm install to install dependencies.",
|
||||||
|
"solution": "Run `npm install` to install dependencies.",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "`npm install`",
|
||||||
|
"message": "Wrap <kbd>npm install</kbd> with backticks: <kbd>`npm install`</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
25
lessons/98-playground.json
Normal file
25
lessons/98-playground.json
Normal file
@@ -0,0 +1,25 @@
|
|||||||
|
{
|
||||||
|
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||||
|
"id": "playground",
|
||||||
|
"title": "Playground",
|
||||||
|
"description": "Experiment freely with HTML and CSS",
|
||||||
|
"mode": "playground",
|
||||||
|
"difficulty": "beginner",
|
||||||
|
"excludeFromProgress": true,
|
||||||
|
"lessons": [
|
||||||
|
{
|
||||||
|
"id": "sandbox",
|
||||||
|
"title": "HTML & CSS Editor",
|
||||||
|
"mode": "playground",
|
||||||
|
"description": "",
|
||||||
|
"task": "",
|
||||||
|
"previewHTML": "",
|
||||||
|
"previewBaseCSS": "",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"initialCode": "<style>\n body {\n font-family: system-ui, sans-serif;\n padding: 20px;\n }\n \n h1 {\n color: steelblue;\n }\n</style>\n\n<h1>Hello World</h1>\n<p>Write any HTML and CSS here!</p>",
|
||||||
|
"solution": "",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": []
|
||||||
|
}
|
||||||
|
]
|
||||||
|
}
|
||||||
@@ -2,20 +2,21 @@
|
|||||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||||
"id": "goodbye",
|
"id": "goodbye",
|
||||||
"title": "What's Next?",
|
"title": "What's Next?",
|
||||||
"description": "Congratulations on completing your learning journey!",
|
"description": "Continue your learning journey",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
|
"excludeFromProgress": true,
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "congratulations",
|
"id": "next-steps",
|
||||||
"title": "Well Done!",
|
"title": "Keep Going!",
|
||||||
"description": "<strong>Congratulations!</strong> You've made it through Code Crispies!<br><br>You've learned:<br>• <strong>HTML</strong> - Semantic elements, forms, tables, and more<br>• <strong>CSS</strong> - Selectors, box model, flexbox, grid, animations<br>• <strong>Responsive Design</strong> - Media queries and mobile-first layouts<br><br>These are the building blocks of every website on the internet. Keep practicing and building!",
|
"description": "<strong>Great progress!</strong> You're building real web development skills.<br><br><strong>Continue learning:</strong><br>• <a href=\"https://developer.mozilla.org\" target=\"_blank\">MDN Web Docs</a> - The definitive reference<br>• <a href=\"https://css-tricks.com\" target=\"_blank\">CSS-Tricks</a> - Practical techniques<br><br><strong>Practice ideas:</strong><br>• Build your portfolio site<br>• Recreate a website you like<br>• Try the <a href=\"#playground/0\">Playground</a> to experiment freely<br><br><strong>Contribute:</strong> Code Crispies is <a href=\"https://git.librete.ch/libretech/code-crispies\" target=\"_blank\">open source</a>. Add lessons, fix bugs, or translate!<br><br><strong>Support:</strong> Help keep it free and open source!<br><a href=\"https://liberapay.com/libretech/donate\" target=\"_blank\" onclick=\"typeof umami !== 'undefined' && umami.track('support_click', {location: 'goodbye'})\"><img alt=\"Donate using Liberapay\" src=\"https://liberapay.com/assets/widgets/donate.svg\" style=\"margin-top: 8px;\"></a>",
|
||||||
"task": "Type <code>Thank you!</code>",
|
"task": "Type <code>Thank you!</code>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; } p { font-size: 1.5rem; color: #6366f1; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 1.5rem; color: #6366f1; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<p>Thank you!</p>",
|
"solution": "Thank you!",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
@@ -24,44 +25,6 @@
|
|||||||
"message": "Type <code>Thank you!</code>"
|
"message": "Type <code>Thank you!</code>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "contribute",
|
|
||||||
"title": "Contribute",
|
|
||||||
"description": "<strong>Help others learn!</strong><br><br>Code Crispies is open source and always looking for new lessons. You can contribute by:<br><br>• <strong>Writing new lessons</strong> - Share your knowledge on topics we haven't covered<br>• <strong>Translating lessons</strong> - Help make Code Crispies accessible in more languages<br>• <strong>Fixing bugs</strong> - Found something broken? Help us fix it!<br>• <strong>Improving content</strong> - Make existing lessons clearer and better<br><br><strong>Get started:</strong><br>• <a href=\"https://git.librete.ch/libretech/code-crispies\" target=\"_blank\">Gitea Repository</a> - Main source code<br>• <a href=\"https://github.com/nextlevelshit/code-crispies\" target=\"_blank\">GitHub Mirror</a> - Alternative access<br><br>Lessons are simple JSON files in the <code>lessons/</code> folder. Check existing lessons for the format!",
|
|
||||||
"task": "Click Next to continue",
|
|
||||||
"previewHTML": "<div class=\"card\"><h2>Your First Lesson</h2><p>Create a JSON file in <code>lessons/</code></p><pre>{\n \"id\": \"my-lesson\",\n \"title\": \"My Lesson\",\n \"lessons\": [...]\n}</pre></div>",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; } .card { background: #f8fafc; border: 1px solid #e2e8f0; border-radius: 8px; padding: 20px; } h2 { color: #6366f1; margin-top: 0; } pre { background: #1e293b; color: #e2e8f0; padding: 12px; border-radius: 4px; overflow-x: auto; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<div class=\"card\">\n <h2>Your First Lesson</h2>\n <p>Create a JSON file in <code>lessons/</code></p>\n <pre>{\n \"id\": \"my-lesson\",\n \"title\": \"My Lesson\",\n \"lessons\": [...]\n}</pre>\n</div>",
|
|
||||||
"solution": "<div class=\"card\">\n <h2>Your First Lesson</h2>\n <p>Create a JSON file in <code>lessons/</code></p>\n <pre>{\n \"id\": \"my-lesson\",\n \"title\": \"My Lesson\",\n \"lessons\": [...]\n}</pre>\n</div>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "card",
|
|
||||||
"message": "Click Next to continue"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "keep-learning",
|
|
||||||
"title": "Keep Learning",
|
|
||||||
"description": "<strong>Your journey continues!</strong><br><br><strong>Recommended resources:</strong><br>• <a href=\"https://developer.mozilla.org/en-US/docs/Web\" target=\"_blank\">MDN Web Docs</a> - The definitive web development reference<br>• <a href=\"https://css-tricks.com\" target=\"_blank\">CSS-Tricks</a> - Practical CSS techniques and tutorials<br>• <a href=\"https://web.dev\" target=\"_blank\">web.dev</a> - Modern web development best practices<br><br><strong>Practice projects:</strong><br>• Build your personal portfolio website<br>• Recreate your favorite website's layout<br>• Create a responsive landing page<br>• Build a CSS-only interactive component<br><br><strong>Remember:</strong> The best way to learn is by building. Start small, iterate, and have fun!<br><br><em>Thank you for learning with Code Crispies!</em>",
|
|
||||||
"task": "Explore the Playground",
|
|
||||||
"previewHTML": "<div class=\"message\"><span class=\"emoji\">🎉</span><h1>Happy Coding!</h1><p>See you in the code!</p></div>",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; display: flex; align-items: center; justify-content: center; min-height: 200px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } .message { text-align: center; color: white; } .emoji { font-size: 4rem; display: block; margin-bottom: 1rem; } h1 { margin: 0 0 0.5rem; } p { margin: 0; opacity: 0.9; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<div class=\"message\">\n <span class=\"emoji\">🎉</span>\n <h1>Happy Coding!</h1>\n <p>See you in the code!</p>\n</div>",
|
|
||||||
"solution": "<div class=\"message\">\n <span class=\"emoji\">🎉</span>\n <h1>Happy Coding!</h1>\n <p>See you in the code!</p>\n</div>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "message",
|
|
||||||
"message": "Explore the Playground"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,548 +1,257 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "css-basic-selectors",
|
"id": "css-basic-selectors",
|
||||||
"title": "CSS Selectors",
|
"title": "أساسيات CSS",
|
||||||
"description": "CSS selectors are the foundation of styling web pages, allowing you to target specific HTML elements for styling. This module introduces fundamental selector types including element type selectors, class selectors, ID selectors, and the universal selector.",
|
"description": "تعلم اللبنات الأساسية لـ CSS: الخصائص والقيم والمحددات. يعلمك هذا الوحدة قواعد الصياغة التي يتبعها كل إعلان CSS.",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "introduction-to-selectors",
|
"id": "css-properties",
|
||||||
"title": "What's a Selector?",
|
"title": "خصائص CSS",
|
||||||
"description": "A CSS selector is the first part of a CSS rule that tells the browser which HTML elements should receive the styles defined in the declaration block. Selectors are essentially patterns that match against elements in your HTML document. Understanding selectors is fundamental because they determine which elements your CSS rules will affect. The element or elements targeted by a selector are referred to as the 'subject of the selector.' When writing a CSS rule, you first specify the selector, followed by curly braces that contain the style declarations.<br/>For example, to change the text color of elements, you can use the <kbd>color</kbd> property within your declaration block.<br><br><pre>/* Element selector */\np {\n color: orangered;\n /* │ └─── Indicates the value of the expression\n │ \n └─────────── Indicates the property of the expression */\n}</pre>",
|
"description": "تقوم CSS بتنسيق العناصر باستخدام <strong>الإعلانات</strong> - أزواج من الخصائص والقيم. كل إعلان يتبع نفس النمط:<br><br><pre>property: value;</pre><br><strong>الخصاصية</strong> هي ما تريد تغييره (مثل <kbd>color</kbd> أو <kbd>background</kbd>). <strong>القيمة</strong> هي ما تضبطها عليه. النقطتان تفصلهما، والفاصلة المنقوطة تنهي السطر.<br><br>القيم تأتي بأنواع مختلفة:<br>• <strong>كلمات مفتاحية:</strong> <kbd>red</kbd>، <kbd>bold</kbd>، <kbd>center</kbd><br>• <strong>أرقام بوحدات:</strong> <kbd>16px</kbd>، <kbd>2rem</kbd>، <kbd>100%</kbd><br>• <strong>ألوان:</strong> <kbd>steelblue</kbd>، <kbd>#ff0000</kbd>",
|
||||||
"task": "Write a CSS rule using a type selector that targets all paragraph elements <kbd>p</kbd> in the document. Make the text blue by setting the <kbd>color</kbd> property to <kbd>blue</kbd>.",
|
"task": "أكمل الإعلان بإضافة <kbd>color: coral;</kbd> لتغيير لون النص.",
|
||||||
"previewHTML": "<h1>Introduction to CSS Selectors</h1>\n<p>This paragraph should turn blue.</p>\n<div>This div element should remain unchanged.</div>\n<p>This second paragraph should also turn blue.</p>",
|
"previewHTML": "<p class=\"text\">This text should turn coral.</p>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } .text { font-size: 1.25rem; }",
|
||||||
"sandboxCSS": "h1, p, div { padding: 8px; margin-bottom: 10px; border: 1px dashed #ccc; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Write a type selector to target all paragraph elements */\n",
|
"codePrefix": ".text {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "\n}",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"solution": "p { color: blue }",
|
"solution": "color: coral;",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "property_value",
|
||||||
"value": "^p\\s*{",
|
"value": { "property": "color", "expected": "coral" },
|
||||||
"message": "Start your rule with <kbd>p { … }</kbd> to select all paragraph elements",
|
"message": "أضف <kbd>color: coral;</kbd>"
|
||||||
"options": {
|
}
|
||||||
"caseSensitive": false
|
]
|
||||||
}
|
},
|
||||||
},
|
{
|
||||||
|
"id": "multiple-properties",
|
||||||
|
"title": "خصائص متعددة",
|
||||||
|
"description": "يمكن أن تحتوي القاعدة على إعلانات متعددة. كل واحدة تذهب في سطرها الخاص، وكل واحدة تحتاج فاصلة منقوطة في النهاية:<br><br><pre>.box {<br> background: gold;<br> color: navy;<br> padding: 1rem;<br>}</pre><br>الترتيب عادةً لا يهم - CSS تطبقها جميعًا. عند التعارض، الأخيرة تفوز.",
|
||||||
|
"task": "أضف إعلانين: <kbd>background: lavender;</kbd> و <kbd>padding: 1rem;</kbd>",
|
||||||
|
"previewHTML": "<div class=\"card\">A styled card with background and padding.</div>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } .card { border-radius: 8px; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".card {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "background: lavender;\n padding: 1rem;",
|
||||||
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "color:",
|
"value": { "property": "background", "expected": "lavender" },
|
||||||
"message": "Include the <kbd>color:</kbd> property in your CSS rule"
|
"message": "أضف <kbd>background: lavender;</kbd>"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "blue",
|
|
||||||
"message": "Set the color value to <kbd>blue</kbd>"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"property": "color",
|
"message": "أضف <kbd>padding: 1rem;</kbd>"
|
||||||
"expected": "blue"
|
|
||||||
},
|
|
||||||
"message": "Use <kbd>color: blue</kbd> to set the text color"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "p\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "type-selectors",
|
"id": "type-selectors",
|
||||||
"title": "Type Selectors",
|
"title": "محددات النوع",
|
||||||
"description": "Type selectors (also called tag name selectors or element selectors) target HTML elements based on their tag name. For example, <kbd>p</kbd> selects all paragraph elements, <kbd>h1</kbd> selects all level-one headings, and <kbd>div</kbd> selects all division elements. Type selectors are the most fundamental way to select elements, applying styles consistently to all instances of a particular HTML element throughout your document. You can define a variety of CSS properties with type selectors, such as <kbd>color</kbd> for text color, <kbd>background-color</kbd> for the background, and <kbd>font-weight</kbd> for text emphasis. They provide a broad approach for styling your page and are often the starting point for more specific styling using other selector types.",
|
"description": "<strong>المحدد</strong> يخبر المتصفح أي العناصر يجب تنسيقها. أبسط محدد هو <strong>محدد النوع</strong> — مجرد اسم وسم HTML.<br><br><pre>p {<br> color: steelblue;<br>}</pre><br>هذه القاعدة تستهدف كل عنصر <kbd><p></kbd> في الصفحة. محددات النوع رائعة لتعيين الأنماط الأساسية.",
|
||||||
"task": "Write three separate CSS rules using type selectors to target specific HTML elements: make <kbd>h2</kbd> headings <kbd>purple</kbd>, give <kbd>span</kbd> elements a <kbd>yellow</kbd> background, and make <kbd>strong</kbd> elements <kbd>red</kbd>.",
|
"task": "نسق جميع الفقرات. اكتب قاعدة مع <kbd>p</kbd> كمحدد واضبط <kbd>color: steelblue</kbd>.",
|
||||||
"previewHTML": "<h2>Type Selectors Example</h2>\n<p>Regular paragraph text <span>with a highlighted span</span> that should have a yellow background.</p>\n<p>Another paragraph with <strong>strong important text</strong> that should be red.</p>\n<h2>Another Heading</h2>",
|
"previewHTML": "<article>\n <h2>Fresh Roasted Coffee</h2>\n <p>Our beans are sourced from small farms in Colombia and Ethiopia.</p>\n <p>Each batch is roasted weekly to ensure peak freshness.</p>\n</article>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } h2 { margin: 0 0 1rem; }",
|
||||||
"sandboxCSS": "h2, p, span, strong { padding: 3px; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Write three separate type selectors below */\n\n",
|
"codePrefix": "",
|
||||||
"initialCode": "/* 1. Make h2 headings purple */\n\n\n/* 2. Give span elements a yellow background */\n\n\n/* 3. Make strong elements red */\n",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"solution": "/* 1. Make h2 headings purple */\nh2 {\n color: purple;\n}\n\n/* 2. Give span elements a yellow background */\nspan {\n background-color: yellow;\n}\n\n/* 3. Make strong elements red */\nstrong {\n color: red;\n}",
|
"solution": "p {\n color: steelblue;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^h2\\s*{",
|
"value": "p\\s*\\{",
|
||||||
"message": "Include an <kbd>h2 { … }</kbd> selector"
|
"message": "ابدأ بـ <kbd>p {</kbd> لاختيار الفقرات"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "color", "expected": "steelblue" },
|
||||||
"property": "color",
|
"message": "اضبط <kbd>color: steelblue</kbd>"
|
||||||
"expected": "purple"
|
}
|
||||||
},
|
]
|
||||||
"message": "Set the <kbd>color</kbd> property to <kbd>purple</kbd> for h2 elements"
|
},
|
||||||
},
|
{
|
||||||
|
"id": "styling-links",
|
||||||
|
"title": "تنسيق الروابط",
|
||||||
|
"description": "محددات النوع تعمل مع أي عنصر HTML. المحدد <kbd>a</kbd> يستهدف جميع الروابط في الصفحة.<br><br>الروابط لها لون أزرق وخط تحتها افتراضيًا. يمكنك تغيير كليهما مع CSS — استخدم <kbd>color</kbd> للنص و <kbd>text-decoration: none</kbd> لإزالة الخط.",
|
||||||
|
"task": "نسق روابط التنقل. اكتب قاعدة مع <kbd>a</kbd> كمحدد واضبط <kbd>color: coral</kbd>.",
|
||||||
|
"previewHTML": "<nav>\n <a href=\"#\">Home</a>\n <a href=\"#\">Menu</a>\n <a href=\"#\">About</a>\n <a href=\"#\">Contact</a>\n</nav>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } nav { display: flex; gap: 1.5rem; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "a {\n color: coral;\n}",
|
||||||
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "h2\\s*{[^}]*}",
|
"value": "a\\s*\\{",
|
||||||
"message": "Make sure to close your h2 rule with a closing brace <kbd>}</kbd>"
|
"message": "ابدأ بـ <kbd>a {</kbd> لاختيار الروابط"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^span\\s*{",
|
|
||||||
"message": "Include a <kbd>span { … }</kbd> selector"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "color", "expected": "coral" },
|
||||||
"property": "background-color",
|
"message": "اضبط <kbd>color: coral</kbd>"
|
||||||
"expected": "yellow"
|
|
||||||
},
|
|
||||||
"message": "Set a <kbd>background-color: yellow</kbd> for span elements"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "span\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your span rule with a closing brace <kbd>}</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^strong\\s*{",
|
|
||||||
"message": "Include a <kbd>strong { … }</kbd> selector"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "strong\\s*{\\s*color:\\s*red;[^}]*}",
|
|
||||||
"message": "Set the <kbd>color: red</kbd> for strong elements"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "class-selectors",
|
"id": "class-selectors",
|
||||||
"title": "Class Selectors",
|
"title": "محددات الفئة",
|
||||||
"description": "Class selectors target elements with a specific class attribute value. They begin with a dot (.) followed by the class name. Classes are powerful because they allow you to apply the same styles to multiple elements regardless of their type. An HTML element can have multiple classes (separated by spaces in the class attribute), and a class can be applied to any number of elements. When using class selectors, you can apply properties like <kbd>background-color</kbd> to set the background color of elements, and <kbd>font-weight</kbd> to control text thickness, making text bold or normal. This flexibility makes class selectors one of the most commonly used methods for applying styles in CSS, allowing for modular and reusable styling across your website.",
|
"description": "محددات النوع تنسق <em>جميع</em> العناصر من ذلك النوع. لكن ماذا لو أردت تنسيق بعضها فقط؟<br><br><strong>محددات الفئة</strong> تستهدف العناصر ذات سمة <kbd>class</kbd> محددة. تبدأ بنقطة:<br><br><pre>.badge {<br> background: coral;<br>}</pre><br>هذا ينسق فقط العناصر ذات <kbd>class=\"badge\"</kbd>.",
|
||||||
"task": "Create a CSS rule using a class selector that targets elements with the class <kbd>highlight</kbd>. Give these elements a <kbd>yellow</kbd> background and <kbd>bold</kbd> text.",
|
"task": "نسق شارة الإشعارات. اكتب قاعدة مع <kbd>.badge</kbd> كمحدد واضبط <kbd>background: tomato</kbd>.",
|
||||||
"previewHTML": "<h2>Using Class Selectors</h2>\n<p>This is a regular paragraph, but <span class=\"highlight\">this span has the highlight class</span> applied to it.</p>\n<p class=\"highlight\">This entire paragraph has the highlight class.</p>\n<ul>\n <li>Regular list item</li>\n <li class=\"highlight\">This list item is highlighted</li>\n</ul>",
|
"previewHTML": "<header>\n <h1>Dashboard</h1>\n <span class=\"badge\">3</span>\n</header>\n<p>You have new notifications waiting.</p>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } header { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem; } h1 { margin: 0; font-size: 1.5rem; } .badge { color: white; padding: 0.25rem 0.5rem; border-radius: 999px; font-size: 0.875rem; } p { color: #555; margin: 0; }",
|
||||||
"sandboxCSS": "h2, p, li { padding: 5px; margin-bottom: 10px; }",
|
|
||||||
"codePrefix": "/* Create a class selector for elements with the 'highlight' class */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^\\.highlight\\s*{",
|
|
||||||
"message": "Start your rule with <kbd>.highlight { … }</kbd> to create a class selector",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Include the <kbd>background-color:</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "yellow"
|
|
||||||
},
|
|
||||||
"message": "Set the background color to <kbd>yellow</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "font-weight:",
|
|
||||||
"message": "Include the <kbd>font-weight:</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "font-weight",
|
|
||||||
"expected": "bold"
|
|
||||||
},
|
|
||||||
"message": "Set the font-weight to <kbd>bold</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "\\.highlight\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "multiple-classes",
|
|
||||||
"title": "Multiple Classes",
|
|
||||||
"description": "HTML elements can have multiple classes applied simultaneously, allowing for composable and modular CSS designs. When an element has multiple classes, it will receive styles from all matching class selectors. This approach enables you to build a library of reusable CSS classes that can be combined in different ways. You can also target elements that have a specific combination of classes by chaining class selectors together without spaces (e.g., <kbd>.class1.class2</kbd>). When styling these elements, you might use properties like <kbd>border-color</kbd> to change the color of element borders, and <kbd>background-color</kbd> to set the background color of elements. This technique lets you create conditional styles that only apply when certain classes appear together.",
|
|
||||||
"task": "Complete the CSS rule that targets elements with both <kbd>card</kbd> and <kbd>featured</kbd> classes by chaining the selectors. Set the border-color to gold and the background-color to lemonchiffon to make featured cards stand out.",
|
|
||||||
"previewHTML": "<h2>Multiple Class Combinations</h2>\n<div class=\"card\">Regular Card</div>\n<div class=\"card featured\">Featured Card</div>\n<div class=\"featured\">Just Featured (not a card)</div>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } .card { border: 2px solid gray; padding: 15px; margin-bottom: 10px; border-radius: 5px; }",
|
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* The .card class already has basic styling */\n/* Now target elements with BOTH classes: 'card' AND 'featured' */\n",
|
"codePrefix": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"solution": ".card.featured { border-color: gold; background-color: lemonchiffon }",
|
"solution": ".badge {\n background: tomato;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^\\.card\\.featured\\s*{",
|
"value": "\\.badge\\s*\\{",
|
||||||
"message": "Chain the selectors as <kbd>.card.featured</kbd> (no space between them)",
|
"message": "ابدأ بـ <kbd>.badge {</kbd> (لا تنسَ النقطة!)"
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "border-color:",
|
|
||||||
"message": "Include the <kbd>border-color</kbd> property"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "background", "expected": "tomato" },
|
||||||
"property": "border-color",
|
"message": "اضبط <kbd>background: tomato</kbd>"
|
||||||
"expected": "gold"
|
|
||||||
},
|
|
||||||
"message": "Set the border color to <kbd>gold</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "\\.card\\.featured\\s*{[^}]*;",
|
|
||||||
"message": "Make sure to end your CSS rule with a semicolon <kbd>;</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Include the <kbd>background-color</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "lemonchiffon"
|
|
||||||
},
|
|
||||||
"message": "Set the background color to <kbd>lemonchiffon</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "\\.card\\.featured\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "class-with-type",
|
"id": "button-variants",
|
||||||
"title": "Combining Types",
|
"title": "متغيرات الأزرار",
|
||||||
"description": "You can combine type selectors with class selectors to target specific HTML elements that have a certain class. This creates a more specific selector that only matches when both conditions are true: the element is of the specified type AND it has the specified class. For example, <kbd>p.note</kbd> would select paragraph elements with the class <kbd>note</kbd>, but would not select divs or spans with that same class. You can style these combined selections using properties like <kbd>background-color</kbd> to set a colored background for your elements. This approach allows you to apply different styles to the same class when it appears on different element types.",
|
"description": "يمكن أن تحتوي العناصر على فئات متعددة. عندما تسلسل محددات الفئة بدون مسافات، تستهدف العناصر التي لديها <em>جميع</em> تلك الفئات:<br><br><pre>.btn.primary {<br> background: steelblue;<br>}</pre><br>هذا يستهدف العناصر ذات <kbd>class=\"btn primary\"</kbd>، وليس فقط <kbd>.btn</kbd> أو فقط <kbd>.primary</kbd>.",
|
||||||
"task": "Create a CSS rule that specifically targets <kbd><span></kbd> elements with the class <kbd>highlight</kbd>. Make those elements have an orange background, while other elements with the highlight class remain untouched.",
|
"task": "نسق الزر الأساسي. اكتب قاعدة مع <kbd>.btn.primary</kbd> كمحدد واضبط <kbd>background: steelblue</kbd>.",
|
||||||
"previewHTML": "<h2>Type and Class Combinations</h2>\n<p>This paragraph has a <span class=\"highlight\">highlighted span</span> that should have an orange background.</p>\n<p class=\"highlight\">This paragraph has the highlight class but should NOT have an orange background.</p>",
|
"previewHTML": "<div class=\"actions\">\n <button class=\"btn\">Cancel</button>\n <button class=\"btn primary\">Save Changes</button>\n</div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } .highlight { font-weight: bold; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } .actions { display: flex; gap: 0.75rem; } .btn { padding: 0.5rem 1rem; border: none; border-radius: 6px; font-size: 1rem; cursor: pointer; background: #e0e0e0; color: #333; }",
|
||||||
"sandboxCSS": "h2, p, span { padding: 5px; }",
|
|
||||||
"codePrefix": "/* The .highlight class already sets font-weight to bold */\n/* Now target ONLY span elements with the highlight class */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^span\\.highlight\\s*{",
|
|
||||||
"message": "Use <kbd>span.highlight</kbd> selector (no space between element and class)",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Include the <kbd>background-color</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "orange"
|
|
||||||
},
|
|
||||||
"message": "Set the background color to <kbd>orange</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "span\\.highlight\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "id-selectors",
|
|
||||||
"title": "ID Selectors",
|
|
||||||
"description": "ID selectors target elements with a specific id attribute. They begin with a hash/pound sign (#) followed by the ID name. Unlike classes, IDs must be unique within a document—each ID value should be used only once per page. ID selectors have higher specificity than class or element selectors, meaning they override those selectors when conflicts arise. When styling with ID selectors, you can use properties like <kbd>color</kbd> to define text color, and <kbd>text-decoration</kbd> to control the appearance of text, such as adding underlines to elements. Because of their uniqueness requirement, IDs are best used for one-of-a-kind elements like page headers, main navigation, or specific unique components that appear only once on a page.",
|
|
||||||
"task": "Create a CSS rule with an ID selector that targets the element with the ID <kbd>main-title</kbd>. Set its color to purple and add an underline with <kbd>text-decoration: underline</kbd>.",
|
|
||||||
"previewHTML": "<h1 id=\"main-title\">Main Page Title</h1>\n<p>Regular paragraph content.</p>\n<h2>Secondary Heading</h2>\n<p id=\"intro\">Introduction paragraph (different ID).</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
|
||||||
"sandboxCSS": "h1, h2, p { padding: 8px; margin-bottom: 10px; border: 1px dashed #ccc; }",
|
|
||||||
"codePrefix": "/* Create an ID selector to target the element with id=\"main-title\" */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^#main-title\\s*{",
|
|
||||||
"message": "Start your rule with <kbd>#main-title</kbd> to create an ID selector",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "color:",
|
|
||||||
"message": "Include the <kbd>color</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "color",
|
|
||||||
"expected": "purple"
|
|
||||||
},
|
|
||||||
"message": "Set the color to <kbd>purple</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "text-decoration:",
|
|
||||||
"message": "Include the <kbd>text-decoration</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "text-decoration",
|
|
||||||
"expected": "underline"
|
|
||||||
},
|
|
||||||
"message": "Set the text-decoration to <kbd>underline</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "#main-title\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "id-with-type",
|
|
||||||
"title": "Type + ID",
|
|
||||||
"description": "Similar to how you can combine type and class selectors, you can also combine type selectors with ID selectors. For example, <kbd>h1#title</kbd> targets an h1 element with the ID 'title'. When using this combined approach, you can apply CSS properties like <kbd>font-style</kbd> to control the slant of the text, making it italic or normal. While this selector combination is more specific than using just the ID selector, it's often unnecessary since IDs should already be unique in a document. However, this technique can be useful for improving code readability or when you want to emphasize that a particular ID should only appear on a specific element type.",
|
|
||||||
"task": "Create a CSS rule that combines a type selector with an ID selector to target specifically a paragraph element with the ID <kbd>special</kbd>. Set its font style to italic.",
|
|
||||||
"previewHTML": "<h2 id=\"special\">Heading with ID \"special\" (should NOT be affected)</h2>\n<p id=\"special\">Paragraph with ID \"special\" (should become italic)</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
|
||||||
"sandboxCSS": "h2, p { padding: 8px; margin-bottom: 10px; border: 1px dashed #ccc; }",
|
|
||||||
"codePrefix": "/* Create a combined type+ID selector for a paragraph with id=\"special\" */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^p#special\\s*{",
|
|
||||||
"message": "Use <kbd>p#special</kbd> to target paragraphs with ID <kbd>special</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "font-style:",
|
|
||||||
"message": "Include the <kbd>font-style</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "font-style",
|
|
||||||
"expected": "italic"
|
|
||||||
},
|
|
||||||
"message": "Set the font-style to <kbd>italic</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "p#special\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "selector-lists",
|
|
||||||
"title": "Selector Lists",
|
|
||||||
"description": "When multiple elements need the same styling, you can group them together using a selector list (also known as grouping selectors). Selector lists are created by separating individual selectors with commas. This approach reduces repetition in your CSS, making it more maintainable and efficient. For example, <kbd>h1, h2, h3 { color: blue; }</kbd> applies the same blue color to all three heading levels. When styling multiple selectors at once, you can apply properties like <kbd>background-color</kbd> to set the background, <kbd>border-left</kbd> to create a left border with a specific thickness, style, and color, and <kbd>padding-left</kbd> to create space between the content and the left border. Whitespace around commas is optional, and each selector in the list can be any valid selector type-elements, classes, IDs, or even more complex selectors.",
|
|
||||||
"task": "Create a selector list that applies the same styles to three different elements: paragraphs with class <kbd>note</kbd>, list items with class <kbd>important</kbd>, and the element with ID <kbd>summary</kbd>. Give them a <kbd>lightyellow</kbd> background, a <kbd>gold</kbd> left border, and some left <kbd>padding</kbd>.",
|
|
||||||
"previewHTML": "<p class=\"note\">This is a note paragraph.</p>\n<ul>\n <li>Regular list item</li>\n <li class=\"important\">Important list item</li>\n</ul>\n<div id=\"summary\">Summary section</div>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
|
||||||
"sandboxCSS": "p, li, div { padding: 8px; margin-bottom: 8px; border: 1px dashed gray; }",
|
|
||||||
"codePrefix": "/* Create a selector list to apply the same styles to multiple different elements */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"solution": "p.note,\nli.important,\n#summary {\n background-color: lightyellow;\n border-left: 3px solid gold;\n padding-left: 10px\n}",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "p.note",
|
|
||||||
"message": "Include <kbd>p.note</kbd> in your selector list",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "li.important",
|
|
||||||
"message": "Include <kbd>li.important</kbd> in your selector list",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "#summary",
|
|
||||||
"message": "Include <kbd>#summary</kbd> in your selector list",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "(p\\.note|li\\.important|#summary)\\s*,\\s*(p\\.note|li\\.important|#summary)\\s*,\\s*(p\\.note|li\\.important|#summary)",
|
|
||||||
"message": "Create a comma-separated list with all three selectors",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Include the <kbd>background-color</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "lightyellow"
|
|
||||||
},
|
|
||||||
"message": "Set the background color to <kbd>lightyellow</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "border-left:",
|
|
||||||
"message": "Include the <kbd>border-left</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "border-left",
|
|
||||||
"expected": "3px solid gold"
|
|
||||||
},
|
|
||||||
"message": "Use <kbd>border-left: 3px solid gold</kbd> to create a left border"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "padding-left:",
|
|
||||||
"message": "Include the <kbd>padding-left</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "padding-left",
|
|
||||||
"expected": "10px"
|
|
||||||
},
|
|
||||||
"message": "Use <kbd>padding-left: 10px</kbd> to add left padding"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "universal-selector",
|
|
||||||
"title": "Universal (*)",
|
|
||||||
"description": "The universal selector is denoted by an asterisk (*) and matches any element of any type. It selects everything in the document or, when combined with other selectors, everything within a specific context. For example, <kbd>* { margin: 0; }</kbd> removes margins from all elements, while <kbd>article *</kbd> selects all elements inside article elements. When using the universal selector in combination with other selectors, you can apply properties like <kbd>margin</kbd> to control the spacing around elements. The universal selector is powerful but should be used carefully due to its broad impact. It's commonly used in CSS resets, to override default browser styling, or to target all children of a particular element.",
|
|
||||||
"task": "Use the universal selector to remove margins from all elements inside the container div. Create a rule using <kbd>div.container *</kbd> as the selector and set <kbd>margin: 0</kbd>.",
|
|
||||||
"previewHTML": "<div class=\"container\">\n <h2>Inside Container</h2>\n <p>This paragraph is inside the container.</p>\n <ul>\n <li>List item inside container</li>\n </ul>\n</div>\n<p>This paragraph is outside the container and should not be affected.</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } div.container { border: 2px solid navy; padding: 15px; background-color: lavender; } h2, p, ul, li { margin: 15px 0; }",
|
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Use the universal selector to target all elements inside the container */\n",
|
"codePrefix": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
|
"solution": ".btn.primary {\n background: steelblue;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^div\\.container\\s+\\*\\s*{",
|
"value": "\\.btn\\.primary\\s*\\{",
|
||||||
"message": "Use <kbd>div.container *</kbd> selector (with a space between container and *)",
|
"message": "استخدم <kbd>.btn.primary {</kbd> (بدون مسافة بين الفئات)"
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "margin:",
|
|
||||||
"message": "Include the <kbd>margin</kbd> property"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "background", "expected": "steelblue" },
|
||||||
"property": "margin",
|
"message": "اضبط <kbd>background: steelblue</kbd>"
|
||||||
"expected": "0"
|
|
||||||
},
|
|
||||||
"message": "Set margin to <kbd>0</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "div\\.container\\s+\\*\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "specificity-basics",
|
"id": "specific-elements",
|
||||||
"title": "Specificity",
|
"title": "استهداف عناصر محددة",
|
||||||
"description": "CSS specificity determines which styles take precedence when multiple conflicting rules target the same element. Specificity follows a hierarchical system: inline styles have the highest specificity, followed by ID selectors, then class/attribute/pseudo-class selectors, and finally element/pseudo-element selectors. This can be conceptualized as a four-part score (inline, ID, class, element). When creating multiple rules that may target the same elements, you can use the <kbd>color</kbd> property to set text colors, and specificity will determine which color is actually applied. Understanding specificity is crucial for predictable styling and debugging CSS conflicts. When two selectors have equal specificity, the one that comes last in the stylesheet wins.",
|
"description": "أحيانًا تريد أن تبدو الفئة مختلفة على عناصر مختلفة. اجمع محدد النوع مع محدد الفئة (بدون مسافة) لتكون أكثر تحديدًا:<br><br><pre>a.btn {<br> text-decoration: none;<br>}</pre><br>هذا ينسق فقط عناصر <kbd><a></kbd> ذات الفئة <kbd>btn</kbd>، وليس عناصر <kbd><button></kbd> ذات تلك الفئة.",
|
||||||
"task": "Examine the existing CSS rules and add a new rule with higher specificity to override the text color of the paragraph. Create a rule using '.content p' as the selector and set color: green.",
|
"task": "أزل الخط من أزرار الروابط. اكتب قاعدة مع <kbd>a.btn</kbd> كمحدد واضبط <kbd>text-decoration: none</kbd>.",
|
||||||
"previewHTML": "<div class=\"content\">\n <p>What color will this paragraph be? Look at the CSS rules and their specificity.</p>\n</div>",
|
"previewHTML": "<div class=\"actions\">\n <button class=\"btn\">Regular Button</button>\n <a href=\"#\" class=\"btn\">Link Button</a>\n</div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } .actions { display: flex; gap: 0.75rem; align-items: center; } .btn { padding: 0.5rem 1rem; border: none; border-radius: 6px; font-size: 1rem; cursor: pointer; background: steelblue; color: white; }",
|
||||||
"sandboxCSS": "p { border: 1px dashed gray; padding: 10px; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* These CSS rules target the same paragraph but have different specificity */\n\n/* Rule 1: Element selector (lowest specificity) */\np {\n color: red;\n}\n\n/* Rule 2: Descendant selector (higher specificity than just 'p') */\n",
|
"codePrefix": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "a.btn {\n text-decoration: none;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^\\.content\\s+p\\s*{",
|
"value": "a\\.btn\\s*\\{",
|
||||||
"message": "Use <kbd>.content p</kbd> as your selector (note the space between)",
|
"message": "استخدم <kbd>a.btn {</kbd> (نوع + فئة، بدون مسافة)"
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "color:",
|
"value": { "property": "text-decoration", "expected": "none" },
|
||||||
"message": "Include the <kbd>color</kbd> property"
|
"message": "اضبط <kbd>text-decoration: none</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "grouping-selectors",
|
||||||
|
"title": "تجميع المحددات",
|
||||||
|
"description": "عندما تحتاج عناصر متعددة لنفس الأنماط، اذكرها مفصولة بفواصل. هذا يحافظ على CSS نظيفًا وقابلاً للصيانة.<br><br><pre>h1, h2, h3 {<br> color: steelblue;<br>}</pre><br>هذا يطبق نفس اللون على مستويات العناوين الثلاثة في قاعدة واحدة.",
|
||||||
|
"task": "نسق جميع العناوين بشكل متسق. أضف <kbd>color: steelblue</kbd> إلى المحدد المجمع <kbd>h1, h2, h3</kbd>.",
|
||||||
|
"previewHTML": "<article><h1>Main Title</h1><p>Introduction paragraph with some text.</p><h2>Section Heading</h2><p>More content here.</p><h3>Subsection</h3><p>Final paragraph.</p></article>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } article { max-width: 500px; } p { color: #555; line-height: 1.6; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "h1, h2, h3 {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "color: steelblue;",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "color", "expected": "steelblue" },
|
||||||
|
"message": "اضبط <kbd>color: steelblue</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "descendant-selectors",
|
||||||
|
"title": "محددات الأحفاد",
|
||||||
|
"description": "استهدف العناصر داخل عناصر أخرى باستخدام مسافة بين المحددات. هذا أحد أكثر الأنماط فائدة في CSS.<br><br><pre>.nav a {<br> color: white;<br>}</pre><br>هذا ينسق فقط الروابط داخل <kbd>.nav</kbd>، تاركًا الروابط الأخرى دون تغيير.",
|
||||||
|
"task": "نسق روابط التنقل بشكل مختلف. اكتب قاعدة مع <kbd>.nav a</kbd> كمحدد واضبط <kbd>color: white</kbd>.",
|
||||||
|
"previewHTML": "<nav class=\"nav\"><a href=\"#\">Home</a><a href=\"#\">About</a><a href=\"#\">Contact</a></nav><p>Read more in our <a href=\"#\">documentation</a>.</p>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; margin: 0; } .nav { background: steelblue; padding: 1rem; display: flex; gap: 1rem; border-radius: 8px; margin-bottom: 1rem; } .nav a { text-decoration: none; } p a { color: steelblue; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": ".nav a {\n color: white;\n}",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "\\.nav\\s+a\\s*\\{",
|
||||||
|
"message": "استخدم <kbd>.nav a {</kbd> (مسافة بين .nav و a)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "green",
|
"value": { "property": "color", "expected": "white" },
|
||||||
"message": ""
|
"message": "اضبط <kbd>color: white</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nested-styling",
|
||||||
|
"title": "الأنماط المتداخلة",
|
||||||
|
"description": "محددات الأحفاد تتيح لك إنشاء أنماط سياقية. نفس العنصر يمكن أن يبدو مختلفًا حسب مكان ظهوره.<br><br>على سبيل المثال، الفقرات في <kbd>.card</kbd> قد تكون أصغر من الفقرات في <kbd>article</kbd>.",
|
||||||
|
"task": "اجعل الفقرات داخل البطاقة أصغر. اكتب قاعدة مع <kbd>.card p</kbd> كمحدد واضبط <kbd>font-size: 0.9rem</kbd>.",
|
||||||
|
"previewHTML": "<article><h2>Article Title</h2><p>This is a regular article paragraph with normal-sized text for comfortable reading.</p><div class=\"card\"><strong>Quick Tip</strong><p>Card paragraphs should be slightly smaller to fit the compact design.</p></div><p>Back to regular article text here.</p></article>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } article { max-width: 500px; } h2 { color: steelblue; margin-top: 0; } p { line-height: 1.6; color: #444; } .card { background: #f0f4f8; padding: 1rem; border-radius: 8px; border-left: 4px solid steelblue; } .card strong { color: steelblue; display: block; margin-bottom: 0.5rem; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": ".card p {\n font-size: 0.9rem;\n}",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "\\.card\\s+p\\s*\\{",
|
||||||
|
"message": "استخدم <kbd>.card p {</kbd> (مسافة بين .card و p)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "font-size", "expected": "0.9rem" },
|
||||||
|
"message": "اضبط <kbd>font-size: 0.9rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "welcome",
|
"id": "welcome",
|
||||||
"title": "Code Crispies",
|
"title": "مرحباً",
|
||||||
"description": "Welcome to Code Crispies - your interactive web development learning platform",
|
"description": "ابدأ مع Code Crispies",
|
||||||
"mode": "html",
|
"mode": "css",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
|
"excludeFromProgress": true,
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "get-started",
|
"id": "hello",
|
||||||
"title": "Get Started",
|
"title": "أهلاً!",
|
||||||
"description": "<strong>Code Crispies</strong> is a free, open-source platform for learning web development through hands-on exercises. No account required!<br><br><strong>What you'll learn:</strong><br>• <strong>HTML</strong> - Semantic elements, forms, tables, SVG (<em>HTML Block & Inline</em>, <em>HTML Forms</em>, <em>HTML Tables</em>)<br>• <strong>CSS</strong> - Selectors, box model, flexbox, animations (<em>CSS Selectors</em>, <em>CSS Box Model</em>, <em>CSS Flexbox</em>)<br>• <strong>Responsive Design</strong> - Media queries and mobile-first layouts<br><br><strong>How it works:</strong><br>1. Read the task in the left panel<br>2. Write code in the editor<br>3. See live results in the preview<br>4. Get instant feedback with hints<br><br><strong>Keyboard shortcuts:</strong> <kbd>Ctrl+Z</kbd> to undo, <kbd>Ctrl+Shift+Z</kbd> to redo<br><br><strong>More resources:</strong><br>• <a href=\"https://nextlevelshit.github.io/html-over-js/\" target=\"_blank\">HTML over JS</a> - Native HTML vs JavaScript solutions<br>• <a href=\"https://nextlevelshit.github.io/web-engineering-mandala/\" target=\"_blank\">Web Engineering Mandala</a> - JavaScript technology roadmap",
|
"description": "<strong>مرحباً بك في Code Crispies!</strong> تعلم CSS و HTML من خلال تمارين عملية.<br><br><strong>كيف يعمل:</strong><br>1. اقرأ المهمة على اليسار<br>2. اكتب الكود في المحرر<br>3. شاهد النتائج مباشرة في المعاينة<br><br><strong>نصائح:</strong> استخدم <kbd>Ctrl+Z</kbd> للتراجع. افتح القائمة (☰) لاستكشاف جميع الوحدات.",
|
||||||
"task": "Write <code>Hello World</code>",
|
"task": "اكتب <code>Hello World</code> للبدء",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 2rem; color: #6366f1; font-weight: bold; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 2rem; color: #6366f1; font-weight: bold; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,42 +22,9 @@
|
|||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "Hello World",
|
"value": "Hello World",
|
||||||
"message": "Write <code>Hello World</code>"
|
"message": "اكتب <code>Hello World</code>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "overview",
|
|
||||||
"title": "Overview",
|
|
||||||
"description": "<strong>You're ready!</strong> Open the menu (☰) to explore all modules.<br><br><strong>Recommended learning path:</strong><br>1. <em>HTML Block & Inline</em> - Understand container vs inline elements<br>2. <em>HTML Forms</em> - Build interactive forms with validation<br>3. <em>CSS Selectors</em> - Target elements precisely<br>4. <em>CSS Box Model</em> - Master padding, margin, borders<br>5. <em>CSS Flexbox</em> - Create flexible layouts<br>6. <em>CSS Animations</em> - Add motion and transitions<br><br><strong>Tips:</strong><br>• Use <em>Show Expected</em> to see the target result<br>• Your progress is saved automatically<br>• Try Emmet in HTML mode: <kbd>ul>li*3</kbd> + Tab<br><br><strong>Open Source:</strong><br>• <a href=\"https://git.librete.ch/libretech/code-crispies\" target=\"_blank\">Gitea (Source)</a> · <a href=\"https://github.com/nextlevelshit/code-crispies\" target=\"_blank\">GitHub (Mirror)</a><br>• Made by <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a> · <a href=\"https://www.linkedin.com/in/michael-werner-czechowski\" target=\"_blank\">Michael Czechowski</a>",
|
|
||||||
"task": "Click Next to continue",
|
|
||||||
"previewHTML": "<p>Hello World! 🌍</p><p>Hallo Welt!</p><p>Bonjour le monde!</p><p>¡Hola Mundo!</p><p>Ciao Mondo!</p><p>Olá Mundo!</p><p>こんにちは世界!</p><p>你好世界!</p><p>안녕 세상!</p><p>Привет мир!</p><p dir=\"rtl\">שלום עולם!</p><p dir=\"rtl\">مرحبا بالعالم!</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 12px; } p { margin: 6px 0; padding: 6px 12px; border-radius: 6px; font-weight: 600; font-size: 0.95em; } p:nth-child(1) { background: #fef3c7; color: #92400e; font-size: 1.2em; } p:nth-child(2) { background: #dcfce7; color: #166534; } p:nth-child(3) { background: #dbeafe; color: #1e40af; } p:nth-child(4) { background: #fce7f3; color: #9d174d; } p:nth-child(5) { background: #e0e7ff; color: #4338ca; } p:nth-child(6) { background: #fef9c3; color: #854d0e; } p:nth-child(7) { background: #fee2e2; color: #991b1b; } p:nth-child(8) { background: #f3e8ff; color: #7c3aed; } p:nth-child(9) { background: #ccfbf1; color: #0f766e; } p:nth-child(10) { background: #fae8ff; color: #86198f; } p:nth-child(11) { background: #fef3c7; color: #b45309; } p:nth-child(12) { background: #d1fae5; color: #047857; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<p>Hello World! 🌍</p>\n<p>Hallo Welt!</p>\n<p>Bonjour le monde!</p>\n<p>¡Hola Mundo!</p>\n<p>Ciao Mondo!</p>\n<p>Olá Mundo!</p>\n<p>こんにちは世界!</p>\n<p>你好世界!</p>\n<p>안녕 세상!</p>\n<p>Привет мир!</p>\n<p dir=\"rtl\">שלום עולם!</p>\n<p dir=\"rtl\">مرحبا بالعالم!</p>",
|
|
||||||
"solution": "<p>Hello World! 🌍</p>\n<p>Hallo Welt!</p>\n<p>Bonjour le monde!</p>\n<p>¡Hola Mundo!</p>\n<p>Ciao Mondo!</p>\n<p>Olá Mundo!</p>\n<p>こんにちは世界!</p>\n<p>你好世界!</p>\n<p>안녕 세상!</p>\n<p>Привет мир!</p>\n<p dir=\"rtl\">שלום עולם!</p>\n<p dir=\"rtl\">مرحبا بالعالم!</p>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "Hello World",
|
|
||||||
"message": "Click Next to continue"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "playground",
|
|
||||||
"title": "Playground",
|
|
||||||
"mode": "playground",
|
|
||||||
"description": "",
|
|
||||||
"task": "",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<style>\n body {\n font-family: system-ui, sans-serif;\n padding: 20px;\n }\n</style>\n\n<h1>Hello World</h1>\n<p>Start coding!</p>",
|
|
||||||
"solution": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,18 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "box-model",
|
"id": "box-model",
|
||||||
"title": "CSS Box Model",
|
"title": "CSS Box Model",
|
||||||
"description": "Master the fundamental principles of space management in web design through the CSS box model. This module explores how content, padding, borders, and margins combine to create layout structures that are both visually appealing and structurally sound.",
|
"description": "أتقن المبادئ الأساسية لإدارة المساحة في تصميم الويب من خلال نموذج الصندوق CSS. يستكشف هذا الوحدة كيف يتحد المحتوى والحشو والحدود والهوامش لإنشاء هياكل التخطيط.",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "box-model-1",
|
"id": "box-model-1",
|
||||||
"title": "Box Model Components",
|
"title": "Padding",
|
||||||
"description": "The CSS box model consists of four concentric layers: content area (innermost), padding, border, and margin (outermost). Understanding how these components interact is essential for precise layout control.",
|
"description": "كل عنصر في CSS هو صندوق بأربع طبقات: المحتوى، الحشو (padding)، الحدود، والهامش. <strong>Padding</strong> يخلق مساحة تنفس بين محتواك وحافة الصندوق.<br><br>بدون padding، يضغط النص بشكل محرج على الحدود. Padding يجعل المحتوى قابلاً للقراءة ومتوازناً بصرياً.<br><br><pre>.card {\n padding: 1rem;\n}</pre>",
|
||||||
"task": "Set <kbd>padding</kbd> to <kbd>1rem</kbd> to create space between the content and border.",
|
"task": "بطاقة الملف الشخصي هذه تبدو ضيقة. أضف <kbd>padding: 1rem</kbd> ليكون للنص مجال للتنفس.",
|
||||||
"previewHTML": "<div class=\"box\">Box Model Components</div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: lavender; border: 2px dashed slategray; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".box {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "padding: 1rem;",
|
"solution": "padding: 1rem;",
|
||||||
@@ -22,62 +22,62 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "1rem" },
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"message": "Set <kbd>padding: 1rem</kbd>"
|
"message": "اضبط <kbd>padding: 1rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-2",
|
"id": "box-model-2",
|
||||||
"title": "Adding Borders",
|
"title": "Borders",
|
||||||
"description": "Borders outline an element, creating visual separation from surrounding content. The border shorthand accepts three values: width, style, and color.",
|
"description": "الحدود تنشئ حدوداً مرئية حول العناصر. اختصار <kbd>border</kbd> يقبل ثلاث قيم: العرض، النمط، واللون.<br><br>الأنماط الشائعة: <kbd>solid</kbd>، <kbd>dashed</kbd>، <kbd>dotted</kbd>، <kbd>none</kbd>",
|
||||||
"task": "Set <kbd>border</kbd> to <kbd>2px solid darkslategray</kbd>.",
|
"task": "أضف لمسة يسارية خفيفة للبطاقة باستخدام <kbd>border-left: 4px solid steelblue</kbd>.",
|
||||||
"previewHTML": "<div class=\"box\">This box needs a border</div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: mintcream; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".box {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "border: 2px solid darkslategray;",
|
"solution": "border-left: 4px solid steelblue;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border:\\s*2px\\s+solid\\s+darkslategray",
|
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
|
||||||
"message": "Set <kbd>border: 2px solid darkslategray</kbd>",
|
"message": "اضبط <kbd>border-left: 4px solid steelblue</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-3",
|
"id": "box-model-3",
|
||||||
"title": "Adding Margins",
|
"title": "Margins",
|
||||||
"description": "Margins create space between elements, controlling how they relate to one another within a layout. Unlike padding (which affects internal spacing), margins exist outside the element's border.",
|
"description": "الهوامش تنشئ مساحة <em>خارج</em> العنصر، تفصله عن جيرانه. بينما يدفع padding المحتوى للداخل، الهوامش تدفع العناصر الأخرى بعيداً.",
|
||||||
"task": "Set <kbd>margin</kbd> to <kbd>1rem</kbd> to create space between this element and its neighbors.",
|
"task": "أضف مساحة بين بطاقتي الملف الشخصي هاتين باستخدام <kbd>margin-bottom: 1rem</kbd> على <kbd>.card</kbd>.",
|
||||||
"previewHTML": "<div class=\"container\"><div class=\"outer\">This box needs margins</div><div class=\"neighbor\">Adjacent element</div></div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article><article class=\"card\"><h3>Alex Rivera</h3><p>UX Designer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .outer { background-color: plum; padding: 1rem; border: 2px solid orchid; } .neighbor { background-color: lightblue; padding: 1rem; border: 2px solid steelblue; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; border-left: 4px solid steelblue; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".outer {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "margin: 1rem;",
|
"solution": "margin-bottom: 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "margin", "expected": "1rem" },
|
"value": { "property": "margin-bottom", "expected": "1rem" },
|
||||||
"message": "Set <kbd>margin: 1rem</kbd>"
|
"message": "اضبط <kbd>margin-bottom: 1rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-4",
|
"id": "box-model-4",
|
||||||
"title": "Box Sizing: Border-Box",
|
"title": "Box Sizing",
|
||||||
"description": "The <kbd>box-sizing</kbd> property determines how element dimensions are calculated. The default <kbd>content-box</kbd> excludes padding and border from width/height, while <kbd>border-box</kbd> includes them, making layout calculations more intuitive.",
|
"description": "افتراضياً، <kbd>width</kbd> يحدد فقط عرض المحتوى. Padding والحدود تُضاف للمجموع. هذا يسبب مشاكل في التخطيط.<br><br><kbd>box-sizing: border-box</kbd> يشمل padding والحدود في العرض، مما يجعل التحجيم متوقعاً. معظم المطورين يطبقون هذا على جميع العناصر.",
|
||||||
"task": "Set <kbd>box-sizing</kbd> to <kbd>border-box</kbd> so padding and border are included in the width.",
|
"task": "كلا البطاقتين لهما <kbd>width: 200px</kbd>. اليسرى تستخدم التحجيم الافتراضي (content-box)، مما يجعلها أعرض من المتوقع. أصلح البطاقة اليمنى باستخدام <kbd>box-sizing: border-box</kbd>.",
|
||||||
"previewHTML": "<div class=\"sizing-demo\"><div class=\"box default\">Content-box (default)</div><div class=\"box sized\">Border-box</div></div>",
|
"previewHTML": "<div class=\"demo\"><article class=\"card\">Content-box</article><article class=\"card fix\">Border-box</article></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .sizing-demo { display: flex; gap: 1rem; } .box { width: 200px; padding: 1rem; border: 4px solid teal; background: lightcyan; } .default { box-sizing: content-box; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .demo { display: flex; gap: 1rem; } .card { width: 200px; padding: 1rem; border: 4px solid steelblue; background: white; border-radius: 8px; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".sized {\n ",
|
"codePrefix": ".fix {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "box-sizing: border-box;",
|
"solution": "box-sizing: border-box;",
|
||||||
@@ -86,93 +86,104 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "box-sizing", "expected": "border-box" },
|
"value": { "property": "box-sizing", "expected": "border-box" },
|
||||||
"message": "Set <kbd>box-sizing: border-box</kbd>"
|
"message": "اضبط <kbd>box-sizing: border-box</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-5",
|
"id": "box-model-5",
|
||||||
"title": "Margin Collapse",
|
"title": "Padding Shorthand",
|
||||||
"description": "When two vertical margins meet, they collapse to the larger value instead of adding up. Understanding this behavior is crucial for consistent vertical spacing.",
|
"description": "Padding يقبل 1-4 قيم:<br>• قيمة واحدة: جميع الجوانب<br>• قيمتان: عمودي | أفقي<br>• 4 قيم: أعلى | يمين | أسفل | يسار",
|
||||||
"task": "Set <kbd>margin-bottom</kbd> to <kbd>2rem</kbd>. Notice the space between paragraphs equals 2rem (not 3rem) due to margin collapse.",
|
"task": "هذا الزر يحتاج مساحة أفقية أكثر من العمودية. اضبط <kbd>padding: 8px 1rem</kbd> (8px أعلى/أسفل، 1rem يسار/يمين).",
|
||||||
"previewHTML": "<div class=\"collapse-demo\"><p class=\"first\">This paragraph has a bottom margin.</p><p class=\"second\">This paragraph has a top margin of 1rem.</p></div>",
|
"previewHTML": "<button class=\"btn\">Follow</button>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .collapse-demo { border: 1px solid silver; padding: 8px; background: ghostwhite; } .second { margin-top: 1rem; background: linen; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .btn { background: steelblue; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".first {\n ",
|
"codePrefix": ".btn {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "margin-bottom: 2rem;",
|
"solution": "padding: 8px 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "regex",
|
||||||
"value": { "property": "margin-bottom", "expected": "2rem" },
|
"value": "padding:\\s*8px\\s+1rem",
|
||||||
"message": "Set <kbd>margin-bottom: 2rem</kbd>"
|
"message": "اضبط <kbd>padding: 8px 1rem</kbd>",
|
||||||
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-6",
|
"id": "box-model-6",
|
||||||
"title": "Margin Shorthand Notation",
|
"title": "Margin Shorthand",
|
||||||
"description": "The margin shorthand can set all four sides at once. Two values set vertical (top/bottom) and horizontal (left/right) margins respectively.",
|
"description": "Margin يستخدم نفس نمط الاختصار مثل padding. نمط شائع هو توسيط عناصر الكتلة أفقياً باستخدام <kbd>margin: 0 auto</kbd>.",
|
||||||
"task": "Set <kbd>margin</kbd> to <kbd>1rem 2rem</kbd> for 1rem top/bottom and 2rem left/right.",
|
"task": "وسّط هذه البطاقة أفقياً. اضبط <kbd>margin: 0 auto</kbd> لحساب هوامش يسار/يمين متساوية تلقائياً.",
|
||||||
"previewHTML": "<div class=\"container\"><div class=\"spaced\">This box needs margins: 1rem top/bottom, 2rem left/right</div></div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .spaced { background-color: honeydew; border: 2px solid mediumseagreen; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { width: 250px; background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; border-left: 4px solid steelblue; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".spaced {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "margin: 1rem 2rem;",
|
"solution": "margin: 0 auto;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "margin:\\s*1rem\\s+2rem",
|
"value": "margin:\\s*0\\s+auto",
|
||||||
"message": "Set <kbd>margin: 1rem 2rem</kbd>",
|
"message": "اضبط <kbd>margin: 0 auto</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-7",
|
"id": "box-model-7",
|
||||||
"title": "Padding Shorthand Notation",
|
"title": "Border Radius",
|
||||||
"description": "Like margin, padding shorthand allows setting all sides at once. A single value applies to all four sides equally.",
|
"description": "على الرغم من أنه ليس جزءاً من نموذج الصندوق الكلاسيكي، <kbd>border-radius</kbd> يُدوّر زوايا صندوق حدود العنصر. استخدم <kbd>50%</kbd> على عنصر مربع لإنشاء دائرة.",
|
||||||
"task": "Set <kbd>padding</kbd> to <kbd>2rem</kbd> to add equal padding on all sides.",
|
"task": "اجعل صورة الأفاتار دائرية باستخدام <kbd>border-radius: 50%</kbd>.",
|
||||||
"previewHTML": "<div class=\"padded\">This box needs equal padding on all sides</div>",
|
"previewHTML": "<article class=\"card\"><img class=\"avatar\" src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='35' r='25' fill='%23666'/%3E%3Ccircle cx='50' cy='90' r='40' fill='%23666'/%3E%3C/svg%3E\" alt=\"Avatar\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .padded { background-color: papayawhip; border: 2px solid orange; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; text-align: center; } .avatar { width: 80px; height: 80px; background: #ddd; margin-bottom: 8px; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".padded {\n ",
|
"codePrefix": ".avatar {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "padding: 2rem;",
|
"solution": "border-radius: 50%;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "2rem" },
|
"value": { "property": "border-radius", "expected": "50%" },
|
||||||
"message": "Set <kbd>padding: 2rem</kbd>"
|
"message": "اضبط <kbd>border-radius: 50%</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-8",
|
"id": "box-model-8",
|
||||||
"title": "Border on Specific Sides",
|
"title": "Complete Card",
|
||||||
"description": "For granular control, you can target specific sides with <kbd>border-top</kbd>, <kbd>border-right</kbd>, <kbd>border-bottom</kbd>, or <kbd>border-left</kbd>.",
|
"description": "لنجمع كل شيء معاً. بطاقة الإشعار هذه تحتاج تنسيقاً لتبدو احترافية.",
|
||||||
"task": "Set <kbd>border-bottom</kbd> to <kbd>4px solid dodgerblue</kbd>.",
|
"task": "نسّق الإشعار: أضف <kbd>padding: 1rem</kbd>، <kbd>border-left: 4px solid coral</kbd>، و<kbd>border-radius: 4px</kbd>.",
|
||||||
"previewHTML": "<div class=\"line\">This element needs only a bottom border</div>",
|
"previewHTML": "<div class=\"alert\"><strong>New message</strong><p>You have 3 unread notifications</p></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .line { padding: 1rem; background-color: aliceblue; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .alert { background: seashell; } .alert strong { color: coral; } .alert p { margin: 4px 0 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".line {\n ",
|
"codePrefix": ".alert {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "border-bottom: 4px solid dodgerblue;",
|
"solution": "padding: 1rem;\n border-left: 4px solid coral;\n border-radius: 4px;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
|
"message": "اضبط <kbd>padding: 1rem</kbd>"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border-bottom:\\s*4px\\s+solid\\s+dodgerblue",
|
"value": "border-left:\\s*4px\\s+solid\\s+coral",
|
||||||
"message": "Set <kbd>border-bottom: 4px solid dodgerblue</kbd>",
|
"message": "اضبط <kbd>border-left: 4px solid coral</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "border-radius", "expected": "4px" },
|
||||||
|
"message": "اضبط <kbd>border-radius: 4px</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,115 +1,100 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "units-variables",
|
"id": "units-variables",
|
||||||
"title": "CSS Units & Variables",
|
"title": "وحدات CSS والمتغيرات",
|
||||||
"description": "Understand the variety of CSS measurement units and how to define and use custom properties for maintainable styles.",
|
"description": "افهم تنوع وحدات القياس في CSS وكيفية تعريف واستخدام الخصائص المخصصة لأنماط قابلة للصيانة.",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "units-1",
|
"id": "units-1",
|
||||||
"title": "Absolute vs. Relative Units",
|
"title": "Relative Units",
|
||||||
"description": "Learn the difference between px, rem, em, %, and vw/vh for flexible, responsive layouts.",
|
"description": "يقدم CSS نوعين من الوحدات: <em>مطلقة</em> (مثل <kbd>px</kbd>) و<em>نسبية</em> (مثل <kbd>%</kbd> و <kbd>rem</kbd>). الوحدات النسبية تتكيف مع سياقها، مما يجعل التخطيطات مرنة وسهلة الوصول.<br><br><strong>الوحدات النسبية الشائعة:</strong><br>• <kbd>%</kbd> – نسبة للعنصر الأب<br>• <kbd>rem</kbd> – نسبة لحجم خط الجذر (عادة 16px)<br>• <kbd>em</kbd> – نسبة لحجم خط العنصر<br><br>نمط شائع للمحتوى القابل للقراءة: اضبط <kbd>width: 100%</kbd> لملء المساحة المتاحة، ثم <kbd>max-width: 40rem</kbd> لتحديد طول السطر للقراءة.",
|
||||||
"task": "Set the <kbd>width</kbd> of <kbd>.box</kbd> to <kbd>80%</kbd> and <kbd>max-width</kbd> to <kbd>37.5rem</kbd>.",
|
"task": "نص هذه المقالة عريض جداً على الشاشات الكبيرة. أضف <kbd>max-width: 40rem</kbd> للحصول على عرض قراءة مثالي.",
|
||||||
"previewHTML": "<div class=\"box\">Resize me!</div>",
|
"previewHTML": "<article class=\"article\"><h2>The Art of Typography</h2><p>Good typography is invisible. When text is set well, readers absorb information without noticing the design decisions that make it comfortable to read. Line length is crucial—too wide and eyes get lost, too narrow and reading becomes choppy.</p><p>The ideal line length is 45-75 characters per line. At typical font sizes, this works out to roughly 40rem maximum width.</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { background: #f5f5f5; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: Georgia, serif; padding: 1rem; background: #f9f9f9; } .article { background: white; padding: 2rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .article h2 { margin: 0 0 1rem; color: #333; } .article p { margin: 0 0 1rem; line-height: 1.6; color: #444; } .article p:last-child { margin-bottom: 0; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Set flexible sizing */\n.box {",
|
"codePrefix": ".article {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " width: 80%;\n max-width: 37.5rem;",
|
"solution": "max-width: 40rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"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",
|
"type": "property_value",
|
||||||
"value": { "property": "max-width", "expected": "37.5rem" },
|
"value": { "property": "max-width", "expected": "40rem" },
|
||||||
"message": "Set max-width to <kbd>37.5rem</kbd>"
|
"message": "اضبط <kbd>max-width: 40rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "units-2",
|
"id": "units-2",
|
||||||
"title": "CSS Custom Properties",
|
"title": "CSS Variables",
|
||||||
"description": "Define and reuse variables (--custom properties) to centralize your theme values.",
|
"description": "الخصائص المخصصة في CSS (المتغيرات) تتيح لك تعريف قيم قابلة لإعادة الاستخدام. عرّفها بـ <kbd>--اسم</kbd> واستخدمها بـ <kbd>var(--اسم)</kbd>. المتغيرات المعرّفة على <kbd>:root</kbd> متاحة في كل مكان.",
|
||||||
"task": "Create a <kbd>--main-color</kbd> variable in <kbd>:root</kbd> with <kbd>#6200ee</kbd> and apply it as the border color on <kbd>.themed</kbd>.",
|
"task": "عرّف <kbd>--brand: steelblue</kbd> في <kbd>:root</kbd>، ثم استخدمها كلون <kbd>background</kbd> لـ <kbd>.btn</kbd>.",
|
||||||
"previewHTML": "<div class=\"themed\">Variable Box</div>",
|
"previewHTML": "<div class=\"actions\"><button class=\"btn\">Subscribe</button><button class=\"btn\">Learn More</button></div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .themed { padding: 1rem; border: 0.125rem solid #ddd; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .actions { display: flex; gap: 1rem; } .btn { color: white; border: none; padding: 12px 24px; border-radius: 6px; font-size: 1rem; cursor: pointer; background: #ccc; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Define and use a CSS variable */\n:root {",
|
"codePrefix": ":root {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}\n.themed { }",
|
"codeSuffix": "\n}\n\n.btn {\n background: var(--brand);\n}",
|
||||||
"solution": " --main-color: #6200ee;\n}\n.themed {\n border-color: var(--main-color);",
|
"solution": "--brand: steelblue;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "--main-color",
|
"value": "--brand",
|
||||||
"message": "Define <kbd>--main-color</kbd> in :root",
|
"message": "عرّف المتغير <kbd>--brand</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "var(--main-color)",
|
"value": "steelblue",
|
||||||
"message": "Use <kbd>var(--main-color)</kbd>",
|
"message": "اضبط القيمة على <kbd>steelblue</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"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",
|
"id": "units-3",
|
||||||
"title": "Unit Calculations (calc)",
|
"title": "calc() Function",
|
||||||
"description": "Use the <kbd>calc()</kbd> function to combine different units in one expression.",
|
"description": "دالة <kbd>calc()</kbd> تتيح لك خلط وحدات مختلفة في الحسابات. هذا ضروري للتخطيطات التي تجمع بين الأحجام الثابتة والمرنة، مثل تخطيط الشريط الجانبي.",
|
||||||
"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>.",
|
"task": "المحتوى الرئيسي يجب أن يملأ المساحة المتبقية بعد الشريط الجانبي 200px. اضبط <kbd>width: calc(100% - 200px)</kbd> على <kbd>.main</kbd>.",
|
||||||
"previewHTML": "<div class=\"sized\">Calc Demo</div>",
|
"previewHTML": "<div class=\"layout\"><aside class=\"sidebar\">Sidebar<br>Navigation</aside><main class=\"main\"><h2>Main Content</h2><p>This area should fill the remaining width after accounting for the fixed-width sidebar.</p></main></div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sized { background: #e8f5e9; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .layout { display: flex; gap: 1rem; } .sidebar { width: 200px; background: #1a1a2e; color: white; padding: 1rem; border-radius: 8px; flex-shrink: 0; } .main { background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .main h2 { margin: 0 0 8px; } .main p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Use calc for dynamic sizing */\n.sized {",
|
"codePrefix": ".main {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " width: calc(100% - 2rem);\n min-height: calc(10vh + 1rem);",
|
"solution": "width: calc(100% - 200px);",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{ "type": "contains", "value": "calc", "message": "Use <kbd>calc()</kbd> function", "options": { "caseSensitive": false } },
|
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "width:\\s*calc\\(100% - 2rem\\)",
|
"value": "width:\\s*calc\\(\\s*100%\\s*-\\s*200px\\s*\\)",
|
||||||
"message": "Width should be <kbd>calc(100% - 2rem)</kbd>",
|
"message": "اضبط <kbd>width: calc(100% - 200px)</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 }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "units-4",
|
"id": "units-4",
|
||||||
"title": "Viewport & Responsive Units",
|
"title": "Viewport Units",
|
||||||
"description": "Control layouts relative to viewport size with vw, vh, and vmin/vmax units.",
|
"description": "وحدات العرض تحدد حجم العناصر نسبة لنافذة المتصفح:<br>• <kbd>vw</kbd> – 1% من عرض العرض<br>• <kbd>vh</kbd> – 1% من ارتفاع العرض<br><br>هذه مثالية للأقسام بملء الشاشة مثل لافتات hero.",
|
||||||
"task": "Give <kbd>.view</kbd> a <kbd>width</kbd> of <kbd>50vw</kbd> and <kbd>height</kbd> of <kbd>20vh</kbd>.",
|
"task": "اجعل قسم hero هذا يملأ ارتفاع العرض بضبط <kbd>min-height: 100vh</kbd>.",
|
||||||
"previewHTML": "<div class=\"view\">Viewport Box</div>",
|
"previewHTML": "<section class=\"hero\"><h1>Welcome</h1><p>Scroll down to explore</p></section>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .view { background: #ffe0b2; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .hero { background: linear-gradient(135deg, #1a1a2e 0%, steelblue 100%); color: white; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 2rem; } .hero h1 { margin: 0 0 1rem; font-size: 2.5rem; } .hero p { margin: 0; opacity: 0.8; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Use viewport units */\n.view {",
|
"codePrefix": ".hero {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " width: 50vw;\n height: 20vh;",
|
"solution": "min-height: 100vh;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"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",
|
||||||
{ "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Set width to <kbd>50vw</kbd>" },
|
"value": { "property": "min-height", "expected": "100vh" },
|
||||||
{ "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Set height to <kbd>20vh</kbd>" }
|
"message": "اضبط <kbd>min-height: 100vh</kbd>"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "transitions-animations",
|
"id": "transitions-animations",
|
||||||
"title": "CSS Animations",
|
"title": "حركات CSS",
|
||||||
"description": "Bring interactivity to your UI by smoothly transitioning properties and creating keyframe-driven animations.",
|
"description": "أضف التفاعل لواجهتك من خلال انتقالات الخصائص السلسة والحركات المبنية على keyframes.",
|
||||||
"difficulty": "intermediate",
|
"difficulty": "intermediate",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "transitions-1",
|
"id": "transitions-1",
|
||||||
"title": "Transitions",
|
"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>",
|
"description": "تعلم كيفية تطبيق <kbd>transition</kbd> على الخصائص للتغييرات السلسة عند تغيير الحالة.<br><br><pre>transition: property duration;\n/* مثال: 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.",
|
"task": "أضف <kbd>transition: background-color 0.3s</kbd> ليتغير اللون بسلاسة عند التمرير.",
|
||||||
"previewHTML": "<button class=\"btn\">Hover Me</button>",
|
"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; }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
@@ -22,13 +22,13 @@
|
|||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "transition",
|
"value": "transition",
|
||||||
"message": "Use the <kbd>transition</kbd> property",
|
"message": "استخدم خاصية <kbd>transition</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "transition:\\s*background-color\\s*0\\.3s",
|
"value": "transition:\\s*background-color\\s*0\\.3s",
|
||||||
"message": "Set <kbd>transition: background-color 0.3s</kbd>",
|
"message": "اضبط <kbd>transition: background-color 0.3s</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -36,8 +36,8 @@
|
|||||||
{
|
{
|
||||||
"id": "transitions-2",
|
"id": "transitions-2",
|
||||||
"title": "Timing Funcs",
|
"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.",
|
"description": "استكشف دوال التسهيل مثل <kbd>ease</kbd>، <kbd>linear</kbd>، <kbd>ease-in</kbd>، <kbd>ease-out</kbd> للتحكم في إيقاع الحركة.",
|
||||||
"task": "Set <kbd>transition-timing-function</kbd> to <kbd>ease-in-out</kbd> on <kbd>.btn</kbd>.",
|
"task": "اضبط <kbd>transition-timing-function</kbd> على <kbd>ease-in-out</kbd>.",
|
||||||
"previewHTML": "<button class=\"btn\">Timing</button>",
|
"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); }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
@@ -50,21 +50,21 @@
|
|||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "transition-timing-function",
|
"value": "transition-timing-function",
|
||||||
"message": "Use <kbd>transition-timing-function</kbd>",
|
"message": "استخدم <kbd>transition-timing-function</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "transition-timing-function", "expected": "ease-in-out" },
|
"value": { "property": "transition-timing-function", "expected": "ease-in-out" },
|
||||||
"message": "Set timing to <kbd>ease-in-out</kbd>"
|
"message": "اضبط التوقيت على <kbd>ease-in-out</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "transitions-3",
|
"id": "transitions-3",
|
||||||
"title": "Keyframes",
|
"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>",
|
"description": "أنشئ حركات مسماة باستخدام <kbd>@keyframes</kbd> وطبّقها عبر اختصار <kbd>animation</kbd>.<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>.",
|
"task": "عرّف keyframe عند <kbd>50%</kbd> مع <kbd>transform: translateY(-20px)</kbd> وطبّق <kbd>animation: bounce 1s infinite</kbd> على <kbd>.ball</kbd>.",
|
||||||
"previewHTML": "<div class=\"ball\"></div>",
|
"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; }",
|
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: crimson; border-radius: 50%; margin: 2rem auto; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -77,25 +77,25 @@
|
|||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "@keyframes bounce",
|
"value": "@keyframes bounce",
|
||||||
"message": "Define <kbd>@keyframes bounce</kbd>",
|
"message": "عرّف <kbd>@keyframes bounce</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "50%.*transform: translateY\\(-20px\\)",
|
"value": "50%.*transform: translateY\\(-20px\\)",
|
||||||
"message": "At <kbd>50%</kbd>, use <kbd>transform: translateY(-20px)</kbd>",
|
"message": "عند <kbd>50%</kbd>، استخدم <kbd>transform: translateY(-20px)</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "animation",
|
"value": "animation",
|
||||||
"message": "Use <kbd>animation</kbd> property on <kbd>.ball</kbd>",
|
"message": "استخدم خاصية <kbd>animation</kbd> على <kbd>.ball</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "animation:.*bounce.*1s.*infinite",
|
"value": "animation:.*bounce.*1s.*infinite",
|
||||||
"message": "Apply <kbd>animation: bounce 1s infinite</kbd>",
|
"message": "طبّق <kbd>animation: bounce 1s infinite</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -103,8 +103,8 @@
|
|||||||
{
|
{
|
||||||
"id": "transitions-4",
|
"id": "transitions-4",
|
||||||
"title": "Animation Properties",
|
"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>.",
|
"description": "اضبط الحركات بـ <kbd>animation-delay</kbd>، <kbd>animation-iteration-count</kbd>، <kbd>animation-direction</kbd>، و <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>.",
|
"task": "طبّق حركة <kbd>pulse</kbd> على <kbd>.box</kbd> مع <kbd>animation-name: pulse</kbd>، <kbd>animation-duration: 2s</kbd>، <kbd>animation-delay: 1s</kbd>، <kbd>animation-iteration-count: 2</kbd>، و <kbd>animation-fill-mode: forwards</kbd>.",
|
||||||
"previewHTML": "<div class=\"box\">Pulse</div>",
|
"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); } }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
@@ -117,27 +117,27 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "animation-name", "expected": "pulse" },
|
"value": { "property": "animation-name", "expected": "pulse" },
|
||||||
"message": "Set <kbd>animation-name: pulse</kbd>"
|
"message": "اضبط <kbd>animation-name: pulse</kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "animation-duration", "expected": "2s" },
|
"value": { "property": "animation-duration", "expected": "2s" },
|
||||||
"message": "Set <kbd>animation-duration: 2s</kbd>"
|
"message": "اضبط <kbd>animation-duration: 2s</kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "animation-delay", "expected": "1s" },
|
"value": { "property": "animation-delay", "expected": "1s" },
|
||||||
"message": "Set <kbd>animation-delay: 1s</kbd>"
|
"message": "اضبط <kbd>animation-delay: 1s</kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "animation-iteration-count", "expected": "2" },
|
"value": { "property": "animation-iteration-count", "expected": "2" },
|
||||||
"message": "Set <kbd>animation-iteration-count: 2</kbd>"
|
"message": "اضبط <kbd>animation-iteration-count: 2</kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "animation-fill-mode", "expected": "forwards" },
|
"value": { "property": "animation-fill-mode", "expected": "forwards" },
|
||||||
"message": "Set <kbd>animation-fill-mode: forwards</kbd>"
|
"message": "اضبط <kbd>animation-fill-mode: forwards</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "responsive-design",
|
"id": "responsive-design",
|
||||||
"title": "CSS Responsive Design",
|
"title": "CSS Responsive Design",
|
||||||
"description": "Make your layouts adapt to different screen sizes using media queries and fluid design techniques.",
|
"description": "اجعل تخطيطاتك تتكيف مع أحجام الشاشات المختلفة باستخدام media queries وتقنيات التصميم المرن.",
|
||||||
"difficulty": "intermediate",
|
"difficulty": "intermediate",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "responsive-1",
|
"id": "responsive-1",
|
||||||
"title": "Media Queries",
|
"title": "Media Queries",
|
||||||
"description": "Understand the syntax and use cases for CSS media queries to apply styles conditionally based on viewport characteristics.",
|
"description": "افهم صياغة واستخدامات CSS media queries لتطبيق الأنماط شرطياً بناءً على خصائص viewport.<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>.",
|
"task": "اكتب media query باستخدام <kbd>@media (max-width: 600px)</kbd> لتغيير خلفية <kbd>.panel</kbd> إلى <kbd>lightcoral</kbd>.",
|
||||||
"previewHTML": "<div class=\"panel\">Resize the window</div>",
|
"previewHTML": "<div class=\"panel\">Resize the window</div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .panel { padding: 1rem; background: lightblue; }",
|
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .panel { padding: 1rem; background: lightblue; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -22,19 +22,19 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "@media\\s*\\(max-width:\\s*600px\\)",
|
"value": "@media\\s*\\(max-width:\\s*600px\\)",
|
||||||
"message": "Use <kbd>@media (max-width: 600px)</kbd>",
|
"message": "استخدم <kbd>@media (max-width: 600px)</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": ".panel",
|
"value": ".panel",
|
||||||
"message": "Target <kbd>.panel</kbd> inside the media query",
|
"message": "استهدف <kbd>.panel</kbd> داخل media query",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "background", "expected": "lightcoral" },
|
"value": { "property": "background", "expected": "lightcoral" },
|
||||||
"message": "Set <kbd>background: lightcoral</kbd>",
|
"message": "اضبط <kbd>background: lightcoral</kbd>",
|
||||||
"options": { "exact": false }
|
"options": { "exact": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -42,8 +42,8 @@
|
|||||||
{
|
{
|
||||||
"id": "responsive-2",
|
"id": "responsive-2",
|
||||||
"title": "Fluid Type",
|
"title": "Fluid Type",
|
||||||
"description": "Use relative units like <kbd>vw</kbd> to make font sizes scale with the viewport width.",
|
"description": "استخدم وحدات نسبية مثل <kbd>vw</kbd> لجعل أحجام الخطوط تتناسب مع عرض viewport.",
|
||||||
"task": "Set <kbd>font-size: 5vw</kbd> on <kbd>.text</kbd> so it scales as the viewport changes.",
|
"task": "اضبط <kbd>font-size: 5vw</kbd> لتتغير مع viewport.",
|
||||||
"previewHTML": "<p class=\"text\">Fluid Typography</p>",
|
"previewHTML": "<p class=\"text\">Fluid Typography</p>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -53,46 +53,46 @@
|
|||||||
"solution": " font-size: 5vw;",
|
"solution": " font-size: 5vw;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{ "type": "property_value", "value": { "property": "font-size", "expected": "5vw" }, "message": "Set <kbd>font-size: 5vw</kbd>" }
|
{ "type": "property_value", "value": { "property": "font-size", "expected": "5vw" }, "message": "اضبط <kbd>font-size: 5vw</kbd>" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "responsive-3",
|
"id": "responsive-3",
|
||||||
"title": "Flex Grids",
|
"title": "Responsive Grid",
|
||||||
"description": "Combine CSS Grid with <kbd>auto-fit</kbd> or <kbd>auto-fill</kbd> for responsive column layouts.",
|
"description": "ادمج CSS Grid مع <kbd>auto-fit</kbd> أو <kbd>auto-fill</kbd> لتخطيطات أعمدة متجاوبة تضبط عدد الأعمدة تلقائياً بناءً على المساحة المتاحة.",
|
||||||
"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>.",
|
"task": "أضف <kbd>display: grid</kbd> و <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd> و <kbd>gap: 1rem</kbd>.",
|
||||||
"previewHTML": "<div class=\"cards\"><div>1</div><div>2</div><div>3</div><div>4</div></div>",
|
"previewHTML": "<section class=\"features\"><article class=\"feature\"><h3>Fast</h3><p>Lightning quick load times</p></article><article class=\"feature\"><h3>Secure</h3><p>Enterprise-grade security</p></article><article class=\"feature\"><h3>Reliable</h3><p>99.9% uptime guaranteed</p></article><article class=\"feature\"><h3>Support</h3><p>24/7 customer service</p></article></section>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .cards > div { background: #d1c4e9; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .feature { background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .feature h3 { margin: 0 0 8px; color: steelblue; } .feature p { margin: 0; color: #666; font-size: 0.9rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Create a responsive grid */\n.cards {",
|
"codePrefix": ".features {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
|
"solution": "display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "display", "expected": "grid" },
|
"value": { "property": "display", "expected": "grid" },
|
||||||
"message": "Set <kbd>display: grid</kbd>"
|
"message": "اضبط <kbd>display: grid</kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "repeat\\(auto-fit,\\s*minmax\\(200px,\\s*1fr\\)\\)",
|
"value": "repeat\\(auto-fit,\\s*minmax\\(200px,\\s*1fr\\)\\)",
|
||||||
"message": "Use <kbd>repeat(auto-fit, minmax(200px, 1fr))</kbd>",
|
"message": "استخدم <kbd>repeat(auto-fit, minmax(200px, 1fr))</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "gap", "expected": "1rem" },
|
"value": { "property": "gap", "expected": "1rem" },
|
||||||
"message": "Set <kbd>gap: 1rem</kbd>"
|
"message": "اضبط <kbd>gap: 1rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "responsive-4",
|
"id": "responsive-4",
|
||||||
"title": "Mobile-First",
|
"title": "Mobile-First",
|
||||||
"description": "Adopt a mobile-first approach by writing base styles for small screens and enhancing for larger viewports.",
|
"description": "اتبع نهج mobile-first بكتابة أنماط أساسية للشاشات الصغيرة وتحسينها لـ viewports أكبر.",
|
||||||
"task": "Write a media query with <kbd>@media (min-width: 768px)</kbd> that sets <kbd>.sidebar</kbd> width to <kbd>250px</kbd>.",
|
"task": "اكتب media query باستخدام <kbd>@media (min-width: 768px)</kbd> لضبط عرض <kbd>.sidebar</kbd> إلى <kbd>250px</kbd>.",
|
||||||
"previewHTML": "<aside class=\"sidebar\">Sidebar</aside>",
|
"previewHTML": "<aside class=\"sidebar\">Sidebar</aside>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sidebar { background: #c8e6c9; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sidebar { background: #c8e6c9; padding: 1rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -105,19 +105,19 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "@media\\s*\\(min-width:\\s*768px\\)",
|
"value": "@media\\s*\\(min-width:\\s*768px\\)",
|
||||||
"message": "Use <kbd>@media (min-width: 768px)</kbd>",
|
"message": "استخدم <kbd>@media (min-width: 768px)</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": ".sidebar",
|
"value": ".sidebar",
|
||||||
"message": "Target <kbd>.sidebar</kbd> inside media query",
|
"message": "استهدف <kbd>.sidebar</kbd> في media query",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "width", "expected": "250px" },
|
"value": { "property": "width", "expected": "250px" },
|
||||||
"message": "Set <kbd>width: 250px</kbd>",
|
"message": "اضبط <kbd>width: 250px</kbd>",
|
||||||
"options": { "exact": false }
|
"options": { "exact": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,94 +2,65 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-elements",
|
"id": "html-elements",
|
||||||
"title": "HTML Block & Inline",
|
"title": "HTML Block & Inline",
|
||||||
"description": "Understanding the fundamental difference between container (block) and inline elements",
|
"description": "فهم الفرق الأساسي بين عناصر الحاويات (الكتلية) والعناصر السطرية",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "block-vs-inline-intro",
|
"id": "block-vs-inline-intro",
|
||||||
"title": "Block vs Inline Elements",
|
"title": "العناصر الكتلية vs السطرية",
|
||||||
"description": "HTML elements fall into two main categories:<br><br><strong>Block elements</strong> (containers) start on a new line and take full width. Examples: <kbd><div></kbd>, <kbd><p></kbd>, <kbd><h1></kbd>, <kbd><section></kbd><br><br><strong>Inline elements</strong> flow within text and only take needed width. Examples: <kbd><span></kbd>, <kbd><a></kbd>, <kbd><strong></kbd>, <kbd><em></kbd>",
|
"description": "تنقسم عناصر HTML إلى فئتين رئيسيتين:<br><br><strong>العناصر الكتلية</strong> (الحاويات) تبدأ في سطر جديد وتأخذ العرض الكامل. أمثلة: <kbd><div></kbd>, <kbd><p></kbd>, <kbd><h1></kbd>, <kbd><section></kbd><br><br><strong>العناصر السطرية</strong> تتدفق داخل النص وتأخذ العرض المطلوب فقط. أمثلة: <kbd><span></kbd>, <kbd><a></kbd>, <kbd><strong></kbd>, <kbd><em></kbd>",
|
||||||
"task": "Wrap the word <kbd>important</kbd> with <kbd><strong></kbd> tags to make it bold. Notice how the paragraph (block) takes full width while strong (inline) flows with text.",
|
"task": "أحط الكلمة <kbd>مهمة</kbd> بوسوم <kbd><strong></kbd> لجعلها عريضة. لاحظ كيف يأخذ الفقرة (كتلي) العرض الكامل بينما strong (سطري) يتدفق مع النص.",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; } p { background: #e3f2fd; padding: 10px; } strong { background: #ffecb3; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; } p { background: #e3f2fd; padding: 10px; } strong { background: #ffecb3; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "<p>This is a paragraph with an important word.</p>",
|
"initialCode": "<p>هذه فقرة تحتوي على كلمة مهمة.</p>",
|
||||||
"solution": "<p>This is a paragraph with an <strong>important</strong> word.</p>",
|
"solution": "<p>هذه فقرة تحتوي على كلمة <strong>مهمة</strong>.</p>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "p",
|
"value": "p",
|
||||||
"message": "Add a <kbd><p></kbd> paragraph element"
|
"message": "أضف عنصر فقرة <kbd><p></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "parent_child",
|
"type": "parent_child",
|
||||||
"value": { "parent": "p", "child": "strong" },
|
"value": { "parent": "p", "child": "strong" },
|
||||||
"message": "Wrap the word <kbd>important</kbd> with <kbd><strong></kbd> tags"
|
"message": "أحط الكلمة <kbd>مهمة</kbd> بوسوم <kbd><strong></kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "semantic-containers",
|
"id": "semantic-containers",
|
||||||
"title": "Semantic Tags",
|
"title": "الوسوم الدلالية",
|
||||||
"description": "Modern HTML uses semantic containers that describe their content:<br><br><kbd><header></kbd> - Page or section header<br><kbd><nav></kbd> - Navigation links<br><kbd><main></kbd> - Main content area<br><kbd><section></kbd> - Thematic grouping<br><kbd><article></kbd> - Self-contained content<br><kbd><footer></kbd> - Page or section footer",
|
"description": "يستخدم HTML الحديث حاويات دلالية تصف محتواها:<br><br><kbd><header></kbd> - رأس الصفحة أو القسم<br><kbd><nav></kbd> - روابط التنقل<br><kbd><main></kbd> - منطقة المحتوى الرئيسي<br><kbd><section></kbd> - تجميع موضوعي<br><kbd><article></kbd> - محتوى مستقل<br><kbd><footer></kbd> - تذييل الصفحة أو القسم",
|
||||||
"task": "Create a basic page structure:<br>1. Add a <kbd><header></kbd> with an <kbd><h1></kbd> containing the text <code>My Website</code><br>2. Add a <kbd><main></kbd> element with a paragraph saying <code>Welcome to my site!</code><br>3. Add a <kbd><footer></kbd> with a paragraph saying <code>Copyright 2026</code>",
|
"task": "أنشئ هيكل صفحة أساسي:<br>1. أضف <kbd><header></kbd> مع <kbd><h1></kbd> يحتوي على النص <code>موقعي</code><br>2. أضف عنصر <kbd><main></kbd> مع فقرة تقول <code>مرحباً بك في موقعي!</code><br>3. أضف <kbd><footer></kbd> مع فقرة تقول <code>Copyright 2026</code>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; margin: 0; } header { background: #1976d2; color: white; padding: 15px; } main { padding: 20px; min-height: 100px; } footer { background: #424242; color: white; padding: 10px; text-align: center; }",
|
"previewBaseCSS": "body { font-family: system-ui; margin: 0; } header { background: #1976d2; color: white; padding: 15px; } main { padding: 20px; min-height: 100px; } footer { background: #424242; color: white; padding: 10px; text-align: center; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<header>\n <h1>My Website</h1>\n</header>\n<main>\n <p>Welcome to my site!</p>\n</main>\n<footer>\n <p>Copyright 2026</p>\n</footer>",
|
"solution": "<header>\n <h1>موقعي</h1>\n</header>\n<main>\n <p>مرحباً بك في موقعي!</p>\n</main>\n<footer>\n <p>Copyright 2026</p>\n</footer>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "header",
|
"value": "header",
|
||||||
"message": "Add a <kbd><header></kbd> element"
|
"message": "أضف عنصر <kbd><header></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "main",
|
"value": "main",
|
||||||
"message": "Add a <kbd><main></kbd> element"
|
"message": "أضف عنصر <kbd><main></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "footer",
|
"value": "footer",
|
||||||
"message": "Add a <kbd><footer></kbd> element"
|
"message": "أضف عنصر <kbd><footer></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "parent_child",
|
"type": "parent_child",
|
||||||
"value": { "parent": "header", "child": "h1" },
|
"value": { "parent": "header", "child": "h1" },
|
||||||
"message": "Add an <kbd><h1></kbd> heading inside your header"
|
"message": "أضف عنوان <kbd><h1></kbd> داخل header"
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "div-vs-span",
|
|
||||||
"title": "div & span",
|
|
||||||
"description": "When you need a container without semantic meaning:<br><br><kbd><div></kbd> - Generic block container (for layout/grouping)<br><kbd><span></kbd> - Generic inline container (for styling text portions)<br><br>Use semantic elements when possible, div/span when no semantic element fits.",
|
|
||||||
"task": "Wrap the word 'highlighted' in a <kbd><span></kbd> to style it differently. Wrap the whole quote in a <kbd><div></kbd>.",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: Georgia, serif; padding: 20px; } div { background: #f5f5f5; padding: 15px; border-left: 4px solid #1976d2; } span { background: #fff59d; padding: 2px 4px; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "The most highlighted moment was unforgettable.",
|
|
||||||
"solution": "<div>The most <span>highlighted</span> moment was unforgettable.</div>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "div",
|
|
||||||
"message": "Wrap everything in a <kbd><div></kbd> element"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "span",
|
|
||||||
"message": "Add a <kbd><span></kbd> around the word <kbd>highlighted</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_text",
|
|
||||||
"value": { "selector": "span", "text": "highlighted" },
|
|
||||||
"message": "The <kbd><span></kbd> should contain the word <kbd>highlighted</kbd>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +1,100 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-forms-basic",
|
"id": "html-forms-basic",
|
||||||
"title": "HTML Forms",
|
"title": "نماذج HTML",
|
||||||
"description": "Learn to create forms with various input types",
|
"description": "تعلم إنشاء النماذج بأنواع حقول مختلفة",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "form-structure",
|
"id": "form-structure",
|
||||||
"title": "Form Structure",
|
"title": "هيكل النموذج",
|
||||||
"description": "Every form needs a <kbd><form></kbd> wrapper. Inside, use <kbd><label></kbd> to describe inputs and <kbd><input></kbd> for user data entry.<br><br>The <kbd>for</kbd> attribute on labels should match the <kbd>id</kbd> on inputs for accessibility.",
|
"description": "كل نموذج يحتاج غلاف <kbd><form></kbd>. بداخله، استخدم <kbd><label></kbd> لوصف الحقول و <kbd><input></kbd> لإدخال البيانات.<br><br>سمة <kbd>for</kbd> في التسميات يجب أن تطابق <kbd>id</kbd> في الحقول للوصولية.",
|
||||||
"task": "Create a form with:<br>1. A <kbd><label></kbd> with the text <code>Name:</code> and <kbd>for=\"name\"</kbd> attribute<br>2. A text <kbd><input></kbd> with <kbd>id=\"name\"</kbd> and <kbd>name=\"name\"</kbd> attributes",
|
"task": "أنشئ نموذجاً مع:<br>1. <kbd><label></kbd> بالنص <code>الاسم:</code> وسمة <kbd>for=\"name\"</kbd><br>2. <kbd><input></kbd> نصي بسمات <kbd>id=\"name\"</kbd> و <kbd>name=\"name\"</kbd>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-bottom: 5px; font-weight: 500; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-bottom: 5px; font-weight: 500; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<form>\n <label for=\"name\">Name:</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n</form>",
|
"solution": "<form>\n <label for=\"name\">الاسم:</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n</form>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "form",
|
"value": "form",
|
||||||
"message": "Wrap everything in a <kbd><form></kbd> element"
|
"message": "أحط كل شيء بعنصر <kbd><form></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "label",
|
"value": "label",
|
||||||
"message": "Add a <kbd><label></kbd> for your input"
|
"message": "أضف <kbd><label></kbd> لحقلك"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "input",
|
"value": "input",
|
||||||
"message": "Add an <kbd><input></kbd> element"
|
"message": "أضف عنصر <kbd><input></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "label", "attr": "for", "value": null },
|
"value": { "selector": "label", "attr": "for", "value": null },
|
||||||
"message": "Add a <kbd>for</kbd> attribute to your label"
|
"message": "أضف سمة <kbd>for</kbd> للتسمية"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "input", "attr": "id", "value": null },
|
"value": { "selector": "input", "attr": "id", "value": null },
|
||||||
"message": "Add an <kbd>id</kbd> attribute to your input"
|
"message": "أضف سمة <kbd>id</kbd> لحقلك"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "input-types",
|
"id": "input-types",
|
||||||
"title": "Input Types",
|
"title": "أنواع الحقول",
|
||||||
"description": "Different input types provide appropriate keyboards and validation:<br><br><kbd>type=\"text\"</kbd> - General text<br><kbd>type=\"email\"</kbd> - Email with @ validation<br><kbd>type=\"password\"</kbd> - Hidden characters<br><kbd>type=\"number\"</kbd> - Numeric keyboard<br><kbd>type=\"tel\"</kbd> - Phone keyboard",
|
"description": "أنواع الحقول المختلفة توفر لوحات مفاتيح وتحقق مناسب:<br><br><kbd>type=\"text\"</kbd> - نص عام<br><kbd>type=\"email\"</kbd> - بريد إلكتروني مع تحقق @<br><kbd>type=\"password\"</kbd> - أحرف مخفية<br><kbd>type=\"number\"</kbd> - لوحة مفاتيح رقمية<br><kbd>type=\"tel\"</kbd> - لوحة مفاتيح هاتف",
|
||||||
"task": "Create a login form with two fields:<br>1. An email field: <kbd><label for=\"email\">Email:</label></kbd> and <kbd><input type=\"email\" id=\"email\"></kbd><br>2. A password field: <kbd><label for=\"password\">Password:</label></kbd> and <kbd><input type=\"password\" id=\"password\"></kbd>",
|
"task": "أنشئ نموذج تسجيل دخول بحقلين:<br>1. حقل بريد: <kbd><label for=\"email\">البريد:</label></kbd> و <kbd><input type=\"email\" id=\"email\"></kbd><br>2. حقل كلمة مرور: <kbd><label for=\"password\">كلمة المرور:</label></kbd> و <kbd><input type=\"password\" id=\"password\"></kbd>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "<form>\n \n</form>",
|
"initialCode": "<form>\n \n</form>",
|
||||||
"solution": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\">\n</form>",
|
"solution": "<form>\n <label for=\"email\">البريد:</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <label for=\"password\">كلمة المرور:</label>\n <input type=\"password\" id=\"password\" name=\"password\">\n</form>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "input[type='email']",
|
"value": "input[type='email']",
|
||||||
"message": "Add an input with type=\"email\""
|
"message": "أضف حقل بـ type=\"email\""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "input[type='password']",
|
"value": "input[type='password']",
|
||||||
"message": "Add an input with type=\"password\""
|
"message": "أضف حقل بـ type=\"password\""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "label", "min": 2 },
|
"value": { "selector": "label", "min": 2 },
|
||||||
"message": "Add labels for both inputs"
|
"message": "أضف تسميات لكلا الحقلين"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "submit-button",
|
"id": "submit-button",
|
||||||
"title": "Submit Button",
|
"title": "زر الإرسال",
|
||||||
"description": "Forms need a way to submit data. Use:<br><br><kbd><button type=\"submit\"></kbd> - Preferred, flexible content<br><kbd><input type=\"submit\"></kbd> - Simple text-only button<br><br>The button text should be action-oriented (e.g., <code>Sign In</code>, 'Register', 'Send').",
|
"description": "النماذج تحتاج طريقة لإرسال البيانات. استخدم:<br><br><kbd><button type=\"submit\"></kbd> - مفضل، محتوى مرن<br><kbd><input type=\"submit\"></kbd> - زر نص بسيط<br><br>نص الزر يجب أن يكون موجه للعمل (مثل <code>تسجيل الدخول</code>، 'التسجيل'، 'إرسال').",
|
||||||
"task": "Add a submit button to the form with the text <code>Sign In</code>.",
|
"task": "أضف زر إرسال للنموذج بالنص <code>تسجيل الدخول</code>.",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } button { width: 100%; margin-top: 20px; padding: 10px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; } button:hover { background: #1565c0; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } button { width: 100%; margin-top: 20px; padding: 10px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; } button:hover { background: #1565c0; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\">\n \n</form>",
|
"initialCode": "<form>\n <label for=\"email\">البريد:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">كلمة المرور:</label>\n <input type=\"password\" id=\"password\">\n \n</form>",
|
||||||
"solution": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\">\n \n <button type=\"submit\">Sign In</button>\n</form>",
|
"solution": "<form>\n <label for=\"email\">البريد:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">كلمة المرور:</label>\n <input type=\"password\" id=\"password\">\n \n <button type=\"submit\">تسجيل الدخول</button>\n</form>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "button[type='submit'], input[type='submit']",
|
"value": "button[type='submit'], input[type='submit']",
|
||||||
"message": "Add a submit button to your form"
|
"message": "أضف زر إرسال لنموذجك"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_text",
|
"type": "element_text",
|
||||||
"value": { "selector": "button", "text": "Sign In" },
|
"value": { "selector": "button", "text": "تسجيل الدخول" },
|
||||||
"message": "The button should say <kbd>Sign In</kbd>"
|
"message": "يجب أن يعرض الزر <kbd>تسجيل الدخول</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,110 +1,32 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-forms-validation",
|
"id": "html-forms-validation",
|
||||||
"title": "HTML Validation",
|
"title": "تحقق النماذج",
|
||||||
"description": "Learn HTML5 built-in form validation attributes",
|
"description": "استخدم تحقق HTML5 المدمج لتجربة مستخدم أفضل",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "intermediate",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "required-fields",
|
"id": "required-fields",
|
||||||
"title": "Required Fields",
|
"title": "الحقول المطلوبة",
|
||||||
"description": "The <kbd>required</kbd> attribute prevents form submission if the field is empty.<br><br>Add it to any input that must be filled:<br><kbd><input type=\"text\" required></kbd><br><br>The browser shows a validation message automatically.",
|
"description": "سمة <kbd>required</kbd> تمنع إرسال النموذج إذا كان الحقل فارغاً. يعرض المتصفح رسالة تحقق تلقائياً - بدون JavaScript!<br><br>أضفها لأي حقل يجب ملؤه:<br><kbd><input type=\"text\" required></kbd>",
|
||||||
"task": "Make both the name and email fields required by adding the <kbd>required</kbd> attribute.",
|
"task": "اجعل كلا الحقلين (الاسم والبريد) مطلوبين بإضافة سمة <kbd>required</kbd> لكل حقل.",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 350px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-of-type { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } input:invalid { border-color: #d32f2f; } button { margin-top: 20px; padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 320px; } label { display: block; margin-top: 15px; margin-bottom: 5px; font-weight: 500; } label:first-of-type { margin-top: 0; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; box-sizing: border-box; } input:focus { outline: 2px solid steelblue; border-color: transparent; } button { margin-top: 20px; padding: 10px 20px; background: steelblue; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 500; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "<form>\n <label for=\"name\">Name: *</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n \n <label for=\"email\">Email: *</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <button type=\"submit\">Submit</button>\n</form>",
|
"initialCode": "<form>\n <label for=\"name\">الاسم *</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n \n <label for=\"email\">البريد *</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <button type=\"submit\">إرسال</button>\n</form>",
|
||||||
"solution": "<form>\n <label for=\"name\">Name: *</label>\n <input type=\"text\" id=\"name\" name=\"name\" required>\n \n <label for=\"email\">Email: *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <button type=\"submit\">Submit</button>\n</form>",
|
"solution": "<form>\n <label for=\"name\">الاسم *</label>\n <input type=\"text\" id=\"name\" name=\"name\" required>\n \n <label for=\"email\">البريد *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <button type=\"submit\">إرسال</button>\n</form>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "input[name='name']", "attr": "required", "value": true },
|
"value": { "selector": "input[name='name']", "attr": "required", "value": true },
|
||||||
"message": "Add the <kbd>required</kbd> attribute to the name input"
|
"message": "أضف <kbd>required</kbd> لحقل الاسم"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "input[name='email']", "attr": "required", "value": true },
|
"value": { "selector": "input[name='email']", "attr": "required", "value": true },
|
||||||
"message": "Add the <kbd>required</kbd> attribute to the email input"
|
"message": "أضف <kbd>required</kbd> لحقل البريد"
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "input-constraints",
|
|
||||||
"title": "Constraints",
|
|
||||||
"description": "Control what users can enter:<br><br><kbd>minlength</kbd> / <kbd>maxlength</kbd> - Text length limits<br><kbd>min</kbd> / <kbd>max</kbd> - Number range<br><kbd>pattern</kbd> - Regex pattern matching<br><kbd>placeholder</kbd> - Hint text (not a label!)",
|
|
||||||
"task": "Add validation to the password input:<br>1. Add <kbd>minlength=\"8\"</kbd> for minimum length<br>2. Add <kbd>maxlength=\"20\"</kbd> for maximum length<br>3. Add <kbd>placeholder=\"Enter password\"</kbd> as a hint",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 350px; } label { display: block; margin-bottom: 5px; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } input:invalid:not(:placeholder-shown) { border-color: #d32f2f; } small { display: block; font-size: 12px; color: #666; margin-top: 4px; } button { margin-top: 20px; padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<form>\n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required aria-describedby=\"password-hint\">\n <small id=\"password-hint\">Must be 8-20 characters</small>\n \n <button type=\"submit\">Create Account</button>\n</form>",
|
|
||||||
"solution": "<form>\n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required minlength=\"8\" maxlength=\"20\" placeholder=\"Enter password\" aria-describedby=\"password-hint\">\n <small id=\"password-hint\">Must be 8-20 characters</small>\n \n <button type=\"submit\">Create Account</button>\n</form>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "input[type='password']", "attr": "minlength", "value": "8" },
|
|
||||||
"message": "Add <kbd>minlength</kbd>=\"8\" to the password input"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "input[type='password']", "attr": "maxlength", "value": "20" },
|
|
||||||
"message": "Add <kbd>maxlength</kbd>=\"20\" to the password input"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "input[type='password']", "attr": "placeholder", "value": null },
|
|
||||||
"message": "Add a <kbd>placeholder</kbd> to hint what to enter"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "complete-registration",
|
|
||||||
"title": "Full Form",
|
|
||||||
"description": "Build a complete registration form with all validation concepts:<br><br>- Required fields marked with *<br>- Email validation (use type=\"email\")<br>- Password with length constraints<br>- Terms checkbox (required)<br>- Submit button",
|
|
||||||
"task": "Complete the registration form. Add required attributes, proper input types, and validation constraints.",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 400px; background: #f5f5f5; padding: 25px; border-radius: 8px; } h2 { margin-top: 0; margin-bottom: 20px; } label { display: block; margin-top: 15px; margin-bottom: 5px; font-weight: 500; } input[type='text'], input[type='email'], input[type='password'] { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; font-size: 14px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; } input[type='checkbox'] { width: auto; margin-right: 8px; vertical-align: middle; } label:has(input[type='checkbox']) { display: flex; align-items: center; font-weight: normal; } button { width: 100%; margin-top: 20px; padding: 12px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: 500; } button:hover { background: #1565c0; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<form>\n <h2>Create Account</h2>\n \n <label for=\"fullname\">Full Name *</label>\n <input type=\"text\" id=\"fullname\" name=\"fullname\">\n \n <label for=\"email\">Email *</label>\n <input id=\"email\" name=\"email\">\n \n <label for=\"password\">Password *</label>\n <input id=\"password\" name=\"password\">\n \n <label>\n <input type=\"checkbox\" id=\"terms\" name=\"terms\">\n I agree to the Terms of Service *\n </label>\n \n <button type=\"submit\">Register</button>\n</form>",
|
|
||||||
"solution": "<form>\n <h2>Create Account</h2>\n \n <label for=\"fullname\">Full Name *</label>\n <input type=\"text\" id=\"fullname\" name=\"fullname\" required>\n \n <label for=\"email\">Email *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <label for=\"password\">Password *</label>\n <input type=\"password\" id=\"password\" name=\"password\" required minlength=\"8\">\n \n <label>\n <input type=\"checkbox\" id=\"terms\" name=\"terms\" required>\n I agree to the Terms of Service *\n </label>\n \n <button type=\"submit\">Register</button>\n</form>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#fullname", "attr": "required", "value": true },
|
|
||||||
"message": "Make the full name field <kbd>required</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#email", "attr": "type", "value": "email" },
|
|
||||||
"message": "Set the email input <kbd>type=\"email\"</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#email", "attr": "required", "value": true },
|
|
||||||
"message": "Make the email field <kbd>required</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#password", "attr": "type", "value": "password" },
|
|
||||||
"message": "Set the password input <kbd>type=\"password\"</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#password", "attr": "required", "value": true },
|
|
||||||
"message": "Make the password field <kbd>required</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#password", "attr": "minlength", "value": "8" },
|
|
||||||
"message": "Add <kbd>minlength</kbd>=\"8\" to password"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#terms", "attr": "required", "value": true },
|
|
||||||
"message": "Make the terms checkbox <kbd>required</kbd>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-details-summary",
|
"id": "html-details-summary",
|
||||||
"title": "HTML Details & Summary",
|
"title": "HTML Details & Summary",
|
||||||
"description": "Create expandable content sections without JavaScript",
|
"description": "أنشئ أقسام قابلة للتوسيع بدون JavaScript",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "details-summary-basic",
|
"id": "details-summary-basic",
|
||||||
"title": "First Widget",
|
"title": "أول عنصر تفاعلي",
|
||||||
"description": "The <kbd><details></kbd> element creates a collapsible section. The <kbd><summary></kbd> provides the clickable label.<br><br>Click the summary to toggle the hidden content - no JavaScript needed!",
|
"description": "عنصر <kbd><details></kbd> ينشئ قسماً قابلاً للطي. عنصر <kbd><summary></kbd> يوفر التسمية القابلة للنقر.<br><br>انقر على الملخص لإظهار المحتوى المخفي - بدون JavaScript!",
|
||||||
"task": "Create a <kbd><details></kbd> element with:<br>1. A <kbd><summary></kbd> saying <code>Click to reveal</code><br>2. A <kbd><p></kbd> with the text <code>This content was hidden!</code>",
|
"task": "أنشئ عنصر <kbd><details></kbd> مع:<br>1. عنصر <kbd><summary></kbd> يقول <code>Click to reveal</code><br>2. عنصر <kbd><p></kbd> بالنص <code>This content was hidden!</code>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; } summary { font-weight: 600; cursor: pointer; } summary:hover { color: #1976d2; } details p { margin-top: 15px; color: #666; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; } summary { font-weight: 600; cursor: pointer; } summary:hover { color: #1976d2; } details p { margin-top: 15px; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,30 +21,30 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "details",
|
"value": "details",
|
||||||
"message": "Add a <kbd><details></kbd> element"
|
"message": "أضف عنصر <kbd><details></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "summary",
|
"value": "summary",
|
||||||
"message": "Add a <kbd><summary></kbd> inside the details"
|
"message": "أضف <kbd><summary></kbd> داخل details"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "parent_child",
|
"type": "parent_child",
|
||||||
"value": { "parent": "details", "child": "summary" },
|
"value": { "parent": "details", "child": "summary" },
|
||||||
"message": "The <kbd><summary></kbd> must be inside <kbd><details></kbd>"
|
"message": "يجب أن يكون <kbd><summary></kbd> داخل <kbd><details></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "parent_child",
|
"type": "parent_child",
|
||||||
"value": { "parent": "details", "child": "p" },
|
"value": { "parent": "details", "child": "p" },
|
||||||
"message": "Add a <kbd><p></kbd> inside <kbd><details></kbd> for the hidden content"
|
"message": "أضف <kbd><p></kbd> داخل <kbd><details></kbd> للمحتوى المخفي"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "details-open-attribute",
|
"id": "details-open-attribute",
|
||||||
"title": "Pre-expanded Details",
|
"title": "موسع افتراضياً",
|
||||||
"description": "By default, <kbd><details></kbd> is closed. Add the <kbd>open</kbd> attribute to show the content initially.<br><br>This is a boolean attribute - just add <kbd>open</kbd> without a value.",
|
"description": "افتراضياً، <kbd><details></kbd> مغلق. أضف سمة <kbd>open</kbd> لإظهار المحتوى في البداية.<br><br>هذه سمة منطقية - فقط أضف <kbd>open</kbd> بدون قيمة.",
|
||||||
"task": "Add the <kbd>open</kbd> attribute to the <kbd><details></kbd> element to show the content by default.",
|
"task": "أضف سمة <kbd>open</kbd> لعنصر <kbd><details></kbd> لإظهار المحتوى افتراضياً.",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; background: #f9f9f9; } summary { font-weight: 600; cursor: pointer; } details p { margin-top: 15px; color: #666; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; background: #f9f9f9; } summary { font-weight: 600; cursor: pointer; } details p { margin-top: 15px; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -55,15 +55,15 @@
|
|||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "details", "attr": "open", "value": true },
|
"value": { "selector": "details", "attr": "open", "value": true },
|
||||||
"message": "Add the <kbd>open</kbd> attribute to <kbd><details></kbd>"
|
"message": "أضف سمة <kbd>open</kbd> إلى <kbd><details></kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "faq-accordion",
|
"id": "faq-accordion",
|
||||||
"title": "FAQ Accordion",
|
"title": "أكورديون FAQ",
|
||||||
"description": "Multiple <kbd><details></kbd> elements create an accordion-style FAQ. Each question can be expanded independently.<br><br><b>Pro tip:</b> Type <kbd>details*3>summary+p</kbd> and press Tab for Emmet expansion. <kbd>*3</kbd> creates 3 elements, <kbd>></kbd> nests inside, <kbd>+</kbd> adds siblings.",
|
"description": "عناصر <kbd><details></kbd> المتعددة تنشئ FAQ بأسلوب الأكورديون. كل سؤال يمكن توسيعه بشكل مستقل.<br><br><b>نصيحة:</b> اكتب <kbd>details*3>summary+p</kbd> واضغط Tab لتوسيع Emmet. <kbd>*3</kbd> ينشئ 3 عناصر، <kbd>></kbd> يضع بالداخل، <kbd>+</kbd> يضيف أشقاء.",
|
||||||
"task": "Create an FAQ section with:<br>1. An <kbd><h1></kbd> saying <code>Frequently Asked Questions</code><br>2. Three <kbd><details></kbd> elements, each with a question in <kbd><summary></kbd> and an answer in <kbd><p></kbd>",
|
"task": "أنشئ قسم FAQ مع:<br>1. عنصر <kbd><h1></kbd> يقول <code>Frequently Asked Questions</code><br>2. ثلاثة عناصر <kbd><details></kbd>، كل واحد بسؤال في <kbd><summary></kbd> وإجابة في <kbd><p></kbd>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; min-height: 100vh; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); display: flex; flex-direction: column; justify-content: center; padding: 40px; margin: 0; box-sizing: border-box; } h1 { font-size: 2.5rem; color: #4a4a4a; text-align: center; margin: 0 0 30px 0; } details { background: white; border-radius: 12px; margin-bottom: 15px; padding: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } summary { font-size: 1.3rem; font-weight: 600; color: #5c5c5c; cursor: pointer; list-style: none; } summary::before { content: '▸ '; color: #fcb69f; } details[open] summary::before { content: '▾ '; } details p { margin: 15px 0 0 0; color: #666; line-height: 1.6; }",
|
"previewBaseCSS": "body { font-family: system-ui; min-height: 100vh; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); display: flex; flex-direction: column; justify-content: center; padding: 40px; margin: 0; box-sizing: border-box; } h1 { font-size: 2.5rem; color: #4a4a4a; text-align: center; margin: 0 0 30px 0; } details { background: white; border-radius: 12px; margin-bottom: 15px; padding: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } summary { font-size: 1.3rem; font-weight: 600; color: #5c5c5c; cursor: pointer; list-style: none; } summary::before { content: '▸ '; color: #fcb69f; } details[open] summary::before { content: '▾ '; } details p { margin: 15px 0 0 0; color: #666; line-height: 1.6; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -74,22 +74,22 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "h1",
|
"value": "h1",
|
||||||
"message": "Add an <kbd><h1></kbd> heading for the FAQ title"
|
"message": "أضف عنوان <kbd><h1></kbd> لعنوان FAQ"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "details", "min": 3 },
|
"value": { "selector": "details", "min": 3 },
|
||||||
"message": "Create at least 3 <kbd><details></kbd> elements for the FAQ"
|
"message": "أنشئ على الأقل 3 عناصر <kbd><details></kbd> للـ FAQ"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "summary", "min": 3 },
|
"value": { "selector": "summary", "min": 3 },
|
||||||
"message": "Each <kbd><details></kbd> needs a <kbd><summary></kbd> for the question"
|
"message": "كل <kbd><details></kbd> يحتاج <kbd><summary></kbd> للسؤال"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "details p", "min": 3 },
|
"value": { "selector": "details p", "min": 3 },
|
||||||
"message": "Each <kbd><details></kbd> needs a <kbd><p></kbd> for the answer"
|
"message": "كل <kbd><details></kbd> يحتاج <kbd><p></kbd> للإجابة"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-progress-meter",
|
"id": "html-progress-meter",
|
||||||
"title": "HTML Progress & Meter",
|
"title": "HTML Progress & Meter",
|
||||||
"description": "Display completion status and scalar measurements natively",
|
"description": "اعرض حالة الإكمال والقياسات بشكل أصلي",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "progress-basic",
|
"id": "progress-basic",
|
||||||
"title": "Progress Bars",
|
"title": "أشرطة التقدم",
|
||||||
"description": "The <kbd><progress></kbd> element shows task completion. Use <kbd>value</kbd> for current progress and <kbd>max</kbd> for the total.<br><br><b>Note:</b> This is not a self-closing tag! Write <kbd><progress>...</progress></kbd> with fallback text inside for older browsers.",
|
"description": "عنصر <kbd><progress></kbd> يُظهر إكمال المهمة. استخدم <kbd>value</kbd> للتقدم الحالي و <kbd>max</kbd> للإجمالي.<br><br><b>ملاحظة:</b> هذا ليس وسماً ذاتي الإغلاق! اكتب <kbd><progress>...</progress></kbd> مع نص بديل بالداخل للمتصفحات القديمة.",
|
||||||
"task": "Create a progress bar showing 70% completion:<br>1. Add a <kbd><label></kbd> saying <code>Download:</code><br>2. Add a <kbd><progress></kbd> with <kbd>value=\"70\"</kbd> and <kbd>max=\"100\"</kbd>",
|
"task": "أنشئ شريط تقدم يُظهر 70% إكمال:<br>1. أضف <kbd><label></kbd> يقول <code>Download:</code><br>2. أضف <kbd><progress></kbd> مع <kbd>value=\"70\"</kbd> و <kbd>max=\"100\"</kbd>",
|
||||||
"previewHTML": "",
|
"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; }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,30 +21,30 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "progress",
|
"value": "progress",
|
||||||
"message": "Add a <kbd><progress></kbd> element"
|
"message": "أضف عنصر <kbd><progress></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "progress", "attr": "value", "value": "70" },
|
"value": { "selector": "progress", "attr": "value", "value": "70" },
|
||||||
"message": "Set <kbd>value=</kbd>\"70\" on the progress element"
|
"message": "عيّن <kbd>value=</kbd>\"70\" في عنصر progress"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "progress", "attr": "max", "value": "100" },
|
"value": { "selector": "progress", "attr": "max", "value": "100" },
|
||||||
"message": "Set <kbd>max=</kbd>\"100\" on the progress element"
|
"message": "عيّن <kbd>max=</kbd>\"100\" في عنصر progress"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "label",
|
"value": "label",
|
||||||
"message": "Add a <kbd><label></kbd> for the progress bar"
|
"message": "أضف <kbd><label></kbd> لشريط التقدم"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "progress-indeterminate",
|
"id": "progress-indeterminate",
|
||||||
"title": "Indeterminate Progress",
|
"title": "تقدم غير محدد",
|
||||||
"description": "When progress is unknown (like loading), omit the <kbd>value</kbd> attribute. This creates an animated indeterminate state.<br><br>Useful for network requests or processes with unknown duration.",
|
"description": "عندما يكون التقدم غير معروف (مثل التحميل)، احذف سمة <kbd>value</kbd>. هذا ينشئ حالة متحركة غير محددة.<br><br>مفيد لطلبات الشبكة أو العمليات ذات المدة غير المعروفة.",
|
||||||
"task": "Create a loading indicator:<br>1. Add a <kbd><p></kbd> saying <code>Loading...</code><br>2. Add a <kbd><progress></kbd> without a value attribute",
|
"task": "أنشئ مؤشر تحميل:<br>1. أضف <kbd><p></kbd> يقول <code>Loading...</code><br>2. أضف <kbd><progress></kbd> بدون سمة value",
|
||||||
"previewHTML": "",
|
"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; }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
@@ -55,20 +55,20 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "progress",
|
"value": "progress",
|
||||||
"message": "Add a <kbd><progress></kbd> element"
|
"message": "أضف عنصر <kbd><progress></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "p",
|
"value": "p",
|
||||||
"message": "Add a <kbd><p></kbd> with loading text"
|
"message": "أضف <kbd><p></kbd> مع نص التحميل"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "meter-gauge",
|
"id": "meter-gauge",
|
||||||
"title": "Meter Gauges",
|
"title": "مقاييس meter",
|
||||||
"description": "The <kbd><meter></kbd> element displays a scalar value within a range. Use it for measurements like disk space, battery, or ratings.<br><br>Set <kbd>low</kbd>, <kbd>high</kbd>, and <kbd>optimum</kbd> to define good/bad ranges - the browser colors it accordingly!",
|
"description": "عنصر <kbd><meter></kbd> يعرض قيمة قياسية ضمن نطاق. استخدمه للقياسات مثل مساحة القرص، البطارية، أو التقييمات.<br><br>عيّن <kbd>low</kbd> و <kbd>high</kbd> و <kbd>optimum</kbd> لتحديد النطاقات الجيدة/السيئة - المتصفح يلونها وفقاً لذلك!",
|
||||||
"task": "Create a battery level meter:<br>1. Add a <kbd><label></kbd> saying <code>Battery:</code><br>2. Add a <kbd><meter></kbd> with:<br> - <kbd>value=\"0.8\"</kbd><br> - <kbd>min=\"0\"</kbd> and <kbd>max=\"1\"</kbd><br> - <kbd>low=\"0.2\"</kbd> and <kbd>high=\"0.8\"</kbd><br> - <kbd>optimum=\"1\"</kbd>",
|
"task": "أنشئ مقياس مستوى البطارية:<br>1. أضف <kbd><label></kbd> يقول <code>Battery:</code><br>2. أضف <kbd><meter></kbd> مع:<br> - <kbd>value=\"0.8\"</kbd><br> - <kbd>min=\"0\"</kbd> و <kbd>max=\"1\"</kbd><br> - <kbd>low=\"0.2\"</kbd> و <kbd>high=\"0.8\"</kbd><br> - <kbd>optimum=\"1\"</kbd>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -79,22 +79,42 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "meter",
|
"value": "meter",
|
||||||
"message": "Add a <kbd><meter></kbd> element"
|
"message": "أضف عنصر <kbd><meter></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "meter", "attr": "value", "value": "0.8" },
|
"value": { "selector": "meter", "attr": "value", "value": "0.8" },
|
||||||
"message": "Set <kbd>value=</kbd>\"0.8\" on the meter"
|
"message": "عيّن <kbd>value=</kbd>\"0.8\" في meter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "meter", "attr": "min", "value": "0" },
|
||||||
|
"message": "عيّن <kbd>min=</kbd>\"0\" في meter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "meter", "attr": "max", "value": "1" },
|
||||||
|
"message": "عيّن <kbd>max=</kbd>\"1\" في meter"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "meter", "attr": "low", "value": "0.2" },
|
"value": { "selector": "meter", "attr": "low", "value": "0.2" },
|
||||||
"message": "Set <kbd>low=</kbd>\"0.2\" to define the low threshold"
|
"message": "عيّن <kbd>low=</kbd>\"0.2\" لتحديد العتبة المنخفضة"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "meter", "attr": "high", "value": "0.8" },
|
||||||
|
"message": "عيّن <kbd>high=</kbd>\"0.8\" لتحديد العتبة العالية"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "meter", "attr": "optimum", "value": "1" },
|
||||||
|
"message": "عيّن <kbd>optimum=</kbd>\"1\" للإشارة إلى القيمة المثلى"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "label",
|
"value": "label",
|
||||||
"message": "Add a <kbd><label></kbd> for the meter"
|
"message": "أضف <kbd><label></kbd> للـ meter"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-datalist",
|
"id": "html-datalist",
|
||||||
"title": "Datalist",
|
"title": "قائمة البيانات",
|
||||||
"description": "Provide suggestions for text inputs without JavaScript",
|
"description": "قدم اقتراحات لحقول النص بدون JavaScript",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "datalist-basic",
|
"id": "datalist-basic",
|
||||||
"title": "Input with Suggestions",
|
"title": "حقل مع اقتراحات",
|
||||||
"description": "The <kbd><datalist></kbd> element provides autocomplete suggestions for inputs. Connect it using the <kbd>list</kbd> attribute on the input matching the datalist's <kbd>id</kbd>.<br><br>Users can still type freely - suggestions are just helpers!",
|
"description": "عنصر <kbd><datalist></kbd> يوفر اقتراحات الإكمال التلقائي للحقول. اربطه باستخدام سمة <kbd>list</kbd> في الحقل بما يتطابق مع <kbd>id</kbd> قائمة البيانات.<br><br>يمكن للمستخدمين الكتابة بحرية - الاقتراحات مجرد مساعدات!",
|
||||||
"task": "Create a browser selector:<br>1. Add a <kbd><label></kbd> saying <code>Browser:</code><br>2. Add an <kbd><input></kbd> with <kbd>list=\"browsers\"</kbd><br>3. Add a <kbd><datalist id=\"browsers\"></kbd> with options for Chrome, Firefox, and Safari",
|
"task": "أنشئ محدد متصفح:<br>1. أضف <kbd><label></kbd> يقول <code>المتصفح:</code><br>2. أضف <kbd><input></kbd> مع <kbd>list=\"browsers\"</kbd><br>3. أضف <kbd><datalist id=\"browsers\"></kbd> مع خيارات Chrome و Firefox و Safari",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; font-size: 16px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; font-size: 16px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,30 +21,30 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "datalist",
|
"value": "datalist",
|
||||||
"message": "Add a <kbd><datalist></kbd> element"
|
"message": "أضف عنصر <kbd><datalist></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "input", "attr": "list", "value": "browsers" },
|
"value": { "selector": "input", "attr": "list", "value": "browsers" },
|
||||||
"message": "Connect the input to datalist using <kbd>list=</kbd>\"browsers\""
|
"message": "اربط الحقل بقائمة البيانات باستخدام <kbd>list=</kbd>\"browsers\""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "option", "min": 3 },
|
"value": { "selector": "option", "min": 3 },
|
||||||
"message": "Add at least 3 <kbd><option></kbd> elements inside <kbd><datalist></kbd>"
|
"message": "أضف على الأقل 3 عناصر <kbd><option></kbd> داخل <kbd><datalist></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "label",
|
"value": "label",
|
||||||
"message": "Add a <kbd><label></kbd> for the input"
|
"message": "أضف <kbd><label></kbd> للحقل"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "datalist-countries",
|
"id": "datalist-countries",
|
||||||
"title": "Country Selector",
|
"title": "محدد الدول",
|
||||||
"description": "Datalists work great for long lists like countries. Users can type to filter suggestions instantly.<br><br>The <kbd>value</kbd> attribute is what gets entered, and you can add display text after it.",
|
"description": "قوائم البيانات تعمل بشكل رائع للقوائم الطويلة مثل الدول. يمكن للمستخدمين الكتابة لتصفية الاقتراحات فوراً.<br><br>سمة <kbd>value</kbd> هي ما يتم إدخاله، ويمكنك إضافة نص عرض بعدها.",
|
||||||
"task": "Create a country input:<br>1. Add a <kbd><label></kbd> saying <code>Country:</code><br>2. Add an <kbd><input></kbd> with <kbd>list=\"countries\"</kbd><br>3. Add a <kbd><datalist id=\"countries\"></kbd> with at least 4 country options",
|
"task": "أنشئ حقل دولة:<br>1. أضف <kbd><label></kbd> يقول <code>الدولة:</code><br>2. أضف <kbd><input></kbd> مع <kbd>list=\"countries\"</kbd><br>3. أضف <kbd><datalist id=\"countries\"></kbd> مع 4 خيارات دول على الأقل",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 30px; background: linear-gradient(135deg, #e0f7fa 0%, #b2ebf2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } label { display: block; margin-bottom: 10px; font-weight: 600; color: #00695c; } input { width: 100%; padding: 12px 15px; border: 2px solid #26a69a; border-radius: 8px; font-size: 16px; background: white; } input:focus { outline: none; border-color: #00695c; box-shadow: 0 0 0 3px rgba(38,166,154,0.2); }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 30px; background: linear-gradient(135deg, #e0f7fa 0%, #b2ebf2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } label { display: block; margin-bottom: 10px; font-weight: 600; color: #00695c; } input { width: 100%; padding: 12px 15px; border: 2px solid #26a69a; border-radius: 8px; font-size: 16px; background: white; } input:focus { outline: none; border-color: #00695c; box-shadow: 0 0 0 3px rgba(38,166,154,0.2); }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -55,22 +55,22 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "datalist",
|
"value": "datalist",
|
||||||
"message": "Add a <kbd><datalist></kbd> element"
|
"message": "أضف عنصر <kbd><datalist></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "datalist", "attr": "id", "value": "countries" },
|
"value": { "selector": "datalist", "attr": "id", "value": "countries" },
|
||||||
"message": "Set <kbd>id=</kbd>\"countries\" on the datalist"
|
"message": "عيّن <kbd>id=</kbd>\"countries\" في قائمة البيانات"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "input", "attr": "list", "value": "countries" },
|
"value": { "selector": "input", "attr": "list", "value": "countries" },
|
||||||
"message": "Connect the input using <kbd>list=</kbd>\"countries\""
|
"message": "اربط الحقل باستخدام <kbd>list=</kbd>\"countries\""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "option", "min": 4 },
|
"value": { "selector": "option", "min": 4 },
|
||||||
"message": "Add at least 4 country options"
|
"message": "أضف على الأقل 4 خيارات دول"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-dialog",
|
"id": "html-dialog",
|
||||||
"title": "Dialogs",
|
"title": "مربعات الحوار",
|
||||||
"description": "Create modal dialogs without JavaScript libraries",
|
"description": "أنشئ مربعات حوار نموذجية بدون مكتبات JavaScript",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "dialog-basic",
|
"id": "dialog-basic",
|
||||||
"title": "Open Dialog",
|
"title": "فتح مربع الحوار",
|
||||||
"description": "The <kbd><dialog></kbd> element creates a native modal. Add the <kbd>open</kbd> attribute to show it.<br><br>Use <kbd><form method=\"dialog\"></kbd> inside to close it when the form submits - no JavaScript needed!",
|
"description": "عنصر <kbd><dialog></kbd> ينشئ نافذة نموذجية أصلية. أضف سمة <kbd>open</kbd> لعرضها.<br><br>استخدم <kbd><form method=\"dialog\"></kbd> بداخلها لإغلاقها عند إرسال النموذج - بدون JavaScript!",
|
||||||
"task": "Create a dialog with:<br>1. The <kbd>open</kbd> attribute to show it<br>2. An <kbd><h2></kbd> saying <code>Welcome!</code><br>3. A <kbd><p></kbd> with a greeting message<br>4. A <kbd><form method=\"dialog\"></kbd> with a close button",
|
"task": "أنشئ مربع حوار مع:<br>1. سمة <kbd>open</kbd> لعرضه<br>2. عنصر <kbd><h2></kbd> يقول <code>أهلاً!</code><br>3. عنصر <kbd><p></kbd> مع رسالة ترحيب<br>4. عنصر <kbd><form method=\"dialog\"></kbd> مع زر إغلاق",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: #f5f5f5; } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 400px; } dialog::backdrop { background: rgba(0,0,0,0.5); } dialog h2 { margin: 0 0 15px 0; color: #1976d2; } dialog p { color: #666; margin: 0 0 20px 0; } dialog button { background: #1976d2; color: white; border: none; padding: 10px 25px; border-radius: 6px; cursor: pointer; font-size: 16px; } dialog button:hover { background: #1565c0; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: #f5f5f5; } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 400px; } dialog::backdrop { background: rgba(0,0,0,0.5); } dialog h2 { margin: 0 0 15px 0; color: #1976d2; } dialog p { color: #666; margin: 0 0 20px 0; } dialog button { background: #1976d2; color: white; border: none; padding: 10px 25px; border-radius: 6px; cursor: pointer; font-size: 16px; } dialog button:hover { background: #1565c0; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,35 +21,35 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "dialog",
|
"value": "dialog",
|
||||||
"message": "Add a <kbd><dialog></kbd> element"
|
"message": "أضف عنصر <kbd><dialog></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "dialog", "attr": "open", "value": true },
|
"value": { "selector": "dialog", "attr": "open", "value": true },
|
||||||
"message": "Add the <kbd>open</kbd> attribute to show the dialog"
|
"message": "أضف سمة <kbd>open</kbd> لعرض مربع الحوار"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "dialog h2",
|
"value": "dialog h2",
|
||||||
"message": "Add an <kbd><h2></kbd> heading inside the dialog"
|
"message": "أضف عنوان <kbd><h2></kbd> داخل مربع الحوار"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "form[method='dialog']",
|
"value": "form[method='dialog']",
|
||||||
"message": "Add a <kbd><form method=\"dialog\"></kbd> for closing"
|
"message": "أضف <kbd><form method=\"dialog\"></kbd> للإغلاق"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "dialog button",
|
"value": "dialog button",
|
||||||
"message": "Add a close button inside the form"
|
"message": "أضف زر إغلاق داخل النموذج"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "dialog-form",
|
"id": "dialog-form",
|
||||||
"title": "Dialog + Form",
|
"title": "حوار + نموذج",
|
||||||
"description": "Dialogs can contain full forms. The <kbd>method=\"dialog\"</kbd> makes the form close the dialog on submit instead of sending data.<br><br>This pattern is perfect for confirmation dialogs, quick inputs, or settings panels.",
|
"description": "مربعات الحوار يمكن أن تحتوي على نماذج كاملة. <kbd>method=\"dialog\"</kbd> يجعل النموذج يغلق مربع الحوار عند الإرسال بدلاً من إرسال البيانات.<br><br>هذا النمط مثالي لحوارات التأكيد، المدخلات السريعة، أو لوحات الإعدادات.",
|
||||||
"task": "Create a confirmation dialog:<br>1. Add <kbd>open</kbd> to show it<br>2. An <kbd><h2></kbd> saying <code>Confirm Delete</code><br>3. A <kbd><p></kbd> asking <code>Are you sure?</code><br>4. A <kbd><form method=\"dialog\"></kbd> with Cancel and Delete buttons",
|
"task": "أنشئ مربع حوار تأكيد:<br>1. أضف <kbd>open</kbd> لعرضه<br>2. عنصر <kbd><h2></kbd> يقول <code>تأكيد الحذف</code><br>3. عنصر <kbd><p></kbd> يسأل <code>هل أنت متأكد؟</code><br>4. عنصر <kbd><form method=\"dialog\"></kbd> مع أزرار إلغاء وحذف",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: linear-gradient(135deg, #fce4ec 0%, #f8bbd9 100%); } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 350px; text-align: center; } dialog h2 { margin: 0 0 10px 0; color: #c62828; } dialog p { color: #666; margin: 0 0 20px 0; } dialog form { display: flex; gap: 10px; justify-content: center; } dialog button { padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; border: none; } dialog button:first-child { background: #e0e0e0; color: #333; } dialog button:last-child { background: #c62828; color: white; } dialog button:hover { opacity: 0.9; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: linear-gradient(135deg, #fce4ec 0%, #f8bbd9 100%); } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 350px; text-align: center; } dialog h2 { margin: 0 0 10px 0; color: #c62828; } dialog p { color: #666; margin: 0 0 20px 0; } dialog form { display: flex; gap: 10px; justify-content: center; } dialog button { padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; border: none; } dialog button:first-child { background: #e0e0e0; color: #333; } dialog button:last-child { background: #c62828; color: white; } dialog button:hover { opacity: 0.9; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -60,22 +60,22 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "dialog[open]",
|
"value": "dialog[open]",
|
||||||
"message": "Add a <kbd><dialog></kbd> with the open attribute"
|
"message": "أضف <kbd><dialog></kbd> مع سمة open"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "dialog h2",
|
"value": "dialog h2",
|
||||||
"message": "Add a heading to the dialog"
|
"message": "أضف عنواناً لمربع الحوار"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "form[method='dialog']",
|
"value": "form[method='dialog']",
|
||||||
"message": "Add a <kbd><form method=\"dialog\"></kbd> for the buttons"
|
"message": "أضف <kbd><form method=\"dialog\"></kbd> للأزرار"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "dialog button", "min": 2 },
|
"value": { "selector": "dialog button", "min": 2 },
|
||||||
"message": "Add at least 2 buttons (Cancel and Confirm)"
|
"message": "أضف على الأقل زرين (إلغاء وتأكيد)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-forms-fieldset",
|
"id": "html-forms-fieldset",
|
||||||
"title": "Fieldsets",
|
"title": "مجموعة الحقول",
|
||||||
"description": "Group form controls with fieldset and legend elements",
|
"description": "جمّع عناصر التحكم في النموذج باستخدام fieldset و legend",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "fieldset-basic",
|
"id": "fieldset-basic",
|
||||||
"title": "Grouping with Fieldset",
|
"title": "التجميع مع Fieldset",
|
||||||
"description": "The <kbd><fieldset></kbd> element groups related form controls together. Add a <kbd><legend></kbd> as the first child to give the group a title.<br><br>This helps with accessibility and visual organization of complex forms.",
|
"description": "عنصر <kbd><fieldset></kbd> يجمع عناصر التحكم المرتبطة في النموذج معاً. أضف <kbd><legend></kbd> كأول عنصر فرعي لإعطاء المجموعة عنواناً.<br><br>هذا يساعد في إمكانية الوصول والتنظيم البصري للنماذج المعقدة.",
|
||||||
"task": "Create a form with a fieldset:<br>1. A <kbd><form></kbd> element<br>2. A <kbd><fieldset></kbd> inside<br>3. A <kbd><legend></kbd> saying <code>Personal Info</code><br>4. Two labeled inputs for name and email",
|
"task": "أنشئ نموذجاً مع fieldset:<br>1. عنصر <kbd><form></kbd><br>2. عنصر <kbd><fieldset></kbd> بداخله<br>3. عنصر <kbd><legend></kbd> يقول <code>المعلومات الشخصية</code><br>4. حقلين مُعنونين للاسم والبريد الإلكتروني",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f0f4f8; } form { max-width: 400px; } fieldset { border: 2px solid #3498db; border-radius: 10px; padding: 20px; background: white; } legend { color: #3498db; font-weight: 600; padding: 0 10px; font-size: 1.1rem; } label { display: block; margin: 15px 0 5px; color: #333; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; } input:focus { outline: 2px solid #3498db; border-color: transparent; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f0f4f8; } form { max-width: 400px; } fieldset { border: 2px solid #3498db; border-radius: 10px; padding: 20px; background: white; } legend { color: #3498db; font-weight: 600; padding: 0 10px; font-size: 1.1rem; } label { display: block; margin: 15px 0 5px; color: #333; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; } input:focus { outline: 2px solid #3498db; border-color: transparent; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,35 +21,35 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "form",
|
"value": "form",
|
||||||
"message": "Add a <kbd><form></kbd> element"
|
"message": "أضف عنصر <kbd><form></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "fieldset",
|
"value": "fieldset",
|
||||||
"message": "Add a <kbd><fieldset></kbd> inside the form"
|
"message": "أضف <kbd><fieldset></kbd> داخل النموذج"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "legend",
|
"value": "legend",
|
||||||
"message": "Add a <kbd><legend></kbd> to title your fieldset"
|
"message": "أضف <kbd><legend></kbd> لعنونة مجموعة الحقول"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "label", "min": 2 },
|
"value": { "selector": "label", "min": 2 },
|
||||||
"message": "Add at least 2 labels"
|
"message": "أضف على الأقل عنوانين"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "input", "min": 2 },
|
"value": { "selector": "input", "min": 2 },
|
||||||
"message": "Add at least 2 input fields"
|
"message": "أضف على الأقل حقلي إدخال"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "fieldset-textarea",
|
"id": "fieldset-textarea",
|
||||||
"title": "Adding Textarea",
|
"title": "إضافة Textarea",
|
||||||
"description": "The <kbd><textarea></kbd> element creates a multi-line text input, perfect for longer content like messages or descriptions.<br><br>Use <kbd>rows</kbd> and <kbd>cols</kbd> attributes to set default size.",
|
"description": "عنصر <kbd><textarea></kbd> ينشئ حقل إدخال نص متعدد الأسطر، مثالي للمحتوى الطويل مثل الرسائل أو الأوصاف.<br><br>استخدم سمات <kbd>rows</kbd> و <kbd>cols</kbd> لتعيين الحجم الافتراضي.",
|
||||||
"task": "Create a contact form:<br>1. A <kbd><fieldset></kbd> with <kbd><legend></kbd> <code>Contact Us</code><br>2. A labeled <kbd><input></kbd> for email<br>3. A labeled <kbd><textarea></kbd> for the message<br>4. A submit <kbd><button></kbd>",
|
"task": "أنشئ نموذج اتصال:<br>1. عنصر <kbd><fieldset></kbd> مع <kbd><legend></kbd> <code>تواصل معنا</code><br>2. حقل <kbd><input></kbd> مُعنون للبريد الإلكتروني<br>3. حقل <kbd><textarea></kbd> مُعنون للرسالة<br>4. زر <kbd><button></kbd> للإرسال",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } form { max-width: 450px; margin: 0 auto; } fieldset { border: none; border-radius: 15px; padding: 30px; background: white; box-shadow: 0 10px 40px rgba(0,0,0,0.2); } legend { color: #667eea; font-weight: 700; padding: 0; font-size: 1.5rem; margin-bottom: 10px; } label { display: block; margin: 20px 0 8px; color: #333; font-weight: 500; } input, textarea { width: 100%; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: none; border-color: #667eea; } textarea { resize: vertical; min-height: 100px; } button { margin-top: 20px; width: 100%; padding: 14px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; } button:hover { opacity: 0.9; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } form { max-width: 450px; margin: 0 auto; } fieldset { border: none; border-radius: 15px; padding: 30px; background: white; box-shadow: 0 10px 40px rgba(0,0,0,0.2); } legend { color: #667eea; font-weight: 700; padding: 0; font-size: 1.5rem; margin-bottom: 10px; } label { display: block; margin: 20px 0 8px; color: #333; font-weight: 500; } input, textarea { width: 100%; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: none; border-color: #667eea; } textarea { resize: vertical; min-height: 100px; } button { margin-top: 20px; width: 100%; padding: 14px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; } button:hover { opacity: 0.9; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -60,35 +60,35 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "fieldset",
|
"value": "fieldset",
|
||||||
"message": "Add a <kbd><fieldset></kbd> element"
|
"message": "أضف عنصر <kbd><fieldset></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "legend",
|
"value": "legend",
|
||||||
"message": "Add a <kbd><legend></kbd> element"
|
"message": "أضف عنصر <kbd><legend></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "textarea",
|
"value": "textarea",
|
||||||
"message": "Add a <kbd><textarea></kbd> for the message"
|
"message": "أضف <kbd><textarea></kbd> للرسالة"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "button",
|
"value": "button",
|
||||||
"message": "Add a submit button"
|
"message": "أضف زر إرسال"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "input",
|
"value": "input",
|
||||||
"message": "Add an input field for email"
|
"message": "أضف حقل للبريد الإلكتروني"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "fieldset-multiple",
|
"id": "fieldset-multiple",
|
||||||
"title": "Multiple Fieldsets",
|
"title": "مجموعات حقول متعددة",
|
||||||
"description": "Complex forms can use multiple <kbd><fieldset></kbd> elements to organize different sections.<br><br>This improves usability for long forms like registration or checkout pages.",
|
"description": "النماذج المعقدة يمكنها استخدام عناصر <kbd><fieldset></kbd> متعددة لتنظيم أقسام مختلفة.<br><br>هذا يحسن قابلية الاستخدام للنماذج الطويلة مثل التسجيل أو الدفع.",
|
||||||
"task": "Create a registration form with 2 fieldsets:<br>1. <code>Account Info</code> with username and password inputs<br>2. <code>Preferences</code> with a textarea for bio<br>3. A submit button outside the fieldsets",
|
"task": "أنشئ نموذج تسجيل مع 2 مجموعات حقول:<br>1. <code>معلومات الحساب</code> مع حقول اسم المستخدم وكلمة المرور<br>2. <code>التفضيلات</code> مع textarea للسيرة الذاتية<br>3. زر إرسال خارج مجموعات الحقول",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } form { max-width: 500px; } fieldset { border: 1px solid #ddd; border-radius: 10px; padding: 20px; margin-bottom: 20px; background: white; } legend { color: #2c3e50; font-weight: 600; padding: 0 10px; } label { display: block; margin: 15px 0 5px; color: #555; } input, textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: 2px solid #3498db; border-color: transparent; } textarea { resize: vertical; min-height: 80px; } button { width: 100%; padding: 14px; background: #2c3e50; color: white; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; } button:hover { background: #34495e; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } form { max-width: 500px; } fieldset { border: 1px solid #ddd; border-radius: 10px; padding: 20px; margin-bottom: 20px; background: white; } legend { color: #2c3e50; font-weight: 600; padding: 0 10px; } label { display: block; margin: 15px 0 5px; color: #555; } input, textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: 2px solid #3498db; border-color: transparent; } textarea { resize: vertical; min-height: 80px; } button { width: 100%; padding: 14px; background: #2c3e50; color: white; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; } button:hover { background: #34495e; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -99,27 +99,27 @@
|
|||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "fieldset", "min": 2 },
|
"value": { "selector": "fieldset", "min": 2 },
|
||||||
"message": "Create at least 2 fieldsets"
|
"message": "أنشئ على الأقل 2 مجموعات حقول"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "legend", "min": 2 },
|
"value": { "selector": "legend", "min": 2 },
|
||||||
"message": "Add a legend to each fieldset"
|
"message": "أضف legend لكل مجموعة حقول"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "textarea",
|
"value": "textarea",
|
||||||
"message": "Add a textarea for the bio"
|
"message": "أضف textarea للسيرة الذاتية"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "button",
|
"value": "button",
|
||||||
"message": "Add a submit button"
|
"message": "أضف زر إرسال"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "input", "min": 2 },
|
"value": { "selector": "input", "min": 2 },
|
||||||
"message": "Add at least 2 input fields"
|
"message": "أضف على الأقل حقلي إدخال"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,125 +1,42 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-tables",
|
"id": "html-tables",
|
||||||
"title": "HTML Tables",
|
"title": "جداول HTML",
|
||||||
"description": "Create structured data tables with headers and captions",
|
"description": "أنشئ جداول بيانات منظمة بترميز دلالي",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "table-basic",
|
"id": "table-basic",
|
||||||
"title": "Basic Table Structure",
|
"title": "جداول البيانات",
|
||||||
"description": "Tables use <kbd><table></kbd> with <kbd><tr></kbd> for rows. Inside rows, use <kbd><th></kbd> for headers and <kbd><td></kbd> for data cells.<br><br>The <kbd><caption></kbd> element provides an accessible title for the table.",
|
"description": "الجداول تعرض البيانات المنظمة في صفوف وأعمدة. استخدم <kbd><table></kbd> كحاوية، <kbd><tr></kbd> للصفوف، <kbd><th></kbd> لخلايا الرأس و <kbd><td></kbd> لخلايا البيانات.<br><br>أضف <kbd><caption></kbd> لعنوان قابل للوصول يصف محتوى الجدول.",
|
||||||
"task": "Create a simple table with:<br>1. A <kbd><caption></kbd> saying <code>Fruit Prices</code><br>2. A header row with <code>Fruit</code> and <code>Price</code> columns<br>3. At least 2 data rows",
|
"task": "أنشئ جدول أسعار:<br>1. عنصر <kbd><caption></kbd> يقول <code>Pricing</code><br>2. صف رأس مع <code>Plan</code> و <code>Price</code><br>3. صفين بيانات لـ Basic ($9) و Pro ($29)",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px; font-weight: 600; font-size: 1.2rem; color: #333; background: #f8f9fa; } th, td { padding: 12px 20px; text-align: left; border-bottom: 1px solid #eee; } th { background: #3498db; color: white; font-weight: 500; } tr:hover { background: #f8f9fa; } tr:last-child td { border-bottom: none; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; } table { border-collapse: collapse; width: 100%; max-width: 350px; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 16px; font-weight: 600; font-size: 1.1rem; color: #333; background: #f8f9fa; } th, td { padding: 14px 20px; text-align: left; border-bottom: 1px solid #eee; } th { background: steelblue; color: white; font-weight: 500; } tr:last-child td { border-bottom: none; } tr:hover td { background: #f8f9fa; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<table>\n <caption>Fruit Prices</caption>\n <tr>\n <th>Fruit</th>\n <th>Price</th>\n </tr>\n <tr>\n <td>Apple</td>\n <td>$1.50</td>\n </tr>\n <tr>\n <td>Banana</td>\n <td>$0.75</td>\n </tr>\n</table>",
|
"solution": "<table>\n <caption>Pricing</caption>\n <tr>\n <th>Plan</th>\n <th>Price</th>\n </tr>\n <tr>\n <td>Basic</td>\n <td>$9</td>\n </tr>\n <tr>\n <td>Pro</td>\n <td>$29</td>\n </tr>\n</table>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "table",
|
"value": "table",
|
||||||
"message": "Add a <kbd><table></kbd> element"
|
"message": "أضف عنصر <kbd><table></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "caption",
|
"value": "caption",
|
||||||
"message": "Add a <kbd><caption></kbd> for the table title"
|
"message": "أضف <kbd><caption></kbd> لعنوان الجدول"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "th", "min": 2 },
|
"value": { "selector": "th", "min": 2 },
|
||||||
"message": "Add at least 2 header cells (th)"
|
"message": "أضف خلايا رأس (<kbd><th></kbd>) لـ Plan و Price"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "tr", "min": 3 },
|
"value": { "selector": "tr", "min": 3 },
|
||||||
"message": "Add at least 3 rows (1 header + 2 data rows)"
|
"message": "أضف 3 صفوف (1 رأس + 2 صفوف بيانات)"
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "table-thead-tbody",
|
|
||||||
"title": "Table Head & Body",
|
|
||||||
"description": "Use <kbd><thead></kbd> to group header rows and <kbd><tbody></kbd> to group data rows. This helps browsers and assistive technology understand the table structure.<br><br>You can also use <kbd><tfoot></kbd> for footer rows like totals.",
|
|
||||||
"task": "Create a structured table:<br>1. A <kbd><caption></kbd> with <code>Monthly Sales</code><br>2. A <kbd><thead></kbd> with Month and Revenue headers<br>3. A <kbd><tbody></kbd> with at least 2 data rows",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } table { border-collapse: collapse; width: 100%; max-width: 450px; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.2); } caption { padding: 20px; font-weight: 700; font-size: 1.3rem; color: white; background: transparent; text-shadow: 0 2px 4px rgba(0,0,0,0.2); caption-side: top; } thead { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } th { padding: 15px 20px; text-align: left; color: white; font-weight: 500; } tbody tr { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } td { padding: 15px 20px; color: #333; } tbody tr:last-child { border-bottom: none; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "",
|
|
||||||
"solution": "<table>\n <caption>Monthly Sales</caption>\n <thead>\n <tr>\n <th>Month</th>\n <th>Revenue</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>January</td>\n <td>$12,500</td>\n </tr>\n <tr>\n <td>February</td>\n <td>$14,200</td>\n </tr>\n </tbody>\n</table>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "table",
|
|
||||||
"message": "Add a <kbd><table></kbd> element"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "caption",
|
|
||||||
"message": "Add a <kbd><caption></kbd> element"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "thead",
|
|
||||||
"message": "Add a <kbd><thead></kbd> for the header section"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "tbody",
|
|
||||||
"message": "Add a <kbd><tbody></kbd> for the data rows"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_count",
|
|
||||||
"value": { "selector": "tbody tr", "min": 2 },
|
|
||||||
"message": "Add at least 2 data rows in tbody"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "table-complete",
|
|
||||||
"title": "Complete Table with Footer",
|
|
||||||
"description": "Add <kbd><tfoot></kbd> to create a footer section for totals or summary data. The footer stays at the bottom even if tbody has many rows.<br><br>Combine all sections for a fully structured, accessible table.",
|
|
||||||
"task": "Create a complete table:<br>1. A <kbd><caption></kbd> with <code>Order Summary</code><br>2. A <kbd><thead></kbd> with Item and Price headers<br>3. A <kbd><tbody></kbd> with 2 items<br>4. A <kbd><tfoot></kbd> with a Total row",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px 20px; font-weight: 600; font-size: 1.1rem; color: #333; text-align: left; background: #f8f9fa; border-bottom: 2px solid #eee; } th, td { padding: 12px 20px; text-align: left; } thead th { background: #2c3e50; color: white; } tbody td { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } tfoot { background: #ecf0f1; font-weight: 600; } tfoot td { border-top: 2px solid #2c3e50; color: #2c3e50; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "",
|
|
||||||
"solution": "<table>\n <caption>Order Summary</caption>\n <thead>\n <tr>\n <th>Item</th>\n <th>Price</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Widget</td>\n <td>$25.00</td>\n </tr>\n <tr>\n <td>Gadget</td>\n <td>$35.00</td>\n </tr>\n </tbody>\n <tfoot>\n <tr>\n <td>Total</td>\n <td>$60.00</td>\n </tr>\n </tfoot>\n</table>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "table",
|
|
||||||
"message": "Add a <kbd><table></kbd> element"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "caption",
|
|
||||||
"message": "Add a <kbd><caption></kbd> element"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "thead",
|
|
||||||
"message": "Add a <kbd><thead></kbd> section"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "tbody",
|
|
||||||
"message": "Add a <kbd><tbody></kbd> section"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "tfoot",
|
|
||||||
"message": "Add a <kbd><tfoot></kbd> section for the total"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_count",
|
|
||||||
"value": { "selector": "tbody tr", "min": 2 },
|
|
||||||
"message": "Add at least 2 item rows in tbody"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-marquee",
|
"id": "html-marquee",
|
||||||
"title": "HTML Marquee",
|
"title": "HTML Marquee",
|
||||||
"description": "Create scrolling text with the classic (deprecated but fun!) marquee element",
|
"description": "أنشئ نصاً متحركاً باستخدام عنصر marquee الكلاسيكي (قديم لكنه ممتع!)",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "marquee-basic",
|
"id": "marquee-basic",
|
||||||
"title": "Scrolling Text",
|
"title": "النص المتحرك",
|
||||||
"description": "The <kbd><marquee></kbd> element creates scrolling text - a classic from the early web! While deprecated, it still works in most browsers.<br><br>Note: For production, use CSS animations instead. But for learning and fun, marquee is great!",
|
"description": "عنصر <kbd><marquee></kbd> ينشئ نصاً متحركاً - كلاسيكي من الويب القديم! رغم أنه قديم، لا يزال يعمل في معظم المتصفحات.<br><br>ملاحظة: للإنتاج، استخدم حركات CSS بدلاً منه. لكن للتعلم والمرح، marquee رائع!",
|
||||||
"task": "Create a simple marquee:<br>1. Add a <kbd><marquee></kbd> element<br>2. Put some text inside like <code>Welcome to my website!</code>",
|
"task": "أنشئ marquee بسيط:<br>1. أضف عنصر <kbd><marquee></kbd><br>2. ضع نصاً بداخله مثل <code>مرحباً بك في موقعي!</code>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2rem; color: #00ff00; text-shadow: 0 0 10px #00ff00, 0 0 20px #00ff00; font-family: 'Courier New', monospace; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2rem; color: #00ff00; text-shadow: 0 0 10px #00ff00, 0 0 20px #00ff00; font-family: 'Courier New', monospace; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,15 +21,15 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "marquee",
|
"value": "marquee",
|
||||||
"message": "Add a <kbd><marquee></kbd> element"
|
"message": "أضف عنصر <kbd><marquee></kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "marquee-direction",
|
"id": "marquee-direction",
|
||||||
"title": "Direction & Behavior",
|
"title": "الاتجاه والسلوك",
|
||||||
"description": "Control the marquee with attributes:<br>• <kbd>direction</kbd>: left, right, up, down<br>• <kbd>behavior</kbd>: scroll (default), slide (stops at edge), alternate (bounces)<br>• <kbd>scrollamount</kbd>: speed (default is 6)",
|
"description": "تحكم في marquee باستخدام السمات:<br>• <kbd>direction</kbd>: left، right، up، down<br>• <kbd>behavior</kbd>: scroll (افتراضي)، slide (يتوقف عند الحافة)، alternate (يرتد)<br>• <kbd>scrollamount</kbd>: السرعة (الافتراضي 6)",
|
||||||
"task": "Create a bouncing marquee:<br>1. Add a <kbd><marquee></kbd> element<br>2. Set <kbd>behavior=\"alternate\"</kbd> to make it bounce<br>3. Add some fun text",
|
"task": "أنشئ marquee مرتداً:<br>1. أضف عنصر <kbd><marquee></kbd><br>2. اضبط <kbd>behavior=\"alternate\"</kbd> ليرتد<br>3. أضف نصاً ممتعاً",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2.5rem; color: white; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); font-weight: bold; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2.5rem; color: white; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); font-weight: bold; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -40,20 +40,20 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "marquee",
|
"value": "marquee",
|
||||||
"message": "Add a <kbd><marquee></kbd> element"
|
"message": "أضف عنصر <kbd><marquee></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "marquee", "attr": "behavior", "value": "alternate" },
|
"value": { "selector": "marquee", "attr": "behavior", "value": "alternate" },
|
||||||
"message": "Add <kbd>behavior=</kbd>\"alternate\" to make it bounce"
|
"message": "أضف <kbd>behavior=</kbd>\"alternate\" ليرتد"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "marquee-retro",
|
"id": "marquee-retro",
|
||||||
"title": "Retro News Ticker",
|
"title": "شريط أخبار كلاسيكي",
|
||||||
"description": "Combine multiple marquee attributes for a classic news ticker effect. You can even put multiple elements inside!<br><br>Remember: This is deprecated HTML. Modern sites use CSS animations, but marquee is great for understanding web history.",
|
"description": "اجمع عدة سمات marquee لتأثير شريط أخبار كلاسيكي. يمكنك حتى وضع عناصر متعددة بداخله!<br><br>تذكر: هذا HTML قديم. المواقع الحديثة تستخدم حركات CSS، لكن marquee رائع لفهم تاريخ الويب.",
|
||||||
"task": "Create a news ticker:<br>1. A <kbd><marquee></kbd> with <kbd>direction=\"left\"</kbd><br>2. Set <kbd>scrollamount=\"5\"</kbd> for smooth scrolling<br>3. Add a breaking news headline inside",
|
"task": "أنشئ شريط أخبار:<br>1. عنصر <kbd><marquee></kbd> مع <kbd>direction=\"left\"</kbd><br>2. اضبط <kbd>scrollamount=\"5\"</kbd> للتمرير السلس<br>3. أضف عنواناً إخبارياً عاجلاً بداخله",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 0; margin: 0; background: #1a1a2e; } marquee { background: linear-gradient(90deg, #c0392b 0%, #e74c3c 50%, #c0392b 100%); padding: 15px 0; font-size: 1.3rem; color: white; font-weight: 500; text-transform: uppercase; letter-spacing: 1px; border-top: 3px solid #f1c40f; border-bottom: 3px solid #f1c40f; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 0; margin: 0; background: #1a1a2e; } marquee { background: linear-gradient(90deg, #c0392b 0%, #e74c3c 50%, #c0392b 100%); padding: 15px 0; font-size: 1.3rem; color: white; font-weight: 500; text-transform: uppercase; letter-spacing: 1px; border-top: 3px solid #f1c40f; border-bottom: 3px solid #f1c40f; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -64,17 +64,17 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "marquee",
|
"value": "marquee",
|
||||||
"message": "Add a <kbd><marquee></kbd> element"
|
"message": "أضف عنصر <kbd><marquee></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "marquee", "attr": "direction", "value": "left" },
|
"value": { "selector": "marquee", "attr": "direction", "value": "left" },
|
||||||
"message": "Add <kbd>direction=</kbd>\"left\" for horizontal scrolling"
|
"message": "أضف <kbd>direction=</kbd>\"left\" للتمرير الأفقي"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "marquee", "attr": "scrollamount", "value": "5" },
|
"value": { "selector": "marquee", "attr": "scrollamount", "value": "5" },
|
||||||
"message": "Add <kbd>scrollamount=</kbd>\"5\" for smooth speed"
|
"message": "أضف <kbd>scrollamount=</kbd>\"5\" لسرعة سلسة"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,99 +2,169 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-svg",
|
"id": "html-svg",
|
||||||
"title": "HTML SVG",
|
"title": "HTML SVG",
|
||||||
"description": "Draw scalable vector graphics directly in HTML",
|
"description": "ارسم رسومات متجهة قابلة للتحجيم مباشرة في HTML",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "svg-circle",
|
"id": "svg-circle",
|
||||||
"title": "Drawing Circles",
|
"title": "رسم الدوائر",
|
||||||
"description": "SVG (Scalable Vector Graphics) lets you draw shapes directly in HTML. The <kbd><svg></kbd> element is the container, with <kbd>width</kbd> and <kbd>height</kbd> attributes.<br><br>Use <kbd><circle></kbd> with <kbd>cx</kbd>, <kbd>cy</kbd> (center) and <kbd>r</kbd> (radius) to draw circles.",
|
"description": "SVG (Scalable Vector Graphics) يتيح لك رسم الأشكال مباشرة في HTML. عنصر <kbd><svg></kbd> هو الحاوية بسمات <kbd>width</kbd> و <kbd>height</kbd>.<br><br>استخدم <kbd><circle></kbd> مع <kbd>cx</kbd> و <kbd>cy</kbd> (المركز) و <kbd>r</kbd> (نصف القطر) لرسم الدوائر.",
|
||||||
"task": "Create an SVG with a circle:<br>1. An <kbd><svg></kbd> with width=\"200\" and height=\"200\"<br>2. A <kbd><circle></kbd> centered at (100,100) with radius 50<br>3. Add a <kbd>fill</kbd> color",
|
"task": "أنشئ SVG مع دائرة:<br>1. عنصر <kbd><svg></kbd> بـ width=\"200\" و height=\"200\"<br>2. عنصر <kbd><circle></kbd> متمركز عند (100,100) بنصف قطر 50<br>3. أضف لون <kbd>fill</kbd>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; display: flex; justify-content: center; } svg { background: white; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; display: flex; justify-content: center; } svg { background: white; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"50\" fill=\"#3498db\" />\n</svg>",
|
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"50\" fill=\"steelblue\" />\n</svg>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "svg",
|
"value": "svg",
|
||||||
"message": "Add an <kbd><svg></kbd> element"
|
"message": "أضف عنصر <kbd><svg></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "circle",
|
"value": "circle",
|
||||||
"message": "Add a <kbd><circle></kbd> element inside the SVG"
|
"message": "أضف عنصر <kbd><circle></kbd> داخل SVG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "svg", "attr": "width", "value": "200" },
|
||||||
|
"message": "عيّن <kbd>width=</kbd>\"200\" في SVG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "svg", "attr": "height", "value": "200" },
|
||||||
|
"message": "عيّن <kbd>height=</kbd>\"200\" في SVG"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "circle", "attr": "cx", "value": "100" },
|
"value": { "selector": "circle", "attr": "cx", "value": "100" },
|
||||||
"message": "Set <kbd>cx=</kbd>\"100\" for the circle's horizontal center"
|
"message": "عيّن <kbd>cx=</kbd>\"100\" للمركز الأفقي للدائرة"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "circle", "attr": "cy", "value": "100" },
|
"value": { "selector": "circle", "attr": "cy", "value": "100" },
|
||||||
"message": "Set <kbd>cy=</kbd>\"100\" for the circle's vertical center"
|
"message": "عيّن <kbd>cy=</kbd>\"100\" للمركز العمودي للدائرة"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "circle", "attr": "r", "value": "50" },
|
||||||
|
"message": "عيّن <kbd>r=</kbd>\"50\" لنصف قطر الدائرة"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "svg-rect-line",
|
"id": "svg-rect-line",
|
||||||
"title": "Rectangles & Lines",
|
"title": "المستطيلات والخطوط",
|
||||||
"description": "Draw rectangles with <kbd><rect></kbd> using <kbd>x</kbd>, <kbd>y</kbd>, <kbd>width</kbd>, <kbd>height</kbd>.<br><br>Draw lines with <kbd><line></kbd> using <kbd>x1</kbd>, <kbd>y1</kbd> (start) and <kbd>x2</kbd>, <kbd>y2</kbd> (end). Lines need a <kbd>stroke</kbd> color!",
|
"description": "ارسم المستطيلات باستخدام <kbd><rect></kbd> مع <kbd>x</kbd> و <kbd>y</kbd> و <kbd>width</kbd> و <kbd>height</kbd>.<br><br>ارسم الخطوط باستخدام <kbd><line></kbd> مع <kbd>x1</kbd> و <kbd>y1</kbd> (البداية) و <kbd>x2</kbd> و <kbd>y2</kbd> (النهاية). الخطوط تحتاج لون <kbd>stroke</kbd>!",
|
||||||
"task": "Create an SVG with:<br>1. An <kbd><svg></kbd> (200x150)<br>2. A <kbd><rect></kbd> at position (20,20) with size 80x60<br>3. A <kbd><line></kbd> from (120,30) to (180,90) with a stroke color",
|
"task": "أنشئ SVG مع:<br>1. عنصر <kbd><svg></kbd> (200x150)<br>2. عنصر <kbd><rect></kbd> في الموضع (20,20) بحجم 80x60<br>3. عنصر <kbd><line></kbd> من (120,30) إلى (180,90) مع لون stroke",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 200px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 200px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<svg width=\"200\" height=\"150\">\n <rect x=\"20\" y=\"20\" width=\"80\" height=\"60\" fill=\"#e74c3c\" />\n <line x1=\"120\" y1=\"30\" x2=\"180\" y2=\"90\" stroke=\"#2c3e50\" stroke-width=\"3\" />\n</svg>",
|
"solution": "<svg width=\"200\" height=\"150\">\n <rect x=\"20\" y=\"20\" width=\"80\" height=\"60\" fill=\"tomato\" />\n <line x1=\"120\" y1=\"30\" x2=\"180\" y2=\"90\" stroke=\"slategray\" stroke-width=\"3\" />\n</svg>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "svg",
|
"value": "svg",
|
||||||
"message": "Add an <kbd><svg></kbd> element"
|
"message": "أضف عنصر <kbd><svg></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "rect",
|
"value": "rect",
|
||||||
"message": "Add a <kbd><rect></kbd> element"
|
"message": "أضف عنصر <kbd><rect></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "line",
|
"value": "line",
|
||||||
"message": "Add a <kbd><line></kbd> element"
|
"message": "أضف عنصر <kbd><line></kbd>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "svg", "attr": "width", "value": "200" },
|
||||||
|
"message": "عيّن <kbd>width=</kbd>\"200\" في SVG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "svg", "attr": "height", "value": "150" },
|
||||||
|
"message": "عيّن <kbd>height=</kbd>\"150\" في SVG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "rect", "attr": "x", "value": "20" },
|
||||||
|
"message": "عيّن <kbd>x=</kbd>\"20\" في rect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "rect", "attr": "y", "value": "20" },
|
||||||
|
"message": "عيّن <kbd>y=</kbd>\"20\" في rect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "rect", "attr": "width", "value": "80" },
|
||||||
|
"message": "عيّن <kbd>width=</kbd>\"80\" في rect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "rect", "attr": "height", "value": "60" },
|
||||||
|
"message": "عيّن <kbd>height=</kbd>\"60\" في rect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "line", "attr": "x1", "value": "120" },
|
||||||
|
"message": "عيّن <kbd>x1=</kbd>\"120\" في line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "line", "attr": "y1", "value": "30" },
|
||||||
|
"message": "عيّن <kbd>y1=</kbd>\"30\" في line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "line", "attr": "x2", "value": "180" },
|
||||||
|
"message": "عيّن <kbd>x2=</kbd>\"180\" في line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "line", "attr": "y2", "value": "90" },
|
||||||
|
"message": "عيّن <kbd>y2=</kbd>\"90\" في line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "stroke",
|
||||||
|
"message": "أضف لون <kbd>stroke</kbd> إلى line"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "svg-shapes",
|
"id": "svg-shapes",
|
||||||
"title": "Multiple Shapes",
|
"title": "أشكال متعددة",
|
||||||
"description": "Combine shapes to create simple graphics! Add <kbd>stroke</kbd> for outlines and <kbd>stroke-width</kbd> for thickness.<br><br>Use <kbd>fill=\"none\"</kbd> for hollow shapes. Shapes stack in order - later elements appear on top.",
|
"description": "ادمج الأشكال لإنشاء رسومات بسيطة! أضف <kbd>stroke</kbd> للحدود و <kbd>stroke-width</kbd> للسمك.<br><br>استخدم <kbd>fill=\"none\"</kbd> للأشكال المجوفة. الأشكال تتراكم بالترتيب - العناصر اللاحقة تظهر في الأعلى.",
|
||||||
"task": "Create a simple face:<br>1. An <kbd><svg></kbd> (200x200)<br>2. A large <kbd><circle></kbd> for the face<br>3. Two small <kbd><circle></kbd> elements for eyes<br>4. A <kbd><line></kbd> for the smile",
|
"task": "أنشئ وجهاً بسيطاً:<br>1. عنصر <kbd><svg></kbd> (200x200)<br>2. <kbd><circle></kbd> كبير للوجه<br>3. اثنين <kbd><circle></kbd> صغيرين للعيون<br>4. عنصر <kbd><line></kbd> للابتسامة",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); min-height: 250px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); min-height: 250px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"80\" fill=\"#f1c40f\" stroke=\"#e67e22\" stroke-width=\"4\" />\n <circle cx=\"70\" cy=\"80\" r=\"10\" fill=\"#2c3e50\" />\n <circle cx=\"130\" cy=\"80\" r=\"10\" fill=\"#2c3e50\" />\n <line x1=\"60\" y1=\"130\" x2=\"140\" y2=\"130\" stroke=\"#2c3e50\" stroke-width=\"4\" stroke-linecap=\"round\" />\n</svg>",
|
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"80\" fill=\"gold\" stroke=\"orange\" stroke-width=\"4\" />\n <circle cx=\"70\" cy=\"80\" r=\"10\" fill=\"darkslategray\" />\n <circle cx=\"130\" cy=\"80\" r=\"10\" fill=\"darkslategray\" />\n <line x1=\"60\" y1=\"130\" x2=\"140\" y2=\"130\" stroke=\"darkslategray\" stroke-width=\"4\" stroke-linecap=\"round\" />\n</svg>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "svg",
|
"value": "svg",
|
||||||
"message": "Add an <kbd><svg></kbd> element"
|
"message": "أضف عنصر <kbd><svg></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "circle", "min": 3 },
|
"value": { "selector": "circle", "min": 3 },
|
||||||
"message": "Add at least 3 circles (1 face + 2 eyes)"
|
"message": "أضف على الأقل 3 دوائر (1 للوجه + 2 للعيون)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "line",
|
"value": "line",
|
||||||
"message": "Add a <kbd><line></kbd> for the smile"
|
"message": "أضف <kbd><line></kbd> للابتسامة"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,18 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "flexbox",
|
"id": "flexbox",
|
||||||
"title": "CSS Flexbox",
|
"title": "CSS Flexbox",
|
||||||
"description": "Master the flexible box layout model for modern responsive designs",
|
"description": "أتقن نموذج تخطيط الصندوق المرن للتصاميم المتجاوبة الحديثة",
|
||||||
"difficulty": "intermediate",
|
"difficulty": "intermediate",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "flexbox-1",
|
"id": "flexbox-1",
|
||||||
"title": "Container",
|
"title": "Container",
|
||||||
"description": "Learn how to create a flex container and understand the main and cross axes.",
|
"description": "قبل flexbox، كانت حتى التخطيطات البسيطة تتطلب floats أو حيل التموضع أو تخطيطات الجداول. ثورة Flexbox (تخطيط الصندوق المرن) في CSS من خلال توفير نظام تخطيط أحادي البعد مصمم خصيصاً لتوزيع المساحة ومحاذاة المحتوى.<br><br><strong>كيف يعمل:</strong> عندما تضبط <kbd>display: flex</kbd> على عنصر، يصبح <em>حاوية flex</em>. أبناؤه المباشرون يصبحون تلقائياً <em>عناصر flex</em> تتدفق على طول المحور الرئيسي (أفقياً بشكل افتراضي). هذه الخاصية الواحدة تحول العناصر الكتلية المتراصة إلى صف أفقي.<br><br><strong>المحوران:</strong><br>• <em>المحور الرئيسي</em> – الاتجاه الأساسي لتدفق العناصر (row = يسار→يمين)<br>• <em>المحور المتقاطع</em> – عمودي على الرئيسي (row = أعلى→أسفل)<br><br><pre>.nav {\n display: flex;\n}</pre>",
|
||||||
"task": "Add <kbd>display: flex</kbd> to <kbd>.wrap</kbd> to create a flexbox layout.",
|
"task": "قائمة التنقل هذه تتراص عمودياً. أضف <kbd>display: flex</kbd> لترتيب الروابط أفقياً.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box'>2</div><div class='box'>3</div></div>",
|
"previewHTML": "<nav class=\"nav\"><a href=\"#\">Home</a><a href=\"#\">Products</a><a href=\"#\">About</a><a href=\"#\">Contact</a></nav>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .nav { background: #1a1a2e; padding: 1rem; } .nav a { color: white; text-decoration: none; padding: 8px 1rem; border-radius: 4px; } .nav a:hover { background: rgba(255,255,255,0.1); }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".wrap {\n ",
|
"codePrefix": ".nav {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "display: flex;",
|
"solution": "display: flex;",
|
||||||
@@ -21,61 +21,41 @@
|
|||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "display", "expected": "flex" },
|
||||||
"property": "display",
|
"message": "اضبط <kbd>display: flex</kbd>"
|
||||||
"expected": "flex"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>display: flex</kbd>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-2",
|
"id": "flexbox-2",
|
||||||
"title": "Direction & Wrap",
|
"title": "Gap",
|
||||||
"description": "Control the direction and wrapping of flex items within a container.",
|
"description": "خاصية <kbd>gap</kbd> تضيف تباعداً متسقاً بين عناصر flex بدون الحاجة إلى الهوامش. تُنشئ مساحة فقط بين العناصر، وليس على الحواف.",
|
||||||
"task": "Add <kbd>flex-direction: column</kbd> and <kbd>flex-wrap: wrap</kbd> to <kbd>.wrap</kbd>.",
|
"task": "أضف <kbd>gap: 1rem</kbd> لتوزيع روابط التنقل بالتساوي.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box'>2</div><div class='box'>3</div><div class='box'>4</div><div class='box'>5</div></div>",
|
"previewHTML": "<nav class=\"nav\"><a href=\"#\">Home</a><a href=\"#\">Products</a><a href=\"#\">About</a><a href=\"#\">Contact</a></nav>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; width: 3rem; height: 3rem; display: flex; align-items: center; justify-content: center; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .nav { background: #1a1a2e; padding: 1rem; display: flex; } .nav a { color: white; text-decoration: none; padding: 8px 1rem; border-radius: 4px; background: rgba(255,255,255,0.1); }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; height: 16rem; display: flex; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".wrap {\n ",
|
"codePrefix": ".nav {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "flex-direction: column;\n flex-wrap: wrap;",
|
"solution": "gap: 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "gap", "expected": "1rem" },
|
||||||
"property": "flex-direction",
|
"message": "اضبط <kbd>gap: 1rem</kbd>"
|
||||||
"expected": "column"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>flex-direction: column</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "flex-wrap",
|
|
||||||
"expected": "wrap"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>flex-wrap: wrap</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-3",
|
"id": "flexbox-3",
|
||||||
"title": "Justify Content",
|
"title": "Justify Content",
|
||||||
"description": "Learn how to align flex items along the main axis of the flex container.",
|
"description": "<kbd>justify-content</kbd> يوزع العناصر على طول المحور الرئيسي. القيم الشائعة:<br>• <kbd>flex-start</kbd> – تجميع في البداية<br>• <kbd>flex-end</kbd> – تجميع في النهاية<br>• <kbd>center</kbd> – توسيط العناصر<br>• <kbd>space-between</kbd> – مسافة متساوية بين العناصر<br>• <kbd>space-around</kbd> – مسافة متساوية حول العناصر",
|
||||||
"task": "Add <kbd>justify-content: space-between</kbd> to <kbd>.wrap</kbd> to distribute the boxes evenly.",
|
"task": "ادفع زر \"Login\" إلى اليمين بضبط <kbd>justify-content: space-between</kbd> على التنقل.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box'>2</div><div class='box'>3</div></div>",
|
"previewHTML": "<nav class=\"nav\"><div class=\"links\"><a href=\"#\">Home</a><a href=\"#\">Products</a><a href=\"#\">About</a></div><a href=\"#\" class=\"login\">Login</a></nav>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; text-align: center; font-weight: bold; width: 3rem; height: 3rem; display: flex; align-items: center; justify-content: center; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .nav { background: #1a1a2e; padding: 1rem; display: flex; } .links { display: flex; gap: 8px; } .nav a { color: white; text-decoration: none; padding: 8px 1rem; border-radius: 4px; } .nav a:hover { background: rgba(255,255,255,0.1); } .login { background: steelblue; }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".wrap {\n ",
|
"codePrefix": ".nav {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "justify-content: space-between;",
|
"solution": "justify-content: space-between;",
|
||||||
@@ -83,26 +63,20 @@
|
|||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "justify-content", "expected": "space-between" },
|
||||||
"property": "justify-content",
|
"message": "اضبط <kbd>justify-content: space-between</kbd>"
|
||||||
"expected": "space-between"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>justify-content: space-between</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-4",
|
"id": "flexbox-4",
|
||||||
"title": "Align Items",
|
"title": "Align Items",
|
||||||
"description": "Control how flex items are aligned along the cross axis of the flex container.",
|
"description": "<kbd>align-items</kbd> يتحكم في المحاذاة على المحور المتقاطع (عمودياً عندما يكون flex-direction هو row). القيم تشمل:<br>• <kbd>stretch</kbd> – تمديد للملء (افتراضي)<br>• <kbd>flex-start</kbd> – محاذاة للأعلى<br>• <kbd>flex-end</kbd> – محاذاة للأسفل<br>• <kbd>center</kbd> – توسيط عمودي",
|
||||||
"task": "Add <kbd>align-items: center</kbd> to <kbd>.wrap</kbd> to vertically center the boxes.",
|
"task": "الشعار وروابط التنقل لها ارتفاعات مختلفة. وسّطها عمودياً باستخدام <kbd>align-items: center</kbd>.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box tall'>1</div><div class='box'>2</div><div class='box short'>3</div></div>",
|
"previewHTML": "<header class=\"header\"><div class=\"logo\">ACME</div><nav><a href=\"#\">Products</a><a href=\"#\">Pricing</a><a href=\"#\">Docs</a></nav></header>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; width: 3rem; display: flex; justify-content: center; } .tall { height: 6rem; } .short { height: 3rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .header { background: white; padding: 1rem 2rem; display: flex; justify-content: space-between; border-bottom: 1px solid #eee; } .logo { font-size: 1.5rem; font-weight: bold; color: steelblue; } nav { display: flex; gap: 1rem; } nav a { color: #333; text-decoration: none; font-size: 0.9rem; }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; height: 10rem; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".wrap {\n ",
|
"codePrefix": ".header {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "align-items: center;",
|
"solution": "align-items: center;",
|
||||||
@@ -110,62 +84,50 @@
|
|||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "align-items", "expected": "center" },
|
||||||
"property": "align-items",
|
"message": "اضبط <kbd>align-items: center</kbd>"
|
||||||
"expected": "center"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>align-items: center</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-5",
|
"id": "flexbox-5",
|
||||||
"title": "Flex Grow",
|
"title": "Flex Wrap",
|
||||||
"description": "The <kbd>flex</kbd> property controls how much an item grows relative to others.",
|
"description": "بشكل افتراضي، تنضغط عناصر flex في سطر واحد. <kbd>flex-wrap: wrap</kbd> يسمح للعناصر بالتدفق إلى أسطر متعددة عندما تنفد المساحة.",
|
||||||
"task": "Add <kbd>flex: 2</kbd> to <kbd>.box2</kbd> to make it grow twice as wide.",
|
"task": "هذه البطاقات تتجاوز الحاوية. أضف <kbd>flex-wrap: wrap</kbd> للسماح لها بالانتقال إلى صفوف جديدة.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box box1'>1</div><div class='box box2'>2</div><div class='box box3'>3</div></div>",
|
"previewHTML": "<div class=\"cards\"><article class=\"card\">Card 1</article><article class=\"card\">Card 2</article><article class=\"card\">Card 3</article><article class=\"card\">Card 4</article><article class=\"card\">Card 5</article><article class=\"card\">Card 6</article></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; display: flex; align-items: center; justify-content: center; } .box1 { background: coral; flex: 1; } .box2 { background: mediumseagreen; } .box3 { background: gold; flex: 1; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .cards { display: flex; gap: 1rem; } .card { background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); min-width: 120px; text-align: center; }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".box2 {\n ",
|
"codePrefix": ".cards {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "flex: 2;",
|
"solution": "flex-wrap: wrap;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "flex-wrap", "expected": "wrap" },
|
||||||
"property": "flex",
|
"message": "اضبط <kbd>flex-wrap: wrap</kbd>"
|
||||||
"expected": "2"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>flex: 2</kbd>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-6",
|
"id": "flexbox-6",
|
||||||
"title": "Align Self",
|
"title": "Flex Grow",
|
||||||
"description": "Use <kbd>align-self</kbd> to override alignment for a single flex item.",
|
"description": "خاصية <kbd>flex</kbd> على العناصر تتحكم في كيفية نموها وانكماشها. <kbd>flex: 1</kbd> يجعل العنصر ينمو لملء المساحة المتاحة. عناصر متعددة مع <kbd>flex: 1</kbd> تتشارك المساحة بالتساوي.",
|
||||||
"task": "Add <kbd>align-self: flex-start</kbd> to <kbd>.middle</kbd> to move it to the top.",
|
"task": "اجعل حقل البحث يتوسع لملء المساحة المتاحة بضبط <kbd>flex: 1</kbd> على <kbd>.search</kbd>.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box middle'>2</div><div class='box'>3</div></div>",
|
"previewHTML": "<div class=\"toolbar\"><input class=\"search\" type=\"text\" placeholder=\"Search...\"><button class=\"btn\">Search</button><button class=\"btn\">Filters</button></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; width: 3rem; height: 3rem; display: flex; align-items: center; justify-content: center; } .middle { background: mediumseagreen; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .toolbar { display: flex; gap: 8px; padding: 1rem; background: #f5f5f5; border-radius: 8px; } .search { padding: 8px 1rem; border: 1px solid #ddd; border-radius: 4px; font-size: 1rem; } .btn { padding: 8px 1rem; background: steelblue; color: white; border: none; border-radius: 4px; cursor: pointer; }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; height: 12rem; align-items: center; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".middle {\n ",
|
"codePrefix": ".search {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "align-self: flex-start;",
|
"solution": "flex: 1;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "flex", "expected": "1" },
|
||||||
"property": "align-self",
|
"message": "اضبط <kbd>flex: 1</kbd>"
|
||||||
"expected": "flex-start"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>align-self: flex-start</kbd>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,548 +1,257 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "css-basic-selectors",
|
"id": "css-basic-selectors",
|
||||||
"title": "CSS Selektoren",
|
"title": "CSS Grundlagen",
|
||||||
"description": "CSS-Selektoren sind die Grundlage für das Stylen von Webseiten und ermöglichen es dir, bestimmte HTML-Elemente für die Gestaltung auszuwählen. Dieses Modul stellt grundlegende Selektortypen vor, einschließlich Element-Typ-Selektoren, Klassen-Selektoren, ID-Selektoren und des universellen Selektors.",
|
"description": "Lerne die grundlegenden Bausteine von CSS: Eigenschaften, Werte und Selektoren. Dieses Modul vermittelt dir die Syntaxregeln, denen jede CSS-Deklaration folgt.",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "introduction-to-selectors",
|
"id": "css-properties",
|
||||||
"title": "Was ist ein CSS-Selektor?",
|
"title": "CSS-Eigenschaften",
|
||||||
"description": "Ein CSS-Selektor ist der erste Teil einer CSS-Regel, der dem Browser mitteilt, welche HTML-Elemente die im Deklarationsblock definierten Stile erhalten sollen. Selektoren sind im Wesentlichen Muster, die mit Elementen in deinem HTML-Dokument übereinstimmen. Das Verstehen von Selektoren ist grundlegend, da sie bestimmen, welche Elemente von deinen CSS-Regeln betroffen sind. Das Element oder die Elemente, die von einem Selektor anvisiert werden, werden als 'Subjekt des Selektors' bezeichnet. Beim Schreiben einer CSS-Regel gibst du zuerst den Selektor an, gefolgt von geschweiften Klammern, die die Stil-Deklarationen enthalten.<br/>Um beispielsweise die Textfarbe von Elementen zu ändern, kannst du die <kbd>color</kbd>-Eigenschaft in deinem Deklarationsblock verwenden.<br><br><pre>/* Element-Selektor */\np {\n color: orangered;\n /* │ └─── Gibt den Wert des Ausdrucks an\n │ \n └─────────── Gibt die Eigenschaft des Ausdrucks an */\n}</pre>",
|
"description": "CSS gestaltet Elemente mit <strong>Deklarationen</strong> - Paaren aus Eigenschaften und Werten. Jede Deklaration folgt dem gleichen Muster:<br><br><pre>property: value;</pre><br>Die <strong>Eigenschaft</strong> ist das, was du ändern möchtest (wie <kbd>color</kbd> oder <kbd>background</kbd>). Der <strong>Wert</strong> ist das, worauf du es setzt. Ein Doppelpunkt trennt sie, und ein Semikolon beendet die Zeile.<br><br>Werte gibt es in verschiedenen Typen:<br>• <strong>Schlüsselwörter:</strong> <kbd>red</kbd>, <kbd>bold</kbd>, <kbd>center</kbd><br>• <strong>Zahlen mit Einheiten:</strong> <kbd>16px</kbd>, <kbd>2rem</kbd>, <kbd>100%</kbd><br>• <strong>Farben:</strong> <kbd>steelblue</kbd>, <kbd>#ff0000</kbd>",
|
||||||
"task": "Schreibe eine CSS-Regel mit einem Typ-Selektor, die alle Absatz-Elemente <kbd>p</kbd> im Dokument anvisiert. Mache den Text blau, indem du die <kbd>color</kbd>-Eigenschaft auf <kbd>blue</kbd> setzt.",
|
"task": "Vervollständige die Deklaration, indem du <kbd>color: coral;</kbd> hinzufügst, um die Textfarbe zu ändern.",
|
||||||
"previewHTML": "<h1>Einführung in CSS-Selektoren</h1>\n<p>Dieser Absatz sollte blau werden.</p>\n<div>Dieses div-Element sollte unverändert bleiben.</div>\n<p>Dieser zweite Absatz sollte ebenfalls blau werden.</p>",
|
"previewHTML": "<p class=\"text\">This text should turn coral.</p>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } .text { font-size: 1.25rem; }",
|
||||||
"sandboxCSS": "h1, p, div { padding: 8px; margin-bottom: 10px; border: 1px dashed #ccc; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Schreibe einen Typ-Selektor, um alle Absatz-Elemente anzuvisieren */\n",
|
"codePrefix": ".text {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "\n}",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"solution": "p { color: blue }",
|
"solution": "color: coral;",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "property_value",
|
||||||
"value": "^p\\s*{",
|
"value": { "property": "color", "expected": "coral" },
|
||||||
"message": "Beginne deine Regel mit <kbd>p { … }</kbd>, um alle Absatz-Elemente auszuwählen",
|
"message": "Füge <kbd>color: coral;</kbd> hinzu"
|
||||||
"options": {
|
}
|
||||||
"caseSensitive": false
|
]
|
||||||
}
|
},
|
||||||
},
|
{
|
||||||
|
"id": "multiple-properties",
|
||||||
|
"title": "Mehrere Eigenschaften",
|
||||||
|
"description": "Eine Regel kann mehrere Deklarationen enthalten. Jede steht in einer eigenen Zeile und jede benötigt ein Semikolon am Ende:<br><br><pre>.box {<br> background: gold;<br> color: navy;<br> padding: 1rem;<br>}</pre><br>Die Reihenfolge spielt normalerweise keine Rolle - CSS wendet alle an. Bei Konflikten gewinnt die letzte.",
|
||||||
|
"task": "Füge zwei Deklarationen hinzu: <kbd>background: lavender;</kbd> und <kbd>padding: 1rem;</kbd>",
|
||||||
|
"previewHTML": "<div class=\"card\">A styled card with background and padding.</div>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } .card { border-radius: 8px; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".card {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "background: lavender;\n padding: 1rem;",
|
||||||
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "color:",
|
"value": { "property": "background", "expected": "lavender" },
|
||||||
"message": "Füge die <kbd>color:</kbd>-Eigenschaft in deine CSS-Regel ein"
|
"message": "Füge <kbd>background: lavender;</kbd> hinzu"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "blue",
|
|
||||||
"message": "Setze den Farbwert auf <kbd>blue</kbd>"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"property": "color",
|
"message": "Füge <kbd>padding: 1rem;</kbd> hinzu"
|
||||||
"expected": "blue"
|
|
||||||
},
|
|
||||||
"message": "Verwende <kbd>color: blue</kbd>, um die Textfarbe zu setzen"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "p\\s*{[^}]*}",
|
|
||||||
"message": "Vergiss nicht, deine CSS-Regel mit einer schließenden Klammer <kbd>}</kbd> zu beenden",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "type-selectors",
|
"id": "type-selectors",
|
||||||
"title": "Typ-Selektoren: HTML-Elemente anvisieren",
|
"title": "Typ-Selektoren",
|
||||||
"description": "Typ-Selektoren (auch Tag-Name-Selektoren oder Element-Selektoren genannt) visieren HTML-Elemente basierend auf ihrem Tag-Namen an. Zum Beispiel wählt <kbd>p</kbd> alle Absatz-Elemente, <kbd>h1</kbd> alle Überschriften der ersten Ebene und <kbd>div</kbd> alle Division-Elemente aus. Typ-Selektoren sind die grundlegendste Art, Elemente auszuwählen, und wenden Stile einheitlich auf alle Instanzen eines bestimmten HTML-Elements in deinem Dokument an. Du kannst verschiedene CSS-Eigenschaften mit Typ-Selektoren definieren, wie <kbd>color</kbd> für Textfarbe, <kbd>background-color</kbd> für den Hintergrund und <kbd>font-weight</kbd> für Textbetonung. Sie bieten einen breiten Ansatz für das Styling deiner Seite und sind oft der Ausgangspunkt für spezifischeres Styling mit anderen Selektortypen.",
|
"description": "Ein <strong>Selektor</strong> sagt dem Browser, welche Elemente gestylt werden sollen. Der einfachste Selektor ist ein <strong>Typ-Selektor</strong> — einfach der HTML-Tag-Name.<br><br><pre>p {<br> color: steelblue;<br>}</pre><br>Diese Regel zielt auf jedes <kbd><p></kbd>-Element auf der Seite. Typ-Selektoren eignen sich hervorragend für Basis-Styles.",
|
||||||
"task": "Schreibe drei separate CSS-Regeln mit Typ-Selektoren, um bestimmte HTML-Elemente anzuvisieren: mache <kbd>h2</kbd>-Überschriften <kbd>purple</kbd>, gib <kbd>span</kbd>-Elementen einen <kbd>yellow</kbd>-Hintergrund und mache <kbd>strong</kbd>-Elemente <kbd>red</kbd>.",
|
"task": "Style alle Absätze. Schreibe eine Regel mit <kbd>p</kbd> als Selektor und setze <kbd>color: steelblue</kbd>.",
|
||||||
"previewHTML": "<h2>Typ-Selektoren Beispiel</h2>\n<p>Normaler Absatztext <span>mit einem hervorgehobenen Span</span>, der einen gelben Hintergrund haben sollte.</p>\n<p>Ein weiterer Absatz mit <strong>wichtigem, fetten Text</strong>, der rot sein sollte.</p>\n<h2>Eine weitere Überschrift</h2>",
|
"previewHTML": "<article>\n <h2>Fresh Roasted Coffee</h2>\n <p>Our beans are sourced from small farms in Colombia and Ethiopia.</p>\n <p>Each batch is roasted weekly to ensure peak freshness.</p>\n</article>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } h2 { margin: 0 0 1rem; }",
|
||||||
"sandboxCSS": "h2, p, span, strong { padding: 3px; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Schreibe drei separate Typ-Selektoren unten */\n\n",
|
"codePrefix": "",
|
||||||
"initialCode": "/* 1. Mache h2-Überschriften lila */\n\n\n/* 2. Gib span-Elementen einen gelben Hintergrund */\n\n\n/* 3. Mache strong-Elemente rot */\n",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"solution": "/* 1. Mache h2-Überschriften lila */\nh2 {\n color: purple;\n}\n\n/* 2. Gib span-Elementen einen gelben Hintergrund */\nspan {\n background-color: yellow;\n}\n\n/* 3. Mache strong-Elemente rot */\nstrong {\n color: red;\n}",
|
"solution": "p {\n color: steelblue;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^h2\\s*{",
|
"value": "p\\s*\\{",
|
||||||
"message": "Füge einen <kbd>h2 { … }</kbd>-Selektor hinzu"
|
"message": "Beginne mit <kbd>p {</kbd>, um Absätze auszuwählen"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "color", "expected": "steelblue" },
|
||||||
"property": "color",
|
"message": "Setze <kbd>color: steelblue</kbd>"
|
||||||
"expected": "purple"
|
}
|
||||||
},
|
]
|
||||||
"message": "Setze die <kbd>color</kbd>-Eigenschaft auf <kbd>purple</kbd> für h2-Elemente"
|
},
|
||||||
},
|
{
|
||||||
|
"id": "styling-links",
|
||||||
|
"title": "Links stylen",
|
||||||
|
"description": "Typ-Selektoren funktionieren für jedes HTML-Element. Der <kbd>a</kbd>-Selektor zielt auf alle Links einer Seite.<br><br>Links haben standardmäßig eine blaue Farbe und Unterstreichung. Du kannst beides mit CSS ändern — verwende <kbd>color</kbd> für den Text und <kbd>text-decoration: none</kbd>, um die Unterstreichung zu entfernen.",
|
||||||
|
"task": "Style die Navigationslinks. Schreibe eine Regel mit <kbd>a</kbd> als Selektor und setze <kbd>color: coral</kbd>.",
|
||||||
|
"previewHTML": "<nav>\n <a href=\"#\">Home</a>\n <a href=\"#\">Menu</a>\n <a href=\"#\">About</a>\n <a href=\"#\">Contact</a>\n</nav>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } nav { display: flex; gap: 1.5rem; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "a {\n color: coral;\n}",
|
||||||
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "h2\\s*{[^}]*}",
|
"value": "a\\s*\\{",
|
||||||
"message": "Vergiss nicht, deine h2-Regel mit einer schließenden Klammer <kbd>}</kbd> zu beenden"
|
"message": "Beginne mit <kbd>a {</kbd>, um Links auszuwählen"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^span\\s*{",
|
|
||||||
"message": "Füge einen <kbd>span { … }</kbd>-Selektor hinzu"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "color", "expected": "coral" },
|
||||||
"property": "background-color",
|
"message": "Setze <kbd>color: coral</kbd>"
|
||||||
"expected": "yellow"
|
|
||||||
},
|
|
||||||
"message": "Setze <kbd>background-color: yellow</kbd> für span-Elemente"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "span\\s*{[^}]*}",
|
|
||||||
"message": "Vergiss nicht, deine span-Regel mit einer schließenden Klammer <kbd>}</kbd> zu beenden"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^strong\\s*{",
|
|
||||||
"message": "Füge einen <kbd>strong { … }</kbd>-Selektor hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "strong\\s*{\\s*color:\\s*red;[^}]*}",
|
|
||||||
"message": "Setze <kbd>color: red</kbd> für strong-Elemente"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "class-selectors",
|
"id": "class-selectors",
|
||||||
"title": "Klassen-Selektoren: Elementgruppen stylen",
|
"title": "Klassen-Selektoren",
|
||||||
"description": "Klassen-Selektoren visieren Elemente mit einem bestimmten Klassen-Attributwert an. Sie beginnen mit einem Punkt (.) gefolgt vom Klassennamen. Klassen sind mächtig, weil sie es ermöglichen, die gleichen Stile auf mehrere Elemente anzuwenden, unabhängig von ihrem Typ. Ein HTML-Element kann mehrere Klassen haben (durch Leerzeichen im class-Attribut getrennt), und eine Klasse kann auf beliebig viele Elemente angewendet werden. Bei der Verwendung von Klassen-Selektoren kannst du Eigenschaften wie <kbd>background-color</kbd> zum Setzen der Hintergrundfarbe und <kbd>font-weight</kbd> zur Kontrolle der Textdicke anwenden. Diese Flexibilität macht Klassen-Selektoren zu einer der am häufigsten verwendeten Methoden zur Anwendung von Stilen in CSS und ermöglicht modulares und wiederverwendbares Styling auf deiner gesamten Website.",
|
"description": "Typ-Selektoren stylen <em>alle</em> Elemente dieses Typs. Aber was, wenn du nur einige davon stylen möchtest?<br><br><strong>Klassen-Selektoren</strong> zielen auf Elemente mit einem bestimmten <kbd>class</kbd>-Attribut. Sie beginnen mit einem Punkt:<br><br><pre>.badge {<br> background: coral;<br>}</pre><br>Das stylt nur Elemente mit <kbd>class=\"badge\"</kbd>.",
|
||||||
"task": "Erstelle eine CSS-Regel mit einem Klassen-Selektor, die Elemente mit der Klasse <kbd>highlight</kbd> anvisiert. Gib diesen Elementen einen <kbd>yellow</kbd>-Hintergrund und <kbd>bold</kbd>-Text.",
|
"task": "Style das Benachrichtigungs-Badge. Schreibe eine Regel mit <kbd>.badge</kbd> als Selektor und setze <kbd>background: tomato</kbd>.",
|
||||||
"previewHTML": "<h2>Verwendung von Klassen-Selektoren</h2>\n<p>Dies ist ein normaler Absatz, aber <span class=\"highlight\">dieser Span hat die highlight-Klasse</span> angewendet.</p>\n<p class=\"highlight\">Dieser gesamte Absatz hat die highlight-Klasse.</p>\n<ul>\n <li>Normales Listenelement</li>\n <li class=\"highlight\">Dieses Listenelement ist hervorgehoben</li>\n</ul>",
|
"previewHTML": "<header>\n <h1>Dashboard</h1>\n <span class=\"badge\">3</span>\n</header>\n<p>You have new notifications waiting.</p>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } header { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem; } h1 { margin: 0; font-size: 1.5rem; } .badge { color: white; padding: 0.25rem 0.5rem; border-radius: 999px; font-size: 0.875rem; } p { color: #555; margin: 0; }",
|
||||||
"sandboxCSS": "h2, p, li { padding: 5px; margin-bottom: 10px; }",
|
|
||||||
"codePrefix": "/* Erstelle einen Klassen-Selektor für Elemente mit der 'highlight'-Klasse */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^\\.highlight\\s*{",
|
|
||||||
"message": "Beginne deine Regel mit <kbd>.highlight { … }</kbd>, um einen Klassen-Selektor zu erstellen",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Füge die <kbd>background-color:</kbd>-Eigenschaft hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "yellow"
|
|
||||||
},
|
|
||||||
"message": "Setze die Hintergrundfarbe auf <kbd>yellow</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "font-weight:",
|
|
||||||
"message": "Füge die <kbd>font-weight:</kbd>-Eigenschaft hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "font-weight",
|
|
||||||
"expected": "bold"
|
|
||||||
},
|
|
||||||
"message": "Setze font-weight auf <kbd>bold</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "\\.highlight\\s*{[^}]*}",
|
|
||||||
"message": "Vergiss nicht, deine CSS-Regel mit einer schließenden Klammer <kbd>}</kbd> zu beenden",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "multiple-classes",
|
|
||||||
"title": "Mit mehreren Klassen arbeiten",
|
|
||||||
"description": "HTML-Elemente können mehrere Klassen gleichzeitig haben, was komponierbare und modulare CSS-Designs ermöglicht. Wenn ein Element mehrere Klassen hat, erhält es Stile von allen passenden Klassen-Selektoren. Dieser Ansatz ermöglicht es dir, eine Bibliothek wiederverwendbarer CSS-Klassen aufzubauen, die auf verschiedene Arten kombiniert werden können. Du kannst auch Elemente anvisieren, die eine bestimmte Kombination von Klassen haben, indem du Klassen-Selektoren ohne Leerzeichen verkettst (z.B. <kbd>.class1.class2</kbd>). Beim Stylen dieser Elemente könntest du Eigenschaften wie <kbd>border-color</kbd> verwenden, um die Farbe von Element-Rahmen zu ändern, und <kbd>background-color</kbd>, um die Hintergrundfarbe von Elementen zu setzen. Diese Technik ermöglicht bedingte Stile, die nur gelten, wenn bestimmte Klassen zusammen erscheinen.",
|
|
||||||
"task": "Vervollständige die CSS-Regel, die Elemente mit sowohl <kbd>card</kbd> als auch <kbd>featured</kbd>-Klassen anvisiert, indem du die Selektoren verkettest. Setze border-color auf gold und background-color auf lemonchiffon, um hervorgehobene Karten hervorzuheben.",
|
|
||||||
"previewHTML": "<h2>Mehrfach-Klassen-Kombinationen</h2>\n<div class=\"card\">Normale Karte</div>\n<div class=\"card featured\">Hervorgehobene Karte</div>\n<div class=\"featured\">Nur hervorgehoben (keine Karte)</div>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } .card { border: 2px solid gray; padding: 15px; margin-bottom: 10px; border-radius: 5px; }",
|
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Die .card-Klasse hat bereits grundlegendes Styling */\n/* Visiere jetzt Elemente mit BEIDEN Klassen an: 'card' UND 'featured' */\n",
|
"codePrefix": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"solution": ".card.featured { border-color: gold; background-color: lemonchiffon }",
|
"solution": ".badge {\n background: tomato;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^\\.card\\.featured\\s*{",
|
"value": "\\.badge\\s*\\{",
|
||||||
"message": "Verkette die Selektoren als <kbd>.card.featured</kbd> (kein Leerzeichen dazwischen)",
|
"message": "Beginne mit <kbd>.badge {</kbd> (vergiss den Punkt nicht!)"
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "border-color:",
|
|
||||||
"message": "Füge die <kbd>border-color</kbd>-Eigenschaft hinzu"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "background", "expected": "tomato" },
|
||||||
"property": "border-color",
|
"message": "Setze <kbd>background: tomato</kbd>"
|
||||||
"expected": "gold"
|
|
||||||
},
|
|
||||||
"message": "Setze die Rahmenfarbe auf <kbd>gold</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "\\.card\\.featured\\s*{[^}]*;",
|
|
||||||
"message": "Vergiss nicht, deine CSS-Regel mit einem Semikolon <kbd>;</kbd> zu beenden"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Füge die <kbd>background-color</kbd>-Eigenschaft hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "lemonchiffon"
|
|
||||||
},
|
|
||||||
"message": "Setze die Hintergrundfarbe auf <kbd>lemonchiffon</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "\\.card\\.featured\\s*{[^}]*}",
|
|
||||||
"message": "Vergiss nicht, deine CSS-Regel mit einer schließenden Klammer <kbd>}</kbd> zu beenden",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "class-with-type",
|
"id": "button-variants",
|
||||||
"title": "Typ- und Klassen-Selektoren kombinieren",
|
"title": "Button-Varianten",
|
||||||
"description": "Du kannst Typ-Selektoren mit Klassen-Selektoren kombinieren, um bestimmte HTML-Elemente anzuvisieren, die eine bestimmte Klasse haben. Dies erstellt einen spezifischeren Selektor, der nur passt, wenn beide Bedingungen wahr sind: das Element ist vom angegebenen Typ UND es hat die angegebene Klasse. Zum Beispiel würde <kbd>p.note</kbd> Absatz-Elemente mit der Klasse <kbd>note</kbd> auswählen, aber keine divs oder spans mit derselben Klasse. Du kannst diese kombinierten Selektionen mit Eigenschaften wie <kbd>background-color</kbd> stylen, um einen farbigen Hintergrund für deine Elemente zu setzen. Dieser Ansatz ermöglicht es dir, verschiedene Stile auf dieselbe Klasse anzuwenden, wenn sie auf verschiedenen Elementtypen erscheint.",
|
"description": "Elemente können mehrere Klassen haben. Wenn du Klassen-Selektoren ohne Leerzeichen verkettst, zielst du auf Elemente, die <em>alle</em> diese Klassen haben:<br><br><pre>.btn.primary {<br> background: steelblue;<br>}</pre><br>Das zielt auf Elemente mit <kbd>class=\"btn primary\"</kbd>, nicht nur auf <kbd>.btn</kbd> oder nur <kbd>.primary</kbd>.",
|
||||||
"task": "Erstelle eine CSS-Regel, die speziell <kbd><span></kbd>-Elemente mit der Klasse <kbd>highlight</kbd> anvisiert. Gib diesen Elementen einen orangen Hintergrund, während andere Elemente mit der highlight-Klasse unverändert bleiben.",
|
"task": "Style den primären Button. Schreibe eine Regel mit <kbd>.btn.primary</kbd> als Selektor und setze <kbd>background: steelblue</kbd>.",
|
||||||
"previewHTML": "<h2>Typ- und Klassen-Kombinationen</h2>\n<p>Dieser Absatz hat einen <span class=\"highlight\">hervorgehobenen Span</span>, der einen orangen Hintergrund haben sollte.</p>\n<p class=\"highlight\">Dieser Absatz hat die highlight-Klasse, sollte aber KEINEN orangen Hintergrund haben.</p>",
|
"previewHTML": "<div class=\"actions\">\n <button class=\"btn\">Cancel</button>\n <button class=\"btn primary\">Save Changes</button>\n</div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } .highlight { font-weight: bold; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } .actions { display: flex; gap: 0.75rem; } .btn { padding: 0.5rem 1rem; border: none; border-radius: 6px; font-size: 1rem; cursor: pointer; background: #e0e0e0; color: #333; }",
|
||||||
"sandboxCSS": "h2, p, span { padding: 5px; }",
|
|
||||||
"codePrefix": "/* Die .highlight-Klasse setzt bereits font-weight auf bold */\n/* Visiere jetzt NUR span-Elemente mit der highlight-Klasse an */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^span\\.highlight\\s*{",
|
|
||||||
"message": "Verwende den <kbd>span.highlight</kbd>-Selektor (kein Leerzeichen zwischen Element und Klasse)",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Füge die <kbd>background-color</kbd>-Eigenschaft hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "orange"
|
|
||||||
},
|
|
||||||
"message": "Setze die Hintergrundfarbe auf <kbd>orange</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "span\\.highlight\\s*{[^}]*}",
|
|
||||||
"message": "Vergiss nicht, deine CSS-Regel mit einer schließenden Klammer <kbd>}</kbd> zu beenden",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "id-selectors",
|
|
||||||
"title": "ID-Selektoren: Einzigartige Elemente anvisieren",
|
|
||||||
"description": "ID-Selektoren visieren Elemente mit einem bestimmten id-Attribut an. Sie beginnen mit einem Raute-/Hash-Zeichen (#) gefolgt vom ID-Namen. Im Gegensatz zu Klassen müssen IDs innerhalb eines Dokuments einzigartig sein – jeder ID-Wert sollte nur einmal pro Seite verwendet werden. ID-Selektoren haben eine höhere Spezifität als Klassen- oder Element-Selektoren, was bedeutet, dass sie diese Selektoren bei Konflikten überschreiben. Beim Stylen mit ID-Selektoren kannst du Eigenschaften wie <kbd>color</kbd> verwenden, um die Textfarbe zu definieren, und <kbd>text-decoration</kbd>, um das Erscheinungsbild von Text zu kontrollieren, wie das Hinzufügen von Unterstreichungen zu Elementen. Wegen ihrer Einzigartigkeitsanforderung werden IDs am besten für einmalige Elemente wie Seitenköpfe, Hauptnavigation oder spezifische einzigartige Komponenten verwendet, die nur einmal auf einer Seite erscheinen.",
|
|
||||||
"task": "Erstelle eine CSS-Regel mit einem ID-Selektor, die das Element mit der ID <kbd>main-title</kbd> anvisiert. Setze seine Farbe auf purple und füge eine Unterstreichung mit <kbd>text-decoration: underline</kbd> hinzu.",
|
|
||||||
"previewHTML": "<h1 id=\"main-title\">Haupt-Seitentitel</h1>\n<p>Normaler Absatzinhalt.</p>\n<h2>Sekundäre Überschrift</h2>\n<p id=\"intro\">Einführungsabsatz (andere ID).</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
|
||||||
"sandboxCSS": "h1, h2, p { padding: 8px; margin-bottom: 10px; border: 1px dashed #ccc; }",
|
|
||||||
"codePrefix": "/* Erstelle einen ID-Selektor für das Element mit id=\"main-title\" */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^#main-title\\s*{",
|
|
||||||
"message": "Beginne deine Regel mit <kbd>#main-title</kbd>, um einen ID-Selektor zu erstellen",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "color:",
|
|
||||||
"message": "Füge die <kbd>color</kbd>-Eigenschaft hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "color",
|
|
||||||
"expected": "purple"
|
|
||||||
},
|
|
||||||
"message": "Setze die Farbe auf <kbd>purple</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "text-decoration:",
|
|
||||||
"message": "Füge die <kbd>text-decoration</kbd>-Eigenschaft hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "text-decoration",
|
|
||||||
"expected": "underline"
|
|
||||||
},
|
|
||||||
"message": "Setze text-decoration auf <kbd>underline</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "#main-title\\s*{[^}]*}",
|
|
||||||
"message": "Vergiss nicht, deine CSS-Regel mit einer schließenden Klammer <kbd>}</kbd> zu beenden",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "id-with-type",
|
|
||||||
"title": "Typ- und ID-Selektoren kombinieren",
|
|
||||||
"description": "Ähnlich wie du Typ- und Klassen-Selektoren kombinieren kannst, kannst du auch Typ-Selektoren mit ID-Selektoren kombinieren. Zum Beispiel visiert <kbd>h1#title</kbd> ein h1-Element mit der ID 'title' an. Bei diesem kombinierten Ansatz kannst du CSS-Eigenschaften wie <kbd>font-style</kbd> anwenden, um die Neigung des Textes zu kontrollieren und ihn kursiv oder normal zu machen. Obwohl diese Selektor-Kombination spezifischer ist als die Verwendung nur des ID-Selektors, ist sie oft unnötig, da IDs bereits einzigartig im Dokument sein sollten. Diese Technik kann jedoch nützlich sein, um die Lesbarkeit des Codes zu verbessern oder wenn du betonen möchtest, dass eine bestimmte ID nur auf einem bestimmten Elementtyp erscheinen sollte.",
|
|
||||||
"task": "Erstelle eine CSS-Regel, die einen Typ-Selektor mit einem ID-Selektor kombiniert, um speziell ein Absatz-Element mit der ID <kbd>special</kbd> anzuvisieren. Setze seinen Schriftstil auf kursiv.",
|
|
||||||
"previewHTML": "<h2 id=\"special\">Überschrift mit ID \"special\" (sollte NICHT betroffen sein)</h2>\n<p id=\"special\">Absatz mit ID \"special\" (sollte kursiv werden)</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
|
||||||
"sandboxCSS": "h2, p { padding: 8px; margin-bottom: 10px; border: 1px dashed #ccc; }",
|
|
||||||
"codePrefix": "/* Erstelle einen kombinierten Typ+ID-Selektor für einen Absatz mit id=\"special\" */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^p#special\\s*{",
|
|
||||||
"message": "Verwende <kbd>p#special</kbd>, um Absätze mit ID <kbd>special</kbd> anzuvisieren",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "font-style:",
|
|
||||||
"message": "Füge die <kbd>font-style</kbd>-Eigenschaft hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "font-style",
|
|
||||||
"expected": "italic"
|
|
||||||
},
|
|
||||||
"message": "Setze font-style auf <kbd>italic</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "p#special\\s*{[^}]*}",
|
|
||||||
"message": "Vergiss nicht, deine CSS-Regel mit einer schließenden Klammer <kbd>}</kbd> zu beenden",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "selector-lists",
|
|
||||||
"title": "Selektor-Listen: Gleiche Regeln auf mehrere Selektoren anwenden",
|
|
||||||
"description": "Wenn mehrere Elemente das gleiche Styling benötigen, kannst du sie mit einer Selektor-Liste (auch Gruppierungs-Selektoren genannt) zusammenfassen. Selektor-Listen werden erstellt, indem einzelne Selektoren durch Kommas getrennt werden. Dieser Ansatz reduziert Wiederholungen in deinem CSS und macht es wartbarer und effizienter. Zum Beispiel wendet <kbd>h1, h2, h3 { color: blue; }</kbd> die gleiche blaue Farbe auf alle drei Überschriftenebenen an. Beim Stylen mehrerer Selektoren gleichzeitig kannst du Eigenschaften wie <kbd>background-color</kbd> für den Hintergrund, <kbd>border-left</kbd> für einen linken Rahmen mit bestimmter Dicke, Stil und Farbe, und <kbd>padding-left</kbd> anwenden, um Abstand zwischen dem Inhalt und dem linken Rahmen zu schaffen. Leerzeichen um Kommas sind optional, und jeder Selektor in der Liste kann ein beliebiger gültiger Selektortyp sein – Elemente, Klassen, IDs oder sogar komplexere Selektoren.",
|
|
||||||
"task": "Erstelle eine Selektor-Liste, die die gleichen Stile auf drei verschiedene Elemente anwendet: Absätze mit der Klasse <kbd>note</kbd>, Listenelemente mit der Klasse <kbd>important</kbd> und das Element mit der ID <kbd>summary</kbd>. Gib ihnen einen <kbd>lightyellow</kbd>-Hintergrund, einen <kbd>gold</kbd>-linken Rahmen und etwas linkes <kbd>padding</kbd>.",
|
|
||||||
"previewHTML": "<p class=\"note\">Dies ist ein Notiz-Absatz.</p>\n<ul>\n <li>Normales Listenelement</li>\n <li class=\"important\">Wichtiges Listenelement</li>\n</ul>\n<div id=\"summary\">Zusammenfassungs-Abschnitt</div>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
|
||||||
"sandboxCSS": "p, li, div { padding: 8px; margin-bottom: 8px; border: 1px dashed gray; }",
|
|
||||||
"codePrefix": "/* Erstelle eine Selektor-Liste, um die gleichen Stile auf mehrere verschiedene Elemente anzuwenden */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"solution": "p.note,\nli.important,\n#summary {\n background-color: lightyellow;\n border-left: 3px solid gold;\n padding-left: 10px\n}",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "p.note",
|
|
||||||
"message": "Füge <kbd>p.note</kbd> in deine Selektor-Liste ein",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "li.important",
|
|
||||||
"message": "Füge <kbd>li.important</kbd> in deine Selektor-Liste ein",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "#summary",
|
|
||||||
"message": "Füge <kbd>#summary</kbd> in deine Selektor-Liste ein",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "(p\\.note|li\\.important|#summary)\\s*,\\s*(p\\.note|li\\.important|#summary)\\s*,\\s*(p\\.note|li\\.important|#summary)",
|
|
||||||
"message": "Erstelle eine kommagetrennte Liste mit allen drei Selektoren",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Füge die <kbd>background-color</kbd>-Eigenschaft hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "lightyellow"
|
|
||||||
},
|
|
||||||
"message": "Setze die Hintergrundfarbe auf <kbd>lightyellow</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "border-left:",
|
|
||||||
"message": "Füge die <kbd>border-left</kbd>-Eigenschaft hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "border-left",
|
|
||||||
"expected": "3px solid gold"
|
|
||||||
},
|
|
||||||
"message": "Verwende <kbd>border-left: 3px solid gold</kbd>, um einen linken Rahmen zu erstellen"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "padding-left:",
|
|
||||||
"message": "Füge die <kbd>padding-left</kbd>-Eigenschaft hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "padding-left",
|
|
||||||
"expected": "10px"
|
|
||||||
},
|
|
||||||
"message": "Verwende <kbd>padding-left: 10px</kbd>, um linkes Padding hinzuzufügen"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "universal-selector",
|
|
||||||
"title": "Der universelle Selektor: Alles anvisieren",
|
|
||||||
"description": "Der universelle Selektor wird durch ein Sternchen (*) gekennzeichnet und passt auf jedes Element jedes Typs. Er wählt alles im Dokument aus oder, wenn er mit anderen Selektoren kombiniert wird, alles innerhalb eines bestimmten Kontexts. Zum Beispiel entfernt <kbd>* { margin: 0; }</kbd> Ränder von allen Elementen, während <kbd>article *</kbd> alle Elemente innerhalb von article-Elementen auswählt. Bei der Verwendung des universellen Selektors in Kombination mit anderen Selektoren kannst du Eigenschaften wie <kbd>margin</kbd> anwenden, um die Abstände um Elemente zu kontrollieren. Der universelle Selektor ist mächtig, sollte aber wegen seiner breiten Auswirkung vorsichtig verwendet werden. Er wird häufig in CSS-Resets verwendet, um Standard-Browser-Styling zu überschreiben, oder um alle Kinder eines bestimmten Elements anzuvisieren.",
|
|
||||||
"task": "Verwende den universellen Selektor, um Ränder von allen Elementen innerhalb des Container-divs zu entfernen. Erstelle eine Regel mit <kbd>div.container *</kbd> als Selektor und setze <kbd>margin: 0</kbd>.",
|
|
||||||
"previewHTML": "<div class=\"container\">\n <h2>Innerhalb des Containers</h2>\n <p>Dieser Absatz ist innerhalb des Containers.</p>\n <ul>\n <li>Listenelement innerhalb des Containers</li>\n </ul>\n</div>\n<p>Dieser Absatz ist außerhalb des Containers und sollte nicht betroffen sein.</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } div.container { border: 2px solid navy; padding: 15px; background-color: lavender; } h2, p, ul, li { margin: 15px 0; }",
|
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Verwende den universellen Selektor, um alle Elemente innerhalb des Containers anzuvisieren */\n",
|
"codePrefix": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
|
"solution": ".btn.primary {\n background: steelblue;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^div\\.container\\s+\\*\\s*{",
|
"value": "\\.btn\\.primary\\s*\\{",
|
||||||
"message": "Verwende <kbd>div.container *</kbd>-Selektor (mit einem Leerzeichen zwischen container und *)",
|
"message": "Verwende <kbd>.btn.primary {</kbd> (kein Leerzeichen zwischen den Klassen)"
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "margin:",
|
|
||||||
"message": "Füge die <kbd>margin</kbd>-Eigenschaft hinzu"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "background", "expected": "steelblue" },
|
||||||
"property": "margin",
|
"message": "Setze <kbd>background: steelblue</kbd>"
|
||||||
"expected": "0"
|
|
||||||
},
|
|
||||||
"message": "Setze margin auf <kbd>0</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "div\\.container\\s+\\*\\s*{[^}]*}",
|
|
||||||
"message": "Vergiss nicht, deine CSS-Regel mit einer schließenden Klammer <kbd>}</kbd> zu beenden",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "specificity-basics",
|
"id": "specific-elements",
|
||||||
"title": "Selektor-Spezifität verstehen",
|
"title": "Spezifische Elemente ansprechen",
|
||||||
"description": "CSS-Spezifität bestimmt, welche Stile Vorrang haben, wenn mehrere widersprüchliche Regeln auf dasselbe Element abzielen. Spezifität folgt einem hierarchischen System: Inline-Stile haben die höchste Spezifität, gefolgt von ID-Selektoren, dann Klassen-/Attribut-/Pseudo-Klassen-Selektoren und schließlich Element-/Pseudo-Element-Selektoren. Dies kann als vierteilige Punktzahl (inline, ID, Klasse, Element) konzeptualisiert werden. Beim Erstellen mehrerer Regeln, die auf dieselben Elemente abzielen könnten, kannst du die <kbd>color</kbd>-Eigenschaft verwenden, um Textfarben zu setzen, und die Spezifität bestimmt, welche Farbe tatsächlich angewendet wird. Das Verstehen von Spezifität ist entscheidend für vorhersagbares Styling und das Debuggen von CSS-Konflikten. Wenn zwei Selektoren gleiche Spezifität haben, gewinnt derjenige, der zuletzt im Stylesheet kommt.",
|
"description": "Manchmal soll eine Klasse auf verschiedenen Elementen unterschiedlich aussehen. Kombiniere einen Typ-Selektor mit einem Klassen-Selektor (ohne Leerzeichen), um spezifischer zu sein:<br><br><pre>a.btn {<br> text-decoration: none;<br>}</pre><br>Das stylt nur <kbd><a></kbd>-Elemente mit der <kbd>btn</kbd>-Klasse, nicht <kbd><button></kbd>-Elemente mit dieser Klasse.",
|
||||||
"task": "Untersuche die vorhandenen CSS-Regeln und füge eine neue Regel mit höherer Spezifität hinzu, um die Textfarbe des Absatzes zu überschreiben. Erstelle eine Regel mit '.content p' als Selektor und setze color: green.",
|
"task": "Entferne die Unterstreichung von Link-Buttons. Schreibe eine Regel mit <kbd>a.btn</kbd> als Selektor und setze <kbd>text-decoration: none</kbd>.",
|
||||||
"previewHTML": "<div class=\"content\">\n <p>Welche Farbe wird dieser Absatz haben? Schau dir die CSS-Regeln und ihre Spezifität an.</p>\n</div>",
|
"previewHTML": "<div class=\"actions\">\n <button class=\"btn\">Regular Button</button>\n <a href=\"#\" class=\"btn\">Link Button</a>\n</div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } .actions { display: flex; gap: 0.75rem; align-items: center; } .btn { padding: 0.5rem 1rem; border: none; border-radius: 6px; font-size: 1rem; cursor: pointer; background: steelblue; color: white; }",
|
||||||
"sandboxCSS": "p { border: 1px dashed gray; padding: 10px; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Diese CSS-Regeln visieren denselben Absatz an, haben aber unterschiedliche Spezifität */\n\n/* Regel 1: Element-Selektor (niedrigste Spezifität) */\np {\n color: red;\n}\n\n/* Regel 2: Nachfahren-Selektor (höhere Spezifität als nur 'p') */\n",
|
"codePrefix": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "a.btn {\n text-decoration: none;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^\\.content\\s+p\\s*{",
|
"value": "a\\.btn\\s*\\{",
|
||||||
"message": "Verwende <kbd>.content p</kbd> als deinen Selektor (beachte das Leerzeichen dazwischen)",
|
"message": "Verwende <kbd>a.btn {</kbd> (Typ + Klasse, kein Leerzeichen)"
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "color:",
|
"value": { "property": "text-decoration", "expected": "none" },
|
||||||
"message": "Füge die <kbd>color</kbd>-Eigenschaft hinzu"
|
"message": "Setze <kbd>text-decoration: none</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "grouping-selectors",
|
||||||
|
"title": "Selektoren gruppieren",
|
||||||
|
"description": "Wenn mehrere Elemente die gleichen Styles brauchen, liste sie durch Kommas getrennt auf. Das hält dein CSS sauber und wartbar.<br><br><pre>h1, h2, h3 {<br> color: steelblue;<br>}</pre><br>Das wendet die gleiche Farbe auf alle drei Überschriftenebenen in einer Regel an.",
|
||||||
|
"task": "Style alle Überschriften einheitlich. Füge <kbd>color: steelblue</kbd> zum gruppierten <kbd>h1, h2, h3</kbd>-Selektor hinzu.",
|
||||||
|
"previewHTML": "<article><h1>Main Title</h1><p>Introduction paragraph with some text.</p><h2>Section Heading</h2><p>More content here.</p><h3>Subsection</h3><p>Final paragraph.</p></article>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } article { max-width: 500px; } p { color: #555; line-height: 1.6; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "h1, h2, h3 {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "color: steelblue;",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "color", "expected": "steelblue" },
|
||||||
|
"message": "Setze <kbd>color: steelblue</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "descendant-selectors",
|
||||||
|
"title": "Nachfahren-Selektoren",
|
||||||
|
"description": "Ziele auf Elemente innerhalb anderer Elemente mit einem Leerzeichen zwischen den Selektoren. Das ist eines der nützlichsten Muster in CSS.<br><br><pre>.nav a {<br> color: white;<br>}</pre><br>Das stylt nur Links innerhalb von <kbd>.nav</kbd> und lässt andere Links unverändert.",
|
||||||
|
"task": "Style Navigationslinks anders. Schreibe eine Regel mit <kbd>.nav a</kbd> als Selektor und setze <kbd>color: white</kbd>.",
|
||||||
|
"previewHTML": "<nav class=\"nav\"><a href=\"#\">Home</a><a href=\"#\">About</a><a href=\"#\">Contact</a></nav><p>Read more in our <a href=\"#\">documentation</a>.</p>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; margin: 0; } .nav { background: steelblue; padding: 1rem; display: flex; gap: 1rem; border-radius: 8px; margin-bottom: 1rem; } .nav a { text-decoration: none; } p a { color: steelblue; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": ".nav a {\n color: white;\n}",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "\\.nav\\s+a\\s*\\{",
|
||||||
|
"message": "Verwende <kbd>.nav a {</kbd> (Leerzeichen zwischen .nav und a)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "green",
|
"value": { "property": "color", "expected": "white" },
|
||||||
"message": ""
|
"message": "Setze <kbd>color: white</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nested-styling",
|
||||||
|
"title": "Verschachtelte Styles",
|
||||||
|
"description": "Nachfahren-Selektoren ermöglichen kontextabhängige Styles. Das gleiche Element kann je nach Position unterschiedlich aussehen.<br><br>Zum Beispiel könnten Absätze in einer <kbd>.card</kbd> kleiner sein als Absätze in einem <kbd>article</kbd>.",
|
||||||
|
"task": "Mache Absätze innerhalb der Karte kleiner. Schreibe eine Regel mit <kbd>.card p</kbd> als Selektor und setze <kbd>font-size: 0.9rem</kbd>.",
|
||||||
|
"previewHTML": "<article><h2>Article Title</h2><p>This is a regular article paragraph with normal-sized text for comfortable reading.</p><div class=\"card\"><strong>Quick Tip</strong><p>Card paragraphs should be slightly smaller to fit the compact design.</p></div><p>Back to regular article text here.</p></article>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } article { max-width: 500px; } h2 { color: steelblue; margin-top: 0; } p { line-height: 1.6; color: #444; } .card { background: #f0f4f8; padding: 1rem; border-radius: 8px; border-left: 4px solid steelblue; } .card strong { color: steelblue; display: block; margin-bottom: 0.5rem; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": ".card p {\n font-size: 0.9rem;\n}",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "\\.card\\s+p\\s*\\{",
|
||||||
|
"message": "Verwende <kbd>.card p {</kbd> (Leerzeichen zwischen .card und p)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "font-size", "expected": "0.9rem" },
|
||||||
|
"message": "Setze <kbd>font-size: 0.9rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "welcome",
|
"id": "welcome",
|
||||||
"title": "Code Crispies",
|
"title": "Willkommen",
|
||||||
"description": "Willkommen bei Code Crispies - deine interaktive Lernplattform für Webentwicklung",
|
"description": "Erste Schritte mit Code Crispies",
|
||||||
"mode": "html",
|
"mode": "css",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
|
"excludeFromProgress": true,
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "get-started",
|
"id": "hello",
|
||||||
"title": "Los geht's",
|
"title": "Hallo!",
|
||||||
"description": "<strong>Code Crispies</strong> ist eine kostenlose Open-Source-Plattform zum Erlernen von Webentwicklung durch praktische Übungen. Kein Konto erforderlich!<br><br><strong>Was du lernst:</strong><br>• <strong>HTML</strong> - Semantische Elemente, Formulare, Tabellen, SVG (<em>HTML Block & Inline</em>, <em>HTML Formulare</em>, <em>HTML Tabellen</em>)<br>• <strong>CSS</strong> - Selektoren, Box-Model, Flexbox, Animationen (<em>CSS Selektoren</em>, <em>CSS Box-Model</em>, <em>CSS Flexbox</em>)<br>• <strong>Responsive Design</strong> - Media Queries und Mobile-First Layouts<br><br><strong>So funktioniert's:</strong><br>1. Lies die Aufgabe im linken Bereich<br>2. Schreibe Code im Editor<br>3. Sieh Live-Ergebnisse in der Vorschau<br>4. Bekomme sofortiges Feedback mit Hinweisen<br><br><strong>Tastenkürzel:</strong> <kbd>Strg+Z</kbd> rückgängig, <kbd>Strg+Umschalt+Z</kbd> wiederholen<br><br><strong>Weitere Ressourcen:</strong><br>• <a href=\"https://nextlevelshit.github.io/html-over-js/\" target=\"_blank\">HTML over JS</a> - Native HTML vs JavaScript-Lösungen<br>• <a href=\"https://nextlevelshit.github.io/web-engineering-mandala/\" target=\"_blank\">Web Engineering Mandala</a> - JavaScript Technologie-Roadmap",
|
"description": "<strong>Willkommen bei Code Crispies!</strong> Lerne CSS und HTML durch praktische Übungen.<br><br><strong>So funktioniert's:</strong><br>1. Lies die Aufgabe links<br>2. Schreibe Code im Editor<br>3. Sieh Live-Ergebnisse in der Vorschau<br><br><strong>Tipps:</strong> Nutze <kbd>Strg+Z</kbd> zum Rückgängigmachen. Öffne das Menü (☰) um alle Module zu erkunden.",
|
||||||
"task": "Schreibe <code>Hello World</code>",
|
"task": "Schreibe <code>Hello World</code> zum Starten",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 2rem; color: #6366f1; font-weight: bold; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 2rem; color: #6366f1; font-weight: bold; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -24,39 +25,6 @@
|
|||||||
"message": "Schreibe <code>Hello World</code>"
|
"message": "Schreibe <code>Hello World</code>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "overview",
|
|
||||||
"title": "Übersicht",
|
|
||||||
"description": "<strong>Du bist bereit!</strong> Öffne das Menü (☰) um alle Module zu erkunden.<br><br><strong>Empfohlener Lernpfad:</strong><br>1. <em>HTML Block & Inline</em> - Container vs Inline-Elemente verstehen<br>2. <em>HTML Formulare</em> - Interaktive Formulare mit Validierung erstellen<br>3. <em>CSS Selektoren</em> - Elemente präzise ansprechen<br>4. <em>CSS Box-Model</em> - Padding, Margin, Borders meistern<br>5. <em>CSS Flexbox</em> - Flexible Layouts erstellen<br>6. <em>CSS Animationen</em> - Bewegung und Übergänge hinzufügen<br><br><strong>Tipps:</strong><br>• Nutze <em>Lösung einblenden</em> um das Zielergebnis zu sehen<br>• Dein Fortschritt wird automatisch gespeichert<br>• Probiere Emmet im HTML-Modus: <kbd>ul>li*3</kbd> + Tab<br><br><strong>Open Source:</strong><br>• <a href=\"https://git.librete.ch/libretech/code-crispies\" target=\"_blank\">Gitea (Source)</a> · <a href=\"https://github.com/nextlevelshit/code-crispies\" target=\"_blank\">GitHub (Mirror)</a><br>• Entwickelt von <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a> · <a href=\"https://www.linkedin.com/in/michael-werner-czechowski\" target=\"_blank\">Michael Czechowski</a>",
|
|
||||||
"task": "Klicke auf Weiter",
|
|
||||||
"previewHTML": "<p>Hello World! 🌍</p><p>Hallo Welt!</p><p>Bonjour le monde!</p><p>¡Hola Mundo!</p><p>Ciao Mondo!</p><p>Olá Mundo!</p><p>こんにちは世界!</p><p>你好世界!</p><p>안녕 세상!</p><p>Привет мир!</p><p dir=\"rtl\">שלום עולם!</p><p dir=\"rtl\">مرحبا بالعالم!</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 12px; } p { margin: 6px 0; padding: 6px 12px; border-radius: 6px; font-weight: 600; font-size: 0.95em; } p:nth-child(1) { background: #fef3c7; color: #92400e; font-size: 1.2em; } p:nth-child(2) { background: #dcfce7; color: #166534; } p:nth-child(3) { background: #dbeafe; color: #1e40af; } p:nth-child(4) { background: #fce7f3; color: #9d174d; } p:nth-child(5) { background: #e0e7ff; color: #4338ca; } p:nth-child(6) { background: #fef9c3; color: #854d0e; } p:nth-child(7) { background: #fee2e2; color: #991b1b; } p:nth-child(8) { background: #f3e8ff; color: #7c3aed; } p:nth-child(9) { background: #ccfbf1; color: #0f766e; } p:nth-child(10) { background: #fae8ff; color: #86198f; } p:nth-child(11) { background: #fef3c7; color: #b45309; } p:nth-child(12) { background: #d1fae5; color: #047857; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<p>Hello World! 🌍</p>\n<p>Hallo Welt!</p>\n<p>Bonjour le monde!</p>\n<p>¡Hola Mundo!</p>\n<p>Ciao Mondo!</p>\n<p>Olá Mundo!</p>\n<p>こんにちは世界!</p>\n<p>你好世界!</p>\n<p>안녕 세상!</p>\n<p>Привет мир!</p>\n<p dir=\"rtl\">שלום עולם!</p>\n<p dir=\"rtl\">مرحبا بالعالم!</p>",
|
|
||||||
"solution": "<p>Hello World! 🌍</p>\n<p>Hallo Welt!</p>\n<p>Bonjour le monde!</p>\n<p>¡Hola Mundo!</p>\n<p>Ciao Mondo!</p>\n<p>Olá Mundo!</p>\n<p>こんにちは世界!</p>\n<p>你好世界!</p>\n<p>안녕 세상!</p>\n<p>Привет мир!</p>\n<p dir=\"rtl\">שלום עולם!</p>\n<p dir=\"rtl\">مرحبا بالعالم!</p>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "Hello",
|
|
||||||
"message": "Klicke auf Weiter"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "playground",
|
|
||||||
"title": "Playground",
|
|
||||||
"mode": "playground",
|
|
||||||
"description": "",
|
|
||||||
"task": "",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<style>\n body {\n font-family: system-ui, sans-serif;\n padding: 20px;\n }\n</style>\n\n<h1>Hello World</h1>\n<p>Start coding!</p>",
|
|
||||||
"solution": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,18 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "box-model",
|
"id": "box-model",
|
||||||
"title": "CSS Box Model",
|
"title": "CSS Box Model",
|
||||||
"description": "Beherrsche die Grundprinzipien der Raumverwaltung im Webdesign durch das CSS Box-Modell.",
|
"description": "Beherrsche die Grundprinzipien der Raumverwaltung im Webdesign durch das CSS Box-Modell. Dieses Modul erkundet, wie Inhalt, Padding, Rahmen und Margins zusammenwirken, um Layout-Strukturen zu erstellen.",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "box-model-1",
|
"id": "box-model-1",
|
||||||
"title": "Box-Modell Komponenten",
|
"title": "Padding",
|
||||||
"description": "Das CSS Box-Modell besteht aus vier konzentrischen Schichten: Inhalt (innerste), Padding, Border und Margin (äußerste). Zu verstehen, wie diese Komponenten zusammenwirken, ist essentiell für präzise Layout-Kontrolle.",
|
"description": "Jedes Element in CSS ist eine Box mit vier Schichten: Inhalt, Padding, Rahmen und Margin. <strong>Padding</strong> schafft Freiraum zwischen deinem Inhalt und dem Rand der Box.<br><br>Ohne Padding drückt sich Text unangenehm gegen Rahmen. Padding macht Inhalte lesbar und visuell ausgewogen.<br><br><pre>.card {\n padding: 1rem;\n}</pre>",
|
||||||
"task": "Setze <kbd>padding</kbd> auf <kbd>1rem</kbd>, um Abstand zwischen Inhalt und Rahmen zu schaffen.",
|
"task": "Diese Profilkarte sieht beengt aus. Füge <kbd>padding: 1rem</kbd> hinzu, damit der Text Platz zum Atmen hat.",
|
||||||
"previewHTML": "<div class=\"box\">Box-Modell Komponenten</div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: lavender; border: 2px dashed slategray; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".box {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "padding: 1rem;",
|
"solution": "padding: 1rem;",
|
||||||
@@ -28,56 +28,56 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-2",
|
"id": "box-model-2",
|
||||||
"title": "Rahmen hinzufügen",
|
"title": "Borders",
|
||||||
"description": "Rahmen umranden ein Element und schaffen visuelle Trennung. Die border-Kurzschreibweise akzeptiert drei Werte: Breite, Stil und Farbe.",
|
"description": "Rahmen erstellen visuelle Grenzen um Elemente. Die <kbd>border</kbd>-Kurzschreibweise akzeptiert drei Werte: Breite, Stil und Farbe.<br><br>Häufige Stile: <kbd>solid</kbd>, <kbd>dashed</kbd>, <kbd>dotted</kbd>, <kbd>none</kbd>",
|
||||||
"task": "Setze <kbd>border</kbd> auf <kbd>2px solid darkslategray</kbd>.",
|
"task": "Füge der Karte einen dezenten linken Akzent hinzu mit <kbd>border-left: 4px solid steelblue</kbd>.",
|
||||||
"previewHTML": "<div class=\"box\">Diese Box braucht einen Rahmen</div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: mintcream; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".box {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "border: 2px solid darkslategray;",
|
"solution": "border-left: 4px solid steelblue;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border:\\s*2px\\s+solid\\s+darkslategray",
|
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
|
||||||
"message": "Setze <kbd>border: 2px solid darkslategray</kbd>",
|
"message": "Setze <kbd>border-left: 4px solid steelblue</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-3",
|
"id": "box-model-3",
|
||||||
"title": "Außenabstände hinzufügen",
|
"title": "Margins",
|
||||||
"description": "Margins schaffen Abstand zwischen Elementen. Anders als Padding (das den inneren Abstand beeinflusst) existiert Margin außerhalb des Rahmens.",
|
"description": "Margins schaffen Abstand <em>außerhalb</em> des Elements und trennen es von Nachbarn. Während Padding den Inhalt nach innen drückt, drücken Margins andere Elemente weg.",
|
||||||
"task": "Setze <kbd>margin</kbd> auf <kbd>1rem</kbd>, um Abstand zum benachbarten Element zu schaffen.",
|
"task": "Füge Abstand zwischen diesen beiden Profilkarten hinzu mit <kbd>margin-bottom: 1rem</kbd> auf <kbd>.card</kbd>.",
|
||||||
"previewHTML": "<div class=\"container\"><div class=\"outer\">Diese Box braucht Margins</div><div class=\"neighbor\">Benachbartes Element</div></div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article><article class=\"card\"><h3>Alex Rivera</h3><p>UX Designer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .outer { background-color: plum; padding: 1rem; border: 2px solid orchid; } .neighbor { background-color: lightblue; padding: 1rem; border: 2px solid steelblue; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; border-left: 4px solid steelblue; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".outer {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "margin: 1rem;",
|
"solution": "margin-bottom: 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "margin", "expected": "1rem" },
|
"value": { "property": "margin-bottom", "expected": "1rem" },
|
||||||
"message": "Setze <kbd>margin: 1rem</kbd>"
|
"message": "Setze <kbd>margin-bottom: 1rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-4",
|
"id": "box-model-4",
|
||||||
"title": "Box-Sizing: Border-Box",
|
"title": "Box Sizing",
|
||||||
"description": "Die box-sizing Eigenschaft bestimmt, wie Elementdimensionen berechnet werden. 'content-box' (Standard) schließt Padding und Border aus, während 'border-box' sie einschließt.",
|
"description": "Standardmäßig setzt <kbd>width</kbd> nur die Inhaltsbreite. Padding und Rahmen werden addiert. Das verursacht Layout-Probleme.<br><br><kbd>box-sizing: border-box</kbd> bezieht Padding und Rahmen in die Breite ein, was das Sizing vorhersehbar macht. Die meisten Entwickler wenden dies auf alle Elemente an.",
|
||||||
"task": "Setze <kbd>box-sizing</kbd> auf <kbd>border-box</kbd>, damit Padding und Border in die Breite einbezogen werden.",
|
"task": "Beide Karten haben <kbd>width: 200px</kbd>. Die linke nutzt Standard-Sizing (content-box) und wird breiter als erwartet. Korrigiere die rechte Karte mit <kbd>box-sizing: border-box</kbd>.",
|
||||||
"previewHTML": "<div class=\"sizing-demo\"><div class=\"box default\">Content-box (Standard)</div><div class=\"box sized\">Border-box</div></div>",
|
"previewHTML": "<div class=\"demo\"><article class=\"card\">Content-box</article><article class=\"card fix\">Border-box</article></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .sizing-demo { display: flex; gap: 1rem; } .box { width: 200px; padding: 1rem; border: 4px solid teal; background: lightcyan; } .default { box-sizing: content-box; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .demo { display: flex; gap: 1rem; } .card { width: 200px; padding: 1rem; border: 4px solid steelblue; background: white; border-radius: 8px; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".sized {\n ",
|
"codePrefix": ".fix {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "box-sizing: border-box;",
|
"solution": "box-sizing: border-box;",
|
||||||
@@ -92,87 +92,98 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-5",
|
"id": "box-model-5",
|
||||||
"title": "Margin-Kollaps",
|
"title": "Padding Shorthand",
|
||||||
"description": "Wenn zwei vertikale Margins aufeinandertreffen, kollabieren sie zum größeren der beiden Werte statt zu addieren.",
|
"description": "Padding akzeptiert 1-4 Werte:<br>• 1 Wert: alle Seiten<br>• 2 Werte: vertikal | horizontal<br>• 4 Werte: oben | rechts | unten | links",
|
||||||
"task": "Setze <kbd>margin-bottom</kbd> auf <kbd>2rem</kbd>. Der Abstand zwischen den Absätzen beträgt 2rem (nicht 3rem) durch Margin-Kollaps.",
|
"task": "Dieser Button braucht mehr horizontalen als vertikalen Platz. Setze <kbd>padding: 8px 1rem</kbd> (8px oben/unten, 1rem links/rechts).",
|
||||||
"previewHTML": "<div class=\"collapse-demo\"><p class=\"first\">Dieser Absatz hat einen Bottom-Margin.</p><p class=\"second\">Dieser Absatz hat einen Top-Margin von 1rem.</p></div>",
|
"previewHTML": "<button class=\"btn\">Follow</button>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .collapse-demo { border: 1px solid silver; padding: 8px; background: ghostwhite; } .second { margin-top: 1rem; background: linen; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .btn { background: steelblue; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".first {\n ",
|
"codePrefix": ".btn {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "margin-bottom: 2rem;",
|
"solution": "padding: 8px 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "regex",
|
||||||
"value": { "property": "margin-bottom", "expected": "2rem" },
|
"value": "padding:\\s*8px\\s+1rem",
|
||||||
"message": "Setze <kbd>margin-bottom: 2rem</kbd>"
|
"message": "Setze <kbd>padding: 8px 1rem</kbd>",
|
||||||
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-6",
|
"id": "box-model-6",
|
||||||
"title": "Margin-Kurzschreibweise",
|
"title": "Margin Shorthand",
|
||||||
"description": "Die Margin-Kurzschreibweise kann alle vier Seiten setzen. Zwei Werte setzen vertikale (oben/unten) und horizontale (links/rechts) Margins.",
|
"description": "Margin nutzt das gleiche Kurzschreibweisen-Muster wie Padding. Ein häufiges Muster ist das horizontale Zentrieren von Block-Elementen mit <kbd>margin: 0 auto</kbd>.",
|
||||||
"task": "Setze <kbd>margin</kbd> auf <kbd>1rem 2rem</kbd> für 1rem oben/unten und 2rem links/rechts.",
|
"task": "Zentriere diese Karte horizontal. Setze <kbd>margin: 0 auto</kbd>, um automatisch gleiche links/rechts-Margins zu berechnen.",
|
||||||
"previewHTML": "<div class=\"container\"><div class=\"spaced\">Diese Box braucht Margins: 1rem oben/unten, 2rem links/rechts</div></div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .spaced { background-color: honeydew; border: 2px solid mediumseagreen; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { width: 250px; background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; border-left: 4px solid steelblue; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".spaced {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "margin: 1rem 2rem;",
|
"solution": "margin: 0 auto;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "margin:\\s*1rem\\s+2rem",
|
"value": "margin:\\s*0\\s+auto",
|
||||||
"message": "Setze <kbd>margin: 1rem 2rem</kbd>",
|
"message": "Setze <kbd>margin: 0 auto</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-7",
|
"id": "box-model-7",
|
||||||
"title": "Padding-Kurzschreibweise",
|
"title": "Border Radius",
|
||||||
"description": "Wie Margin erlaubt Padding-Kurzschreibweise das Setzen aller Seiten. Ein einzelner Wert gilt für alle vier Seiten.",
|
"description": "Obwohl nicht Teil des klassischen Box-Modells, rundet <kbd>border-radius</kbd> die Ecken der Rahmen-Box eines Elements. Verwende <kbd>50%</kbd> bei einem quadratischen Element, um einen Kreis zu erstellen.",
|
||||||
"task": "Setze <kbd>padding</kbd> auf <kbd>2rem</kbd> für gleichmäßiges Padding.",
|
"task": "Mache das Avatar-Bild rund mit <kbd>border-radius: 50%</kbd>.",
|
||||||
"previewHTML": "<div class=\"padded\">Diese Box braucht gleichmäßiges Padding</div>",
|
"previewHTML": "<article class=\"card\"><img class=\"avatar\" src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='35' r='25' fill='%23666'/%3E%3Ccircle cx='50' cy='90' r='40' fill='%23666'/%3E%3C/svg%3E\" alt=\"Avatar\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .padded { background-color: papayawhip; border: 2px solid orange; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; text-align: center; } .avatar { width: 80px; height: 80px; background: #ddd; margin-bottom: 8px; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".padded {\n ",
|
"codePrefix": ".avatar {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "padding: 2rem;",
|
"solution": "border-radius: 50%;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "2rem" },
|
"value": { "property": "border-radius", "expected": "50%" },
|
||||||
"message": "Setze <kbd>padding: 2rem</kbd>"
|
"message": "Setze <kbd>border-radius: 50%</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-8",
|
"id": "box-model-8",
|
||||||
"title": "Rahmen auf einzelnen Seiten",
|
"title": "Complete Card",
|
||||||
"description": "Für feinere Kontrolle können einzelne Seiten mit border-top, border-right, border-bottom oder border-left angesprochen werden.",
|
"description": "Kombinieren wir alles. Diese Benachrichtigungskarte braucht Styling, um professionell auszusehen.",
|
||||||
"task": "Setze <kbd>border-bottom</kbd> auf <kbd>4px solid dodgerblue</kbd>.",
|
"task": "Style die Benachrichtigung: füge <kbd>padding: 1rem</kbd>, <kbd>border-left: 4px solid coral</kbd> und <kbd>border-radius: 4px</kbd> hinzu.",
|
||||||
"previewHTML": "<div class=\"line\">Dieses Element braucht nur einen unteren Rahmen</div>",
|
"previewHTML": "<div class=\"alert\"><strong>New message</strong><p>You have 3 unread notifications</p></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .line { padding: 1rem; background-color: aliceblue; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .alert { background: seashell; } .alert strong { color: coral; } .alert p { margin: 4px 0 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".line {\n ",
|
"codePrefix": ".alert {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "border-bottom: 4px solid dodgerblue;",
|
"solution": "padding: 1rem;\n border-left: 4px solid coral;\n border-radius: 4px;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
|
"message": "Setze <kbd>padding: 1rem</kbd>"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border-bottom:\\s*4px\\s+solid\\s+dodgerblue",
|
"value": "border-left:\\s*4px\\s+solid\\s+coral",
|
||||||
"message": "Setze <kbd>border-bottom: 4px solid dodgerblue</kbd>",
|
"message": "Setze <kbd>border-left: 4px solid coral</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "border-radius", "expected": "4px" },
|
||||||
|
"message": "Setze <kbd>border-radius: 4px</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,119 +7,94 @@
|
|||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "units-1",
|
"id": "units-1",
|
||||||
"title": "Absolute vs. Relative Einheiten",
|
"title": "Relative Units",
|
||||||
"description": "Lerne den Unterschied zwischen px, rem, em, % und vw/vh für flexible, responsive Layouts.",
|
"description": "CSS bietet zwei Arten von Einheiten: <em>absolute</em> (wie <kbd>px</kbd>) und <em>relative</em> (wie <kbd>%</kbd> und <kbd>rem</kbd>). Relative Einheiten passen sich ihrem Kontext an und machen Layouts flexibel und zugänglich.<br><br><strong>Häufige relative Einheiten:</strong><br>• <kbd>%</kbd> – Relativ zum Elternelement<br>• <kbd>rem</kbd> – Relativ zur Root-Schriftgröße (typisch 16px)<br>• <kbd>em</kbd> – Relativ zur Schriftgröße des Elements<br><br>Ein häufiges Muster für lesbaren Inhalt: setze <kbd>width: 100%</kbd>, damit es den verfügbaren Platz füllt, dann <kbd>max-width: 40rem</kbd> um die Zeilenlänge für Lesbarkeit zu begrenzen.",
|
||||||
"task": "Setze die Breite von <kbd>.box</kbd> auf <kbd>80%</kbd> und max-width auf <kbd>37.5rem</kbd>.",
|
"task": "Dieser Artikeltext läuft auf großen Bildschirmen zu breit. Füge <kbd>max-width: 40rem</kbd> hinzu für optimale Lesebreite.",
|
||||||
"previewHTML": "<div class=\"box\">Ändere meine Größe!</div>",
|
"previewHTML": "<article class=\"article\"><h2>The Art of Typography</h2><p>Good typography is invisible. When text is set well, readers absorb information without noticing the design decisions that make it comfortable to read. Line length is crucial—too wide and eyes get lost, too narrow and reading becomes choppy.</p><p>The ideal line length is 45-75 characters per line. At typical font sizes, this works out to roughly 40rem maximum width.</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { background: #f5f5f5; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: Georgia, serif; padding: 1rem; background: #f9f9f9; } .article { background: white; padding: 2rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .article h2 { margin: 0 0 1rem; color: #333; } .article p { margin: 0 0 1rem; line-height: 1.6; color: #444; } .article p:last-child { margin-bottom: 0; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Setze flexible Größen */\n.box {",
|
"codePrefix": ".article {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " width: 80%;\n max-width: 37.5rem;",
|
"solution": "max-width: 40rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "width",
|
|
||||||
"message": "Verwende die <kbd>width</kbd> Eigenschaft",
|
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
},
|
|
||||||
{ "type": "property_value", "value": { "property": "width", "expected": "80%" }, "message": "Setze width auf <kbd>80%</kbd>" },
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "max-width",
|
|
||||||
"message": "Verwende die <kbd>max-width</kbd> Eigenschaft",
|
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "max-width", "expected": "37.5rem" },
|
"value": { "property": "max-width", "expected": "40rem" },
|
||||||
"message": "Setze max-width auf <kbd>37.5rem</kbd>"
|
"message": "Setze <kbd>max-width: 40rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "units-2",
|
"id": "units-2",
|
||||||
"title": "CSS Custom Properties",
|
"title": "CSS Variables",
|
||||||
"description": "Definiere und verwende Variablen (--custom properties) wieder, um deine Theme-Werte zu zentralisieren.",
|
"description": "CSS Custom Properties (Variablen) erlauben dir, wiederverwendbare Werte zu definieren. Definiere sie mit <kbd>--name</kbd> und verwende sie mit <kbd>var(--name)</kbd>. Variablen auf <kbd>:root</kbd> sind überall verfügbar.",
|
||||||
"task": "Erstelle eine <code>--main-color</code> Variable in :root mit <kbd>#6200ee</kbd> und wende sie als Rahmenfarbe auf <kbd>.themed</kbd> an.",
|
"task": "Definiere <kbd>--brand: steelblue</kbd> in <kbd>:root</kbd>, dann verwende es als <kbd>background</kbd>-Farbe für <kbd>.btn</kbd>.",
|
||||||
"previewHTML": "<div class=\"themed\">Variablen-Box</div>",
|
"previewHTML": "<div class=\"actions\"><button class=\"btn\">Subscribe</button><button class=\"btn\">Learn More</button></div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .themed { padding: 1rem; border: 0.125rem solid #ddd; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .actions { display: flex; gap: 1rem; } .btn { color: white; border: none; padding: 12px 24px; border-radius: 6px; font-size: 1rem; cursor: pointer; background: #ccc; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Definiere und verwende eine CSS-Variable */\n:root {",
|
"codePrefix": ":root {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}\n.themed { }",
|
"codeSuffix": "\n}\n\n.btn {\n background: var(--brand);\n}",
|
||||||
"solution": " --main-color: #6200ee;\n}\n.themed {\n border-color: var(--main-color);",
|
"solution": "--brand: steelblue;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "--main-color",
|
"value": "--brand",
|
||||||
"message": "Definiere <kbd>--main-color</kbd> in :root",
|
"message": "Definiere die <kbd>--brand</kbd> Variable",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "var(--main-color)",
|
"value": "steelblue",
|
||||||
"message": "Verwende <kbd>var(--main-color)</kbd>",
|
"message": "Setze den Wert auf <kbd>steelblue</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": { "property": "border", "expected": "var(--main-color)" },
|
|
||||||
"message": "Wende die Variable auf die Rahmenfarbe an",
|
|
||||||
"options": { "exact": false }
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "units-3",
|
"id": "units-3",
|
||||||
"title": "Einheiten-Berechnungen (calc)",
|
"title": "calc() Function",
|
||||||
"description": "Verwende die <code>calc()</code> Funktion, um verschiedene Einheiten in einem Ausdruck zu kombinieren.",
|
"description": "Die <kbd>calc()</kbd> Funktion ermöglicht das Mischen verschiedener Einheiten in Berechnungen. Das ist essenziell für Layouts, die feste und flexible Größen kombinieren, wie ein Sidebar-Layout.",
|
||||||
"task": "Setze die Breite von <kbd>.sized</kbd> auf <kbd>calc(100% - 2rem)</kbd> und min-height auf <kbd>calc(10vh + 1rem)</kbd>.",
|
"task": "Der Hauptinhalt soll den verbleibenden Platz nach der 200px Sidebar füllen. Setze <kbd>width: calc(100% - 200px)</kbd> auf <kbd>.main</kbd>.",
|
||||||
"previewHTML": "<div class=\"sized\">Calc Demo</div>",
|
"previewHTML": "<div class=\"layout\"><aside class=\"sidebar\">Sidebar<br>Navigation</aside><main class=\"main\"><h2>Main Content</h2><p>This area should fill the remaining width after accounting for the fixed-width sidebar.</p></main></div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sized { background: #e8f5e9; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .layout { display: flex; gap: 1rem; } .sidebar { width: 200px; background: #1a1a2e; color: white; padding: 1rem; border-radius: 8px; flex-shrink: 0; } .main { background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .main h2 { margin: 0 0 8px; } .main p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Verwende calc für dynamische Größen */\n.sized {",
|
"codePrefix": ".main {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " width: calc(100% - 2rem);\n min-height: calc(10vh + 1rem);",
|
"solution": "width: calc(100% - 200px);",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{ "type": "contains", "value": "calc", "message": "Verwende die <kbd>calc()</kbd> Funktion", "options": { "caseSensitive": false } },
|
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "width:\\s*calc\\(100% - 2rem\\)",
|
"value": "width:\\s*calc\\(\\s*100%\\s*-\\s*200px\\s*\\)",
|
||||||
"message": "Width sollte calc(100% - 2rem) sein",
|
"message": "Setze <kbd>width: calc(100% - 200px)</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "min-height:\\s*calc\\(10vh \\+ 1rem\\)",
|
|
||||||
"message": "Min-height sollte calc(10vh + 1rem) sein",
|
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "units-4",
|
"id": "units-4",
|
||||||
"title": "Viewport & Responsive Einheiten",
|
"title": "Viewport Units",
|
||||||
"description": "Steuere Layouts relativ zur Viewport-Größe mit vw, vh und vmin/vmax Einheiten.",
|
"description": "Viewport-Einheiten dimensionieren Elemente relativ zum Browserfenster:<br>• <kbd>vw</kbd> – 1% der Viewport-Breite<br>• <kbd>vh</kbd> – 1% der Viewport-Höhe<br><br>Diese sind perfekt für Vollbild-Sektionen wie Hero-Banner.",
|
||||||
"task": "Gib <kbd>.view</kbd> eine Breite von <kbd>50vw</kbd> und Höhe von <kbd>20vh</kbd>.",
|
"task": "Mache diese Hero-Sektion so hoch wie das Viewport mit <kbd>min-height: 100vh</kbd>.",
|
||||||
"previewHTML": "<div class=\"view\">Viewport-Box</div>",
|
"previewHTML": "<section class=\"hero\"><h1>Welcome</h1><p>Scroll down to explore</p></section>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .view { background: #ffe0b2; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .hero { background: linear-gradient(135deg, #1a1a2e 0%, steelblue 100%); color: white; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 2rem; } .hero h1 { margin: 0 0 1rem; font-size: 2.5rem; } .hero p { margin: 0; opacity: 0.8; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Verwende Viewport-Einheiten */\n.view {",
|
"codePrefix": ".hero {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " width: 50vw;\n height: 20vh;",
|
"solution": "min-height: 100vh;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{ "type": "contains", "value": "vw", "message": "Verwende <kbd>vw</kbd> Einheit", "options": { "caseSensitive": false } },
|
{
|
||||||
{ "type": "contains", "value": "vh", "message": "Verwende <kbd>vh</kbd> Einheit", "options": { "caseSensitive": false } },
|
"type": "property_value",
|
||||||
{ "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Setze width auf <kbd>50vw</kbd>" },
|
"value": { "property": "min-height", "expected": "100vh" },
|
||||||
{ "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Setze height auf <kbd>20vh</kbd>" }
|
"message": "Setze <kbd>min-height: 100vh</kbd>"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -7,13 +7,13 @@
|
|||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "transitions-1",
|
"id": "transitions-1",
|
||||||
"title": "Einfache Transitions",
|
"title": "Transitions",
|
||||||
"description": "Lerne, wie du <kbd>transition</kbd> auf Eigenschaften anwendest für sanfte Änderungen bei Zustandswechseln.<br><br><pre>transition: property duration;\n/* z.B. transition: background-color 0.3s; */</pre>",
|
"description": "Lerne, wie du <kbd>transition</kbd> auf Eigenschaften anwendest für sanfte Änderungen bei Zustandswechseln.<br><br><pre>transition: property duration;\n/* z.B. transition: background-color 0.3s; */</pre>",
|
||||||
"task": "Füge <kbd>transition: background-color 0.3s</kbd> zu <kbd>.btn</kbd> hinzu, damit die Farbe beim Hover sanft überblendet.",
|
"task": "Füge <kbd>transition: background-color 0.3s</kbd> hinzu, damit die Farbe beim Hover sanft überblendet.",
|
||||||
"previewHTML": "<button class=\"btn\">Hover mich</button>",
|
"previewHTML": "<button class=\"btn\">Hover Me</button>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: #6200ee; color: white; padding: 0.5rem 1rem; border: none; } .btn:hover { background: #3700b3; }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Füge Transition hinzu */\n.btn {",
|
"codePrefix": "/* Add transition */\n.btn {",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "}",
|
||||||
"solution": " transition: background-color 0.3s;",
|
"solution": " transition: background-color 0.3s;",
|
||||||
@@ -35,13 +35,13 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "transitions-2",
|
"id": "transitions-2",
|
||||||
"title": "Transition Timing-Funktionen",
|
"title": "Timing Funcs",
|
||||||
"description": "Erkunde Easing-Funktionen wie <kbd>ease</kbd>, <kbd>linear</kbd>, <kbd>ease-in</kbd>, <kbd>ease-out</kbd>, um das Animationstempo zu steuern.",
|
"description": "Erkunde Easing-Funktionen wie <kbd>ease</kbd>, <kbd>linear</kbd>, <kbd>ease-in</kbd>, <kbd>ease-out</kbd>, um das Animationstempo zu steuern.",
|
||||||
"task": "Setze <kbd>transition-timing-function</kbd> auf <kbd>ease-in-out</kbd> bei <kbd>.btn</kbd>.",
|
"task": "Setze <kbd>transition-timing-function</kbd> auf <kbd>ease-in-out</kbd>.",
|
||||||
"previewHTML": "<button class=\"btn\">Timing</button>",
|
"previewHTML": "<button class=\"btn\">Timing</button>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: #6200ee; color: white; padding: 0.5rem 1rem; border: none; transition: background-color 0.3s; } .btn:hover { background: #03dac6; }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Setze Timing-Funktion */\n.btn {",
|
"codePrefix": "/* Set timing function */\n.btn {",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "}",
|
||||||
"solution": " transition-timing-function: ease-in-out;",
|
"solution": " transition-timing-function: ease-in-out;",
|
||||||
@@ -56,19 +56,19 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "transition-timing-function", "expected": "ease-in-out" },
|
"value": { "property": "transition-timing-function", "expected": "ease-in-out" },
|
||||||
"message": "Setze <kbd>transition-timing-function: ease-in-out</kbd>"
|
"message": "Setze timing auf <kbd>ease-in-out</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "transitions-3",
|
"id": "transitions-3",
|
||||||
"title": "Keyframe-Animationen Grundlagen",
|
"title": "Keyframes",
|
||||||
"description": "Erstelle benannte Animationen mit <kbd>@keyframes</kbd> und wende sie mit der <kbd>animation</kbd> Kurzschreibweise an.<br><br><pre>@keyframes bounce {\n 50% { transform: translateY(-20px); }\n}\n.ball {\n animation: bounce 1s infinite;\n}</pre>",
|
"description": "Erstelle benannte Animationen mit <kbd>@keyframes</kbd> und wende sie mit der <kbd>animation</kbd> Kurzschreibweise an.<br><br><pre>@keyframes bounce {\n 50% { transform: translateY(-20px); }\n}\n.ball {\n animation: bounce 1s infinite;\n}</pre>",
|
||||||
"task": "Definiere bei <kbd>50%</kbd> ein <kbd>transform: translateY(-20px)</kbd> und wende <kbd>animation: bounce 1s infinite</kbd> auf <kbd>.ball</kbd> an.",
|
"task": "Definiere bei <kbd>50%</kbd> ein <kbd>transform: translateY(-20px)</kbd> und wende <kbd>animation: bounce 1s infinite</kbd> auf <kbd>.ball</kbd> an.",
|
||||||
"previewHTML": "<div class=\"ball\"></div>",
|
"previewHTML": "<div class=\"ball\"></div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: #ff0266; border-radius: 50%; margin: 2rem auto; }",
|
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: crimson; border-radius: 50%; margin: 2rem auto; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Definiere Keyframes und wende Animation an */\n@keyframes bounce {",
|
"codePrefix": "/* Define keyframes and apply animation */\n@keyframes bounce {",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}\n.ball { }",
|
"codeSuffix": "}\n.ball { }",
|
||||||
"solution": " 50% { transform: translateY(-20px); }\n}\n.ball {\n animation: bounce 1s infinite;",
|
"solution": " 50% { transform: translateY(-20px); }\n}\n.ball {\n animation: bounce 1s infinite;",
|
||||||
@@ -102,35 +102,22 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "transitions-4",
|
"id": "transitions-4",
|
||||||
"title": "Animations-Eigenschaften im Detail",
|
"title": "Animation Properties",
|
||||||
"description": "Verfeinere Animationen mit <kbd>animation-delay</kbd>, <kbd>animation-iteration-count</kbd>, <kbd>animation-direction</kbd> und <kbd>animation-fill-mode</kbd>.",
|
"description": "Verfeinere Animationen mit <kbd>animation-delay</kbd>, <kbd>animation-iteration-count</kbd>, <kbd>animation-direction</kbd> und <kbd>animation-fill-mode</kbd>.",
|
||||||
"task": "Wende die <kbd>fade</kbd> Animation auf <kbd>.box</kbd> an mit <kbd>animation-name: fade</kbd>, <kbd>animation-duration: 2s</kbd>, <kbd>animation-delay: 1s</kbd>, <kbd>animation-iteration-count: 2</kbd> und <kbd>animation-fill-mode: forwards</kbd>.",
|
"task": "Wende die <kbd>pulse</kbd> Animation auf <kbd>.box</kbd> an mit <kbd>animation-name: pulse</kbd>, <kbd>animation-duration: 2s</kbd>, <kbd>animation-delay: 1s</kbd>, <kbd>animation-iteration-count: 2</kbd> und <kbd>animation-fill-mode: forwards</kbd>.",
|
||||||
"previewHTML": "<div class=\"box\">Fade Demo</div>",
|
"previewHTML": "<div class=\"box\">Pulse</div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { width: 100px; height: 100px; background: #4caf50; margin: 2rem auto; }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Definiere fade und setze Eigenschaften */\n@keyframes fade { from { opacity: 0; } to { opacity: 1; } }\n.box {",
|
"codePrefix": "/* Apply animation properties */\n.box {",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "}",
|
||||||
"solution": " animation-name: fade;\n animation-duration: 2s;\n animation-delay: 1s;\n animation-iteration-count: 2;\n animation-fill-mode: forwards;",
|
"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",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "animation-delay",
|
"value": { "property": "animation-name", "expected": "pulse" },
|
||||||
"message": "Verwende <kbd>animation-delay</kbd>",
|
"message": "Setze <kbd>animation-name: pulse</kbd>"
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "animation-iteration-count",
|
|
||||||
"message": "Verwende <kbd>animation-iteration-count</kbd>",
|
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "animation-fill-mode",
|
|
||||||
"message": "Verwende <kbd>animation-fill-mode</kbd>",
|
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
|
|||||||
@@ -7,13 +7,13 @@
|
|||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "responsive-1",
|
"id": "responsive-1",
|
||||||
"title": "Einführung in Media Queries",
|
"title": "Media Queries",
|
||||||
"description": "Verstehe die Syntax und Anwendungsfälle für CSS Media Queries, um Stile bedingt basierend auf Viewport-Eigenschaften anzuwenden.",
|
"description": "Verstehe die Syntax und Anwendungsfälle für CSS Media Queries, um Stile bedingt basierend auf Viewport-Eigenschaften anzuwenden.<br><br><pre>@media (max-width: 600px) {\n .panel {\n background: lightcoral;\n }\n}</pre>",
|
||||||
"task": "Schreibe eine Media Query, die gilt, wenn der Viewport maximal 600px breit ist, und ändere den Hintergrund von <kbd>.panel</kbd> auf <kbd>lightcoral</kbd>.",
|
"task": "Schreibe eine Media Query mit <kbd>@media (max-width: 600px)</kbd>, die den Hintergrund von <kbd>.panel</kbd> auf <kbd>lightcoral</kbd> ändert.",
|
||||||
"previewHTML": "<div class=\"panel\">Ändere die Fenstergröße</div>",
|
"previewHTML": "<div class=\"panel\">Resize the window</div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .panel { padding: 1rem; background: lightblue; }",
|
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .panel { padding: 1rem; background: lightblue; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Füge deine Media Query unten ein */\n",
|
"codePrefix": "/* Add your media query below */\n",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"solution": "@media (max-width: 600px) {\n .panel {\n background: lightcoral;\n }\n}",
|
"solution": "@media (max-width: 600px) {\n .panel {\n background: lightcoral;\n }\n}",
|
||||||
@@ -22,7 +22,7 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "@media\\s*\\(max-width:\\s*600px\\)",
|
"value": "@media\\s*\\(max-width:\\s*600px\\)",
|
||||||
"message": "Verwende eine Media Query für max-width: 600px",
|
"message": "Verwende <kbd>@media (max-width: 600px)</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -31,68 +31,49 @@
|
|||||||
"message": "Adressiere <kbd>.panel</kbd> innerhalb der Media Query",
|
"message": "Adressiere <kbd>.panel</kbd> innerhalb der Media Query",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background",
|
|
||||||
"message": "Ändere die <kbd>background</kbd> Eigenschaft",
|
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "background", "expected": "lightcoral" },
|
"value": { "property": "background", "expected": "lightcoral" },
|
||||||
"message": "Setze background auf <kbd>lightcoral</kbd>",
|
"message": "Setze <kbd>background: lightcoral</kbd>",
|
||||||
"options": { "exact": false }
|
"options": { "exact": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "responsive-2",
|
"id": "responsive-2",
|
||||||
"title": "Flüssige Typografie",
|
"title": "Fluid Type",
|
||||||
"description": "Verwende relative Einheiten wie vw, damit Schriftgrößen mit der Viewport-Breite skalieren.",
|
"description": "Verwende relative Einheiten wie <kbd>vw</kbd>, damit Schriftgrößen mit der Viewport-Breite skalieren.",
|
||||||
"task": "Setze die font-size von <kbd>.text</kbd> auf <kbd>5vw</kbd>, damit sie sich mit dem Viewport ändert.",
|
"task": "Setze <kbd>font-size: 5vw</kbd>, damit sie sich mit dem Viewport ändert.",
|
||||||
"previewHTML": "<p class=\"text\">Flüssige Typografie</p>",
|
"previewHTML": "<p class=\"text\">Fluid Typography</p>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Wende flüssige Schriftgröße an */\n.text {",
|
"codePrefix": "/* Apply fluid font sizing */\n.text {",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "}",
|
||||||
"solution": " font-size: 5vw;",
|
"solution": " font-size: 5vw;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{ "type": "property_value", "value": { "property": "font-size", "expected": "5vw" }, "message": "Setze <kbd>font-size: 5vw</kbd>" }
|
||||||
"type": "contains",
|
|
||||||
"value": "font-size",
|
|
||||||
"message": "Verwende die <kbd>font-size</kbd> Eigenschaft",
|
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "vw",
|
|
||||||
"message": "Verwende <kbd>vw</kbd> Einheit für flüssige Größe",
|
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
},
|
|
||||||
{ "type": "property_value", "value": { "property": "font-size", "expected": "5vw" }, "message": "Setze font-size auf <kbd>5vw</kbd>" }
|
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "responsive-3",
|
"id": "responsive-3",
|
||||||
"title": "Flexible Raster",
|
"title": "Responsive Grid",
|
||||||
"description": "Kombiniere CSS Grid mit auto-fit oder auto-fill für responsive Spaltenlayouts.",
|
"description": "Kombiniere CSS Grid mit <kbd>auto-fit</kbd> oder <kbd>auto-fill</kbd> für responsive Spaltenlayouts, die automatisch die Anzahl der Spalten basierend auf verfügbarem Platz anpassen.",
|
||||||
"task": "Definiere <kbd>.cards</kbd> mit <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd> und einem gap von <kbd>1rem</kbd>.",
|
"task": "Füge <kbd>display: grid</kbd>, <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd> und <kbd>gap: 1rem</kbd> hinzu.",
|
||||||
"previewHTML": "<div class=\"cards\"><div>1</div><div>2</div><div>3</div><div>4</div></div>",
|
"previewHTML": "<section class=\"features\"><article class=\"feature\"><h3>Fast</h3><p>Lightning quick load times</p></article><article class=\"feature\"><h3>Secure</h3><p>Enterprise-grade security</p></article><article class=\"feature\"><h3>Reliable</h3><p>99.9% uptime guaranteed</p></article><article class=\"feature\"><h3>Support</h3><p>24/7 customer service</p></article></section>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .cards > div { background: #d1c4e9; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .feature { background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .feature h3 { margin: 0 0 8px; color: steelblue; } .feature p { margin: 0; color: #666; font-size: 0.9rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Erstelle ein responsives Raster */\n.cards {",
|
"codePrefix": ".features {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
|
"solution": "display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "grid-template-columns",
|
"value": { "property": "display", "expected": "grid" },
|
||||||
"message": "Definiere <kbd>grid-template-columns</kbd>",
|
"message": "Setze <kbd>display: grid</kbd>"
|
||||||
"options": { "caseSensitive": false }
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
@@ -100,18 +81,22 @@
|
|||||||
"message": "Verwende <kbd>repeat(auto-fit, minmax(200px, 1fr))</kbd>",
|
"message": "Verwende <kbd>repeat(auto-fit, minmax(200px, 1fr))</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{ "type": "contains", "value": "gap", "message": "Verwende die <kbd>gap</kbd> Eigenschaft", "options": { "caseSensitive": false } }
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "gap", "expected": "1rem" },
|
||||||
|
"message": "Setze <kbd>gap: 1rem</kbd>"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "responsive-4",
|
"id": "responsive-4",
|
||||||
"title": "Mobile-First Media Queries",
|
"title": "Mobile-First",
|
||||||
"description": "Verfolge einen Mobile-First-Ansatz: Schreibe Basis-Stile für kleine Bildschirme und erweitere für größere Viewports.",
|
"description": "Verfolge einen Mobile-First-Ansatz: Schreibe Basis-Stile für kleine Bildschirme und erweitere für größere Viewports.",
|
||||||
"task": "Schreibe eine Media Query für min-width 768px, die die Breite von <kbd>.sidebar</kbd> auf <kbd>250px</kbd> setzt.",
|
"task": "Schreibe eine Media Query mit <kbd>@media (min-width: 768px)</kbd>, die die Breite von <kbd>.sidebar</kbd> auf <kbd>250px</kbd> setzt.",
|
||||||
"previewHTML": "<aside class=\"sidebar\">Seitenleiste</aside>",
|
"previewHTML": "<aside class=\"sidebar\">Sidebar</aside>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sidebar { background: #c8e6c9; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sidebar { background: #c8e6c9; padding: 1rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Füge Mobile-First-Erweiterung hinzu */\n",
|
"codePrefix": "/* Add mobile-first enhancement */\n",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"solution": "@media (min-width: 768px) {\n .sidebar {\n width: 250px;\n }\n}",
|
"solution": "@media (min-width: 768px) {\n .sidebar {\n width: 250px;\n }\n}",
|
||||||
@@ -120,7 +105,7 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "@media\\s*\\(min-width:\\s*768px\\)",
|
"value": "@media\\s*\\(min-width:\\s*768px\\)",
|
||||||
"message": "Verwende eine Media Query für min-width: 768px",
|
"message": "Verwende <kbd>@media (min-width: 768px)</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
@@ -132,7 +117,7 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "width", "expected": "250px" },
|
"value": { "property": "width", "expected": "250px" },
|
||||||
"message": "Setze width auf <kbd>250px</kbd>",
|
"message": "Setze <kbd>width: 250px</kbd>",
|
||||||
"options": { "exact": false }
|
"options": { "exact": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -63,35 +63,6 @@
|
|||||||
"message": "Füge eine <kbd><h1></kbd>-Überschrift in deinem Header hinzu"
|
"message": "Füge eine <kbd><h1></kbd>-Überschrift in deinem Header hinzu"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "div-vs-span",
|
|
||||||
"title": "Generische Container: div und span",
|
|
||||||
"description": "Wenn du einen Container ohne semantische Bedeutung benötigst:<br><br><kbd><div></kbd> - Generischer Block-Container (für Layout/Gruppierung)<br><kbd><span></kbd> - Generischer Inline-Container (zum Stylen von Textteilen)<br><br>Verwende semantische Elemente wenn möglich, div/span wenn kein semantisches Element passt.",
|
|
||||||
"task": "Umschließe das Wort 'hervorgehoben' mit einem <kbd><span></kbd>, um es anders zu gestalten. Umschließe das gesamte Zitat mit einem <kbd><div></kbd>.",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: Georgia, serif; padding: 20px; } div { background: #f5f5f5; padding: 15px; border-left: 4px solid #1976d2; } span { background: #fff59d; padding: 2px 4px; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "Der hervorgehoben Moment war unvergesslich.",
|
|
||||||
"solution": "<div>Der <span>hervorgehoben</span> Moment war unvergesslich.</div>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "div",
|
|
||||||
"message": "Umschließe alles mit einem <kbd><div></kbd>-Element"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "span",
|
|
||||||
"message": "Füge ein <kbd><span></kbd> um das Wort 'hervorgehoben' hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_text",
|
|
||||||
"value": { "selector": "span", "text": "hervorgehoben" },
|
|
||||||
"message": "Das <kbd><span></kbd> sollte das Wort 'hervorgehoben' enthalten"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,21 +1,21 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-forms-validation",
|
"id": "html-forms-validation",
|
||||||
"title": "HTML Validierung",
|
"title": "Formularvalidierung",
|
||||||
"description": "Lerne die eingebauten HTML5-Formular-Validierungsattribute kennen",
|
"description": "Verwende die eingebaute HTML5-Validierung für bessere Benutzererfahrung",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "intermediate",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "required-fields",
|
"id": "required-fields",
|
||||||
"title": "Pflichtfelder",
|
"title": "Pflichtfelder",
|
||||||
"description": "Das <kbd>required</kbd>-Attribut verhindert das Absenden des Formulars, wenn das Feld leer ist.<br><br>Füge es zu jeder Eingabe hinzu, die ausgefüllt werden muss:<br><kbd><input type=\"text\" required></kbd><br><br>Der Browser zeigt automatisch eine Validierungsmeldung an.",
|
"description": "Das <kbd>required</kbd>-Attribut verhindert das Absenden des Formulars, wenn das Feld leer ist. Der Browser zeigt automatisch eine Validierungsmeldung an - kein JavaScript nötig!<br><br>Füge es zu jeder Eingabe hinzu, die ausgefüllt werden muss:<br><kbd><input type=\"text\" required></kbd>",
|
||||||
"task": "Mache sowohl das Name- als auch das E-Mail-Feld zu Pflichtfeldern, indem du das <kbd>required</kbd>-Attribut hinzufügst.",
|
"task": "Mache sowohl das Name- als auch das E-Mail-Feld zu Pflichtfeldern, indem du das <kbd>required</kbd>-Attribut zu jeder Eingabe hinzufügst.",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 350px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-of-type { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } input:invalid { border-color: #d32f2f; } button { margin-top: 20px; padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 320px; } label { display: block; margin-top: 15px; margin-bottom: 5px; font-weight: 500; } label:first-of-type { margin-top: 0; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; box-sizing: border-box; } input:focus { outline: 2px solid steelblue; border-color: transparent; } button { margin-top: 20px; padding: 10px 20px; background: steelblue; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 500; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "<form>\n <label for=\"name\">Name: *</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n \n <label for=\"email\">E-Mail: *</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <button type=\"submit\">Absenden</button>\n</form>",
|
"initialCode": "<form>\n <label for=\"name\">Name *</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n \n <label for=\"email\">E-Mail *</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <button type=\"submit\">Absenden</button>\n</form>",
|
||||||
"solution": "<form>\n <label for=\"name\">Name: *</label>\n <input type=\"text\" id=\"name\" name=\"name\" required>\n \n <label for=\"email\">E-Mail: *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <button type=\"submit\">Absenden</button>\n</form>",
|
"solution": "<form>\n <label for=\"name\">Name *</label>\n <input type=\"text\" id=\"name\" name=\"name\" required>\n \n <label for=\"email\">E-Mail *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <button type=\"submit\">Absenden</button>\n</form>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
@@ -29,84 +29,6 @@
|
|||||||
"message": "Füge <kbd>required</kbd> zum E-Mail-Feld hinzu"
|
"message": "Füge <kbd>required</kbd> zum E-Mail-Feld hinzu"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "input-constraints",
|
|
||||||
"title": "Eingabebeschränkungen",
|
|
||||||
"description": "Kontrolliere, was Benutzer eingeben können:<br><br><kbd>minlength</kbd> / <kbd>maxlength</kbd> - Textlängenbegrenzung<br><kbd>min</kbd> / <kbd>max</kbd> - Zahlenbereich<br><kbd>pattern</kbd> - Regex-Musterabgleich<br><kbd>placeholder</kbd> - Hinweistext (kein Label!)",
|
|
||||||
"task": "Füge Validierung zur Passwort-Eingabe hinzu:<br>1. Füge <kbd>minlength=\"8\"</kbd> für die Mindestlänge hinzu<br>2. Füge <kbd>maxlength=\"20\"</kbd> für die Maximallänge hinzu<br>3. Füge <kbd>placeholder=\"Passwort eingeben\"</kbd> als Hinweis hinzu",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 350px; } label { display: block; margin-bottom: 5px; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } input:invalid:not(:placeholder-shown) { border-color: #d32f2f; } small { display: block; font-size: 12px; color: #666; margin-top: 4px; } button { margin-top: 20px; padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<form>\n <label for=\"password\">Passwort:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required aria-describedby=\"password-hint\">\n <small id=\"password-hint\">Muss 8-20 Zeichen lang sein</small>\n \n <button type=\"submit\">Konto erstellen</button>\n</form>",
|
|
||||||
"solution": "<form>\n <label for=\"password\">Passwort:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required minlength=\"8\" maxlength=\"20\" placeholder=\"Passwort eingeben\" aria-describedby=\"password-hint\">\n <small id=\"password-hint\">Muss 8-20 Zeichen lang sein</small>\n \n <button type=\"submit\">Konto erstellen</button>\n</form>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "input[type='password']", "attr": "minlength", "value": "8" },
|
|
||||||
"message": "Füge <kbd>minlength</kbd>=\"8\" zum Passwort hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "input[type='password']", "attr": "maxlength", "value": "20" },
|
|
||||||
"message": "Füge <kbd>maxlength</kbd>=\"20\" zum Passwort hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "input[type='password']", "attr": "placeholder", "value": null },
|
|
||||||
"message": "Füge einen <kbd>placeholder</kbd> hinzu, der andeutet, was einzugeben ist"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "complete-registration",
|
|
||||||
"title": "Vollständiges Registrierungsformular",
|
|
||||||
"description": "Erstelle ein vollständiges Registrierungsformular mit allen Validierungskonzepten:<br><br>- Pflichtfelder mit * markiert<br>- E-Mail-Validierung (type=\"email\" verwenden)<br>- Passwort mit Längenbeschränkungen<br>- AGB-Checkbox (Pflichtfeld)<br>- Absende-Button",
|
|
||||||
"task": "Vervollständige das Registrierungsformular. Füge <kbd>required</kbd>-Attribute, passende Eingabetypen und Validierungsbeschränkungen hinzu.",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 400px; background: #f5f5f5; padding: 25px; border-radius: 8px; } h2 { margin-top: 0; margin-bottom: 20px; } label { display: block; margin-top: 15px; margin-bottom: 5px; font-weight: 500; } input[type='text'], input[type='email'], input[type='password'] { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; font-size: 14px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; } input[type='checkbox'] { width: auto; margin-right: 8px; vertical-align: middle; } label:has(input[type='checkbox']) { display: flex; align-items: center; font-weight: normal; } button { width: 100%; margin-top: 20px; padding: 12px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: 500; } button:hover { background: #1565c0; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<form>\n <h2>Konto erstellen</h2>\n \n <label for=\"fullname\">Vollständiger Name *</label>\n <input type=\"text\" id=\"fullname\" name=\"fullname\">\n \n <label for=\"email\">E-Mail *</label>\n <input id=\"email\" name=\"email\">\n \n <label for=\"password\">Passwort *</label>\n <input id=\"password\" name=\"password\">\n \n <label>\n <input type=\"checkbox\" id=\"terms\" name=\"terms\">\n Ich stimme den Nutzungsbedingungen zu *\n </label>\n \n <button type=\"submit\">Registrieren</button>\n</form>",
|
|
||||||
"solution": "<form>\n <h2>Konto erstellen</h2>\n \n <label for=\"fullname\">Vollständiger Name *</label>\n <input type=\"text\" id=\"fullname\" name=\"fullname\" required>\n \n <label for=\"email\">E-Mail *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <label for=\"password\">Passwort *</label>\n <input type=\"password\" id=\"password\" name=\"password\" required minlength=\"8\">\n \n <label>\n <input type=\"checkbox\" id=\"terms\" name=\"terms\" required>\n Ich stimme den Nutzungsbedingungen zu *\n </label>\n \n <button type=\"submit\">Registrieren</button>\n</form>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#fullname", "attr": "required", "value": true },
|
|
||||||
"message": "Mache das Feld für den vollständigen Namen zum Pflichtfeld"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#email", "attr": "type", "value": "email" },
|
|
||||||
"message": "Setze den Eingabetyp für E-Mail auf <kbd>email</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#email", "attr": "required", "value": true },
|
|
||||||
"message": "Mache das E-Mail-Feld zum Pflichtfeld"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#password", "attr": "type", "value": "password" },
|
|
||||||
"message": "Setze den Eingabetyp für Passwort auf <kbd>password</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#password", "attr": "required", "value": true },
|
|
||||||
"message": "Mache das Passwort-Feld zum Pflichtfeld"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#password", "attr": "minlength", "value": "8" },
|
|
||||||
"message": "Füge <kbd>minlength</kbd>=\"8\" zum Passwort hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#terms", "attr": "required", "value": true },
|
|
||||||
"message": "Mache die AGB-Checkbox zum Pflichtfeld"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -44,12 +44,12 @@
|
|||||||
"id": "progress-indeterminate",
|
"id": "progress-indeterminate",
|
||||||
"title": "Unbestimmter Fortschritt",
|
"title": "Unbestimmter Fortschritt",
|
||||||
"description": "Wenn der Fortschritt unbekannt ist (wie beim Laden), lasse das <kbd>value</kbd>-Attribut weg. Dies erstellt einen animierten unbestimmten Zustand.<br><br>Nützlich für Netzwerkanfragen oder Prozesse mit unbekannter Dauer.",
|
"description": "Wenn der Fortschritt unbekannt ist (wie beim Laden), lasse das <kbd>value</kbd>-Attribut weg. Dies erstellt einen animierten unbestimmten Zustand.<br><br>Nützlich für Netzwerkanfragen oder Prozesse mit unbekannter Dauer.",
|
||||||
"task": "Erstelle eine Ladeanzeige:<br>1. Füge ein <kbd><p></kbd> mit <code>Lädt...</code> hinzu<br>2. Füge ein <kbd><progress></kbd> ohne value-Attribut hinzu",
|
"task": "Erstelle eine Ladeanzeige:<br>1. Füge ein <kbd><p></kbd> mit <code>Loading...</code> hinzu<br>2. Füge ein <kbd><progress></kbd> ohne value-Attribut hinzu",
|
||||||
"previewHTML": "",
|
"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; }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<p>Lädt...</p>\n<progress></progress>",
|
"solution": "<p>Loading...</p>\n<progress></progress>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
@@ -68,12 +68,12 @@
|
|||||||
"id": "meter-gauge",
|
"id": "meter-gauge",
|
||||||
"title": "Meter-Anzeigen",
|
"title": "Meter-Anzeigen",
|
||||||
"description": "Das <kbd><meter></kbd>-Element zeigt einen Skalarwert innerhalb eines Bereichs. Verwende es für Messungen wie Speicherplatz, Akku oder Bewertungen.<br><br>Setze <kbd>low</kbd>, <kbd>high</kbd> und <kbd>optimum</kbd>, um gute/schlechte Bereiche zu definieren - der Browser färbt es entsprechend ein!",
|
"description": "Das <kbd><meter></kbd>-Element zeigt einen Skalarwert innerhalb eines Bereichs. Verwende es für Messungen wie Speicherplatz, Akku oder Bewertungen.<br><br>Setze <kbd>low</kbd>, <kbd>high</kbd> und <kbd>optimum</kbd>, um gute/schlechte Bereiche zu definieren - der Browser färbt es entsprechend ein!",
|
||||||
"task": "Erstelle eine Akku-Anzeige:<br>1. Füge ein <kbd><label></kbd> mit <code>Akku:</code> hinzu<br>2. Füge ein <kbd><meter></kbd> hinzu mit:<br> - <kbd>value=\"0.8\"</kbd><br> - <kbd>min=\"0\"</kbd> und <kbd>max=\"1\"</kbd><br> - <kbd>low=\"0.2\"</kbd> und <kbd>high=\"0.8\"</kbd><br> - <kbd>optimum=\"1\"</kbd>",
|
"task": "Erstelle eine Akku-Anzeige:<br>1. Füge ein <kbd><label></kbd> mit <code>Battery:</code> hinzu<br>2. Füge ein <kbd><meter></kbd> hinzu mit:<br> - <kbd>value=\"0.8\"</kbd><br> - <kbd>min=\"0\"</kbd> und <kbd>max=\"1\"</kbd><br> - <kbd>low=\"0.2\"</kbd> und <kbd>high=\"0.8\"</kbd><br> - <kbd>optimum=\"1\"</kbd>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<label for=\"battery\">Akku:</label>\n<meter id=\"battery\" value=\"0.8\" min=\"0\" max=\"1\" low=\"0.2\" high=\"0.8\" optimum=\"1\">80%</meter>",
|
"solution": "<label for=\"battery\">Battery:</label>\n<meter id=\"battery\" value=\"0.8\" min=\"0\" max=\"1\" low=\"0.2\" high=\"0.8\" optimum=\"1\">80%</meter>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
@@ -86,11 +86,31 @@
|
|||||||
"value": { "selector": "meter", "attr": "value", "value": "0.8" },
|
"value": { "selector": "meter", "attr": "value", "value": "0.8" },
|
||||||
"message": "Setze <kbd>value=</kbd>\"0.8\" beim Meter"
|
"message": "Setze <kbd>value=</kbd>\"0.8\" beim Meter"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "meter", "attr": "min", "value": "0" },
|
||||||
|
"message": "Setze <kbd>min=</kbd>\"0\" beim Meter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "meter", "attr": "max", "value": "1" },
|
||||||
|
"message": "Setze <kbd>max=</kbd>\"1\" beim Meter"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "meter", "attr": "low", "value": "0.2" },
|
"value": { "selector": "meter", "attr": "low", "value": "0.2" },
|
||||||
"message": "Setze <kbd>low=</kbd>\"0.2\", um den niedrigen Schwellenwert zu definieren"
|
"message": "Setze <kbd>low=</kbd>\"0.2\", um den niedrigen Schwellenwert zu definieren"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "meter", "attr": "high", "value": "0.8" },
|
||||||
|
"message": "Setze <kbd>high=</kbd>\"0.8\", um den hohen Schwellenwert zu definieren"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "meter", "attr": "optimum", "value": "1" },
|
||||||
|
"message": "Setze <kbd>optimum=</kbd>\"1\", um den optimalen Wert anzugeben"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "label",
|
"value": "label",
|
||||||
|
|||||||
@@ -2,20 +2,20 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-tables",
|
"id": "html-tables",
|
||||||
"title": "HTML Tabellen",
|
"title": "HTML Tabellen",
|
||||||
"description": "Erstelle strukturierte Datentabellen mit Überschriften und Beschriftungen",
|
"description": "Erstelle strukturierte Datentabellen mit semantischem Markup",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "table-basic",
|
"id": "table-basic",
|
||||||
"title": "Grundlegende Tabellenstruktur",
|
"title": "Datentabellen",
|
||||||
"description": "Tabellen verwenden <kbd><table></kbd> mit <kbd><tr></kbd> für Zeilen. In Zeilen nutze <kbd><th></kbd> für Überschriften und <kbd><td></kbd> für Datenzellen.<br><br>Das <kbd><caption></kbd>-Element bietet einen zugänglichen Titel für die Tabelle.",
|
"description": "Tabellen zeigen strukturierte Daten in Zeilen und Spalten. Verwende <kbd><table></kbd> als Container, <kbd><tr></kbd> für Zeilen, <kbd><th></kbd> für Kopfzellen und <kbd><td></kbd> für Datenzellen.<br><br>Füge <kbd><caption></kbd> hinzu für einen zugänglichen Titel, der den Tabelleninhalt beschreibt.",
|
||||||
"task": "Erstelle eine einfache Tabelle mit:<br>1. Einer <kbd><caption></kbd> mit <code>Obstpreise</code><br>2. Einer Kopfzeile mit <code>Obst</code> und <code>Preis</code> Spalten<br>3. Mindestens 2 Datenzeilen",
|
"task": "Erstelle eine Preistabelle:<br>1. Eine <kbd><caption></kbd> mit <code>Pricing</code><br>2. Eine Kopfzeile mit <code>Plan</code> und <code>Price</code><br>3. Zwei Datenzeilen für Basic ($9) und Pro ($29)",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px; font-weight: 600; font-size: 1.2rem; color: #333; background: #f8f9fa; } th, td { padding: 12px 20px; text-align: left; border-bottom: 1px solid #eee; } th { background: #3498db; color: white; font-weight: 500; } tr:hover { background: #f8f9fa; } tr:last-child td { border-bottom: none; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; } table { border-collapse: collapse; width: 100%; max-width: 350px; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 16px; font-weight: 600; font-size: 1.1rem; color: #333; background: #f8f9fa; } th, td { padding: 14px 20px; text-align: left; border-bottom: 1px solid #eee; } th { background: steelblue; color: white; font-weight: 500; } tr:last-child td { border-bottom: none; } tr:hover td { background: #f8f9fa; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<table>\n <caption>Obstpreise</caption>\n <tr>\n <th>Obst</th>\n <th>Preis</th>\n </tr>\n <tr>\n <td>Apfel</td>\n <td>1,50 €</td>\n </tr>\n <tr>\n <td>Banane</td>\n <td>0,75 €</td>\n </tr>\n</table>",
|
"solution": "<table>\n <caption>Pricing</caption>\n <tr>\n <th>Plan</th>\n <th>Price</th>\n </tr>\n <tr>\n <td>Basic</td>\n <td>$9</td>\n </tr>\n <tr>\n <td>Pro</td>\n <td>$29</td>\n </tr>\n</table>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
@@ -26,100 +26,17 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "caption",
|
"value": "caption",
|
||||||
"message": "Füge eine <kbd><caption></kbd> als Tabellentitel hinzu"
|
"message": "Füge eine <kbd><caption></kbd> für den Tabellentitel hinzu"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "th", "min": 2 },
|
"value": { "selector": "th", "min": 2 },
|
||||||
"message": "Füge mindestens 2 Überschriftszellen (th) hinzu"
|
"message": "Füge Kopfzellen (<kbd><th></kbd>) für Plan und Price hinzu"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "tr", "min": 3 },
|
"value": { "selector": "tr", "min": 3 },
|
||||||
"message": "Füge mindestens 3 Zeilen hinzu (1 Kopf + 2 Daten)"
|
"message": "Füge 3 Zeilen hinzu (1 Kopf + 2 Datenzeilen)"
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "table-thead-tbody",
|
|
||||||
"title": "Tabellenkopf & -körper",
|
|
||||||
"description": "Verwende <kbd><thead></kbd> zum Gruppieren von Kopfzeilen und <kbd><tbody></kbd> zum Gruppieren von Datenzeilen. Das hilft Browsern und Hilfstechnologien, die Tabellenstruktur zu verstehen.<br><br>Du kannst auch <kbd><tfoot></kbd> für Fußzeilen wie Summen verwenden.",
|
|
||||||
"task": "Erstelle eine strukturierte Tabelle:<br>1. Eine <kbd><caption></kbd> mit <code>Monatliche Verkäufe</code><br>2. Ein <kbd><thead></kbd> mit Monat und Umsatz Überschriften<br>3. Ein <kbd><tbody></kbd> mit mindestens 2 Datenzeilen",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } table { border-collapse: collapse; width: 100%; max-width: 450px; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.2); } caption { padding: 20px; font-weight: 700; font-size: 1.3rem; color: white; background: transparent; text-shadow: 0 2px 4px rgba(0,0,0,0.2); caption-side: top; } thead { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } th { padding: 15px 20px; text-align: left; color: white; font-weight: 500; } tbody tr { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } td { padding: 15px 20px; color: #333; } tbody tr:last-child { border-bottom: none; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "",
|
|
||||||
"solution": "<table>\n <caption>Monatliche Verkäufe</caption>\n <thead>\n <tr>\n <th>Monat</th>\n <th>Umsatz</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Januar</td>\n <td>12.500 €</td>\n </tr>\n <tr>\n <td>Februar</td>\n <td>14.200 €</td>\n </tr>\n </tbody>\n</table>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "table",
|
|
||||||
"message": "Füge ein <kbd><table></kbd>-Element hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "caption",
|
|
||||||
"message": "Füge ein <kbd><caption></kbd>-Element hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "thead",
|
|
||||||
"message": "Füge ein <kbd><thead></kbd> für den Kopfbereich hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "tbody",
|
|
||||||
"message": "Füge ein <kbd><tbody></kbd> für die Datenzeilen hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_count",
|
|
||||||
"value": { "selector": "tbody tr", "min": 2 },
|
|
||||||
"message": "Füge mindestens 2 Datenzeilen in tbody hinzu"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "table-complete",
|
|
||||||
"title": "Vollständige Tabelle mit Fuß",
|
|
||||||
"description": "Füge <kbd><tfoot></kbd> hinzu, um einen Fußbereich für Summen oder Zusammenfassungen zu erstellen. Der Fuß bleibt unten, auch wenn tbody viele Zeilen hat.<br><br>Kombiniere alle Abschnitte für eine vollständig strukturierte, zugängliche Tabelle.",
|
|
||||||
"task": "Erstelle eine vollständige Tabelle:<br>1. Eine <kbd><caption></kbd> mit <code>Bestellübersicht</code><br>2. Ein <kbd><thead></kbd> mit Artikel und Preis Überschriften<br>3. Ein <kbd><tbody></kbd> mit 2 Artikeln<br>4. Ein <kbd><tfoot></kbd> mit einer Summenzeile",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px 20px; font-weight: 600; font-size: 1.1rem; color: #333; text-align: left; background: #f8f9fa; border-bottom: 2px solid #eee; } th, td { padding: 12px 20px; text-align: left; } thead th { background: #2c3e50; color: white; } tbody td { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } tfoot { background: #ecf0f1; font-weight: 600; } tfoot td { border-top: 2px solid #2c3e50; color: #2c3e50; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "",
|
|
||||||
"solution": "<table>\n <caption>Bestellübersicht</caption>\n <thead>\n <tr>\n <th>Artikel</th>\n <th>Preis</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Widget</td>\n <td>25,00 €</td>\n </tr>\n <tr>\n <td>Gadget</td>\n <td>35,00 €</td>\n </tr>\n </tbody>\n <tfoot>\n <tr>\n <td>Gesamt</td>\n <td>60,00 €</td>\n </tr>\n </tfoot>\n</table>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "table",
|
|
||||||
"message": "Füge ein <kbd><table></kbd>-Element hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "caption",
|
|
||||||
"message": "Füge ein <kbd><caption></kbd>-Element hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "thead",
|
|
||||||
"message": "Füge einen <kbd><thead></kbd>-Abschnitt hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "tbody",
|
|
||||||
"message": "Füge einen <kbd><tbody></kbd>-Abschnitt hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "tfoot",
|
|
||||||
"message": "Füge einen <kbd><tfoot></kbd>-Abschnitt für die Summe hinzu"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_count",
|
|
||||||
"value": { "selector": "tbody tr", "min": 2 },
|
|
||||||
"message": "Füge mindestens 2 Artikelzeilen in tbody hinzu"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -15,7 +15,7 @@
|
|||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; display: flex; justify-content: center; } svg { background: white; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; display: flex; justify-content: center; } svg { background: white; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"50\" fill=\"#3498db\" />\n</svg>",
|
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"50\" fill=\"steelblue\" />\n</svg>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
@@ -28,6 +28,16 @@
|
|||||||
"value": "circle",
|
"value": "circle",
|
||||||
"message": "Füge ein <kbd><circle></kbd>-Element in das SVG ein"
|
"message": "Füge ein <kbd><circle></kbd>-Element in das SVG ein"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "svg", "attr": "width", "value": "200" },
|
||||||
|
"message": "Setze <kbd>width=</kbd>\"200\" beim SVG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "svg", "attr": "height", "value": "200" },
|
||||||
|
"message": "Setze <kbd>height=</kbd>\"200\" beim SVG"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "circle", "attr": "cx", "value": "100" },
|
"value": { "selector": "circle", "attr": "cx", "value": "100" },
|
||||||
@@ -37,6 +47,11 @@
|
|||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "circle", "attr": "cy", "value": "100" },
|
"value": { "selector": "circle", "attr": "cy", "value": "100" },
|
||||||
"message": "Setze <kbd>cy=</kbd>\"100\" für das vertikale Zentrum"
|
"message": "Setze <kbd>cy=</kbd>\"100\" für das vertikale Zentrum"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "circle", "attr": "r", "value": "50" },
|
||||||
|
"message": "Setze <kbd>r=</kbd>\"50\" für den Radius"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -49,7 +64,7 @@
|
|||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 200px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 200px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<svg width=\"200\" height=\"150\">\n <rect x=\"20\" y=\"20\" width=\"80\" height=\"60\" fill=\"#e74c3c\" />\n <line x1=\"120\" y1=\"30\" x2=\"180\" y2=\"90\" stroke=\"#2c3e50\" stroke-width=\"3\" />\n</svg>",
|
"solution": "<svg width=\"200\" height=\"150\">\n <rect x=\"20\" y=\"20\" width=\"80\" height=\"60\" fill=\"tomato\" />\n <line x1=\"120\" y1=\"30\" x2=\"180\" y2=\"90\" stroke=\"slategray\" stroke-width=\"3\" />\n</svg>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
@@ -66,6 +81,61 @@
|
|||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "line",
|
"value": "line",
|
||||||
"message": "Füge ein <kbd><line></kbd>-Element hinzu"
|
"message": "Füge ein <kbd><line></kbd>-Element hinzu"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "svg", "attr": "width", "value": "200" },
|
||||||
|
"message": "Setze <kbd>width=</kbd>\"200\" beim SVG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "svg", "attr": "height", "value": "150" },
|
||||||
|
"message": "Setze <kbd>height=</kbd>\"150\" beim SVG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "rect", "attr": "x", "value": "20" },
|
||||||
|
"message": "Setze <kbd>x=</kbd>\"20\" beim rect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "rect", "attr": "y", "value": "20" },
|
||||||
|
"message": "Setze <kbd>y=</kbd>\"20\" beim rect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "rect", "attr": "width", "value": "80" },
|
||||||
|
"message": "Setze <kbd>width=</kbd>\"80\" beim rect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "rect", "attr": "height", "value": "60" },
|
||||||
|
"message": "Setze <kbd>height=</kbd>\"60\" beim rect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "line", "attr": "x1", "value": "120" },
|
||||||
|
"message": "Setze <kbd>x1=</kbd>\"120\" bei der line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "line", "attr": "y1", "value": "30" },
|
||||||
|
"message": "Setze <kbd>y1=</kbd>\"30\" bei der line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "line", "attr": "x2", "value": "180" },
|
||||||
|
"message": "Setze <kbd>x2=</kbd>\"180\" bei der line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "line", "attr": "y2", "value": "90" },
|
||||||
|
"message": "Setze <kbd>y2=</kbd>\"90\" bei der line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "stroke",
|
||||||
|
"message": "Füge eine <kbd>stroke</kbd>-Farbe zur line hinzu"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
@@ -78,7 +148,7 @@
|
|||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); min-height: 250px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); min-height: 250px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"80\" fill=\"#f1c40f\" stroke=\"#e67e22\" stroke-width=\"4\" />\n <circle cx=\"70\" cy=\"80\" r=\"10\" fill=\"#2c3e50\" />\n <circle cx=\"130\" cy=\"80\" r=\"10\" fill=\"#2c3e50\" />\n <line x1=\"60\" y1=\"130\" x2=\"140\" y2=\"130\" stroke=\"#2c3e50\" stroke-width=\"4\" stroke-linecap=\"round\" />\n</svg>",
|
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"80\" fill=\"gold\" stroke=\"orange\" stroke-width=\"4\" />\n <circle cx=\"70\" cy=\"80\" r=\"10\" fill=\"darkslategray\" />\n <circle cx=\"130\" cy=\"80\" r=\"10\" fill=\"darkslategray\" />\n <line x1=\"60\" y1=\"130\" x2=\"140\" y2=\"130\" stroke=\"darkslategray\" stroke-width=\"4\" stroke-linecap=\"round\" />\n</svg>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
|
|||||||
@@ -7,13 +7,13 @@
|
|||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "flexbox-1",
|
"id": "flexbox-1",
|
||||||
"title": "Flex Container",
|
"title": "Container",
|
||||||
"description": "Flexbox ist ein eindimensionales Layout-System. Mit <kbd>display: flex</kbd> wird ein Element zum Flex-Container. Alle direkten Kinder werden automatisch zu Flex-Items und richten sich horizontal (Hauptachse) aus. Die Querachse verläuft senkrecht dazu.",
|
"description": "Vor Flexbox erforderten selbst einfache Layouts Floats, Positionierungs-Hacks oder tabellenbasierte Layouts. Flexbox (Flexible Box Layout) revolutionierte CSS, indem es ein eindimensionales Layout-System speziell für Platzverteilung und Inhaltsausrichtung bereitstellte.<br><br><strong>So funktioniert es:</strong> Wenn du <kbd>display: flex</kbd> auf ein Element setzt, wird es zum <em>Flex-Container</em>. Seine direkten Kinder werden automatisch zu <em>Flex-Items</em>, die entlang einer Hauptachse fließen (standardmäßig horizontal). Diese eine Eigenschaft verwandelt gestapelte Block-Elemente in eine horizontale Reihe.<br><br><strong>Die zwei Achsen:</strong><br>• <em>Hauptachse</em> – Die primäre Richtung, in der Items fließen (row = links→rechts)<br>• <em>Querachse</em> – Senkrecht zur Hauptachse (row = oben→unten)<br><br><pre>.nav {\n display: flex;\n}</pre>",
|
||||||
"task": "Füge <kbd>display: flex</kbd> zu <kbd>.wrap</kbd> hinzu.",
|
"task": "Dieses Navigationsmenü stapelt sich vertikal. Füge <kbd>display: flex</kbd> hinzu, um die Links horizontal anzuordnen.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box'>2</div><div class='box'>3</div></div>",
|
"previewHTML": "<nav class=\"nav\"><a href=\"#\">Home</a><a href=\"#\">Products</a><a href=\"#\">About</a><a href=\"#\">Contact</a></nav>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .nav { background: #1a1a2e; padding: 1rem; } .nav a { color: white; text-decoration: none; padding: 8px 1rem; border-radius: 4px; } .nav a:hover { background: rgba(255,255,255,0.1); }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".wrap {\n ",
|
"codePrefix": ".nav {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "display: flex;",
|
"solution": "display: flex;",
|
||||||
@@ -28,41 +28,34 @@
|
|||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-2",
|
"id": "flexbox-2",
|
||||||
"title": "Direction & Wrap",
|
"title": "Gap",
|
||||||
"description": "<kbd>flex-direction</kbd> bestimmt die Hauptachse: <kbd>row</kbd> (horizontal, Standard) oder <kbd>column</kbd> (vertikal). Mit <kbd>flex-wrap: wrap</kbd> brechen Items in die nächste Zeile/Spalte um, wenn der Platz nicht reicht.",
|
"description": "Die <kbd>gap</kbd>-Eigenschaft fügt konsistenten Abstand zwischen Flex-Items hinzu, ohne dass Margins nötig sind. Sie erzeugt nur Platz zwischen Items, nicht an den Rändern.",
|
||||||
"task": "Füge <kbd>flex-direction: column</kbd> und <kbd>flex-wrap: wrap</kbd> zu <kbd>.wrap</kbd> hinzu.",
|
"task": "Füge <kbd>gap: 1rem</kbd> hinzu, um die Navigationslinks gleichmäßig zu verteilen.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box'>2</div><div class='box'>3</div><div class='box'>4</div><div class='box'>5</div></div>",
|
"previewHTML": "<nav class=\"nav\"><a href=\"#\">Home</a><a href=\"#\">Products</a><a href=\"#\">About</a><a href=\"#\">Contact</a></nav>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; width: 3rem; height: 3rem; display: flex; align-items: center; justify-content: center; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .nav { background: #1a1a2e; padding: 1rem; display: flex; } .nav a { color: white; text-decoration: none; padding: 8px 1rem; border-radius: 4px; background: rgba(255,255,255,0.1); }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; height: 16rem; display: flex; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".wrap {\n ",
|
"codePrefix": ".nav {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "flex-direction: column;\n flex-wrap: wrap;",
|
"solution": "gap: 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "flex-direction", "expected": "column" },
|
"value": { "property": "gap", "expected": "1rem" },
|
||||||
"message": "Setze <kbd>flex-direction: column</kbd>",
|
"message": "Setze <kbd>gap: 1rem</kbd>"
|
||||||
"options": { "exact": true }
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": { "property": "flex-wrap", "expected": "wrap" },
|
|
||||||
"message": "Setze <kbd>flex-wrap: wrap</kbd>",
|
|
||||||
"options": { "exact": true }
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-3",
|
"id": "flexbox-3",
|
||||||
"title": "Justify Content",
|
"title": "Justify Content",
|
||||||
"description": "<kbd>justify-content</kbd> verteilt Items entlang der Hauptachse. Werte: <kbd>flex-start</kbd> (Anfang), <kbd>flex-end</kbd> (Ende), <kbd>center</kbd> (Mitte), <kbd>space-between</kbd> (gleichmäßig mit Abstand), <kbd>space-around</kbd> (gleichmäßig mit Rand).",
|
"description": "<kbd>justify-content</kbd> verteilt Items entlang der Hauptachse. Häufige Werte:<br>• <kbd>flex-start</kbd> – Items am Anfang<br>• <kbd>flex-end</kbd> – Items am Ende<br>• <kbd>center</kbd> – Items zentrieren<br>• <kbd>space-between</kbd> – Gleicher Abstand zwischen Items<br>• <kbd>space-around</kbd> – Gleicher Abstand um Items",
|
||||||
"task": "Füge <kbd>justify-content: space-between</kbd> zu <kbd>.wrap</kbd> hinzu.",
|
"task": "Schiebe den \"Login\"-Button nach rechts, indem du <kbd>justify-content: space-between</kbd> auf die Navigation setzt.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box'>2</div><div class='box'>3</div></div>",
|
"previewHTML": "<nav class=\"nav\"><div class=\"links\"><a href=\"#\">Home</a><a href=\"#\">Products</a><a href=\"#\">About</a></div><a href=\"#\" class=\"login\">Login</a></nav>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; text-align: center; font-weight: bold; width: 3rem; height: 3rem; display: flex; align-items: center; justify-content: center; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .nav { background: #1a1a2e; padding: 1rem; display: flex; } .links { display: flex; gap: 8px; } .nav a { color: white; text-decoration: none; padding: 8px 1rem; border-radius: 4px; } .nav a:hover { background: rgba(255,255,255,0.1); } .login { background: steelblue; }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".wrap {\n ",
|
"codePrefix": ".nav {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "justify-content: space-between;",
|
"solution": "justify-content: space-between;",
|
||||||
@@ -71,20 +64,19 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "justify-content", "expected": "space-between" },
|
"value": { "property": "justify-content", "expected": "space-between" },
|
||||||
"message": "Setze <kbd>justify-content: space-between</kbd>",
|
"message": "Setze <kbd>justify-content: space-between</kbd>"
|
||||||
"options": { "exact": true }
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-4",
|
"id": "flexbox-4",
|
||||||
"title": "Align Items",
|
"title": "Align Items",
|
||||||
"description": "<kbd>align-items</kbd> richtet Items entlang der Querachse aus (bei <kbd>row</kbd>: vertikal). Werte: <kbd>stretch</kbd> (Standard, füllt Höhe), <kbd>flex-start</kbd> (oben), <kbd>flex-end</kbd> (unten), <kbd>center</kbd> (Mitte), <kbd>baseline</kbd> (Textlinie).",
|
"description": "<kbd>align-items</kbd> steuert die Ausrichtung auf der Querachse (vertikal bei flex-direction: row). Werte sind:<br>• <kbd>stretch</kbd> – Ausdehnen zum Füllen (Standard)<br>• <kbd>flex-start</kbd> – Oben ausrichten<br>• <kbd>flex-end</kbd> – Unten ausrichten<br>• <kbd>center</kbd> – Vertikal zentrieren",
|
||||||
"task": "Füge <kbd>align-items: center</kbd> zu <kbd>.wrap</kbd> hinzu.",
|
"task": "Das Logo und die Nav-Links haben unterschiedliche Höhen. Zentriere sie vertikal mit <kbd>align-items: center</kbd>.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box tall'>1</div><div class='box'>2</div><div class='box short'>3</div></div>",
|
"previewHTML": "<header class=\"header\"><div class=\"logo\">ACME</div><nav><a href=\"#\">Products</a><a href=\"#\">Pricing</a><a href=\"#\">Docs</a></nav></header>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; width: 3rem; display: flex; justify-content: center; } .tall { height: 6rem; } .short { height: 3rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .header { background: white; padding: 1rem 2rem; display: flex; justify-content: space-between; border-bottom: 1px solid #eee; } .logo { font-size: 1.5rem; font-weight: bold; color: steelblue; } nav { display: flex; gap: 1rem; } nav a { color: #333; text-decoration: none; font-size: 0.9rem; }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; height: 10rem; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".wrap {\n ",
|
"codePrefix": ".header {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "align-items: center;",
|
"solution": "align-items: center;",
|
||||||
@@ -93,50 +85,49 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "align-items", "expected": "center" },
|
"value": { "property": "align-items", "expected": "center" },
|
||||||
"message": "Setze <kbd>align-items: center</kbd>",
|
"message": "Setze <kbd>align-items: center</kbd>"
|
||||||
"options": { "exact": true }
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-5",
|
"id": "flexbox-5",
|
||||||
"title": "Flex Grow",
|
"title": "Flex Wrap",
|
||||||
"description": "Die <kbd>flex</kbd>-Eigenschaft ist eine Kurzform für <kbd>flex-grow</kbd>, <kbd>flex-shrink</kbd> und <kbd>flex-basis</kbd>. Ein höherer Wert bedeutet, dass das Element mehr vom verfügbaren Platz einnimmt. <kbd>flex: 2</kbd> wächst doppelt so schnell wie <kbd>flex: 1</kbd>.",
|
"description": "Standardmäßig quetschen sich Flex-Items in eine Zeile. <kbd>flex-wrap: wrap</kbd> erlaubt Items, auf mehrere Zeilen umzubrechen, wenn der Platz nicht reicht.",
|
||||||
"task": "Füge <kbd>flex: 2</kbd> zu <kbd>.box2</kbd> hinzu.",
|
"task": "Diese Karten laufen über den Container hinaus. Füge <kbd>flex-wrap: wrap</kbd> hinzu, damit sie in neue Zeilen umbrechen können.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box box1'>1</div><div class='box box2'>2</div><div class='box box3'>3</div></div>",
|
"previewHTML": "<div class=\"cards\"><article class=\"card\">Card 1</article><article class=\"card\">Card 2</article><article class=\"card\">Card 3</article><article class=\"card\">Card 4</article><article class=\"card\">Card 5</article><article class=\"card\">Card 6</article></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; display: flex; align-items: center; justify-content: center; } .box1 { background: coral; flex: 1; } .box2 { background: mediumseagreen; } .box3 { background: gold; flex: 1; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .cards { display: flex; gap: 1rem; } .card { background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); min-width: 120px; text-align: center; }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".box2 {\n ",
|
"codePrefix": ".cards {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "flex: 2;",
|
"solution": "flex-wrap: wrap;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "flex", "expected": "2" },
|
"value": { "property": "flex-wrap", "expected": "wrap" },
|
||||||
"message": "Setze <kbd>flex: 2</kbd>"
|
"message": "Setze <kbd>flex-wrap: wrap</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-6",
|
"id": "flexbox-6",
|
||||||
"title": "Align Self",
|
"title": "Flex Grow",
|
||||||
"description": "<kbd>align-self</kbd> überschreibt <kbd>align-items</kbd> für ein einzelnes Element. So kannst du ein Item individuell auf der Querachse positionieren, während alle anderen Items ihrer Standard-Ausrichtung folgen.",
|
"description": "Die <kbd>flex</kbd>-Eigenschaft auf Items steuert, wie sie wachsen und schrumpfen. <kbd>flex: 1</kbd> lässt ein Item wachsen, um verfügbaren Platz zu füllen. Mehrere Items mit <kbd>flex: 1</kbd> teilen sich den Platz gleichmäßig.",
|
||||||
"task": "Füge <kbd>align-self: flex-start</kbd> zu <kbd>.middle</kbd> hinzu.",
|
"task": "Lass das Suchfeld den verfügbaren Platz ausfüllen, indem du <kbd>flex: 1</kbd> auf <kbd>.search</kbd> setzt.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box middle'>2</div><div class='box'>3</div></div>",
|
"previewHTML": "<div class=\"toolbar\"><input class=\"search\" type=\"text\" placeholder=\"Search...\"><button class=\"btn\">Search</button><button class=\"btn\">Filters</button></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; width: 3rem; height: 3rem; display: flex; align-items: center; justify-content: center; } .middle { background: mediumseagreen; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .toolbar { display: flex; gap: 8px; padding: 1rem; background: #f5f5f5; border-radius: 8px; } .search { padding: 8px 1rem; border: 1px solid #ddd; border-radius: 4px; font-size: 1rem; } .btn { padding: 8px 1rem; background: steelblue; color: white; border: none; border-radius: 4px; cursor: pointer; }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; height: 12rem; align-items: center; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".middle {\n ",
|
"codePrefix": ".search {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "align-self: flex-start;",
|
"solution": "flex: 1;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "align-self", "expected": "flex-start" },
|
"value": { "property": "flex", "expected": "1" },
|
||||||
"message": "Setze <kbd>align-self: flex-start</kbd>"
|
"message": "Setze <kbd>flex: 1</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,548 +1,257 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "css-basic-selectors",
|
"id": "css-basic-selectors",
|
||||||
"title": "CSS Selectors",
|
"title": "Fundamentos de CSS",
|
||||||
"description": "CSS selectors are the foundation of styling web pages, allowing you to target specific HTML elements for styling. This module introduces fundamental selector types including element type selectors, class selectors, ID selectors, and the universal selector.",
|
"description": "Aprende los bloques fundamentales de CSS: propiedades, valores y selectores. Este módulo te enseña las reglas de sintaxis que sigue cada declaración CSS.",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "introduction-to-selectors",
|
"id": "css-properties",
|
||||||
"title": "What's a Selector?",
|
"title": "Propiedades CSS",
|
||||||
"description": "A CSS selector is the first part of a CSS rule that tells the browser which HTML elements should receive the styles defined in the declaration block. Selectors are essentially patterns that match against elements in your HTML document. Understanding selectors is fundamental because they determine which elements your CSS rules will affect. The element or elements targeted by a selector are referred to as the 'subject of the selector.' When writing a CSS rule, you first specify the selector, followed by curly braces that contain the style declarations.<br/>For example, to change the text color of elements, you can use the <kbd>color</kbd> property within your declaration block.<br><br><pre>/* Element selector */\np {\n color: orangered;\n /* │ └─── Indicates the value of the expression\n │ \n └─────────── Indicates the property of the expression */\n}</pre>",
|
"description": "CSS estiliza elementos usando <strong>declaraciones</strong> - pares de propiedades y valores. Cada declaración sigue el mismo patrón:<br><br><pre>property: value;</pre><br>La <strong>propiedad</strong> es lo que quieres cambiar (como <kbd>color</kbd> o <kbd>background</kbd>). El <strong>valor</strong> es a lo que lo estableces. Dos puntos los separan, y un punto y coma termina la línea.<br><br>Los valores vienen en diferentes tipos:<br>• <strong>Palabras clave:</strong> <kbd>red</kbd>, <kbd>bold</kbd>, <kbd>center</kbd><br>• <strong>Números con unidades:</strong> <kbd>16px</kbd>, <kbd>2rem</kbd>, <kbd>100%</kbd><br>• <strong>Colores:</strong> <kbd>steelblue</kbd>, <kbd>#ff0000</kbd>",
|
||||||
"task": "Write a CSS rule using a type selector that targets all paragraph elements <kbd>p</kbd> in the document. Make the text blue by setting the <kbd>color</kbd> property to <kbd>blue</kbd>.",
|
"task": "Completa la declaración añadiendo <kbd>color: coral;</kbd> para cambiar el color del texto.",
|
||||||
"previewHTML": "<h1>Introduction to CSS Selectors</h1>\n<p>This paragraph should turn blue.</p>\n<div>This div element should remain unchanged.</div>\n<p>This second paragraph should also turn blue.</p>",
|
"previewHTML": "<p class=\"text\">This text should turn coral.</p>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } .text { font-size: 1.25rem; }",
|
||||||
"sandboxCSS": "h1, p, div { padding: 8px; margin-bottom: 10px; border: 1px dashed #ccc; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Write a type selector to target all paragraph elements */\n",
|
"codePrefix": ".text {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "\n}",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"solution": "p { color: blue }",
|
"solution": "color: coral;",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "property_value",
|
||||||
"value": "^p\\s*{",
|
"value": { "property": "color", "expected": "coral" },
|
||||||
"message": "Start your rule with <kbd>p { … }</kbd> to select all paragraph elements",
|
"message": "Añade <kbd>color: coral;</kbd>"
|
||||||
"options": {
|
}
|
||||||
"caseSensitive": false
|
]
|
||||||
}
|
},
|
||||||
},
|
{
|
||||||
|
"id": "multiple-properties",
|
||||||
|
"title": "Múltiples propiedades",
|
||||||
|
"description": "Una regla puede tener múltiples declaraciones. Cada una va en su propia línea, y cada una necesita un punto y coma al final:<br><br><pre>.box {<br> background: gold;<br> color: navy;<br> padding: 1rem;<br>}</pre><br>El orden normalmente no importa - CSS las aplica todas. Cuando hay conflictos, la última gana.",
|
||||||
|
"task": "Añade dos declaraciones: <kbd>background: lavender;</kbd> y <kbd>padding: 1rem;</kbd>",
|
||||||
|
"previewHTML": "<div class=\"card\">A styled card with background and padding.</div>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } .card { border-radius: 8px; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".card {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "background: lavender;\n padding: 1rem;",
|
||||||
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "color:",
|
"value": { "property": "background", "expected": "lavender" },
|
||||||
"message": "Include the <kbd>color:</kbd> property in your CSS rule"
|
"message": "Añade <kbd>background: lavender;</kbd>"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "blue",
|
|
||||||
"message": "Set the color value to <kbd>blue</kbd>"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"property": "color",
|
"message": "Añade <kbd>padding: 1rem;</kbd>"
|
||||||
"expected": "blue"
|
|
||||||
},
|
|
||||||
"message": "Use <kbd>color: blue</kbd> to set the text color"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "p\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "type-selectors",
|
"id": "type-selectors",
|
||||||
"title": "Type Selectors",
|
"title": "Selectores de tipo",
|
||||||
"description": "Type selectors (also called tag name selectors or element selectors) target HTML elements based on their tag name. For example, <kbd>p</kbd> selects all paragraph elements, <kbd>h1</kbd> selects all level-one headings, and <kbd>div</kbd> selects all division elements. Type selectors are the most fundamental way to select elements, applying styles consistently to all instances of a particular HTML element throughout your document. You can define a variety of CSS properties with type selectors, such as <kbd>color</kbd> for text color, <kbd>background-color</kbd> for the background, and <kbd>font-weight</kbd> for text emphasis. They provide a broad approach for styling your page and are often the starting point for more specific styling using other selector types.",
|
"description": "Un <strong>selector</strong> le dice al navegador qué elementos estilizar. El selector más simple es un <strong>selector de tipo</strong> — simplemente el nombre de la etiqueta HTML.<br><br><pre>p {<br> color: steelblue;<br>}</pre><br>Esta regla apunta a cada elemento <kbd><p></kbd> en la página. Los selectores de tipo son geniales para establecer estilos base.",
|
||||||
"task": "Write three separate CSS rules using type selectors to target specific HTML elements: make <kbd>h2</kbd> headings <kbd>purple</kbd>, give <kbd>span</kbd> elements a <kbd>yellow</kbd> background, and make <kbd>strong</kbd> elements <kbd>red</kbd>.",
|
"task": "Estiliza todos los párrafos. Escribe una regla con <kbd>p</kbd> como selector y establece <kbd>color: steelblue</kbd>.",
|
||||||
"previewHTML": "<h2>Type Selectors Example</h2>\n<p>Regular paragraph text <span>with a highlighted span</span> that should have a yellow background.</p>\n<p>Another paragraph with <strong>strong important text</strong> that should be red.</p>\n<h2>Another Heading</h2>",
|
"previewHTML": "<article>\n <h2>Fresh Roasted Coffee</h2>\n <p>Our beans are sourced from small farms in Colombia and Ethiopia.</p>\n <p>Each batch is roasted weekly to ensure peak freshness.</p>\n</article>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } h2 { margin: 0 0 1rem; }",
|
||||||
"sandboxCSS": "h2, p, span, strong { padding: 3px; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Write three separate type selectors below */\n\n",
|
"codePrefix": "",
|
||||||
"initialCode": "/* 1. Make h2 headings purple */\n\n\n/* 2. Give span elements a yellow background */\n\n\n/* 3. Make strong elements red */\n",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"solution": "/* 1. Make h2 headings purple */\nh2 {\n color: purple;\n}\n\n/* 2. Give span elements a yellow background */\nspan {\n background-color: yellow;\n}\n\n/* 3. Make strong elements red */\nstrong {\n color: red;\n}",
|
"solution": "p {\n color: steelblue;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^h2\\s*{",
|
"value": "p\\s*\\{",
|
||||||
"message": "Include an <kbd>h2 { … }</kbd> selector"
|
"message": "Empieza con <kbd>p {</kbd> para seleccionar párrafos"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "color", "expected": "steelblue" },
|
||||||
"property": "color",
|
"message": "Establece <kbd>color: steelblue</kbd>"
|
||||||
"expected": "purple"
|
}
|
||||||
},
|
]
|
||||||
"message": "Set the <kbd>color</kbd> property to <kbd>purple</kbd> for h2 elements"
|
},
|
||||||
},
|
{
|
||||||
|
"id": "styling-links",
|
||||||
|
"title": "Estilizando enlaces",
|
||||||
|
"description": "Los selectores de tipo funcionan para cualquier elemento HTML. El selector <kbd>a</kbd> apunta a todos los enlaces de una página.<br><br>Los enlaces tienen un color azul y subrayado por defecto. Puedes cambiar ambos con CSS — usa <kbd>color</kbd> para el texto y <kbd>text-decoration: none</kbd> para quitar el subrayado.",
|
||||||
|
"task": "Estiliza los enlaces de navegación. Escribe una regla con <kbd>a</kbd> como selector y establece <kbd>color: coral</kbd>.",
|
||||||
|
"previewHTML": "<nav>\n <a href=\"#\">Home</a>\n <a href=\"#\">Menu</a>\n <a href=\"#\">About</a>\n <a href=\"#\">Contact</a>\n</nav>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } nav { display: flex; gap: 1.5rem; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "a {\n color: coral;\n}",
|
||||||
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "h2\\s*{[^}]*}",
|
"value": "a\\s*\\{",
|
||||||
"message": "Make sure to close your h2 rule with a closing brace <kbd>}</kbd>"
|
"message": "Empieza con <kbd>a {</kbd> para seleccionar enlaces"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^span\\s*{",
|
|
||||||
"message": "Include a <kbd>span { … }</kbd> selector"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "color", "expected": "coral" },
|
||||||
"property": "background-color",
|
"message": "Establece <kbd>color: coral</kbd>"
|
||||||
"expected": "yellow"
|
|
||||||
},
|
|
||||||
"message": "Set a <kbd>background-color: yellow</kbd> for span elements"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "span\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your span rule with a closing brace <kbd>}</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^strong\\s*{",
|
|
||||||
"message": "Include a <kbd>strong { … }</kbd> selector"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "strong\\s*{\\s*color:\\s*red;[^}]*}",
|
|
||||||
"message": "Set the <kbd>color: red</kbd> for strong elements"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "class-selectors",
|
"id": "class-selectors",
|
||||||
"title": "Class Selectors",
|
"title": "Selectores de clase",
|
||||||
"description": "Class selectors target elements with a specific class attribute value. They begin with a dot (.) followed by the class name. Classes are powerful because they allow you to apply the same styles to multiple elements regardless of their type. An HTML element can have multiple classes (separated by spaces in the class attribute), and a class can be applied to any number of elements. When using class selectors, you can apply properties like <kbd>background-color</kbd> to set the background color of elements, and <kbd>font-weight</kbd> to control text thickness, making text bold or normal. This flexibility makes class selectors one of the most commonly used methods for applying styles in CSS, allowing for modular and reusable styling across your website.",
|
"description": "Los selectores de tipo estilizan <em>todos</em> los elementos de ese tipo. ¿Pero qué si quieres estilizar solo algunos de ellos?<br><br>Los <strong>selectores de clase</strong> apuntan a elementos con un atributo <kbd>class</kbd> específico. Empiezan con un punto:<br><br><pre>.badge {<br> background: coral;<br>}</pre><br>Esto estiliza solo elementos con <kbd>class=\"badge\"</kbd>.",
|
||||||
"task": "Create a CSS rule using a class selector that targets elements with the class <kbd>highlight</kbd>. Give these elements a <kbd>yellow</kbd> background and <kbd>bold</kbd> text.",
|
"task": "Estiliza el badge de notificación. Escribe una regla con <kbd>.badge</kbd> como selector y establece <kbd>background: tomato</kbd>.",
|
||||||
"previewHTML": "<h2>Using Class Selectors</h2>\n<p>This is a regular paragraph, but <span class=\"highlight\">this span has the highlight class</span> applied to it.</p>\n<p class=\"highlight\">This entire paragraph has the highlight class.</p>\n<ul>\n <li>Regular list item</li>\n <li class=\"highlight\">This list item is highlighted</li>\n</ul>",
|
"previewHTML": "<header>\n <h1>Dashboard</h1>\n <span class=\"badge\">3</span>\n</header>\n<p>You have new notifications waiting.</p>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } header { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem; } h1 { margin: 0; font-size: 1.5rem; } .badge { color: white; padding: 0.25rem 0.5rem; border-radius: 999px; font-size: 0.875rem; } p { color: #555; margin: 0; }",
|
||||||
"sandboxCSS": "h2, p, li { padding: 5px; margin-bottom: 10px; }",
|
|
||||||
"codePrefix": "/* Create a class selector for elements with the 'highlight' class */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^\\.highlight\\s*{",
|
|
||||||
"message": "Start your rule with <kbd>.highlight { … }</kbd> to create a class selector",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Include the <kbd>background-color:</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "yellow"
|
|
||||||
},
|
|
||||||
"message": "Set the background color to <kbd>yellow</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "font-weight:",
|
|
||||||
"message": "Include the <kbd>font-weight:</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "font-weight",
|
|
||||||
"expected": "bold"
|
|
||||||
},
|
|
||||||
"message": "Set the font-weight to <kbd>bold</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "\\.highlight\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "multiple-classes",
|
|
||||||
"title": "Multiple Classes",
|
|
||||||
"description": "HTML elements can have multiple classes applied simultaneously, allowing for composable and modular CSS designs. When an element has multiple classes, it will receive styles from all matching class selectors. This approach enables you to build a library of reusable CSS classes that can be combined in different ways. You can also target elements that have a specific combination of classes by chaining class selectors together without spaces (e.g., <kbd>.class1.class2</kbd>). When styling these elements, you might use properties like <kbd>border-color</kbd> to change the color of element borders, and <kbd>background-color</kbd> to set the background color of elements. This technique lets you create conditional styles that only apply when certain classes appear together.",
|
|
||||||
"task": "Complete the CSS rule that targets elements with both <kbd>card</kbd> and <kbd>featured</kbd> classes by chaining the selectors. Set the border-color to gold and the background-color to lemonchiffon to make featured cards stand out.",
|
|
||||||
"previewHTML": "<h2>Multiple Class Combinations</h2>\n<div class=\"card\">Regular Card</div>\n<div class=\"card featured\">Featured Card</div>\n<div class=\"featured\">Just Featured (not a card)</div>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } .card { border: 2px solid gray; padding: 15px; margin-bottom: 10px; border-radius: 5px; }",
|
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* The .card class already has basic styling */\n/* Now target elements with BOTH classes: 'card' AND 'featured' */\n",
|
"codePrefix": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"solution": ".card.featured { border-color: gold; background-color: lemonchiffon }",
|
"solution": ".badge {\n background: tomato;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^\\.card\\.featured\\s*{",
|
"value": "\\.badge\\s*\\{",
|
||||||
"message": "Chain the selectors as <kbd>.card.featured</kbd> (no space between them)",
|
"message": "Empieza con <kbd>.badge {</kbd> (¡no olvides el punto!)"
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "border-color:",
|
|
||||||
"message": "Include the <kbd>border-color</kbd> property"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "background", "expected": "tomato" },
|
||||||
"property": "border-color",
|
"message": "Establece <kbd>background: tomato</kbd>"
|
||||||
"expected": "gold"
|
|
||||||
},
|
|
||||||
"message": "Set the border color to <kbd>gold</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "\\.card\\.featured\\s*{[^}]*;",
|
|
||||||
"message": "Make sure to end your CSS rule with a semicolon <kbd>;</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Include the <kbd>background-color</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "lemonchiffon"
|
|
||||||
},
|
|
||||||
"message": "Set the background color to <kbd>lemonchiffon</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "\\.card\\.featured\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "class-with-type",
|
"id": "button-variants",
|
||||||
"title": "Combining Types",
|
"title": "Variantes de botón",
|
||||||
"description": "You can combine type selectors with class selectors to target specific HTML elements that have a certain class. This creates a more specific selector that only matches when both conditions are true: the element is of the specified type AND it has the specified class. For example, <kbd>p.note</kbd> would select paragraph elements with the class <kbd>note</kbd>, but would not select divs or spans with that same class. You can style these combined selections using properties like <kbd>background-color</kbd> to set a colored background for your elements. This approach allows you to apply different styles to the same class when it appears on different element types.",
|
"description": "Los elementos pueden tener múltiples clases. Cuando encadenas selectores de clase sin espacios, apuntas a elementos que tienen <em>todas</em> esas clases:<br><br><pre>.btn.primary {<br> background: steelblue;<br>}</pre><br>Esto apunta a elementos con <kbd>class=\"btn primary\"</kbd>, no solo <kbd>.btn</kbd> o solo <kbd>.primary</kbd>.",
|
||||||
"task": "Create a CSS rule that specifically targets <kbd><span></kbd> elements with the class <kbd>highlight</kbd>. Make those elements have an orange background, while other elements with the highlight class remain untouched.",
|
"task": "Estiliza el botón primario. Escribe una regla con <kbd>.btn.primary</kbd> como selector y establece <kbd>background: steelblue</kbd>.",
|
||||||
"previewHTML": "<h2>Type and Class Combinations</h2>\n<p>This paragraph has a <span class=\"highlight\">highlighted span</span> that should have an orange background.</p>\n<p class=\"highlight\">This paragraph has the highlight class but should NOT have an orange background.</p>",
|
"previewHTML": "<div class=\"actions\">\n <button class=\"btn\">Cancel</button>\n <button class=\"btn primary\">Save Changes</button>\n</div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } .highlight { font-weight: bold; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } .actions { display: flex; gap: 0.75rem; } .btn { padding: 0.5rem 1rem; border: none; border-radius: 6px; font-size: 1rem; cursor: pointer; background: #e0e0e0; color: #333; }",
|
||||||
"sandboxCSS": "h2, p, span { padding: 5px; }",
|
|
||||||
"codePrefix": "/* The .highlight class already sets font-weight to bold */\n/* Now target ONLY span elements with the highlight class */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^span\\.highlight\\s*{",
|
|
||||||
"message": "Use <kbd>span.highlight</kbd> selector (no space between element and class)",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Include the <kbd>background-color</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "orange"
|
|
||||||
},
|
|
||||||
"message": "Set the background color to <kbd>orange</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "span\\.highlight\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "id-selectors",
|
|
||||||
"title": "ID Selectors",
|
|
||||||
"description": "ID selectors target elements with a specific id attribute. They begin with a hash/pound sign (#) followed by the ID name. Unlike classes, IDs must be unique within a document—each ID value should be used only once per page. ID selectors have higher specificity than class or element selectors, meaning they override those selectors when conflicts arise. When styling with ID selectors, you can use properties like <kbd>color</kbd> to define text color, and <kbd>text-decoration</kbd> to control the appearance of text, such as adding underlines to elements. Because of their uniqueness requirement, IDs are best used for one-of-a-kind elements like page headers, main navigation, or specific unique components that appear only once on a page.",
|
|
||||||
"task": "Create a CSS rule with an ID selector that targets the element with the ID <kbd>main-title</kbd>. Set its color to purple and add an underline with <kbd>text-decoration: underline</kbd>.",
|
|
||||||
"previewHTML": "<h1 id=\"main-title\">Main Page Title</h1>\n<p>Regular paragraph content.</p>\n<h2>Secondary Heading</h2>\n<p id=\"intro\">Introduction paragraph (different ID).</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
|
||||||
"sandboxCSS": "h1, h2, p { padding: 8px; margin-bottom: 10px; border: 1px dashed #ccc; }",
|
|
||||||
"codePrefix": "/* Create an ID selector to target the element with id=\"main-title\" */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^#main-title\\s*{",
|
|
||||||
"message": "Start your rule with <kbd>#main-title</kbd> to create an ID selector",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "color:",
|
|
||||||
"message": "Include the <kbd>color</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "color",
|
|
||||||
"expected": "purple"
|
|
||||||
},
|
|
||||||
"message": "Set the color to <kbd>purple</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "text-decoration:",
|
|
||||||
"message": "Include the <kbd>text-decoration</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "text-decoration",
|
|
||||||
"expected": "underline"
|
|
||||||
},
|
|
||||||
"message": "Set the text-decoration to <kbd>underline</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "#main-title\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "id-with-type",
|
|
||||||
"title": "Type + ID",
|
|
||||||
"description": "Similar to how you can combine type and class selectors, you can also combine type selectors with ID selectors. For example, <kbd>h1#title</kbd> targets an h1 element with the ID 'title'. When using this combined approach, you can apply CSS properties like <kbd>font-style</kbd> to control the slant of the text, making it italic or normal. While this selector combination is more specific than using just the ID selector, it's often unnecessary since IDs should already be unique in a document. However, this technique can be useful for improving code readability or when you want to emphasize that a particular ID should only appear on a specific element type.",
|
|
||||||
"task": "Create a CSS rule that combines a type selector with an ID selector to target specifically a paragraph element with the ID <kbd>special</kbd>. Set its font style to italic.",
|
|
||||||
"previewHTML": "<h2 id=\"special\">Heading with ID \"special\" (should NOT be affected)</h2>\n<p id=\"special\">Paragraph with ID \"special\" (should become italic)</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
|
||||||
"sandboxCSS": "h2, p { padding: 8px; margin-bottom: 10px; border: 1px dashed #ccc; }",
|
|
||||||
"codePrefix": "/* Create a combined type+ID selector for a paragraph with id=\"special\" */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^p#special\\s*{",
|
|
||||||
"message": "Use <kbd>p#special</kbd> to target paragraphs with ID <kbd>special</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "font-style:",
|
|
||||||
"message": "Include the <kbd>font-style</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "font-style",
|
|
||||||
"expected": "italic"
|
|
||||||
},
|
|
||||||
"message": "Set the font-style to <kbd>italic</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "p#special\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "selector-lists",
|
|
||||||
"title": "Selector Lists",
|
|
||||||
"description": "When multiple elements need the same styling, you can group them together using a selector list (also known as grouping selectors). Selector lists are created by separating individual selectors with commas. This approach reduces repetition in your CSS, making it more maintainable and efficient. For example, <kbd>h1, h2, h3 { color: blue; }</kbd> applies the same blue color to all three heading levels. When styling multiple selectors at once, you can apply properties like <kbd>background-color</kbd> to set the background, <kbd>border-left</kbd> to create a left border with a specific thickness, style, and color, and <kbd>padding-left</kbd> to create space between the content and the left border. Whitespace around commas is optional, and each selector in the list can be any valid selector type-elements, classes, IDs, or even more complex selectors.",
|
|
||||||
"task": "Create a selector list that applies the same styles to three different elements: paragraphs with class <kbd>note</kbd>, list items with class <kbd>important</kbd>, and the element with ID <kbd>summary</kbd>. Give them a <kbd>lightyellow</kbd> background, a <kbd>gold</kbd> left border, and some left <kbd>padding</kbd>.",
|
|
||||||
"previewHTML": "<p class=\"note\">This is a note paragraph.</p>\n<ul>\n <li>Regular list item</li>\n <li class=\"important\">Important list item</li>\n</ul>\n<div id=\"summary\">Summary section</div>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
|
||||||
"sandboxCSS": "p, li, div { padding: 8px; margin-bottom: 8px; border: 1px dashed gray; }",
|
|
||||||
"codePrefix": "/* Create a selector list to apply the same styles to multiple different elements */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"solution": "p.note,\nli.important,\n#summary {\n background-color: lightyellow;\n border-left: 3px solid gold;\n padding-left: 10px\n}",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "p.note",
|
|
||||||
"message": "Include <kbd>p.note</kbd> in your selector list",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "li.important",
|
|
||||||
"message": "Include <kbd>li.important</kbd> in your selector list",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "#summary",
|
|
||||||
"message": "Include <kbd>#summary</kbd> in your selector list",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "(p\\.note|li\\.important|#summary)\\s*,\\s*(p\\.note|li\\.important|#summary)\\s*,\\s*(p\\.note|li\\.important|#summary)",
|
|
||||||
"message": "Create a comma-separated list with all three selectors",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Include the <kbd>background-color</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "lightyellow"
|
|
||||||
},
|
|
||||||
"message": "Set the background color to <kbd>lightyellow</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "border-left:",
|
|
||||||
"message": "Include the <kbd>border-left</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "border-left",
|
|
||||||
"expected": "3px solid gold"
|
|
||||||
},
|
|
||||||
"message": "Use <kbd>border-left: 3px solid gold</kbd> to create a left border"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "padding-left:",
|
|
||||||
"message": "Include the <kbd>padding-left</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "padding-left",
|
|
||||||
"expected": "10px"
|
|
||||||
},
|
|
||||||
"message": "Use <kbd>padding-left: 10px</kbd> to add left padding"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "universal-selector",
|
|
||||||
"title": "Universal (*)",
|
|
||||||
"description": "The universal selector is denoted by an asterisk (*) and matches any element of any type. It selects everything in the document or, when combined with other selectors, everything within a specific context. For example, <kbd>* { margin: 0; }</kbd> removes margins from all elements, while <kbd>article *</kbd> selects all elements inside article elements. When using the universal selector in combination with other selectors, you can apply properties like <kbd>margin</kbd> to control the spacing around elements. The universal selector is powerful but should be used carefully due to its broad impact. It's commonly used in CSS resets, to override default browser styling, or to target all children of a particular element.",
|
|
||||||
"task": "Use the universal selector to remove margins from all elements inside the container div. Create a rule using <kbd>div.container *</kbd> as the selector and set <kbd>margin: 0</kbd>.",
|
|
||||||
"previewHTML": "<div class=\"container\">\n <h2>Inside Container</h2>\n <p>This paragraph is inside the container.</p>\n <ul>\n <li>List item inside container</li>\n </ul>\n</div>\n<p>This paragraph is outside the container and should not be affected.</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } div.container { border: 2px solid navy; padding: 15px; background-color: lavender; } h2, p, ul, li { margin: 15px 0; }",
|
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Use the universal selector to target all elements inside the container */\n",
|
"codePrefix": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
|
"solution": ".btn.primary {\n background: steelblue;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^div\\.container\\s+\\*\\s*{",
|
"value": "\\.btn\\.primary\\s*\\{",
|
||||||
"message": "Use <kbd>div.container *</kbd> selector (with a space between container and *)",
|
"message": "Usa <kbd>.btn.primary {</kbd> (sin espacio entre clases)"
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "margin:",
|
|
||||||
"message": "Include the <kbd>margin</kbd> property"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "background", "expected": "steelblue" },
|
||||||
"property": "margin",
|
"message": "Establece <kbd>background: steelblue</kbd>"
|
||||||
"expected": "0"
|
|
||||||
},
|
|
||||||
"message": "Set margin to <kbd>0</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "div\\.container\\s+\\*\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "specificity-basics",
|
"id": "specific-elements",
|
||||||
"title": "Specificity",
|
"title": "Apuntando a elementos específicos",
|
||||||
"description": "CSS specificity determines which styles take precedence when multiple conflicting rules target the same element. Specificity follows a hierarchical system: inline styles have the highest specificity, followed by ID selectors, then class/attribute/pseudo-class selectors, and finally element/pseudo-element selectors. This can be conceptualized as a four-part score (inline, ID, class, element). When creating multiple rules that may target the same elements, you can use the <kbd>color</kbd> property to set text colors, and specificity will determine which color is actually applied. Understanding specificity is crucial for predictable styling and debugging CSS conflicts. When two selectors have equal specificity, the one that comes last in the stylesheet wins.",
|
"description": "A veces quieres que una clase se vea diferente en diferentes elementos. Combina un selector de tipo con un selector de clase (sin espacio) para ser más específico:<br><br><pre>a.btn {<br> text-decoration: none;<br>}</pre><br>Esto estiliza solo elementos <kbd><a></kbd> con la clase <kbd>btn</kbd>, no elementos <kbd><button></kbd> con esa clase.",
|
||||||
"task": "Examine the existing CSS rules and add a new rule with higher specificity to override the text color of the paragraph. Create a rule using '.content p' as the selector and set color: green.",
|
"task": "Quita el subrayado de los botones-enlace. Escribe una regla con <kbd>a.btn</kbd> como selector y establece <kbd>text-decoration: none</kbd>.",
|
||||||
"previewHTML": "<div class=\"content\">\n <p>What color will this paragraph be? Look at the CSS rules and their specificity.</p>\n</div>",
|
"previewHTML": "<div class=\"actions\">\n <button class=\"btn\">Regular Button</button>\n <a href=\"#\" class=\"btn\">Link Button</a>\n</div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } .actions { display: flex; gap: 0.75rem; align-items: center; } .btn { padding: 0.5rem 1rem; border: none; border-radius: 6px; font-size: 1rem; cursor: pointer; background: steelblue; color: white; }",
|
||||||
"sandboxCSS": "p { border: 1px dashed gray; padding: 10px; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* These CSS rules target the same paragraph but have different specificity */\n\n/* Rule 1: Element selector (lowest specificity) */\np {\n color: red;\n}\n\n/* Rule 2: Descendant selector (higher specificity than just 'p') */\n",
|
"codePrefix": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "a.btn {\n text-decoration: none;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^\\.content\\s+p\\s*{",
|
"value": "a\\.btn\\s*\\{",
|
||||||
"message": "Use <kbd>.content p</kbd> as your selector (note the space between)",
|
"message": "Usa <kbd>a.btn {</kbd> (tipo + clase, sin espacio)"
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "color:",
|
"value": { "property": "text-decoration", "expected": "none" },
|
||||||
"message": "Include the <kbd>color</kbd> property"
|
"message": "Establece <kbd>text-decoration: none</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "grouping-selectors",
|
||||||
|
"title": "Agrupando selectores",
|
||||||
|
"description": "Cuando múltiples elementos necesitan los mismos estilos, lístalos separados por comas. Esto mantiene tu CSS limpio y mantenible.<br><br><pre>h1, h2, h3 {<br> color: steelblue;<br>}</pre><br>Esto aplica el mismo color a los tres niveles de encabezado en una regla.",
|
||||||
|
"task": "Estiliza todos los encabezados consistentemente. Añade <kbd>color: steelblue</kbd> al selector agrupado <kbd>h1, h2, h3</kbd>.",
|
||||||
|
"previewHTML": "<article><h1>Main Title</h1><p>Introduction paragraph with some text.</p><h2>Section Heading</h2><p>More content here.</p><h3>Subsection</h3><p>Final paragraph.</p></article>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } article { max-width: 500px; } p { color: #555; line-height: 1.6; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "h1, h2, h3 {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "color: steelblue;",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "color", "expected": "steelblue" },
|
||||||
|
"message": "Establece <kbd>color: steelblue</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "descendant-selectors",
|
||||||
|
"title": "Selectores descendientes",
|
||||||
|
"description": "Apunta a elementos dentro de otros elementos usando un espacio entre selectores. Este es uno de los patrones más útiles en CSS.<br><br><pre>.nav a {<br> color: white;<br>}</pre><br>Esto estiliza solo enlaces dentro de <kbd>.nav</kbd>, dejando otros enlaces sin cambios.",
|
||||||
|
"task": "Estiliza los enlaces de navegación diferente. Escribe una regla con <kbd>.nav a</kbd> como selector y establece <kbd>color: white</kbd>.",
|
||||||
|
"previewHTML": "<nav class=\"nav\"><a href=\"#\">Home</a><a href=\"#\">About</a><a href=\"#\">Contact</a></nav><p>Read more in our <a href=\"#\">documentation</a>.</p>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; margin: 0; } .nav { background: steelblue; padding: 1rem; display: flex; gap: 1rem; border-radius: 8px; margin-bottom: 1rem; } .nav a { text-decoration: none; } p a { color: steelblue; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": ".nav a {\n color: white;\n}",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "\\.nav\\s+a\\s*\\{",
|
||||||
|
"message": "Usa <kbd>.nav a {</kbd> (espacio entre .nav y a)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "green",
|
"value": { "property": "color", "expected": "white" },
|
||||||
"message": ""
|
"message": "Establece <kbd>color: white</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nested-styling",
|
||||||
|
"title": "Estilos anidados",
|
||||||
|
"description": "Los selectores descendientes te permiten crear estilos contextuales. El mismo elemento puede verse diferente dependiendo de dónde aparezca.<br><br>Por ejemplo, los párrafos en una <kbd>.card</kbd> podrían ser más pequeños que los párrafos en un <kbd>article</kbd>.",
|
||||||
|
"task": "Haz los párrafos dentro de la tarjeta más pequeños. Escribe una regla con <kbd>.card p</kbd> como selector y establece <kbd>font-size: 0.9rem</kbd>.",
|
||||||
|
"previewHTML": "<article><h2>Article Title</h2><p>This is a regular article paragraph with normal-sized text for comfortable reading.</p><div class=\"card\"><strong>Quick Tip</strong><p>Card paragraphs should be slightly smaller to fit the compact design.</p></div><p>Back to regular article text here.</p></article>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } article { max-width: 500px; } h2 { color: steelblue; margin-top: 0; } p { line-height: 1.6; color: #444; } .card { background: #f0f4f8; padding: 1rem; border-radius: 8px; border-left: 4px solid steelblue; } .card strong { color: steelblue; display: block; margin-bottom: 0.5rem; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": ".card p {\n font-size: 0.9rem;\n}",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "\\.card\\s+p\\s*\\{",
|
||||||
|
"message": "Usa <kbd>.card p {</kbd> (espacio entre .card y p)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "font-size", "expected": "0.9rem" },
|
||||||
|
"message": "Establece <kbd>font-size: 0.9rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "welcome",
|
"id": "welcome",
|
||||||
"title": "Code Crispies",
|
"title": "Bienvenido",
|
||||||
"description": "Welcome to Code Crispies - your interactive web development learning platform",
|
"description": "Comienza con Code Crispies",
|
||||||
"mode": "html",
|
"mode": "css",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
|
"excludeFromProgress": true,
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "get-started",
|
"id": "hello",
|
||||||
"title": "Get Started",
|
"title": "¡Hola!",
|
||||||
"description": "<strong>Code Crispies</strong> is a free, open-source platform for learning web development through hands-on exercises. No account required!<br><br><strong>What you'll learn:</strong><br>• <strong>HTML</strong> - Semantic elements, forms, tables, SVG (<em>HTML Block & Inline</em>, <em>HTML Forms</em>, <em>HTML Tables</em>)<br>• <strong>CSS</strong> - Selectors, box model, flexbox, animations (<em>CSS Selectors</em>, <em>CSS Box Model</em>, <em>CSS Flexbox</em>)<br>• <strong>Responsive Design</strong> - Media queries and mobile-first layouts<br><br><strong>How it works:</strong><br>1. Read the task in the left panel<br>2. Write code in the editor<br>3. See live results in the preview<br>4. Get instant feedback with hints<br><br><strong>Keyboard shortcuts:</strong> <kbd>Ctrl+Z</kbd> to undo, <kbd>Ctrl+Shift+Z</kbd> to redo<br><br><strong>More resources:</strong><br>• <a href=\"https://nextlevelshit.github.io/html-over-js/\" target=\"_blank\">HTML over JS</a> - Native HTML vs JavaScript solutions<br>• <a href=\"https://nextlevelshit.github.io/web-engineering-mandala/\" target=\"_blank\">Web Engineering Mandala</a> - JavaScript technology roadmap",
|
"description": "<strong>¡Bienvenido a Code Crispies!</strong> Aprende CSS y HTML con ejercicios prácticos.<br><br><strong>Cómo funciona:</strong><br>1. Lee la tarea a la izquierda<br>2. Escribe código en el editor<br>3. Ve los resultados en vivo en la vista previa<br><br><strong>Consejos:</strong> Usa <kbd>Ctrl+Z</kbd> para deshacer. Abre el menú (☰) para explorar todos los módulos.",
|
||||||
"task": "Write <code>Hello World</code>",
|
"task": "Escribe <code>Hello World</code> para comenzar",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 2rem; color: #6366f1; font-weight: bold; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 2rem; color: #6366f1; font-weight: bold; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,42 +22,9 @@
|
|||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "Hello World",
|
"value": "Hello World",
|
||||||
"message": "Write <code>Hello World</code>"
|
"message": "Escribe <code>Hello World</code>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "overview",
|
|
||||||
"title": "Overview",
|
|
||||||
"description": "<strong>You're ready!</strong> Open the menu (☰) to explore all modules.<br><br><strong>Recommended learning path:</strong><br>1. <em>HTML Block & Inline</em> - Understand container vs inline elements<br>2. <em>HTML Forms</em> - Build interactive forms with validation<br>3. <em>CSS Selectors</em> - Target elements precisely<br>4. <em>CSS Box Model</em> - Master padding, margin, borders<br>5. <em>CSS Flexbox</em> - Create flexible layouts<br>6. <em>CSS Animations</em> - Add motion and transitions<br><br><strong>Tips:</strong><br>• Use <em>Show Expected</em> to see the target result<br>• Your progress is saved automatically<br>• Try Emmet in HTML mode: <kbd>ul>li*3</kbd> + Tab<br><br><strong>Open Source:</strong><br>• <a href=\"https://git.librete.ch/libretech/code-crispies\" target=\"_blank\">Gitea (Source)</a> · <a href=\"https://github.com/nextlevelshit/code-crispies\" target=\"_blank\">GitHub (Mirror)</a><br>• Made by <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a> · <a href=\"https://www.linkedin.com/in/michael-werner-czechowski\" target=\"_blank\">Michael Czechowski</a>",
|
|
||||||
"task": "Click Next to continue",
|
|
||||||
"previewHTML": "<p>Hello World! 🌍</p><p>Hallo Welt!</p><p>Bonjour le monde!</p><p>¡Hola Mundo!</p><p>Ciao Mondo!</p><p>Olá Mundo!</p><p>こんにちは世界!</p><p>你好世界!</p><p>안녕 세상!</p><p>Привет мир!</p><p dir=\"rtl\">שלום עולם!</p><p dir=\"rtl\">مرحبا بالعالم!</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 12px; } p { margin: 6px 0; padding: 6px 12px; border-radius: 6px; font-weight: 600; font-size: 0.95em; } p:nth-child(1) { background: #fef3c7; color: #92400e; font-size: 1.2em; } p:nth-child(2) { background: #dcfce7; color: #166534; } p:nth-child(3) { background: #dbeafe; color: #1e40af; } p:nth-child(4) { background: #fce7f3; color: #9d174d; } p:nth-child(5) { background: #e0e7ff; color: #4338ca; } p:nth-child(6) { background: #fef9c3; color: #854d0e; } p:nth-child(7) { background: #fee2e2; color: #991b1b; } p:nth-child(8) { background: #f3e8ff; color: #7c3aed; } p:nth-child(9) { background: #ccfbf1; color: #0f766e; } p:nth-child(10) { background: #fae8ff; color: #86198f; } p:nth-child(11) { background: #fef3c7; color: #b45309; } p:nth-child(12) { background: #d1fae5; color: #047857; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<p>Hello World! 🌍</p>\n<p>Hallo Welt!</p>\n<p>Bonjour le monde!</p>\n<p>¡Hola Mundo!</p>\n<p>Ciao Mondo!</p>\n<p>Olá Mundo!</p>\n<p>こんにちは世界!</p>\n<p>你好世界!</p>\n<p>안녕 세상!</p>\n<p>Привет мир!</p>\n<p dir=\"rtl\">שלום עולם!</p>\n<p dir=\"rtl\">مرحبا بالعالم!</p>",
|
|
||||||
"solution": "<p>Hello World! 🌍</p>\n<p>Hallo Welt!</p>\n<p>Bonjour le monde!</p>\n<p>¡Hola Mundo!</p>\n<p>Ciao Mondo!</p>\n<p>Olá Mundo!</p>\n<p>こんにちは世界!</p>\n<p>你好世界!</p>\n<p>안녕 세상!</p>\n<p>Привет мир!</p>\n<p dir=\"rtl\">שלום עולם!</p>\n<p dir=\"rtl\">مرحبا بالعالم!</p>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "Hello World",
|
|
||||||
"message": "Click Next to continue"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "playground",
|
|
||||||
"title": "Playground",
|
|
||||||
"mode": "playground",
|
|
||||||
"description": "",
|
|
||||||
"task": "",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<style>\n body {\n font-family: system-ui, sans-serif;\n padding: 20px;\n }\n</style>\n\n<h1>Hello World</h1>\n<p>Start coding!</p>",
|
|
||||||
"solution": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,18 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "box-model",
|
"id": "box-model",
|
||||||
"title": "CSS Box Model",
|
"title": "CSS Box Model",
|
||||||
"description": "Master the fundamental principles of space management in web design through the CSS box model. This module explores how content, padding, borders, and margins combine to create layout structures that are both visually appealing and structurally sound.",
|
"description": "Domina los principios fundamentales de gestión del espacio en diseño web a través del modelo de caja CSS. Este módulo explora cómo el contenido, padding, bordes y márgenes se combinan para crear estructuras de diseño.",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "box-model-1",
|
"id": "box-model-1",
|
||||||
"title": "Box Model Components",
|
"title": "Padding",
|
||||||
"description": "The CSS box model consists of four concentric layers: content area (innermost), padding, border, and margin (outermost). Understanding how these components interact is essential for precise layout control.",
|
"description": "Cada elemento en CSS es una caja con cuatro capas: contenido, padding, borde y margen. <strong>Padding</strong> crea espacio entre tu contenido y el borde de la caja.<br><br>Sin padding, el texto se aprieta incómodamente contra los bordes. El padding hace que el contenido sea legible y visualmente equilibrado.<br><br><pre>.card {\n padding: 1rem;\n}</pre>",
|
||||||
"task": "Set <kbd>padding</kbd> to <kbd>1rem</kbd> to create space between the content and border.",
|
"task": "Esta tarjeta de perfil se ve apretada. Añade <kbd>padding: 1rem</kbd> para que el texto tenga espacio para respirar.",
|
||||||
"previewHTML": "<div class=\"box\">Box Model Components</div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: lavender; border: 2px dashed slategray; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".box {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "padding: 1rem;",
|
"solution": "padding: 1rem;",
|
||||||
@@ -22,62 +22,62 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "1rem" },
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"message": "Set <kbd>padding: 1rem</kbd>"
|
"message": "Establece <kbd>padding: 1rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-2",
|
"id": "box-model-2",
|
||||||
"title": "Adding Borders",
|
"title": "Borders",
|
||||||
"description": "Borders outline an element, creating visual separation from surrounding content. The border shorthand accepts three values: width, style, and color.",
|
"description": "Los bordes crean límites visuales alrededor de los elementos. El atajo <kbd>border</kbd> acepta tres valores: ancho, estilo y color.<br><br>Estilos comunes: <kbd>solid</kbd>, <kbd>dashed</kbd>, <kbd>dotted</kbd>, <kbd>none</kbd>",
|
||||||
"task": "Set <kbd>border</kbd> to <kbd>2px solid darkslategray</kbd>.",
|
"task": "Añade un acento sutil a la izquierda de la tarjeta con <kbd>border-left: 4px solid steelblue</kbd>.",
|
||||||
"previewHTML": "<div class=\"box\">This box needs a border</div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: mintcream; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".box {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "border: 2px solid darkslategray;",
|
"solution": "border-left: 4px solid steelblue;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border:\\s*2px\\s+solid\\s+darkslategray",
|
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
|
||||||
"message": "Set <kbd>border: 2px solid darkslategray</kbd>",
|
"message": "Establece <kbd>border-left: 4px solid steelblue</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-3",
|
"id": "box-model-3",
|
||||||
"title": "Adding Margins",
|
"title": "Margins",
|
||||||
"description": "Margins create space between elements, controlling how they relate to one another within a layout. Unlike padding (which affects internal spacing), margins exist outside the element's border.",
|
"description": "Los márgenes crean espacio <em>fuera</em> del elemento, separándolo de sus vecinos. Mientras que el padding empuja el contenido hacia adentro, los márgenes empujan otros elementos hacia afuera.",
|
||||||
"task": "Set <kbd>margin</kbd> to <kbd>1rem</kbd> to create space between this element and its neighbors.",
|
"task": "Añade espacio entre estas dos tarjetas de perfil con <kbd>margin-bottom: 1rem</kbd> en <kbd>.card</kbd>.",
|
||||||
"previewHTML": "<div class=\"container\"><div class=\"outer\">This box needs margins</div><div class=\"neighbor\">Adjacent element</div></div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article><article class=\"card\"><h3>Alex Rivera</h3><p>UX Designer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .outer { background-color: plum; padding: 1rem; border: 2px solid orchid; } .neighbor { background-color: lightblue; padding: 1rem; border: 2px solid steelblue; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; border-left: 4px solid steelblue; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".outer {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "margin: 1rem;",
|
"solution": "margin-bottom: 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "margin", "expected": "1rem" },
|
"value": { "property": "margin-bottom", "expected": "1rem" },
|
||||||
"message": "Set <kbd>margin: 1rem</kbd>"
|
"message": "Establece <kbd>margin-bottom: 1rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-4",
|
"id": "box-model-4",
|
||||||
"title": "Box Sizing: Border-Box",
|
"title": "Box Sizing",
|
||||||
"description": "The <kbd>box-sizing</kbd> property determines how element dimensions are calculated. The default <kbd>content-box</kbd> excludes padding and border from width/height, while <kbd>border-box</kbd> includes them, making layout calculations more intuitive.",
|
"description": "Por defecto, <kbd>width</kbd> solo establece el ancho del contenido. Padding y bordes se suman al total. Esto causa problemas de diseño.<br><br><kbd>box-sizing: border-box</kbd> incluye padding y borde en el ancho, haciendo el dimensionamiento predecible. La mayoría de desarrolladores aplican esto a todos los elementos.",
|
||||||
"task": "Set <kbd>box-sizing</kbd> to <kbd>border-box</kbd> so padding and border are included in the width.",
|
"task": "Ambas tarjetas tienen <kbd>width: 200px</kbd>. La izquierda usa el tamaño predeterminado (content-box), haciéndola más ancha de lo esperado. Corrige la tarjeta derecha con <kbd>box-sizing: border-box</kbd>.",
|
||||||
"previewHTML": "<div class=\"sizing-demo\"><div class=\"box default\">Content-box (default)</div><div class=\"box sized\">Border-box</div></div>",
|
"previewHTML": "<div class=\"demo\"><article class=\"card\">Content-box</article><article class=\"card fix\">Border-box</article></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .sizing-demo { display: flex; gap: 1rem; } .box { width: 200px; padding: 1rem; border: 4px solid teal; background: lightcyan; } .default { box-sizing: content-box; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .demo { display: flex; gap: 1rem; } .card { width: 200px; padding: 1rem; border: 4px solid steelblue; background: white; border-radius: 8px; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".sized {\n ",
|
"codePrefix": ".fix {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "box-sizing: border-box;",
|
"solution": "box-sizing: border-box;",
|
||||||
@@ -86,93 +86,104 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "box-sizing", "expected": "border-box" },
|
"value": { "property": "box-sizing", "expected": "border-box" },
|
||||||
"message": "Set <kbd>box-sizing: border-box</kbd>"
|
"message": "Establece <kbd>box-sizing: border-box</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-5",
|
"id": "box-model-5",
|
||||||
"title": "Margin Collapse",
|
"title": "Padding Shorthand",
|
||||||
"description": "When two vertical margins meet, they collapse to the larger value instead of adding up. Understanding this behavior is crucial for consistent vertical spacing.",
|
"description": "Padding acepta 1-4 valores:<br>• 1 valor: todos los lados<br>• 2 valores: vertical | horizontal<br>• 4 valores: arriba | derecha | abajo | izquierda",
|
||||||
"task": "Set <kbd>margin-bottom</kbd> to <kbd>2rem</kbd>. Notice the space between paragraphs equals 2rem (not 3rem) due to margin collapse.",
|
"task": "Este botón necesita más espacio horizontal que vertical. Establece <kbd>padding: 8px 1rem</kbd> (8px arriba/abajo, 1rem izquierda/derecha).",
|
||||||
"previewHTML": "<div class=\"collapse-demo\"><p class=\"first\">This paragraph has a bottom margin.</p><p class=\"second\">This paragraph has a top margin of 1rem.</p></div>",
|
"previewHTML": "<button class=\"btn\">Follow</button>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .collapse-demo { border: 1px solid silver; padding: 8px; background: ghostwhite; } .second { margin-top: 1rem; background: linen; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .btn { background: steelblue; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".first {\n ",
|
"codePrefix": ".btn {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "margin-bottom: 2rem;",
|
"solution": "padding: 8px 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "regex",
|
||||||
"value": { "property": "margin-bottom", "expected": "2rem" },
|
"value": "padding:\\s*8px\\s+1rem",
|
||||||
"message": "Set <kbd>margin-bottom: 2rem</kbd>"
|
"message": "Establece <kbd>padding: 8px 1rem</kbd>",
|
||||||
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-6",
|
"id": "box-model-6",
|
||||||
"title": "Margin Shorthand Notation",
|
"title": "Margin Shorthand",
|
||||||
"description": "The margin shorthand can set all four sides at once. Two values set vertical (top/bottom) and horizontal (left/right) margins respectively.",
|
"description": "Margin usa el mismo patrón de atajo que padding. Un patrón común es centrar elementos de bloque horizontalmente con <kbd>margin: 0 auto</kbd>.",
|
||||||
"task": "Set <kbd>margin</kbd> to <kbd>1rem 2rem</kbd> for 1rem top/bottom and 2rem left/right.",
|
"task": "Centra esta tarjeta horizontalmente. Establece <kbd>margin: 0 auto</kbd> para calcular automáticamente márgenes iguales izquierda/derecha.",
|
||||||
"previewHTML": "<div class=\"container\"><div class=\"spaced\">This box needs margins: 1rem top/bottom, 2rem left/right</div></div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .spaced { background-color: honeydew; border: 2px solid mediumseagreen; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { width: 250px; background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; border-left: 4px solid steelblue; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".spaced {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "margin: 1rem 2rem;",
|
"solution": "margin: 0 auto;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "margin:\\s*1rem\\s+2rem",
|
"value": "margin:\\s*0\\s+auto",
|
||||||
"message": "Set <kbd>margin: 1rem 2rem</kbd>",
|
"message": "Establece <kbd>margin: 0 auto</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-7",
|
"id": "box-model-7",
|
||||||
"title": "Padding Shorthand Notation",
|
"title": "Border Radius",
|
||||||
"description": "Like margin, padding shorthand allows setting all sides at once. A single value applies to all four sides equally.",
|
"description": "Aunque no es parte del modelo de caja clásico, <kbd>border-radius</kbd> redondea las esquinas de la caja de borde de un elemento. Usa <kbd>50%</kbd> en un elemento cuadrado para crear un círculo.",
|
||||||
"task": "Set <kbd>padding</kbd> to <kbd>2rem</kbd> to add equal padding on all sides.",
|
"task": "Haz la imagen del avatar circular con <kbd>border-radius: 50%</kbd>.",
|
||||||
"previewHTML": "<div class=\"padded\">This box needs equal padding on all sides</div>",
|
"previewHTML": "<article class=\"card\"><img class=\"avatar\" src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='35' r='25' fill='%23666'/%3E%3Ccircle cx='50' cy='90' r='40' fill='%23666'/%3E%3C/svg%3E\" alt=\"Avatar\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .padded { background-color: papayawhip; border: 2px solid orange; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; text-align: center; } .avatar { width: 80px; height: 80px; background: #ddd; margin-bottom: 8px; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".padded {\n ",
|
"codePrefix": ".avatar {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "padding: 2rem;",
|
"solution": "border-radius: 50%;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "2rem" },
|
"value": { "property": "border-radius", "expected": "50%" },
|
||||||
"message": "Set <kbd>padding: 2rem</kbd>"
|
"message": "Establece <kbd>border-radius: 50%</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-8",
|
"id": "box-model-8",
|
||||||
"title": "Border on Specific Sides",
|
"title": "Complete Card",
|
||||||
"description": "For granular control, you can target specific sides with <kbd>border-top</kbd>, <kbd>border-right</kbd>, <kbd>border-bottom</kbd>, or <kbd>border-left</kbd>.",
|
"description": "Combinemos todo. Esta tarjeta de notificación necesita estilo para verse profesional.",
|
||||||
"task": "Set <kbd>border-bottom</kbd> to <kbd>4px solid dodgerblue</kbd>.",
|
"task": "Estiliza la notificación: añade <kbd>padding: 1rem</kbd>, <kbd>border-left: 4px solid coral</kbd> y <kbd>border-radius: 4px</kbd>.",
|
||||||
"previewHTML": "<div class=\"line\">This element needs only a bottom border</div>",
|
"previewHTML": "<div class=\"alert\"><strong>New message</strong><p>You have 3 unread notifications</p></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .line { padding: 1rem; background-color: aliceblue; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .alert { background: seashell; } .alert strong { color: coral; } .alert p { margin: 4px 0 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".line {\n ",
|
"codePrefix": ".alert {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "border-bottom: 4px solid dodgerblue;",
|
"solution": "padding: 1rem;\n border-left: 4px solid coral;\n border-radius: 4px;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
|
"message": "Establece <kbd>padding: 1rem</kbd>"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border-bottom:\\s*4px\\s+solid\\s+dodgerblue",
|
"value": "border-left:\\s*4px\\s+solid\\s+coral",
|
||||||
"message": "Set <kbd>border-bottom: 4px solid dodgerblue</kbd>",
|
"message": "Establece <kbd>border-left: 4px solid coral</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "border-radius", "expected": "4px" },
|
||||||
|
"message": "Establece <kbd>border-radius: 4px</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,115 +1,100 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "units-variables",
|
"id": "units-variables",
|
||||||
"title": "CSS Units & Variables",
|
"title": "Unidades y Variables CSS",
|
||||||
"description": "Understand the variety of CSS measurement units and how to define and use custom properties for maintainable styles.",
|
"description": "Comprende la variedad de unidades de medida CSS y cómo definir y usar propiedades personalizadas para estilos mantenibles.",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "units-1",
|
"id": "units-1",
|
||||||
"title": "Absolute vs. Relative Units",
|
"title": "Relative Units",
|
||||||
"description": "Learn the difference between px, rem, em, %, and vw/vh for flexible, responsive layouts.",
|
"description": "CSS ofrece dos tipos de unidades: <em>absolutas</em> (como <kbd>px</kbd>) y <em>relativas</em> (como <kbd>%</kbd> y <kbd>rem</kbd>). Las unidades relativas se adaptan a su contexto, haciendo los layouts flexibles y accesibles.<br><br><strong>Unidades relativas comunes:</strong><br>• <kbd>%</kbd> – Relativo al elemento padre<br>• <kbd>rem</kbd> – Relativo al tamaño de fuente raíz (típicamente 16px)<br>• <kbd>em</kbd> – Relativo al tamaño de fuente del elemento<br><br>Un patrón común para contenido legible: establece <kbd>width: 100%</kbd> para llenar el espacio disponible, luego <kbd>max-width: 40rem</kbd> para limitar la longitud de línea para legibilidad.",
|
||||||
"task": "Set the <kbd>width</kbd> of <kbd>.box</kbd> to <kbd>80%</kbd> and <kbd>max-width</kbd> to <kbd>37.5rem</kbd>.",
|
"task": "Este texto de artículo es demasiado ancho en pantallas grandes. Añade <kbd>max-width: 40rem</kbd> para un ancho de lectura óptimo.",
|
||||||
"previewHTML": "<div class=\"box\">Resize me!</div>",
|
"previewHTML": "<article class=\"article\"><h2>The Art of Typography</h2><p>Good typography is invisible. When text is set well, readers absorb information without noticing the design decisions that make it comfortable to read. Line length is crucial—too wide and eyes get lost, too narrow and reading becomes choppy.</p><p>The ideal line length is 45-75 characters per line. At typical font sizes, this works out to roughly 40rem maximum width.</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { background: #f5f5f5; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: Georgia, serif; padding: 1rem; background: #f9f9f9; } .article { background: white; padding: 2rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .article h2 { margin: 0 0 1rem; color: #333; } .article p { margin: 0 0 1rem; line-height: 1.6; color: #444; } .article p:last-child { margin-bottom: 0; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Set flexible sizing */\n.box {",
|
"codePrefix": ".article {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " width: 80%;\n max-width: 37.5rem;",
|
"solution": "max-width: 40rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"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",
|
"type": "property_value",
|
||||||
"value": { "property": "max-width", "expected": "37.5rem" },
|
"value": { "property": "max-width", "expected": "40rem" },
|
||||||
"message": "Set max-width to <kbd>37.5rem</kbd>"
|
"message": "Establece <kbd>max-width: 40rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "units-2",
|
"id": "units-2",
|
||||||
"title": "CSS Custom Properties",
|
"title": "CSS Variables",
|
||||||
"description": "Define and reuse variables (--custom properties) to centralize your theme values.",
|
"description": "Las propiedades personalizadas CSS (variables) te permiten definir valores reutilizables. Defínelas con <kbd>--nombre</kbd> y úsalas con <kbd>var(--nombre)</kbd>. Las variables definidas en <kbd>:root</kbd> están disponibles en todas partes.",
|
||||||
"task": "Create a <kbd>--main-color</kbd> variable in <kbd>:root</kbd> with <kbd>#6200ee</kbd> and apply it as the border color on <kbd>.themed</kbd>.",
|
"task": "Define <kbd>--brand: steelblue</kbd> en <kbd>:root</kbd>, luego úsala como color de <kbd>background</kbd> para <kbd>.btn</kbd>.",
|
||||||
"previewHTML": "<div class=\"themed\">Variable Box</div>",
|
"previewHTML": "<div class=\"actions\"><button class=\"btn\">Subscribe</button><button class=\"btn\">Learn More</button></div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .themed { padding: 1rem; border: 0.125rem solid #ddd; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .actions { display: flex; gap: 1rem; } .btn { color: white; border: none; padding: 12px 24px; border-radius: 6px; font-size: 1rem; cursor: pointer; background: #ccc; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Define and use a CSS variable */\n:root {",
|
"codePrefix": ":root {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}\n.themed { }",
|
"codeSuffix": "\n}\n\n.btn {\n background: var(--brand);\n}",
|
||||||
"solution": " --main-color: #6200ee;\n}\n.themed {\n border-color: var(--main-color);",
|
"solution": "--brand: steelblue;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "--main-color",
|
"value": "--brand",
|
||||||
"message": "Define <kbd>--main-color</kbd> in :root",
|
"message": "Define la variable <kbd>--brand</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "var(--main-color)",
|
"value": "steelblue",
|
||||||
"message": "Use <kbd>var(--main-color)</kbd>",
|
"message": "Establece el valor a <kbd>steelblue</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"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",
|
"id": "units-3",
|
||||||
"title": "Unit Calculations (calc)",
|
"title": "calc() Function",
|
||||||
"description": "Use the <kbd>calc()</kbd> function to combine different units in one expression.",
|
"description": "La función <kbd>calc()</kbd> te permite mezclar diferentes unidades en cálculos. Esto es esencial para layouts que combinan tamaños fijos y flexibles, como un layout con barra lateral.",
|
||||||
"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>.",
|
"task": "El contenido principal debe llenar el espacio restante después de la barra lateral de 200px. Establece <kbd>width: calc(100% - 200px)</kbd> en <kbd>.main</kbd>.",
|
||||||
"previewHTML": "<div class=\"sized\">Calc Demo</div>",
|
"previewHTML": "<div class=\"layout\"><aside class=\"sidebar\">Sidebar<br>Navigation</aside><main class=\"main\"><h2>Main Content</h2><p>This area should fill the remaining width after accounting for the fixed-width sidebar.</p></main></div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sized { background: #e8f5e9; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .layout { display: flex; gap: 1rem; } .sidebar { width: 200px; background: #1a1a2e; color: white; padding: 1rem; border-radius: 8px; flex-shrink: 0; } .main { background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .main h2 { margin: 0 0 8px; } .main p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Use calc for dynamic sizing */\n.sized {",
|
"codePrefix": ".main {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " width: calc(100% - 2rem);\n min-height: calc(10vh + 1rem);",
|
"solution": "width: calc(100% - 200px);",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{ "type": "contains", "value": "calc", "message": "Use <kbd>calc()</kbd> function", "options": { "caseSensitive": false } },
|
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "width:\\s*calc\\(100% - 2rem\\)",
|
"value": "width:\\s*calc\\(\\s*100%\\s*-\\s*200px\\s*\\)",
|
||||||
"message": "Width should be <kbd>calc(100% - 2rem)</kbd>",
|
"message": "Establece <kbd>width: calc(100% - 200px)</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 }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "units-4",
|
"id": "units-4",
|
||||||
"title": "Viewport & Responsive Units",
|
"title": "Viewport Units",
|
||||||
"description": "Control layouts relative to viewport size with vw, vh, and vmin/vmax units.",
|
"description": "Las unidades de viewport dimensionan elementos relativos a la ventana del navegador:<br>• <kbd>vw</kbd> – 1% del ancho del viewport<br>• <kbd>vh</kbd> – 1% de la altura del viewport<br><br>Son perfectas para secciones de pantalla completa como banners hero.",
|
||||||
"task": "Give <kbd>.view</kbd> a <kbd>width</kbd> of <kbd>50vw</kbd> and <kbd>height</kbd> of <kbd>20vh</kbd>.",
|
"task": "Haz que esta sección hero llene la altura del viewport estableciendo <kbd>min-height: 100vh</kbd>.",
|
||||||
"previewHTML": "<div class=\"view\">Viewport Box</div>",
|
"previewHTML": "<section class=\"hero\"><h1>Welcome</h1><p>Scroll down to explore</p></section>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .view { background: #ffe0b2; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .hero { background: linear-gradient(135deg, #1a1a2e 0%, steelblue 100%); color: white; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 2rem; } .hero h1 { margin: 0 0 1rem; font-size: 2.5rem; } .hero p { margin: 0; opacity: 0.8; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Use viewport units */\n.view {",
|
"codePrefix": ".hero {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " width: 50vw;\n height: 20vh;",
|
"solution": "min-height: 100vh;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"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",
|
||||||
{ "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Set width to <kbd>50vw</kbd>" },
|
"value": { "property": "min-height", "expected": "100vh" },
|
||||||
{ "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Set height to <kbd>20vh</kbd>" }
|
"message": "Establece <kbd>min-height: 100vh</kbd>"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "transitions-animations",
|
"id": "transitions-animations",
|
||||||
"title": "CSS Animations",
|
"title": "Animaciones CSS",
|
||||||
"description": "Bring interactivity to your UI by smoothly transitioning properties and creating keyframe-driven animations.",
|
"description": "Añade interactividad a tu UI mediante transiciones suaves de propiedades y animaciones basadas en keyframes.",
|
||||||
"difficulty": "intermediate",
|
"difficulty": "intermediate",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "transitions-1",
|
"id": "transitions-1",
|
||||||
"title": "Transitions",
|
"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>",
|
"description": "Aprende a aplicar <kbd>transition</kbd> a propiedades para cambios suaves en estados.<br><br><pre>transition: property duration;\n/* ej. 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.",
|
"task": "Añade <kbd>transition: background-color 0.3s</kbd> para que el color cambie suavemente al hacer hover.",
|
||||||
"previewHTML": "<button class=\"btn\">Hover Me</button>",
|
"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; }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
@@ -22,13 +22,13 @@
|
|||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "transition",
|
"value": "transition",
|
||||||
"message": "Use the <kbd>transition</kbd> property",
|
"message": "Usa la propiedad <kbd>transition</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "transition:\\s*background-color\\s*0\\.3s",
|
"value": "transition:\\s*background-color\\s*0\\.3s",
|
||||||
"message": "Set <kbd>transition: background-color 0.3s</kbd>",
|
"message": "Establece <kbd>transition: background-color 0.3s</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -36,8 +36,8 @@
|
|||||||
{
|
{
|
||||||
"id": "transitions-2",
|
"id": "transitions-2",
|
||||||
"title": "Timing Funcs",
|
"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.",
|
"description": "Explora funciones de easing como <kbd>ease</kbd>, <kbd>linear</kbd>, <kbd>ease-in</kbd>, <kbd>ease-out</kbd> para controlar el ritmo de la animación.",
|
||||||
"task": "Set <kbd>transition-timing-function</kbd> to <kbd>ease-in-out</kbd> on <kbd>.btn</kbd>.",
|
"task": "Establece <kbd>transition-timing-function</kbd> a <kbd>ease-in-out</kbd>.",
|
||||||
"previewHTML": "<button class=\"btn\">Timing</button>",
|
"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); }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
@@ -50,21 +50,21 @@
|
|||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "transition-timing-function",
|
"value": "transition-timing-function",
|
||||||
"message": "Use <kbd>transition-timing-function</kbd>",
|
"message": "Usa <kbd>transition-timing-function</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "transition-timing-function", "expected": "ease-in-out" },
|
"value": { "property": "transition-timing-function", "expected": "ease-in-out" },
|
||||||
"message": "Set timing to <kbd>ease-in-out</kbd>"
|
"message": "Establece timing a <kbd>ease-in-out</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "transitions-3",
|
"id": "transitions-3",
|
||||||
"title": "Keyframes",
|
"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>",
|
"description": "Crea animaciones nombradas usando <kbd>@keyframes</kbd> y aplícalas con el atajo <kbd>animation</kbd>.<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>.",
|
"task": "Define un keyframe en <kbd>50%</kbd> con <kbd>transform: translateY(-20px)</kbd> y aplica <kbd>animation: bounce 1s infinite</kbd> a <kbd>.ball</kbd>.",
|
||||||
"previewHTML": "<div class=\"ball\"></div>",
|
"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; }",
|
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: crimson; border-radius: 50%; margin: 2rem auto; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -83,19 +83,19 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "50%.*transform: translateY\\(-20px\\)",
|
"value": "50%.*transform: translateY\\(-20px\\)",
|
||||||
"message": "At <kbd>50%</kbd>, use <kbd>transform: translateY(-20px)</kbd>",
|
"message": "En <kbd>50%</kbd>, usa <kbd>transform: translateY(-20px)</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "animation",
|
"value": "animation",
|
||||||
"message": "Use <kbd>animation</kbd> property on <kbd>.ball</kbd>",
|
"message": "Usa la propiedad <kbd>animation</kbd> en <kbd>.ball</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "animation:.*bounce.*1s.*infinite",
|
"value": "animation:.*bounce.*1s.*infinite",
|
||||||
"message": "Apply <kbd>animation: bounce 1s infinite</kbd>",
|
"message": "Aplica <kbd>animation: bounce 1s infinite</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -103,8 +103,8 @@
|
|||||||
{
|
{
|
||||||
"id": "transitions-4",
|
"id": "transitions-4",
|
||||||
"title": "Animation Properties",
|
"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>.",
|
"description": "Ajusta las animaciones con <kbd>animation-delay</kbd>, <kbd>animation-iteration-count</kbd>, <kbd>animation-direction</kbd> y <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>.",
|
"task": "Aplica la animación <kbd>pulse</kbd> a <kbd>.box</kbd> con <kbd>animation-name: pulse</kbd>, <kbd>animation-duration: 2s</kbd>, <kbd>animation-delay: 1s</kbd>, <kbd>animation-iteration-count: 2</kbd> y <kbd>animation-fill-mode: forwards</kbd>.",
|
||||||
"previewHTML": "<div class=\"box\">Pulse</div>",
|
"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); } }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
@@ -117,27 +117,27 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "animation-name", "expected": "pulse" },
|
"value": { "property": "animation-name", "expected": "pulse" },
|
||||||
"message": "Set <kbd>animation-name: pulse</kbd>"
|
"message": "Establece <kbd>animation-name: pulse</kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "animation-duration", "expected": "2s" },
|
"value": { "property": "animation-duration", "expected": "2s" },
|
||||||
"message": "Set <kbd>animation-duration: 2s</kbd>"
|
"message": "Establece <kbd>animation-duration: 2s</kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "animation-delay", "expected": "1s" },
|
"value": { "property": "animation-delay", "expected": "1s" },
|
||||||
"message": "Set <kbd>animation-delay: 1s</kbd>"
|
"message": "Establece <kbd>animation-delay: 1s</kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "animation-iteration-count", "expected": "2" },
|
"value": { "property": "animation-iteration-count", "expected": "2" },
|
||||||
"message": "Set <kbd>animation-iteration-count: 2</kbd>"
|
"message": "Establece <kbd>animation-iteration-count: 2</kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "animation-fill-mode", "expected": "forwards" },
|
"value": { "property": "animation-fill-mode", "expected": "forwards" },
|
||||||
"message": "Set <kbd>animation-fill-mode: forwards</kbd>"
|
"message": "Establece <kbd>animation-fill-mode: forwards</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "responsive-design",
|
"id": "responsive-design",
|
||||||
"title": "CSS Responsive Design",
|
"title": "CSS Responsive Design",
|
||||||
"description": "Make your layouts adapt to different screen sizes using media queries and fluid design techniques.",
|
"description": "Adapta tus layouts a diferentes tamaños de pantalla usando media queries y técnicas de diseño fluido.",
|
||||||
"difficulty": "intermediate",
|
"difficulty": "intermediate",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "responsive-1",
|
"id": "responsive-1",
|
||||||
"title": "Media Queries",
|
"title": "Media Queries",
|
||||||
"description": "Understand the syntax and use cases for CSS media queries to apply styles conditionally based on viewport characteristics.",
|
"description": "Comprende la sintaxis y casos de uso de las media queries de CSS para aplicar estilos condicionalmente basándose en características del viewport.<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>.",
|
"task": "Escribe una media query con <kbd>@media (max-width: 600px)</kbd> que cambie el fondo de <kbd>.panel</kbd> a <kbd>lightcoral</kbd>.",
|
||||||
"previewHTML": "<div class=\"panel\">Resize the window</div>",
|
"previewHTML": "<div class=\"panel\">Resize the window</div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .panel { padding: 1rem; background: lightblue; }",
|
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .panel { padding: 1rem; background: lightblue; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -22,19 +22,19 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "@media\\s*\\(max-width:\\s*600px\\)",
|
"value": "@media\\s*\\(max-width:\\s*600px\\)",
|
||||||
"message": "Use <kbd>@media (max-width: 600px)</kbd>",
|
"message": "Usa <kbd>@media (max-width: 600px)</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": ".panel",
|
"value": ".panel",
|
||||||
"message": "Target <kbd>.panel</kbd> inside the media query",
|
"message": "Selecciona <kbd>.panel</kbd> dentro de la media query",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "background", "expected": "lightcoral" },
|
"value": { "property": "background", "expected": "lightcoral" },
|
||||||
"message": "Set <kbd>background: lightcoral</kbd>",
|
"message": "Establece <kbd>background: lightcoral</kbd>",
|
||||||
"options": { "exact": false }
|
"options": { "exact": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -42,8 +42,8 @@
|
|||||||
{
|
{
|
||||||
"id": "responsive-2",
|
"id": "responsive-2",
|
||||||
"title": "Fluid Type",
|
"title": "Fluid Type",
|
||||||
"description": "Use relative units like <kbd>vw</kbd> to make font sizes scale with the viewport width.",
|
"description": "Usa unidades relativas como <kbd>vw</kbd> para que los tamaños de fuente escalen con el ancho del viewport.",
|
||||||
"task": "Set <kbd>font-size: 5vw</kbd> on <kbd>.text</kbd> so it scales as the viewport changes.",
|
"task": "Establece <kbd>font-size: 5vw</kbd> para que escale con el viewport.",
|
||||||
"previewHTML": "<p class=\"text\">Fluid Typography</p>",
|
"previewHTML": "<p class=\"text\">Fluid Typography</p>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -53,46 +53,50 @@
|
|||||||
"solution": " font-size: 5vw;",
|
"solution": " font-size: 5vw;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{ "type": "property_value", "value": { "property": "font-size", "expected": "5vw" }, "message": "Set <kbd>font-size: 5vw</kbd>" }
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "font-size", "expected": "5vw" },
|
||||||
|
"message": "Establece <kbd>font-size: 5vw</kbd>"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "responsive-3",
|
"id": "responsive-3",
|
||||||
"title": "Flex Grids",
|
"title": "Responsive Grid",
|
||||||
"description": "Combine CSS Grid with <kbd>auto-fit</kbd> or <kbd>auto-fill</kbd> for responsive column layouts.",
|
"description": "Combina CSS Grid con <kbd>auto-fit</kbd> o <kbd>auto-fill</kbd> para layouts de columnas responsivos que ajustan automáticamente el número de columnas según el espacio disponible.",
|
||||||
"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>.",
|
"task": "Añade <kbd>display: grid</kbd>, <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd> y <kbd>gap: 1rem</kbd>.",
|
||||||
"previewHTML": "<div class=\"cards\"><div>1</div><div>2</div><div>3</div><div>4</div></div>",
|
"previewHTML": "<section class=\"features\"><article class=\"feature\"><h3>Fast</h3><p>Lightning quick load times</p></article><article class=\"feature\"><h3>Secure</h3><p>Enterprise-grade security</p></article><article class=\"feature\"><h3>Reliable</h3><p>99.9% uptime guaranteed</p></article><article class=\"feature\"><h3>Support</h3><p>24/7 customer service</p></article></section>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .cards > div { background: #d1c4e9; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .feature { background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .feature h3 { margin: 0 0 8px; color: steelblue; } .feature p { margin: 0; color: #666; font-size: 0.9rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Create a responsive grid */\n.cards {",
|
"codePrefix": ".features {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
|
"solution": "display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "display", "expected": "grid" },
|
"value": { "property": "display", "expected": "grid" },
|
||||||
"message": "Set <kbd>display: grid</kbd>"
|
"message": "Establece <kbd>display: grid</kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "repeat\\(auto-fit,\\s*minmax\\(200px,\\s*1fr\\)\\)",
|
"value": "repeat\\(auto-fit,\\s*minmax\\(200px,\\s*1fr\\)\\)",
|
||||||
"message": "Use <kbd>repeat(auto-fit, minmax(200px, 1fr))</kbd>",
|
"message": "Usa <kbd>repeat(auto-fit, minmax(200px, 1fr))</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "gap", "expected": "1rem" },
|
"value": { "property": "gap", "expected": "1rem" },
|
||||||
"message": "Set <kbd>gap: 1rem</kbd>"
|
"message": "Establece <kbd>gap: 1rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "responsive-4",
|
"id": "responsive-4",
|
||||||
"title": "Mobile-First",
|
"title": "Mobile-First",
|
||||||
"description": "Adopt a mobile-first approach by writing base styles for small screens and enhancing for larger viewports.",
|
"description": "Adopta un enfoque mobile-first escribiendo estilos base para pantallas pequeñas y mejorándolos para viewports más grandes.",
|
||||||
"task": "Write a media query with <kbd>@media (min-width: 768px)</kbd> that sets <kbd>.sidebar</kbd> width to <kbd>250px</kbd>.",
|
"task": "Escribe una media query con <kbd>@media (min-width: 768px)</kbd> que establezca el ancho de <kbd>.sidebar</kbd> a <kbd>250px</kbd>.",
|
||||||
"previewHTML": "<aside class=\"sidebar\">Sidebar</aside>",
|
"previewHTML": "<aside class=\"sidebar\">Sidebar</aside>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sidebar { background: #c8e6c9; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sidebar { background: #c8e6c9; padding: 1rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -105,19 +109,19 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "@media\\s*\\(min-width:\\s*768px\\)",
|
"value": "@media\\s*\\(min-width:\\s*768px\\)",
|
||||||
"message": "Use <kbd>@media (min-width: 768px)</kbd>",
|
"message": "Usa <kbd>@media (min-width: 768px)</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": ".sidebar",
|
"value": ".sidebar",
|
||||||
"message": "Target <kbd>.sidebar</kbd> inside media query",
|
"message": "Selecciona <kbd>.sidebar</kbd> en la media query",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "width", "expected": "250px" },
|
"value": { "property": "width", "expected": "250px" },
|
||||||
"message": "Set <kbd>width: 250px</kbd>",
|
"message": "Establece <kbd>width: 250px</kbd>",
|
||||||
"options": { "exact": false }
|
"options": { "exact": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,94 +2,65 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-elements",
|
"id": "html-elements",
|
||||||
"title": "HTML Block & Inline",
|
"title": "HTML Block & Inline",
|
||||||
"description": "Understanding the fundamental difference between container (block) and inline elements",
|
"description": "Comprende la diferencia fundamental entre elementos contenedores (bloque) y elementos en línea",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "block-vs-inline-intro",
|
"id": "block-vs-inline-intro",
|
||||||
"title": "Block vs Inline Elements",
|
"title": "Elementos de bloque vs en línea",
|
||||||
"description": "HTML elements fall into two main categories:<br><br><strong>Block elements</strong> (containers) start on a new line and take full width. Examples: <kbd><div></kbd>, <kbd><p></kbd>, <kbd><h1></kbd>, <kbd><section></kbd><br><br><strong>Inline elements</strong> flow within text and only take needed width. Examples: <kbd><span></kbd>, <kbd><a></kbd>, <kbd><strong></kbd>, <kbd><em></kbd>",
|
"description": "Los elementos HTML se dividen en dos categorías principales:<br><br><strong>Elementos de bloque</strong> (contenedores) comienzan en una nueva línea y ocupan todo el ancho. Ejemplos: <kbd><div></kbd>, <kbd><p></kbd>, <kbd><h1></kbd>, <kbd><section></kbd><br><br><strong>Elementos en línea</strong> fluyen dentro del texto y solo ocupan el ancho necesario. Ejemplos: <kbd><span></kbd>, <kbd><a></kbd>, <kbd><strong></kbd>, <kbd><em></kbd>",
|
||||||
"task": "Wrap the word <kbd>important</kbd> with <kbd><strong></kbd> tags to make it bold. Notice how the paragraph (block) takes full width while strong (inline) flows with text.",
|
"task": "Envuelve la palabra <kbd>importante</kbd> con etiquetas <kbd><strong></kbd> para ponerla en negrita. Observa cómo el párrafo (bloque) ocupa todo el ancho mientras que strong (en línea) fluye con el texto.",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; } p { background: #e3f2fd; padding: 10px; } strong { background: #ffecb3; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; } p { background: #e3f2fd; padding: 10px; } strong { background: #ffecb3; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "<p>This is a paragraph with an important word.</p>",
|
"initialCode": "<p>Este es un párrafo con una palabra importante.</p>",
|
||||||
"solution": "<p>This is a paragraph with an <strong>important</strong> word.</p>",
|
"solution": "<p>Este es un párrafo con una palabra <strong>importante</strong>.</p>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "p",
|
"value": "p",
|
||||||
"message": "Add a <kbd><p></kbd> paragraph element"
|
"message": "Añade un elemento de párrafo <kbd><p></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "parent_child",
|
"type": "parent_child",
|
||||||
"value": { "parent": "p", "child": "strong" },
|
"value": { "parent": "p", "child": "strong" },
|
||||||
"message": "Wrap the word <kbd>important</kbd> with <kbd><strong></kbd> tags"
|
"message": "Envuelve la palabra <kbd>importante</kbd> con etiquetas <kbd><strong></kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "semantic-containers",
|
"id": "semantic-containers",
|
||||||
"title": "Semantic Tags",
|
"title": "Etiquetas semánticas",
|
||||||
"description": "Modern HTML uses semantic containers that describe their content:<br><br><kbd><header></kbd> - Page or section header<br><kbd><nav></kbd> - Navigation links<br><kbd><main></kbd> - Main content area<br><kbd><section></kbd> - Thematic grouping<br><kbd><article></kbd> - Self-contained content<br><kbd><footer></kbd> - Page or section footer",
|
"description": "El HTML moderno usa contenedores semánticos que describen su contenido:<br><br><kbd><header></kbd> - Encabezado de página o sección<br><kbd><nav></kbd> - Enlaces de navegación<br><kbd><main></kbd> - Área de contenido principal<br><kbd><section></kbd> - Agrupación temática<br><kbd><article></kbd> - Contenido independiente<br><kbd><footer></kbd> - Pie de página o sección",
|
||||||
"task": "Create a basic page structure:<br>1. Add a <kbd><header></kbd> with an <kbd><h1></kbd> containing the text <code>My Website</code><br>2. Add a <kbd><main></kbd> element with a paragraph saying <code>Welcome to my site!</code><br>3. Add a <kbd><footer></kbd> with a paragraph saying <code>Copyright 2026</code>",
|
"task": "Crea una estructura básica de página:<br>1. Añade un <kbd><header></kbd> con un <kbd><h1></kbd> que contenga el texto <code>Mi Sitio Web</code><br>2. Añade un elemento <kbd><main></kbd> con un párrafo que diga <code>¡Bienvenido a mi sitio!</code><br>3. Añade un <kbd><footer></kbd> con un párrafo que diga <code>Copyright 2026</code>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; margin: 0; } header { background: #1976d2; color: white; padding: 15px; } main { padding: 20px; min-height: 100px; } footer { background: #424242; color: white; padding: 10px; text-align: center; }",
|
"previewBaseCSS": "body { font-family: system-ui; margin: 0; } header { background: #1976d2; color: white; padding: 15px; } main { padding: 20px; min-height: 100px; } footer { background: #424242; color: white; padding: 10px; text-align: center; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<header>\n <h1>My Website</h1>\n</header>\n<main>\n <p>Welcome to my site!</p>\n</main>\n<footer>\n <p>Copyright 2026</p>\n</footer>",
|
"solution": "<header>\n <h1>Mi Sitio Web</h1>\n</header>\n<main>\n <p>¡Bienvenido a mi sitio!</p>\n</main>\n<footer>\n <p>Copyright 2026</p>\n</footer>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "header",
|
"value": "header",
|
||||||
"message": "Add a <kbd><header></kbd> element"
|
"message": "Añade un elemento <kbd><header></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "main",
|
"value": "main",
|
||||||
"message": "Add a <kbd><main></kbd> element"
|
"message": "Añade un elemento <kbd><main></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "footer",
|
"value": "footer",
|
||||||
"message": "Add a <kbd><footer></kbd> element"
|
"message": "Añade un elemento <kbd><footer></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "parent_child",
|
"type": "parent_child",
|
||||||
"value": { "parent": "header", "child": "h1" },
|
"value": { "parent": "header", "child": "h1" },
|
||||||
"message": "Add an <kbd><h1></kbd> heading inside your header"
|
"message": "Añade un encabezado <kbd><h1></kbd> dentro de tu header"
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "div-vs-span",
|
|
||||||
"title": "div & span",
|
|
||||||
"description": "When you need a container without semantic meaning:<br><br><kbd><div></kbd> - Generic block container (for layout/grouping)<br><kbd><span></kbd> - Generic inline container (for styling text portions)<br><br>Use semantic elements when possible, div/span when no semantic element fits.",
|
|
||||||
"task": "Wrap the word 'highlighted' in a <kbd><span></kbd> to style it differently. Wrap the whole quote in a <kbd><div></kbd>.",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: Georgia, serif; padding: 20px; } div { background: #f5f5f5; padding: 15px; border-left: 4px solid #1976d2; } span { background: #fff59d; padding: 2px 4px; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "The most highlighted moment was unforgettable.",
|
|
||||||
"solution": "<div>The most <span>highlighted</span> moment was unforgettable.</div>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "div",
|
|
||||||
"message": "Wrap everything in a <kbd><div></kbd> element"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "span",
|
|
||||||
"message": "Add a <kbd><span></kbd> around the word <kbd>highlighted</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_text",
|
|
||||||
"value": { "selector": "span", "text": "highlighted" },
|
|
||||||
"message": "The <kbd><span></kbd> should contain the word <kbd>highlighted</kbd>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +1,100 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-forms-basic",
|
"id": "html-forms-basic",
|
||||||
"title": "HTML Forms",
|
"title": "Formularios HTML",
|
||||||
"description": "Learn to create forms with various input types",
|
"description": "Aprende a crear formularios con varios tipos de campos",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "form-structure",
|
"id": "form-structure",
|
||||||
"title": "Form Structure",
|
"title": "Estructura del formulario",
|
||||||
"description": "Every form needs a <kbd><form></kbd> wrapper. Inside, use <kbd><label></kbd> to describe inputs and <kbd><input></kbd> for user data entry.<br><br>The <kbd>for</kbd> attribute on labels should match the <kbd>id</kbd> on inputs for accessibility.",
|
"description": "Todo formulario necesita un contenedor <kbd><form></kbd>. Dentro, usa <kbd><label></kbd> para describir campos y <kbd><input></kbd> para la entrada de datos.<br><br>El atributo <kbd>for</kbd> en los labels debe coincidir con el <kbd>id</kbd> de los inputs para accesibilidad.",
|
||||||
"task": "Create a form with:<br>1. A <kbd><label></kbd> with the text <code>Name:</code> and <kbd>for=\"name\"</kbd> attribute<br>2. A text <kbd><input></kbd> with <kbd>id=\"name\"</kbd> and <kbd>name=\"name\"</kbd> attributes",
|
"task": "Crea un formulario con:<br>1. Un <kbd><label></kbd> con el texto <code>Nombre:</code> y el atributo <kbd>for=\"name\"</kbd><br>2. Un <kbd><input></kbd> de texto con los atributos <kbd>id=\"name\"</kbd> y <kbd>name=\"name\"</kbd>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-bottom: 5px; font-weight: 500; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-bottom: 5px; font-weight: 500; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<form>\n <label for=\"name\">Name:</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n</form>",
|
"solution": "<form>\n <label for=\"name\">Nombre:</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n</form>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "form",
|
"value": "form",
|
||||||
"message": "Wrap everything in a <kbd><form></kbd> element"
|
"message": "Envuelve todo en un elemento <kbd><form></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "label",
|
"value": "label",
|
||||||
"message": "Add a <kbd><label></kbd> for your input"
|
"message": "Añade un <kbd><label></kbd> para tu campo"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "input",
|
"value": "input",
|
||||||
"message": "Add an <kbd><input></kbd> element"
|
"message": "Añade un elemento <kbd><input></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "label", "attr": "for", "value": null },
|
"value": { "selector": "label", "attr": "for", "value": null },
|
||||||
"message": "Add a <kbd>for</kbd> attribute to your label"
|
"message": "Añade un atributo <kbd>for</kbd> a tu label"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "input", "attr": "id", "value": null },
|
"value": { "selector": "input", "attr": "id", "value": null },
|
||||||
"message": "Add an <kbd>id</kbd> attribute to your input"
|
"message": "Añade un atributo <kbd>id</kbd> a tu campo"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "input-types",
|
"id": "input-types",
|
||||||
"title": "Input Types",
|
"title": "Tipos de campos",
|
||||||
"description": "Different input types provide appropriate keyboards and validation:<br><br><kbd>type=\"text\"</kbd> - General text<br><kbd>type=\"email\"</kbd> - Email with @ validation<br><kbd>type=\"password\"</kbd> - Hidden characters<br><kbd>type=\"number\"</kbd> - Numeric keyboard<br><kbd>type=\"tel\"</kbd> - Phone keyboard",
|
"description": "Diferentes tipos de campos proporcionan teclados apropiados y validación:<br><br><kbd>type=\"text\"</kbd> - Texto general<br><kbd>type=\"email\"</kbd> - Email con validación @<br><kbd>type=\"password\"</kbd> - Caracteres ocultos<br><kbd>type=\"number\"</kbd> - Teclado numérico<br><kbd>type=\"tel\"</kbd> - Teclado telefónico",
|
||||||
"task": "Create a login form with two fields:<br>1. An email field: <kbd><label for=\"email\">Email:</label></kbd> and <kbd><input type=\"email\" id=\"email\"></kbd><br>2. A password field: <kbd><label for=\"password\">Password:</label></kbd> and <kbd><input type=\"password\" id=\"password\"></kbd>",
|
"task": "Crea un formulario de inicio de sesión con dos campos:<br>1. Campo de email: <kbd><label for=\"email\">Email:</label></kbd> y <kbd><input type=\"email\" id=\"email\"></kbd><br>2. Campo de contraseña: <kbd><label for=\"password\">Contraseña:</label></kbd> y <kbd><input type=\"password\" id=\"password\"></kbd>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "<form>\n \n</form>",
|
"initialCode": "<form>\n \n</form>",
|
||||||
"solution": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\">\n</form>",
|
"solution": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <label for=\"password\">Contraseña:</label>\n <input type=\"password\" id=\"password\" name=\"password\">\n</form>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "input[type='email']",
|
"value": "input[type='email']",
|
||||||
"message": "Add an input with type=\"email\""
|
"message": "Añade un campo con type=\"email\""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "input[type='password']",
|
"value": "input[type='password']",
|
||||||
"message": "Add an input with type=\"password\""
|
"message": "Añade un campo con type=\"password\""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "label", "min": 2 },
|
"value": { "selector": "label", "min": 2 },
|
||||||
"message": "Add labels for both inputs"
|
"message": "Añade labels para ambos campos"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "submit-button",
|
"id": "submit-button",
|
||||||
"title": "Submit Button",
|
"title": "Botón de envío",
|
||||||
"description": "Forms need a way to submit data. Use:<br><br><kbd><button type=\"submit\"></kbd> - Preferred, flexible content<br><kbd><input type=\"submit\"></kbd> - Simple text-only button<br><br>The button text should be action-oriented (e.g., <code>Sign In</code>, 'Register', 'Send').",
|
"description": "Los formularios necesitan una forma de enviar datos. Usa:<br><br><kbd><button type=\"submit\"></kbd> - Preferido, contenido flexible<br><kbd><input type=\"submit\"></kbd> - Botón de solo texto<br><br>El texto del botón debe estar orientado a la acción (ej. <code>Iniciar Sesión</code>, 'Registrar', 'Enviar').",
|
||||||
"task": "Add a submit button to the form with the text <code>Sign In</code>.",
|
"task": "Añade un botón de envío al formulario con el texto <code>Iniciar Sesión</code>.",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } button { width: 100%; margin-top: 20px; padding: 10px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; } button:hover { background: #1565c0; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } button { width: 100%; margin-top: 20px; padding: 10px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; } button:hover { background: #1565c0; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\">\n \n</form>",
|
"initialCode": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">Contraseña:</label>\n <input type=\"password\" id=\"password\">\n \n</form>",
|
||||||
"solution": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\">\n \n <button type=\"submit\">Sign In</button>\n</form>",
|
"solution": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">Contraseña:</label>\n <input type=\"password\" id=\"password\">\n \n <button type=\"submit\">Iniciar Sesión</button>\n</form>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "button[type='submit'], input[type='submit']",
|
"value": "button[type='submit'], input[type='submit']",
|
||||||
"message": "Add a submit button to your form"
|
"message": "Añade un botón de envío a tu formulario"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_text",
|
"type": "element_text",
|
||||||
"value": { "selector": "button", "text": "Sign In" },
|
"value": { "selector": "button", "text": "Iniciar Sesión" },
|
||||||
"message": "The button should say <kbd>Sign In</kbd>"
|
"message": "El botón debe decir <kbd>Iniciar Sesión</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,110 +1,32 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-forms-validation",
|
"id": "html-forms-validation",
|
||||||
"title": "HTML Validation",
|
"title": "Validación de formularios",
|
||||||
"description": "Learn HTML5 built-in form validation attributes",
|
"description": "Usa la validación integrada de HTML5 para mejor experiencia de usuario",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "intermediate",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "required-fields",
|
"id": "required-fields",
|
||||||
"title": "Required Fields",
|
"title": "Campos requeridos",
|
||||||
"description": "The <kbd>required</kbd> attribute prevents form submission if the field is empty.<br><br>Add it to any input that must be filled:<br><kbd><input type=\"text\" required></kbd><br><br>The browser shows a validation message automatically.",
|
"description": "El atributo <kbd>required</kbd> evita el envío del formulario si el campo está vacío. ¡El navegador muestra un mensaje de validación automáticamente - sin JavaScript!<br><br>Añádelo a cualquier campo que deba rellenarse:<br><kbd><input type=\"text\" required></kbd>",
|
||||||
"task": "Make both the name and email fields required by adding the <kbd>required</kbd> attribute.",
|
"task": "Haz que ambos campos (nombre y email) sean requeridos añadiendo el atributo <kbd>required</kbd> a cada campo.",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 350px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-of-type { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } input:invalid { border-color: #d32f2f; } button { margin-top: 20px; padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 320px; } label { display: block; margin-top: 15px; margin-bottom: 5px; font-weight: 500; } label:first-of-type { margin-top: 0; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; box-sizing: border-box; } input:focus { outline: 2px solid steelblue; border-color: transparent; } button { margin-top: 20px; padding: 10px 20px; background: steelblue; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 500; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "<form>\n <label for=\"name\">Name: *</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n \n <label for=\"email\">Email: *</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <button type=\"submit\">Submit</button>\n</form>",
|
"initialCode": "<form>\n <label for=\"name\">Nombre *</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n \n <label for=\"email\">Email *</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <button type=\"submit\">Enviar</button>\n</form>",
|
||||||
"solution": "<form>\n <label for=\"name\">Name: *</label>\n <input type=\"text\" id=\"name\" name=\"name\" required>\n \n <label for=\"email\">Email: *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <button type=\"submit\">Submit</button>\n</form>",
|
"solution": "<form>\n <label for=\"name\">Nombre *</label>\n <input type=\"text\" id=\"name\" name=\"name\" required>\n \n <label for=\"email\">Email *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <button type=\"submit\">Enviar</button>\n</form>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "input[name='name']", "attr": "required", "value": true },
|
"value": { "selector": "input[name='name']", "attr": "required", "value": true },
|
||||||
"message": "Add the <kbd>required</kbd> attribute to the name input"
|
"message": "Añade <kbd>required</kbd> al campo de nombre"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "input[name='email']", "attr": "required", "value": true },
|
"value": { "selector": "input[name='email']", "attr": "required", "value": true },
|
||||||
"message": "Add the <kbd>required</kbd> attribute to the email input"
|
"message": "Añade <kbd>required</kbd> al campo de email"
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "input-constraints",
|
|
||||||
"title": "Constraints",
|
|
||||||
"description": "Control what users can enter:<br><br><kbd>minlength</kbd> / <kbd>maxlength</kbd> - Text length limits<br><kbd>min</kbd> / <kbd>max</kbd> - Number range<br><kbd>pattern</kbd> - Regex pattern matching<br><kbd>placeholder</kbd> - Hint text (not a label!)",
|
|
||||||
"task": "Add validation to the password input:<br>1. Add <kbd>minlength=\"8\"</kbd> for minimum length<br>2. Add <kbd>maxlength=\"20\"</kbd> for maximum length<br>3. Add <kbd>placeholder=\"Enter password\"</kbd> as a hint",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 350px; } label { display: block; margin-bottom: 5px; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } input:invalid:not(:placeholder-shown) { border-color: #d32f2f; } small { display: block; font-size: 12px; color: #666; margin-top: 4px; } button { margin-top: 20px; padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<form>\n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required aria-describedby=\"password-hint\">\n <small id=\"password-hint\">Must be 8-20 characters</small>\n \n <button type=\"submit\">Create Account</button>\n</form>",
|
|
||||||
"solution": "<form>\n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required minlength=\"8\" maxlength=\"20\" placeholder=\"Enter password\" aria-describedby=\"password-hint\">\n <small id=\"password-hint\">Must be 8-20 characters</small>\n \n <button type=\"submit\">Create Account</button>\n</form>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "input[type='password']", "attr": "minlength", "value": "8" },
|
|
||||||
"message": "Add <kbd>minlength</kbd>=\"8\" to the password input"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "input[type='password']", "attr": "maxlength", "value": "20" },
|
|
||||||
"message": "Add <kbd>maxlength</kbd>=\"20\" to the password input"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "input[type='password']", "attr": "placeholder", "value": null },
|
|
||||||
"message": "Add a <kbd>placeholder</kbd> to hint what to enter"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "complete-registration",
|
|
||||||
"title": "Full Form",
|
|
||||||
"description": "Build a complete registration form with all validation concepts:<br><br>- Required fields marked with *<br>- Email validation (use type=\"email\")<br>- Password with length constraints<br>- Terms checkbox (required)<br>- Submit button",
|
|
||||||
"task": "Complete the registration form. Add required attributes, proper input types, and validation constraints.",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 400px; background: #f5f5f5; padding: 25px; border-radius: 8px; } h2 { margin-top: 0; margin-bottom: 20px; } label { display: block; margin-top: 15px; margin-bottom: 5px; font-weight: 500; } input[type='text'], input[type='email'], input[type='password'] { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; font-size: 14px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; } input[type='checkbox'] { width: auto; margin-right: 8px; vertical-align: middle; } label:has(input[type='checkbox']) { display: flex; align-items: center; font-weight: normal; } button { width: 100%; margin-top: 20px; padding: 12px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: 500; } button:hover { background: #1565c0; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<form>\n <h2>Create Account</h2>\n \n <label for=\"fullname\">Full Name *</label>\n <input type=\"text\" id=\"fullname\" name=\"fullname\">\n \n <label for=\"email\">Email *</label>\n <input id=\"email\" name=\"email\">\n \n <label for=\"password\">Password *</label>\n <input id=\"password\" name=\"password\">\n \n <label>\n <input type=\"checkbox\" id=\"terms\" name=\"terms\">\n I agree to the Terms of Service *\n </label>\n \n <button type=\"submit\">Register</button>\n</form>",
|
|
||||||
"solution": "<form>\n <h2>Create Account</h2>\n \n <label for=\"fullname\">Full Name *</label>\n <input type=\"text\" id=\"fullname\" name=\"fullname\" required>\n \n <label for=\"email\">Email *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <label for=\"password\">Password *</label>\n <input type=\"password\" id=\"password\" name=\"password\" required minlength=\"8\">\n \n <label>\n <input type=\"checkbox\" id=\"terms\" name=\"terms\" required>\n I agree to the Terms of Service *\n </label>\n \n <button type=\"submit\">Register</button>\n</form>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#fullname", "attr": "required", "value": true },
|
|
||||||
"message": "Make the full name field <kbd>required</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#email", "attr": "type", "value": "email" },
|
|
||||||
"message": "Set the email input <kbd>type=\"email\"</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#email", "attr": "required", "value": true },
|
|
||||||
"message": "Make the email field <kbd>required</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#password", "attr": "type", "value": "password" },
|
|
||||||
"message": "Set the password input <kbd>type=\"password\"</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#password", "attr": "required", "value": true },
|
|
||||||
"message": "Make the password field <kbd>required</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#password", "attr": "minlength", "value": "8" },
|
|
||||||
"message": "Add <kbd>minlength</kbd>=\"8\" to password"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#terms", "attr": "required", "value": true },
|
|
||||||
"message": "Make the terms checkbox <kbd>required</kbd>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-details-summary",
|
"id": "html-details-summary",
|
||||||
"title": "HTML Details & Summary",
|
"title": "HTML Details & Summary",
|
||||||
"description": "Create expandable content sections without JavaScript",
|
"description": "Crea secciones expandibles sin JavaScript",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "details-summary-basic",
|
"id": "details-summary-basic",
|
||||||
"title": "First Widget",
|
"title": "Primer widget",
|
||||||
"description": "The <kbd><details></kbd> element creates a collapsible section. The <kbd><summary></kbd> provides the clickable label.<br><br>Click the summary to toggle the hidden content - no JavaScript needed!",
|
"description": "El elemento <kbd><details></kbd> crea una sección plegable. El <kbd><summary></kbd> proporciona la etiqueta clickeable.<br><br>¡Haz clic en el resumen para mostrar el contenido oculto - sin JavaScript!",
|
||||||
"task": "Create a <kbd><details></kbd> element with:<br>1. A <kbd><summary></kbd> saying <code>Click to reveal</code><br>2. A <kbd><p></kbd> with the text <code>This content was hidden!</code>",
|
"task": "Crea un elemento <kbd><details></kbd> con:<br>1. Un <kbd><summary></kbd> que diga <code>Click to reveal</code><br>2. Un <kbd><p></kbd> con el texto <code>This content was hidden!</code>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; } summary { font-weight: 600; cursor: pointer; } summary:hover { color: #1976d2; } details p { margin-top: 15px; color: #666; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; } summary { font-weight: 600; cursor: pointer; } summary:hover { color: #1976d2; } details p { margin-top: 15px; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,30 +21,30 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "details",
|
"value": "details",
|
||||||
"message": "Add a <kbd><details></kbd> element"
|
"message": "Añade un elemento <kbd><details></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "summary",
|
"value": "summary",
|
||||||
"message": "Add a <kbd><summary></kbd> inside the details"
|
"message": "Añade un <kbd><summary></kbd> dentro del details"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "parent_child",
|
"type": "parent_child",
|
||||||
"value": { "parent": "details", "child": "summary" },
|
"value": { "parent": "details", "child": "summary" },
|
||||||
"message": "The <kbd><summary></kbd> must be inside <kbd><details></kbd>"
|
"message": "El <kbd><summary></kbd> debe estar dentro de <kbd><details></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "parent_child",
|
"type": "parent_child",
|
||||||
"value": { "parent": "details", "child": "p" },
|
"value": { "parent": "details", "child": "p" },
|
||||||
"message": "Add a <kbd><p></kbd> inside <kbd><details></kbd> for the hidden content"
|
"message": "Añade un <kbd><p></kbd> dentro de <kbd><details></kbd> para el contenido oculto"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "details-open-attribute",
|
"id": "details-open-attribute",
|
||||||
"title": "Pre-expanded Details",
|
"title": "Expandido por defecto",
|
||||||
"description": "By default, <kbd><details></kbd> is closed. Add the <kbd>open</kbd> attribute to show the content initially.<br><br>This is a boolean attribute - just add <kbd>open</kbd> without a value.",
|
"description": "Por defecto, <kbd><details></kbd> está cerrado. Añade el atributo <kbd>open</kbd> para mostrar el contenido inicialmente.<br><br>Este es un atributo booleano - solo añade <kbd>open</kbd> sin valor.",
|
||||||
"task": "Add the <kbd>open</kbd> attribute to the <kbd><details></kbd> element to show the content by default.",
|
"task": "Añade el atributo <kbd>open</kbd> al elemento <kbd><details></kbd> para mostrar el contenido por defecto.",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; background: #f9f9f9; } summary { font-weight: 600; cursor: pointer; } details p { margin-top: 15px; color: #666; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; background: #f9f9f9; } summary { font-weight: 600; cursor: pointer; } details p { margin-top: 15px; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -55,15 +55,15 @@
|
|||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "details", "attr": "open", "value": true },
|
"value": { "selector": "details", "attr": "open", "value": true },
|
||||||
"message": "Add the <kbd>open</kbd> attribute to <kbd><details></kbd>"
|
"message": "Añade el atributo <kbd>open</kbd> a <kbd><details></kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "faq-accordion",
|
"id": "faq-accordion",
|
||||||
"title": "FAQ Accordion",
|
"title": "Acordeón FAQ",
|
||||||
"description": "Multiple <kbd><details></kbd> elements create an accordion-style FAQ. Each question can be expanded independently.<br><br><b>Pro tip:</b> Type <kbd>details*3>summary+p</kbd> and press Tab for Emmet expansion. <kbd>*3</kbd> creates 3 elements, <kbd>></kbd> nests inside, <kbd>+</kbd> adds siblings.",
|
"description": "Múltiples elementos <kbd><details></kbd> crean un FAQ estilo acordeón. Cada pregunta puede expandirse independientemente.<br><br><b>Pro tip:</b> Escribe <kbd>details*3>summary+p</kbd> y presiona Tab para expansión Emmet. <kbd>*3</kbd> crea 3 elementos, <kbd>></kbd> anida dentro, <kbd>+</kbd> añade hermanos.",
|
||||||
"task": "Create an FAQ section with:<br>1. An <kbd><h1></kbd> saying <code>Frequently Asked Questions</code><br>2. Three <kbd><details></kbd> elements, each with a question in <kbd><summary></kbd> and an answer in <kbd><p></kbd>",
|
"task": "Crea una sección FAQ con:<br>1. Un <kbd><h1></kbd> que diga <code>Frequently Asked Questions</code><br>2. Tres elementos <kbd><details></kbd>, cada uno con una pregunta en <kbd><summary></kbd> y una respuesta en <kbd><p></kbd>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; min-height: 100vh; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); display: flex; flex-direction: column; justify-content: center; padding: 40px; margin: 0; box-sizing: border-box; } h1 { font-size: 2.5rem; color: #4a4a4a; text-align: center; margin: 0 0 30px 0; } details { background: white; border-radius: 12px; margin-bottom: 15px; padding: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } summary { font-size: 1.3rem; font-weight: 600; color: #5c5c5c; cursor: pointer; list-style: none; } summary::before { content: '▸ '; color: #fcb69f; } details[open] summary::before { content: '▾ '; } details p { margin: 15px 0 0 0; color: #666; line-height: 1.6; }",
|
"previewBaseCSS": "body { font-family: system-ui; min-height: 100vh; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); display: flex; flex-direction: column; justify-content: center; padding: 40px; margin: 0; box-sizing: border-box; } h1 { font-size: 2.5rem; color: #4a4a4a; text-align: center; margin: 0 0 30px 0; } details { background: white; border-radius: 12px; margin-bottom: 15px; padding: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } summary { font-size: 1.3rem; font-weight: 600; color: #5c5c5c; cursor: pointer; list-style: none; } summary::before { content: '▸ '; color: #fcb69f; } details[open] summary::before { content: '▾ '; } details p { margin: 15px 0 0 0; color: #666; line-height: 1.6; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -74,22 +74,22 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "h1",
|
"value": "h1",
|
||||||
"message": "Add an <kbd><h1></kbd> heading for the FAQ title"
|
"message": "Añade un encabezado <kbd><h1></kbd> para el título del FAQ"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "details", "min": 3 },
|
"value": { "selector": "details", "min": 3 },
|
||||||
"message": "Create at least 3 <kbd><details></kbd> elements for the FAQ"
|
"message": "Crea al menos 3 elementos <kbd><details></kbd> para el FAQ"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "summary", "min": 3 },
|
"value": { "selector": "summary", "min": 3 },
|
||||||
"message": "Each <kbd><details></kbd> needs a <kbd><summary></kbd> for the question"
|
"message": "Cada <kbd><details></kbd> necesita un <kbd><summary></kbd> para la pregunta"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "details p", "min": 3 },
|
"value": { "selector": "details p", "min": 3 },
|
||||||
"message": "Each <kbd><details></kbd> needs a <kbd><p></kbd> for the answer"
|
"message": "Cada <kbd><details></kbd> necesita un <kbd><p></kbd> para la respuesta"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-progress-meter",
|
"id": "html-progress-meter",
|
||||||
"title": "HTML Progress & Meter",
|
"title": "HTML Progress & Meter",
|
||||||
"description": "Display completion status and scalar measurements natively",
|
"description": "Muestra el estado de completado y mediciones escalares nativamente",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "progress-basic",
|
"id": "progress-basic",
|
||||||
"title": "Progress Bars",
|
"title": "Barras de progreso",
|
||||||
"description": "The <kbd><progress></kbd> element shows task completion. Use <kbd>value</kbd> for current progress and <kbd>max</kbd> for the total.<br><br><b>Note:</b> This is not a self-closing tag! Write <kbd><progress>...</progress></kbd> with fallback text inside for older browsers.",
|
"description": "El elemento <kbd><progress></kbd> muestra el progreso de una tarea. Usa <kbd>value</kbd> para el progreso actual y <kbd>max</kbd> para el total.<br><br><b>Nota:</b> ¡Esto no es una etiqueta de cierre automático! Escribe <kbd><progress>...</progress></kbd> con texto alternativo dentro para navegadores antiguos.",
|
||||||
"task": "Create a progress bar showing 70% completion:<br>1. Add a <kbd><label></kbd> saying <code>Download:</code><br>2. Add a <kbd><progress></kbd> with <kbd>value=\"70\"</kbd> and <kbd>max=\"100\"</kbd>",
|
"task": "Crea una barra de progreso mostrando 70% de completado:<br>1. Añade un <kbd><label></kbd> que diga <code>Download:</code><br>2. Añade un <kbd><progress></kbd> con <kbd>value=\"70\"</kbd> y <kbd>max=\"100\"</kbd>",
|
||||||
"previewHTML": "",
|
"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; }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,30 +21,30 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "progress",
|
"value": "progress",
|
||||||
"message": "Add a <kbd><progress></kbd> element"
|
"message": "Añade un elemento <kbd><progress></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "progress", "attr": "value", "value": "70" },
|
"value": { "selector": "progress", "attr": "value", "value": "70" },
|
||||||
"message": "Set <kbd>value=</kbd>\"70\" on the progress element"
|
"message": "Establece <kbd>value=</kbd>\"70\" en el elemento progress"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "progress", "attr": "max", "value": "100" },
|
"value": { "selector": "progress", "attr": "max", "value": "100" },
|
||||||
"message": "Set <kbd>max=</kbd>\"100\" on the progress element"
|
"message": "Establece <kbd>max=</kbd>\"100\" en el elemento progress"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "label",
|
"value": "label",
|
||||||
"message": "Add a <kbd><label></kbd> for the progress bar"
|
"message": "Añade un <kbd><label></kbd> para la barra de progreso"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "progress-indeterminate",
|
"id": "progress-indeterminate",
|
||||||
"title": "Indeterminate Progress",
|
"title": "Progreso indeterminado",
|
||||||
"description": "When progress is unknown (like loading), omit the <kbd>value</kbd> attribute. This creates an animated indeterminate state.<br><br>Useful for network requests or processes with unknown duration.",
|
"description": "Cuando el progreso es desconocido (como al cargar), omite el atributo <kbd>value</kbd>. Esto crea un estado animado indeterminado.<br><br>Útil para solicitudes de red o procesos con duración desconocida.",
|
||||||
"task": "Create a loading indicator:<br>1. Add a <kbd><p></kbd> saying <code>Loading...</code><br>2. Add a <kbd><progress></kbd> without a value attribute",
|
"task": "Crea un indicador de carga:<br>1. Añade un <kbd><p></kbd> que diga <code>Loading...</code><br>2. Añade un <kbd><progress></kbd> sin atributo value",
|
||||||
"previewHTML": "",
|
"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; }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
@@ -55,20 +55,20 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "progress",
|
"value": "progress",
|
||||||
"message": "Add a <kbd><progress></kbd> element"
|
"message": "Añade un elemento <kbd><progress></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "p",
|
"value": "p",
|
||||||
"message": "Add a <kbd><p></kbd> with loading text"
|
"message": "Añade un <kbd><p></kbd> con texto de carga"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "meter-gauge",
|
"id": "meter-gauge",
|
||||||
"title": "Meter Gauges",
|
"title": "Indicadores meter",
|
||||||
"description": "The <kbd><meter></kbd> element displays a scalar value within a range. Use it for measurements like disk space, battery, or ratings.<br><br>Set <kbd>low</kbd>, <kbd>high</kbd>, and <kbd>optimum</kbd> to define good/bad ranges - the browser colors it accordingly!",
|
"description": "El elemento <kbd><meter></kbd> muestra un valor escalar dentro de un rango. Úsalo para mediciones como espacio en disco, batería o calificaciones.<br><br>Establece <kbd>low</kbd>, <kbd>high</kbd> y <kbd>optimum</kbd> para definir rangos buenos/malos - ¡el navegador lo colorea correspondientemente!",
|
||||||
"task": "Create a battery level meter:<br>1. Add a <kbd><label></kbd> saying <code>Battery:</code><br>2. Add a <kbd><meter></kbd> with:<br> - <kbd>value=\"0.8\"</kbd><br> - <kbd>min=\"0\"</kbd> and <kbd>max=\"1\"</kbd><br> - <kbd>low=\"0.2\"</kbd> and <kbd>high=\"0.8\"</kbd><br> - <kbd>optimum=\"1\"</kbd>",
|
"task": "Crea un indicador de nivel de batería:<br>1. Añade un <kbd><label></kbd> que diga <code>Battery:</code><br>2. Añade un <kbd><meter></kbd> con:<br> - <kbd>value=\"0.8\"</kbd><br> - <kbd>min=\"0\"</kbd> y <kbd>max=\"1\"</kbd><br> - <kbd>low=\"0.2\"</kbd> y <kbd>high=\"0.8\"</kbd><br> - <kbd>optimum=\"1\"</kbd>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -79,22 +79,42 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "meter",
|
"value": "meter",
|
||||||
"message": "Add a <kbd><meter></kbd> element"
|
"message": "Añade un elemento <kbd><meter></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "meter", "attr": "value", "value": "0.8" },
|
"value": { "selector": "meter", "attr": "value", "value": "0.8" },
|
||||||
"message": "Set <kbd>value=</kbd>\"0.8\" on the meter"
|
"message": "Establece <kbd>value=</kbd>\"0.8\" en el meter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "meter", "attr": "min", "value": "0" },
|
||||||
|
"message": "Establece <kbd>min=</kbd>\"0\" en el meter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "meter", "attr": "max", "value": "1" },
|
||||||
|
"message": "Establece <kbd>max=</kbd>\"1\" en el meter"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "meter", "attr": "low", "value": "0.2" },
|
"value": { "selector": "meter", "attr": "low", "value": "0.2" },
|
||||||
"message": "Set <kbd>low=</kbd>\"0.2\" to define the low threshold"
|
"message": "Establece <kbd>low=</kbd>\"0.2\" para definir el umbral bajo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "meter", "attr": "high", "value": "0.8" },
|
||||||
|
"message": "Establece <kbd>high=</kbd>\"0.8\" para definir el umbral alto"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "meter", "attr": "optimum", "value": "1" },
|
||||||
|
"message": "Establece <kbd>optimum=</kbd>\"1\" para indicar el valor óptimo"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "label",
|
"value": "label",
|
||||||
"message": "Add a <kbd><label></kbd> for the meter"
|
"message": "Añade un <kbd><label></kbd> para el meter"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-datalist",
|
"id": "html-datalist",
|
||||||
"title": "Datalist",
|
"title": "Datalist",
|
||||||
"description": "Provide suggestions for text inputs without JavaScript",
|
"description": "Proporciona sugerencias para campos de texto sin JavaScript",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "datalist-basic",
|
"id": "datalist-basic",
|
||||||
"title": "Input with Suggestions",
|
"title": "Campo con sugerencias",
|
||||||
"description": "The <kbd><datalist></kbd> element provides autocomplete suggestions for inputs. Connect it using the <kbd>list</kbd> attribute on the input matching the datalist's <kbd>id</kbd>.<br><br>Users can still type freely - suggestions are just helpers!",
|
"description": "El elemento <kbd><datalist></kbd> proporciona sugerencias de autocompletado para campos. Conéctalo usando el atributo <kbd>list</kbd> en el input que coincida con el <kbd>id</kbd> del datalist.<br><br>Los usuarios pueden escribir libremente - ¡las sugerencias son solo ayudas!",
|
||||||
"task": "Create a browser selector:<br>1. Add a <kbd><label></kbd> saying <code>Browser:</code><br>2. Add an <kbd><input></kbd> with <kbd>list=\"browsers\"</kbd><br>3. Add a <kbd><datalist id=\"browsers\"></kbd> with options for Chrome, Firefox, and Safari",
|
"task": "Crea un selector de navegador:<br>1. Añade un <kbd><label></kbd> que diga <code>Navegador:</code><br>2. Añade un <kbd><input></kbd> con <kbd>list=\"browsers\"</kbd><br>3. Añade un <kbd><datalist id=\"browsers\"></kbd> con opciones para Chrome, Firefox y Safari",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; font-size: 16px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; font-size: 16px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,30 +21,30 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "datalist",
|
"value": "datalist",
|
||||||
"message": "Add a <kbd><datalist></kbd> element"
|
"message": "Añade un elemento <kbd><datalist></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "input", "attr": "list", "value": "browsers" },
|
"value": { "selector": "input", "attr": "list", "value": "browsers" },
|
||||||
"message": "Connect the input to datalist using <kbd>list=</kbd>\"browsers\""
|
"message": "Conecta el input al datalist usando <kbd>list=</kbd>\"browsers\""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "option", "min": 3 },
|
"value": { "selector": "option", "min": 3 },
|
||||||
"message": "Add at least 3 <kbd><option></kbd> elements inside <kbd><datalist></kbd>"
|
"message": "Añade al menos 3 elementos <kbd><option></kbd> dentro de <kbd><datalist></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "label",
|
"value": "label",
|
||||||
"message": "Add a <kbd><label></kbd> for the input"
|
"message": "Añade un <kbd><label></kbd> para el campo"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "datalist-countries",
|
"id": "datalist-countries",
|
||||||
"title": "Country Selector",
|
"title": "Selector de países",
|
||||||
"description": "Datalists work great for long lists like countries. Users can type to filter suggestions instantly.<br><br>The <kbd>value</kbd> attribute is what gets entered, and you can add display text after it.",
|
"description": "Los datalist funcionan genial para listas largas como países. Los usuarios pueden escribir para filtrar sugerencias al instante.<br><br>El atributo <kbd>value</kbd> es lo que se ingresa, y puedes añadir texto de visualización después.",
|
||||||
"task": "Create a country input:<br>1. Add a <kbd><label></kbd> saying <code>Country:</code><br>2. Add an <kbd><input></kbd> with <kbd>list=\"countries\"</kbd><br>3. Add a <kbd><datalist id=\"countries\"></kbd> with at least 4 country options",
|
"task": "Crea un campo de país:<br>1. Añade un <kbd><label></kbd> que diga <code>País:</code><br>2. Añade un <kbd><input></kbd> con <kbd>list=\"countries\"</kbd><br>3. Añade un <kbd><datalist id=\"countries\"></kbd> con al menos 4 opciones de países",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 30px; background: linear-gradient(135deg, #e0f7fa 0%, #b2ebf2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } label { display: block; margin-bottom: 10px; font-weight: 600; color: #00695c; } input { width: 100%; padding: 12px 15px; border: 2px solid #26a69a; border-radius: 8px; font-size: 16px; background: white; } input:focus { outline: none; border-color: #00695c; box-shadow: 0 0 0 3px rgba(38,166,154,0.2); }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 30px; background: linear-gradient(135deg, #e0f7fa 0%, #b2ebf2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } label { display: block; margin-bottom: 10px; font-weight: 600; color: #00695c; } input { width: 100%; padding: 12px 15px; border: 2px solid #26a69a; border-radius: 8px; font-size: 16px; background: white; } input:focus { outline: none; border-color: #00695c; box-shadow: 0 0 0 3px rgba(38,166,154,0.2); }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -55,22 +55,22 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "datalist",
|
"value": "datalist",
|
||||||
"message": "Add a <kbd><datalist></kbd> element"
|
"message": "Añade un elemento <kbd><datalist></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "datalist", "attr": "id", "value": "countries" },
|
"value": { "selector": "datalist", "attr": "id", "value": "countries" },
|
||||||
"message": "Set <kbd>id=</kbd>\"countries\" on the datalist"
|
"message": "Establece <kbd>id=</kbd>\"countries\" en el datalist"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "input", "attr": "list", "value": "countries" },
|
"value": { "selector": "input", "attr": "list", "value": "countries" },
|
||||||
"message": "Connect the input using <kbd>list=</kbd>\"countries\""
|
"message": "Conecta el input usando <kbd>list=</kbd>\"countries\""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "option", "min": 4 },
|
"value": { "selector": "option", "min": 4 },
|
||||||
"message": "Add at least 4 country options"
|
"message": "Añade al menos 4 opciones de países"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-dialog",
|
"id": "html-dialog",
|
||||||
"title": "Dialogs",
|
"title": "Diálogos",
|
||||||
"description": "Create modal dialogs without JavaScript libraries",
|
"description": "Crea diálogos modales sin bibliotecas JavaScript",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "dialog-basic",
|
"id": "dialog-basic",
|
||||||
"title": "Open Dialog",
|
"title": "Abrir diálogo",
|
||||||
"description": "The <kbd><dialog></kbd> element creates a native modal. Add the <kbd>open</kbd> attribute to show it.<br><br>Use <kbd><form method=\"dialog\"></kbd> inside to close it when the form submits - no JavaScript needed!",
|
"description": "El elemento <kbd><dialog></kbd> crea un modal nativo. Añade el atributo <kbd>open</kbd> para mostrarlo.<br><br>¡Usa <kbd><form method=\"dialog\"></kbd> dentro para cerrarlo al enviar el formulario - sin JavaScript!",
|
||||||
"task": "Create a dialog with:<br>1. The <kbd>open</kbd> attribute to show it<br>2. An <kbd><h2></kbd> saying <code>Welcome!</code><br>3. A <kbd><p></kbd> with a greeting message<br>4. A <kbd><form method=\"dialog\"></kbd> with a close button",
|
"task": "Crea un diálogo con:<br>1. El atributo <kbd>open</kbd> para mostrarlo<br>2. Un <kbd><h2></kbd> que diga <code>¡Bienvenido!</code><br>3. Un <kbd><p></kbd> con un mensaje de saludo<br>4. Un <kbd><form method=\"dialog\"></kbd> con un botón de cerrar",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: #f5f5f5; } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 400px; } dialog::backdrop { background: rgba(0,0,0,0.5); } dialog h2 { margin: 0 0 15px 0; color: #1976d2; } dialog p { color: #666; margin: 0 0 20px 0; } dialog button { background: #1976d2; color: white; border: none; padding: 10px 25px; border-radius: 6px; cursor: pointer; font-size: 16px; } dialog button:hover { background: #1565c0; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: #f5f5f5; } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 400px; } dialog::backdrop { background: rgba(0,0,0,0.5); } dialog h2 { margin: 0 0 15px 0; color: #1976d2; } dialog p { color: #666; margin: 0 0 20px 0; } dialog button { background: #1976d2; color: white; border: none; padding: 10px 25px; border-radius: 6px; cursor: pointer; font-size: 16px; } dialog button:hover { background: #1565c0; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,35 +21,35 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "dialog",
|
"value": "dialog",
|
||||||
"message": "Add a <kbd><dialog></kbd> element"
|
"message": "Añade un elemento <kbd><dialog></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "dialog", "attr": "open", "value": true },
|
"value": { "selector": "dialog", "attr": "open", "value": true },
|
||||||
"message": "Add the <kbd>open</kbd> attribute to show the dialog"
|
"message": "Añade el atributo <kbd>open</kbd> para mostrar el diálogo"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "dialog h2",
|
"value": "dialog h2",
|
||||||
"message": "Add an <kbd><h2></kbd> heading inside the dialog"
|
"message": "Añade un encabezado <kbd><h2></kbd> dentro del diálogo"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "form[method='dialog']",
|
"value": "form[method='dialog']",
|
||||||
"message": "Add a <kbd><form method=\"dialog\"></kbd> for closing"
|
"message": "Añade un <kbd><form method=\"dialog\"></kbd> para cerrar"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "dialog button",
|
"value": "dialog button",
|
||||||
"message": "Add a close button inside the form"
|
"message": "Añade un botón de cerrar dentro del formulario"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "dialog-form",
|
"id": "dialog-form",
|
||||||
"title": "Dialog + Form",
|
"title": "Diálogo + Formulario",
|
||||||
"description": "Dialogs can contain full forms. The <kbd>method=\"dialog\"</kbd> makes the form close the dialog on submit instead of sending data.<br><br>This pattern is perfect for confirmation dialogs, quick inputs, or settings panels.",
|
"description": "Los diálogos pueden contener formularios completos. El <kbd>method=\"dialog\"</kbd> hace que el formulario cierre el diálogo al enviar en lugar de enviar datos.<br><br>Este patrón es perfecto para diálogos de confirmación, entradas rápidas o paneles de configuración.",
|
||||||
"task": "Create a confirmation dialog:<br>1. Add <kbd>open</kbd> to show it<br>2. An <kbd><h2></kbd> saying <code>Confirm Delete</code><br>3. A <kbd><p></kbd> asking <code>Are you sure?</code><br>4. A <kbd><form method=\"dialog\"></kbd> with Cancel and Delete buttons",
|
"task": "Crea un diálogo de confirmación:<br>1. Añade <kbd>open</kbd> para mostrarlo<br>2. Un <kbd><h2></kbd> que diga <code>Confirmar eliminación</code><br>3. Un <kbd><p></kbd> preguntando <code>¿Estás seguro?</code><br>4. Un <kbd><form method=\"dialog\"></kbd> con botones Cancelar y Eliminar",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: linear-gradient(135deg, #fce4ec 0%, #f8bbd9 100%); } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 350px; text-align: center; } dialog h2 { margin: 0 0 10px 0; color: #c62828; } dialog p { color: #666; margin: 0 0 20px 0; } dialog form { display: flex; gap: 10px; justify-content: center; } dialog button { padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; border: none; } dialog button:first-child { background: #e0e0e0; color: #333; } dialog button:last-child { background: #c62828; color: white; } dialog button:hover { opacity: 0.9; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: linear-gradient(135deg, #fce4ec 0%, #f8bbd9 100%); } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 350px; text-align: center; } dialog h2 { margin: 0 0 10px 0; color: #c62828; } dialog p { color: #666; margin: 0 0 20px 0; } dialog form { display: flex; gap: 10px; justify-content: center; } dialog button { padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; border: none; } dialog button:first-child { background: #e0e0e0; color: #333; } dialog button:last-child { background: #c62828; color: white; } dialog button:hover { opacity: 0.9; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -60,22 +60,22 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "dialog[open]",
|
"value": "dialog[open]",
|
||||||
"message": "Add a <kbd><dialog></kbd> with the open attribute"
|
"message": "Añade un <kbd><dialog></kbd> con el atributo open"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "dialog h2",
|
"value": "dialog h2",
|
||||||
"message": "Add a heading to the dialog"
|
"message": "Añade un encabezado al diálogo"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "form[method='dialog']",
|
"value": "form[method='dialog']",
|
||||||
"message": "Add a <kbd><form method=\"dialog\"></kbd> for the buttons"
|
"message": "Añade un <kbd><form method=\"dialog\"></kbd> para los botones"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "dialog button", "min": 2 },
|
"value": { "selector": "dialog button", "min": 2 },
|
||||||
"message": "Add at least 2 buttons (Cancel and Confirm)"
|
"message": "Añade al menos 2 botones (Cancelar y Confirmar)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-forms-fieldset",
|
"id": "html-forms-fieldset",
|
||||||
"title": "Fieldsets",
|
"title": "Fieldsets",
|
||||||
"description": "Group form controls with fieldset and legend elements",
|
"description": "Agrupa controles de formulario con elementos fieldset y legend",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "fieldset-basic",
|
"id": "fieldset-basic",
|
||||||
"title": "Grouping with Fieldset",
|
"title": "Agrupar con Fieldset",
|
||||||
"description": "The <kbd><fieldset></kbd> element groups related form controls together. Add a <kbd><legend></kbd> as the first child to give the group a title.<br><br>This helps with accessibility and visual organization of complex forms.",
|
"description": "El elemento <kbd><fieldset></kbd> agrupa controles de formulario relacionados. Añade un <kbd><legend></kbd> como primer hijo para dar un título al grupo.<br><br>Esto ayuda con la accesibilidad y organización visual de formularios complejos.",
|
||||||
"task": "Create a form with a fieldset:<br>1. A <kbd><form></kbd> element<br>2. A <kbd><fieldset></kbd> inside<br>3. A <kbd><legend></kbd> saying <code>Personal Info</code><br>4. Two labeled inputs for name and email",
|
"task": "Crea un formulario con un fieldset:<br>1. Un elemento <kbd><form></kbd><br>2. Un <kbd><fieldset></kbd> dentro<br>3. Un <kbd><legend></kbd> que diga <code>Información Personal</code><br>4. Dos campos etiquetados para nombre y email",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f0f4f8; } form { max-width: 400px; } fieldset { border: 2px solid #3498db; border-radius: 10px; padding: 20px; background: white; } legend { color: #3498db; font-weight: 600; padding: 0 10px; font-size: 1.1rem; } label { display: block; margin: 15px 0 5px; color: #333; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; } input:focus { outline: 2px solid #3498db; border-color: transparent; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f0f4f8; } form { max-width: 400px; } fieldset { border: 2px solid #3498db; border-radius: 10px; padding: 20px; background: white; } legend { color: #3498db; font-weight: 600; padding: 0 10px; font-size: 1.1rem; } label { display: block; margin: 15px 0 5px; color: #333; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; } input:focus { outline: 2px solid #3498db; border-color: transparent; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,35 +21,35 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "form",
|
"value": "form",
|
||||||
"message": "Add a <kbd><form></kbd> element"
|
"message": "Añade un elemento <kbd><form></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "fieldset",
|
"value": "fieldset",
|
||||||
"message": "Add a <kbd><fieldset></kbd> inside the form"
|
"message": "Añade un <kbd><fieldset></kbd> dentro del formulario"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "legend",
|
"value": "legend",
|
||||||
"message": "Add a <kbd><legend></kbd> to title your fieldset"
|
"message": "Añade un <kbd><legend></kbd> para titular tu fieldset"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "label", "min": 2 },
|
"value": { "selector": "label", "min": 2 },
|
||||||
"message": "Add at least 2 labels"
|
"message": "Añade al menos 2 etiquetas"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "input", "min": 2 },
|
"value": { "selector": "input", "min": 2 },
|
||||||
"message": "Add at least 2 input fields"
|
"message": "Añade al menos 2 campos de entrada"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "fieldset-textarea",
|
"id": "fieldset-textarea",
|
||||||
"title": "Adding Textarea",
|
"title": "Añadiendo Textarea",
|
||||||
"description": "The <kbd><textarea></kbd> element creates a multi-line text input, perfect for longer content like messages or descriptions.<br><br>Use <kbd>rows</kbd> and <kbd>cols</kbd> attributes to set default size.",
|
"description": "El elemento <kbd><textarea></kbd> crea una entrada de texto multilínea, perfecta para contenido largo como mensajes o descripciones.<br><br>Usa los atributos <kbd>rows</kbd> y <kbd>cols</kbd> para establecer el tamaño predeterminado.",
|
||||||
"task": "Create a contact form:<br>1. A <kbd><fieldset></kbd> with <kbd><legend></kbd> <code>Contact Us</code><br>2. A labeled <kbd><input></kbd> for email<br>3. A labeled <kbd><textarea></kbd> for the message<br>4. A submit <kbd><button></kbd>",
|
"task": "Crea un formulario de contacto:<br>1. Un <kbd><fieldset></kbd> con <kbd><legend></kbd> <code>Contáctanos</code><br>2. Un <kbd><input></kbd> etiquetado para email<br>3. Un <kbd><textarea></kbd> etiquetado para el mensaje<br>4. Un <kbd><button></kbd> de envío",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } form { max-width: 450px; margin: 0 auto; } fieldset { border: none; border-radius: 15px; padding: 30px; background: white; box-shadow: 0 10px 40px rgba(0,0,0,0.2); } legend { color: #667eea; font-weight: 700; padding: 0; font-size: 1.5rem; margin-bottom: 10px; } label { display: block; margin: 20px 0 8px; color: #333; font-weight: 500; } input, textarea { width: 100%; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: none; border-color: #667eea; } textarea { resize: vertical; min-height: 100px; } button { margin-top: 20px; width: 100%; padding: 14px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; } button:hover { opacity: 0.9; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } form { max-width: 450px; margin: 0 auto; } fieldset { border: none; border-radius: 15px; padding: 30px; background: white; box-shadow: 0 10px 40px rgba(0,0,0,0.2); } legend { color: #667eea; font-weight: 700; padding: 0; font-size: 1.5rem; margin-bottom: 10px; } label { display: block; margin: 20px 0 8px; color: #333; font-weight: 500; } input, textarea { width: 100%; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: none; border-color: #667eea; } textarea { resize: vertical; min-height: 100px; } button { margin-top: 20px; width: 100%; padding: 14px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; } button:hover { opacity: 0.9; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -60,35 +60,35 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "fieldset",
|
"value": "fieldset",
|
||||||
"message": "Add a <kbd><fieldset></kbd> element"
|
"message": "Añade un elemento <kbd><fieldset></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "legend",
|
"value": "legend",
|
||||||
"message": "Add a <kbd><legend></kbd> element"
|
"message": "Añade un elemento <kbd><legend></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "textarea",
|
"value": "textarea",
|
||||||
"message": "Add a <kbd><textarea></kbd> for the message"
|
"message": "Añade un <kbd><textarea></kbd> para el mensaje"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "button",
|
"value": "button",
|
||||||
"message": "Add a submit button"
|
"message": "Añade un botón de envío"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "input",
|
"value": "input",
|
||||||
"message": "Add an input field for email"
|
"message": "Añade un campo de entrada para el email"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "fieldset-multiple",
|
"id": "fieldset-multiple",
|
||||||
"title": "Multiple Fieldsets",
|
"title": "Múltiples Fieldsets",
|
||||||
"description": "Complex forms can use multiple <kbd><fieldset></kbd> elements to organize different sections.<br><br>This improves usability for long forms like registration or checkout pages.",
|
"description": "Los formularios complejos pueden usar múltiples elementos <kbd><fieldset></kbd> para organizar diferentes secciones.<br><br>Esto mejora la usabilidad en formularios largos como registro o checkout.",
|
||||||
"task": "Create a registration form with 2 fieldsets:<br>1. <code>Account Info</code> with username and password inputs<br>2. <code>Preferences</code> with a textarea for bio<br>3. A submit button outside the fieldsets",
|
"task": "Crea un formulario de registro con 2 fieldsets:<br>1. <code>Información de Cuenta</code> con campos de usuario y contraseña<br>2. <code>Preferencias</code> con un textarea para bio<br>3. Un botón de envío fuera de los fieldsets",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } form { max-width: 500px; } fieldset { border: 1px solid #ddd; border-radius: 10px; padding: 20px; margin-bottom: 20px; background: white; } legend { color: #2c3e50; font-weight: 600; padding: 0 10px; } label { display: block; margin: 15px 0 5px; color: #555; } input, textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: 2px solid #3498db; border-color: transparent; } textarea { resize: vertical; min-height: 80px; } button { width: 100%; padding: 14px; background: #2c3e50; color: white; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; } button:hover { background: #34495e; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } form { max-width: 500px; } fieldset { border: 1px solid #ddd; border-radius: 10px; padding: 20px; margin-bottom: 20px; background: white; } legend { color: #2c3e50; font-weight: 600; padding: 0 10px; } label { display: block; margin: 15px 0 5px; color: #555; } input, textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: 2px solid #3498db; border-color: transparent; } textarea { resize: vertical; min-height: 80px; } button { width: 100%; padding: 14px; background: #2c3e50; color: white; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; } button:hover { background: #34495e; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -99,27 +99,27 @@
|
|||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "fieldset", "min": 2 },
|
"value": { "selector": "fieldset", "min": 2 },
|
||||||
"message": "Create at least 2 fieldsets"
|
"message": "Crea al menos 2 fieldsets"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "legend", "min": 2 },
|
"value": { "selector": "legend", "min": 2 },
|
||||||
"message": "Add a legend to each fieldset"
|
"message": "Añade un legend a cada fieldset"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "textarea",
|
"value": "textarea",
|
||||||
"message": "Add a textarea for the bio"
|
"message": "Añade un textarea para la bio"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "button",
|
"value": "button",
|
||||||
"message": "Add a submit button"
|
"message": "Añade un botón de envío"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "input", "min": 2 },
|
"value": { "selector": "input", "min": 2 },
|
||||||
"message": "Add at least 2 input fields"
|
"message": "Añade al menos 2 campos de entrada"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,125 +1,42 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-tables",
|
"id": "html-tables",
|
||||||
"title": "HTML Tables",
|
"title": "Tablas HTML",
|
||||||
"description": "Create structured data tables with headers and captions",
|
"description": "Crea tablas de datos estructuradas con marcado semántico",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "table-basic",
|
"id": "table-basic",
|
||||||
"title": "Basic Table Structure",
|
"title": "Tablas de datos",
|
||||||
"description": "Tables use <kbd><table></kbd> with <kbd><tr></kbd> for rows. Inside rows, use <kbd><th></kbd> for headers and <kbd><td></kbd> for data cells.<br><br>The <kbd><caption></kbd> element provides an accessible title for the table.",
|
"description": "Las tablas muestran datos estructurados en filas y columnas. Usa <kbd><table></kbd> como contenedor, <kbd><tr></kbd> para filas, <kbd><th></kbd> para celdas de encabezado y <kbd><td></kbd> para celdas de datos.<br><br>Añade <kbd><caption></kbd> para un título accesible que describa el contenido de la tabla.",
|
||||||
"task": "Create a simple table with:<br>1. A <kbd><caption></kbd> saying <code>Fruit Prices</code><br>2. A header row with <code>Fruit</code> and <code>Price</code> columns<br>3. At least 2 data rows",
|
"task": "Crea una tabla de precios:<br>1. Un <kbd><caption></kbd> que diga <code>Pricing</code><br>2. Una fila de encabezado con <code>Plan</code> y <code>Price</code><br>3. Dos filas de datos para Basic ($9) y Pro ($29)",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px; font-weight: 600; font-size: 1.2rem; color: #333; background: #f8f9fa; } th, td { padding: 12px 20px; text-align: left; border-bottom: 1px solid #eee; } th { background: #3498db; color: white; font-weight: 500; } tr:hover { background: #f8f9fa; } tr:last-child td { border-bottom: none; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; } table { border-collapse: collapse; width: 100%; max-width: 350px; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 16px; font-weight: 600; font-size: 1.1rem; color: #333; background: #f8f9fa; } th, td { padding: 14px 20px; text-align: left; border-bottom: 1px solid #eee; } th { background: steelblue; color: white; font-weight: 500; } tr:last-child td { border-bottom: none; } tr:hover td { background: #f8f9fa; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<table>\n <caption>Fruit Prices</caption>\n <tr>\n <th>Fruit</th>\n <th>Price</th>\n </tr>\n <tr>\n <td>Apple</td>\n <td>$1.50</td>\n </tr>\n <tr>\n <td>Banana</td>\n <td>$0.75</td>\n </tr>\n</table>",
|
"solution": "<table>\n <caption>Pricing</caption>\n <tr>\n <th>Plan</th>\n <th>Price</th>\n </tr>\n <tr>\n <td>Basic</td>\n <td>$9</td>\n </tr>\n <tr>\n <td>Pro</td>\n <td>$29</td>\n </tr>\n</table>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "table",
|
"value": "table",
|
||||||
"message": "Add a <kbd><table></kbd> element"
|
"message": "Añade un elemento <kbd><table></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "caption",
|
"value": "caption",
|
||||||
"message": "Add a <kbd><caption></kbd> for the table title"
|
"message": "Añade un <kbd><caption></kbd> para el título de la tabla"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "th", "min": 2 },
|
"value": { "selector": "th", "min": 2 },
|
||||||
"message": "Add at least 2 header cells (th)"
|
"message": "Añade celdas de encabezado (<kbd><th></kbd>) para Plan y Price"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "tr", "min": 3 },
|
"value": { "selector": "tr", "min": 3 },
|
||||||
"message": "Add at least 3 rows (1 header + 2 data rows)"
|
"message": "Añade 3 filas (1 encabezado + 2 filas de datos)"
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "table-thead-tbody",
|
|
||||||
"title": "Table Head & Body",
|
|
||||||
"description": "Use <kbd><thead></kbd> to group header rows and <kbd><tbody></kbd> to group data rows. This helps browsers and assistive technology understand the table structure.<br><br>You can also use <kbd><tfoot></kbd> for footer rows like totals.",
|
|
||||||
"task": "Create a structured table:<br>1. A <kbd><caption></kbd> with <code>Monthly Sales</code><br>2. A <kbd><thead></kbd> with Month and Revenue headers<br>3. A <kbd><tbody></kbd> with at least 2 data rows",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } table { border-collapse: collapse; width: 100%; max-width: 450px; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.2); } caption { padding: 20px; font-weight: 700; font-size: 1.3rem; color: white; background: transparent; text-shadow: 0 2px 4px rgba(0,0,0,0.2); caption-side: top; } thead { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } th { padding: 15px 20px; text-align: left; color: white; font-weight: 500; } tbody tr { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } td { padding: 15px 20px; color: #333; } tbody tr:last-child { border-bottom: none; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "",
|
|
||||||
"solution": "<table>\n <caption>Monthly Sales</caption>\n <thead>\n <tr>\n <th>Month</th>\n <th>Revenue</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>January</td>\n <td>$12,500</td>\n </tr>\n <tr>\n <td>February</td>\n <td>$14,200</td>\n </tr>\n </tbody>\n</table>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "table",
|
|
||||||
"message": "Add a <kbd><table></kbd> element"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "caption",
|
|
||||||
"message": "Add a <kbd><caption></kbd> element"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "thead",
|
|
||||||
"message": "Add a <kbd><thead></kbd> for the header section"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "tbody",
|
|
||||||
"message": "Add a <kbd><tbody></kbd> for the data rows"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_count",
|
|
||||||
"value": { "selector": "tbody tr", "min": 2 },
|
|
||||||
"message": "Add at least 2 data rows in tbody"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "table-complete",
|
|
||||||
"title": "Complete Table with Footer",
|
|
||||||
"description": "Add <kbd><tfoot></kbd> to create a footer section for totals or summary data. The footer stays at the bottom even if tbody has many rows.<br><br>Combine all sections for a fully structured, accessible table.",
|
|
||||||
"task": "Create a complete table:<br>1. A <kbd><caption></kbd> with <code>Order Summary</code><br>2. A <kbd><thead></kbd> with Item and Price headers<br>3. A <kbd><tbody></kbd> with 2 items<br>4. A <kbd><tfoot></kbd> with a Total row",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px 20px; font-weight: 600; font-size: 1.1rem; color: #333; text-align: left; background: #f8f9fa; border-bottom: 2px solid #eee; } th, td { padding: 12px 20px; text-align: left; } thead th { background: #2c3e50; color: white; } tbody td { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } tfoot { background: #ecf0f1; font-weight: 600; } tfoot td { border-top: 2px solid #2c3e50; color: #2c3e50; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "",
|
|
||||||
"solution": "<table>\n <caption>Order Summary</caption>\n <thead>\n <tr>\n <th>Item</th>\n <th>Price</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Widget</td>\n <td>$25.00</td>\n </tr>\n <tr>\n <td>Gadget</td>\n <td>$35.00</td>\n </tr>\n </tbody>\n <tfoot>\n <tr>\n <td>Total</td>\n <td>$60.00</td>\n </tr>\n </tfoot>\n</table>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "table",
|
|
||||||
"message": "Add a <kbd><table></kbd> element"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "caption",
|
|
||||||
"message": "Add a <kbd><caption></kbd> element"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "thead",
|
|
||||||
"message": "Add a <kbd><thead></kbd> section"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "tbody",
|
|
||||||
"message": "Add a <kbd><tbody></kbd> section"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "tfoot",
|
|
||||||
"message": "Add a <kbd><tfoot></kbd> section for the total"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_count",
|
|
||||||
"value": { "selector": "tbody tr", "min": 2 },
|
|
||||||
"message": "Add at least 2 item rows in tbody"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-marquee",
|
"id": "html-marquee",
|
||||||
"title": "HTML Marquee",
|
"title": "HTML Marquee",
|
||||||
"description": "Create scrolling text with the classic (deprecated but fun!) marquee element",
|
"description": "Crea texto desplazable con el clásico (¡obsoleto pero divertido!) elemento marquee",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "marquee-basic",
|
"id": "marquee-basic",
|
||||||
"title": "Scrolling Text",
|
"title": "Texto Desplazable",
|
||||||
"description": "The <kbd><marquee></kbd> element creates scrolling text - a classic from the early web! While deprecated, it still works in most browsers.<br><br>Note: For production, use CSS animations instead. But for learning and fun, marquee is great!",
|
"description": "El elemento <kbd><marquee></kbd> crea texto desplazable - ¡un clásico de la web temprana! Aunque está obsoleto, todavía funciona en la mayoría de navegadores.<br><br>Nota: Para producción, usa animaciones CSS. ¡Pero para aprender y divertirse, marquee es genial!",
|
||||||
"task": "Create a simple marquee:<br>1. Add a <kbd><marquee></kbd> element<br>2. Put some text inside like <code>Welcome to my website!</code>",
|
"task": "Crea un marquee simple:<br>1. Añade un elemento <kbd><marquee></kbd><br>2. Pon texto dentro como <code>¡Bienvenido a mi sitio web!</code>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2rem; color: #00ff00; text-shadow: 0 0 10px #00ff00, 0 0 20px #00ff00; font-family: 'Courier New', monospace; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2rem; color: #00ff00; text-shadow: 0 0 10px #00ff00, 0 0 20px #00ff00; font-family: 'Courier New', monospace; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,15 +21,15 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "marquee",
|
"value": "marquee",
|
||||||
"message": "Add a <kbd><marquee></kbd> element"
|
"message": "Añade un elemento <kbd><marquee></kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "marquee-direction",
|
"id": "marquee-direction",
|
||||||
"title": "Direction & Behavior",
|
"title": "Dirección y Comportamiento",
|
||||||
"description": "Control the marquee with attributes:<br>• <kbd>direction</kbd>: left, right, up, down<br>• <kbd>behavior</kbd>: scroll (default), slide (stops at edge), alternate (bounces)<br>• <kbd>scrollamount</kbd>: speed (default is 6)",
|
"description": "Controla el marquee con atributos:<br>• <kbd>direction</kbd>: left, right, up, down<br>• <kbd>behavior</kbd>: scroll (predeterminado), slide (se detiene en el borde), alternate (rebota)<br>• <kbd>scrollamount</kbd>: velocidad (predeterminado es 6)",
|
||||||
"task": "Create a bouncing marquee:<br>1. Add a <kbd><marquee></kbd> element<br>2. Set <kbd>behavior=\"alternate\"</kbd> to make it bounce<br>3. Add some fun text",
|
"task": "Crea un marquee que rebota:<br>1. Añade un elemento <kbd><marquee></kbd><br>2. Pon <kbd>behavior=\"alternate\"</kbd> para hacerlo rebotar<br>3. Añade texto divertido",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2.5rem; color: white; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); font-weight: bold; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2.5rem; color: white; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); font-weight: bold; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -40,20 +40,20 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "marquee",
|
"value": "marquee",
|
||||||
"message": "Add a <kbd><marquee></kbd> element"
|
"message": "Añade un elemento <kbd><marquee></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "marquee", "attr": "behavior", "value": "alternate" },
|
"value": { "selector": "marquee", "attr": "behavior", "value": "alternate" },
|
||||||
"message": "Add <kbd>behavior=</kbd>\"alternate\" to make it bounce"
|
"message": "Añade <kbd>behavior=</kbd>\"alternate\" para hacerlo rebotar"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "marquee-retro",
|
"id": "marquee-retro",
|
||||||
"title": "Retro News Ticker",
|
"title": "Ticker de Noticias Retro",
|
||||||
"description": "Combine multiple marquee attributes for a classic news ticker effect. You can even put multiple elements inside!<br><br>Remember: This is deprecated HTML. Modern sites use CSS animations, but marquee is great for understanding web history.",
|
"description": "Combina múltiples atributos de marquee para un efecto clásico de ticker de noticias. ¡Puedes poner múltiples elementos dentro!<br><br>Recuerda: Esto es HTML obsoleto. Los sitios modernos usan animaciones CSS, pero marquee es genial para entender la historia de la web.",
|
||||||
"task": "Create a news ticker:<br>1. A <kbd><marquee></kbd> with <kbd>direction=\"left\"</kbd><br>2. Set <kbd>scrollamount=\"5\"</kbd> for smooth scrolling<br>3. Add a breaking news headline inside",
|
"task": "Crea un ticker de noticias:<br>1. Un <kbd><marquee></kbd> con <kbd>direction=\"left\"</kbd><br>2. Pon <kbd>scrollamount=\"5\"</kbd> para desplazamiento suave<br>3. Añade un titular de última hora dentro",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 0; margin: 0; background: #1a1a2e; } marquee { background: linear-gradient(90deg, #c0392b 0%, #e74c3c 50%, #c0392b 100%); padding: 15px 0; font-size: 1.3rem; color: white; font-weight: 500; text-transform: uppercase; letter-spacing: 1px; border-top: 3px solid #f1c40f; border-bottom: 3px solid #f1c40f; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 0; margin: 0; background: #1a1a2e; } marquee { background: linear-gradient(90deg, #c0392b 0%, #e74c3c 50%, #c0392b 100%); padding: 15px 0; font-size: 1.3rem; color: white; font-weight: 500; text-transform: uppercase; letter-spacing: 1px; border-top: 3px solid #f1c40f; border-bottom: 3px solid #f1c40f; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -64,17 +64,17 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "marquee",
|
"value": "marquee",
|
||||||
"message": "Add a <kbd><marquee></kbd> element"
|
"message": "Añade un elemento <kbd><marquee></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "marquee", "attr": "direction", "value": "left" },
|
"value": { "selector": "marquee", "attr": "direction", "value": "left" },
|
||||||
"message": "Add <kbd>direction=</kbd>\"left\" for horizontal scrolling"
|
"message": "Añade <kbd>direction=</kbd>\"left\" para desplazamiento horizontal"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "marquee", "attr": "scrollamount", "value": "5" },
|
"value": { "selector": "marquee", "attr": "scrollamount", "value": "5" },
|
||||||
"message": "Add <kbd>scrollamount=</kbd>\"5\" for smooth speed"
|
"message": "Añade <kbd>scrollamount=</kbd>\"5\" para velocidad suave"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,99 +2,169 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-svg",
|
"id": "html-svg",
|
||||||
"title": "HTML SVG",
|
"title": "HTML SVG",
|
||||||
"description": "Draw scalable vector graphics directly in HTML",
|
"description": "Dibuja gráficos vectoriales escalables directamente en HTML",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "svg-circle",
|
"id": "svg-circle",
|
||||||
"title": "Drawing Circles",
|
"title": "Dibujando círculos",
|
||||||
"description": "SVG (Scalable Vector Graphics) lets you draw shapes directly in HTML. The <kbd><svg></kbd> element is the container, with <kbd>width</kbd> and <kbd>height</kbd> attributes.<br><br>Use <kbd><circle></kbd> with <kbd>cx</kbd>, <kbd>cy</kbd> (center) and <kbd>r</kbd> (radius) to draw circles.",
|
"description": "SVG (Scalable Vector Graphics) permite dibujar formas directamente en HTML. El elemento <kbd><svg></kbd> es el contenedor con atributos <kbd>width</kbd> y <kbd>height</kbd>.<br><br>Usa <kbd><circle></kbd> con <kbd>cx</kbd>, <kbd>cy</kbd> (centro) y <kbd>r</kbd> (radio) para dibujar círculos.",
|
||||||
"task": "Create an SVG with a circle:<br>1. An <kbd><svg></kbd> with width=\"200\" and height=\"200\"<br>2. A <kbd><circle></kbd> centered at (100,100) with radius 50<br>3. Add a <kbd>fill</kbd> color",
|
"task": "Crea un SVG con un círculo:<br>1. Un <kbd><svg></kbd> con width=\"200\" y height=\"200\"<br>2. Un <kbd><circle></kbd> centrado en (100,100) con radio 50<br>3. Añade un color <kbd>fill</kbd>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; display: flex; justify-content: center; } svg { background: white; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; display: flex; justify-content: center; } svg { background: white; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"50\" fill=\"#3498db\" />\n</svg>",
|
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"50\" fill=\"steelblue\" />\n</svg>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "svg",
|
"value": "svg",
|
||||||
"message": "Add an <kbd><svg></kbd> element"
|
"message": "Añade un elemento <kbd><svg></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "circle",
|
"value": "circle",
|
||||||
"message": "Add a <kbd><circle></kbd> element inside the SVG"
|
"message": "Añade un elemento <kbd><circle></kbd> dentro del SVG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "svg", "attr": "width", "value": "200" },
|
||||||
|
"message": "Establece <kbd>width=</kbd>\"200\" en el SVG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "svg", "attr": "height", "value": "200" },
|
||||||
|
"message": "Establece <kbd>height=</kbd>\"200\" en el SVG"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "circle", "attr": "cx", "value": "100" },
|
"value": { "selector": "circle", "attr": "cx", "value": "100" },
|
||||||
"message": "Set <kbd>cx=</kbd>\"100\" for the circle's horizontal center"
|
"message": "Establece <kbd>cx=</kbd>\"100\" para el centro horizontal del círculo"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "circle", "attr": "cy", "value": "100" },
|
"value": { "selector": "circle", "attr": "cy", "value": "100" },
|
||||||
"message": "Set <kbd>cy=</kbd>\"100\" for the circle's vertical center"
|
"message": "Establece <kbd>cy=</kbd>\"100\" para el centro vertical del círculo"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "circle", "attr": "r", "value": "50" },
|
||||||
|
"message": "Establece <kbd>r=</kbd>\"50\" para el radio del círculo"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "svg-rect-line",
|
"id": "svg-rect-line",
|
||||||
"title": "Rectangles & Lines",
|
"title": "Rectángulos y líneas",
|
||||||
"description": "Draw rectangles with <kbd><rect></kbd> using <kbd>x</kbd>, <kbd>y</kbd>, <kbd>width</kbd>, <kbd>height</kbd>.<br><br>Draw lines with <kbd><line></kbd> using <kbd>x1</kbd>, <kbd>y1</kbd> (start) and <kbd>x2</kbd>, <kbd>y2</kbd> (end). Lines need a <kbd>stroke</kbd> color!",
|
"description": "Dibuja rectángulos con <kbd><rect></kbd> usando <kbd>x</kbd>, <kbd>y</kbd>, <kbd>width</kbd>, <kbd>height</kbd>.<br><br>Dibuja líneas con <kbd><line></kbd> usando <kbd>x1</kbd>, <kbd>y1</kbd> (inicio) y <kbd>x2</kbd>, <kbd>y2</kbd> (fin). ¡Las líneas necesitan un color <kbd>stroke</kbd>!",
|
||||||
"task": "Create an SVG with:<br>1. An <kbd><svg></kbd> (200x150)<br>2. A <kbd><rect></kbd> at position (20,20) with size 80x60<br>3. A <kbd><line></kbd> from (120,30) to (180,90) with a stroke color",
|
"task": "Crea un SVG con:<br>1. Un <kbd><svg></kbd> (200x150)<br>2. Un <kbd><rect></kbd> en posición (20,20) con tamaño 80x60<br>3. Una <kbd><line></kbd> de (120,30) a (180,90) con color stroke",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 200px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 200px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<svg width=\"200\" height=\"150\">\n <rect x=\"20\" y=\"20\" width=\"80\" height=\"60\" fill=\"#e74c3c\" />\n <line x1=\"120\" y1=\"30\" x2=\"180\" y2=\"90\" stroke=\"#2c3e50\" stroke-width=\"3\" />\n</svg>",
|
"solution": "<svg width=\"200\" height=\"150\">\n <rect x=\"20\" y=\"20\" width=\"80\" height=\"60\" fill=\"tomato\" />\n <line x1=\"120\" y1=\"30\" x2=\"180\" y2=\"90\" stroke=\"slategray\" stroke-width=\"3\" />\n</svg>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "svg",
|
"value": "svg",
|
||||||
"message": "Add an <kbd><svg></kbd> element"
|
"message": "Añade un elemento <kbd><svg></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "rect",
|
"value": "rect",
|
||||||
"message": "Add a <kbd><rect></kbd> element"
|
"message": "Añade un elemento <kbd><rect></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "line",
|
"value": "line",
|
||||||
"message": "Add a <kbd><line></kbd> element"
|
"message": "Añade un elemento <kbd><line></kbd>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "svg", "attr": "width", "value": "200" },
|
||||||
|
"message": "Establece <kbd>width=</kbd>\"200\" en el SVG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "svg", "attr": "height", "value": "150" },
|
||||||
|
"message": "Establece <kbd>height=</kbd>\"150\" en el SVG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "rect", "attr": "x", "value": "20" },
|
||||||
|
"message": "Establece <kbd>x=</kbd>\"20\" en el rect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "rect", "attr": "y", "value": "20" },
|
||||||
|
"message": "Establece <kbd>y=</kbd>\"20\" en el rect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "rect", "attr": "width", "value": "80" },
|
||||||
|
"message": "Establece <kbd>width=</kbd>\"80\" en el rect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "rect", "attr": "height", "value": "60" },
|
||||||
|
"message": "Establece <kbd>height=</kbd>\"60\" en el rect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "line", "attr": "x1", "value": "120" },
|
||||||
|
"message": "Establece <kbd>x1=</kbd>\"120\" en la line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "line", "attr": "y1", "value": "30" },
|
||||||
|
"message": "Establece <kbd>y1=</kbd>\"30\" en la line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "line", "attr": "x2", "value": "180" },
|
||||||
|
"message": "Establece <kbd>x2=</kbd>\"180\" en la line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "line", "attr": "y2", "value": "90" },
|
||||||
|
"message": "Establece <kbd>y2=</kbd>\"90\" en la line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "stroke",
|
||||||
|
"message": "Añade un color <kbd>stroke</kbd> a la line"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "svg-shapes",
|
"id": "svg-shapes",
|
||||||
"title": "Multiple Shapes",
|
"title": "Múltiples formas",
|
||||||
"description": "Combine shapes to create simple graphics! Add <kbd>stroke</kbd> for outlines and <kbd>stroke-width</kbd> for thickness.<br><br>Use <kbd>fill=\"none\"</kbd> for hollow shapes. Shapes stack in order - later elements appear on top.",
|
"description": "¡Combina formas para crear gráficos simples! Añade <kbd>stroke</kbd> para contornos y <kbd>stroke-width</kbd> para el grosor.<br><br>Usa <kbd>fill=\"none\"</kbd> para formas huecas. Las formas se apilan en orden - los elementos posteriores aparecen encima.",
|
||||||
"task": "Create a simple face:<br>1. An <kbd><svg></kbd> (200x200)<br>2. A large <kbd><circle></kbd> for the face<br>3. Two small <kbd><circle></kbd> elements for eyes<br>4. A <kbd><line></kbd> for the smile",
|
"task": "Crea una cara simple:<br>1. Un <kbd><svg></kbd> (200x200)<br>2. Un <kbd><circle></kbd> grande para la cara<br>3. Dos <kbd><circle></kbd> pequeños para los ojos<br>4. Una <kbd><line></kbd> para la sonrisa",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); min-height: 250px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); min-height: 250px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"80\" fill=\"#f1c40f\" stroke=\"#e67e22\" stroke-width=\"4\" />\n <circle cx=\"70\" cy=\"80\" r=\"10\" fill=\"#2c3e50\" />\n <circle cx=\"130\" cy=\"80\" r=\"10\" fill=\"#2c3e50\" />\n <line x1=\"60\" y1=\"130\" x2=\"140\" y2=\"130\" stroke=\"#2c3e50\" stroke-width=\"4\" stroke-linecap=\"round\" />\n</svg>",
|
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"80\" fill=\"gold\" stroke=\"orange\" stroke-width=\"4\" />\n <circle cx=\"70\" cy=\"80\" r=\"10\" fill=\"darkslategray\" />\n <circle cx=\"130\" cy=\"80\" r=\"10\" fill=\"darkslategray\" />\n <line x1=\"60\" y1=\"130\" x2=\"140\" y2=\"130\" stroke=\"darkslategray\" stroke-width=\"4\" stroke-linecap=\"round\" />\n</svg>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "svg",
|
"value": "svg",
|
||||||
"message": "Add an <kbd><svg></kbd> element"
|
"message": "Añade un elemento <kbd><svg></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "circle", "min": 3 },
|
"value": { "selector": "circle", "min": 3 },
|
||||||
"message": "Add at least 3 circles (1 face + 2 eyes)"
|
"message": "Añade al menos 3 círculos (1 cara + 2 ojos)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "line",
|
"value": "line",
|
||||||
"message": "Add a <kbd><line></kbd> for the smile"
|
"message": "Añade una <kbd><line></kbd> para la sonrisa"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,18 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "flexbox",
|
"id": "flexbox",
|
||||||
"title": "CSS Flexbox",
|
"title": "CSS Flexbox",
|
||||||
"description": "Master the flexible box layout model for modern responsive designs",
|
"description": "Domina el modelo de caja flexible para diseños responsivos modernos",
|
||||||
"difficulty": "intermediate",
|
"difficulty": "intermediate",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "flexbox-1",
|
"id": "flexbox-1",
|
||||||
"title": "Container",
|
"title": "Container",
|
||||||
"description": "Learn how to create a flex container and understand the main and cross axes.",
|
"description": "Antes de flexbox, incluso los diseños simples requerían floats, hacks de posicionamiento o diseños basados en tablas. Flexbox (Flexible Box Layout) revolucionó CSS al proporcionar un sistema de diseño unidimensional diseñado específicamente para distribuir espacio y alinear contenido.<br><br><strong>Cómo funciona:</strong> Cuando estableces <kbd>display: flex</kbd> en un elemento, se convierte en un <em>contenedor flex</em>. Sus hijos directos automáticamente se convierten en <em>elementos flex</em> que fluyen a lo largo de un eje principal (horizontal por defecto). Esta única propiedad transforma elementos de bloque apilados en una fila horizontal.<br><br><strong>Los dos ejes:</strong><br>• <em>Eje principal</em> – La dirección primaria del flujo de elementos (row = izquierda→derecha)<br>• <em>Eje transversal</em> – Perpendicular al principal (row = arriba→abajo)<br><br><pre>.nav {\n display: flex;\n}</pre>",
|
||||||
"task": "Add <kbd>display: flex</kbd> to <kbd>.wrap</kbd> to create a flexbox layout.",
|
"task": "Este menú de navegación se apila verticalmente. Añade <kbd>display: flex</kbd> para organizar los enlaces horizontalmente.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box'>2</div><div class='box'>3</div></div>",
|
"previewHTML": "<nav class=\"nav\"><a href=\"#\">Home</a><a href=\"#\">Products</a><a href=\"#\">About</a><a href=\"#\">Contact</a></nav>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .nav { background: #1a1a2e; padding: 1rem; } .nav a { color: white; text-decoration: none; padding: 8px 1rem; border-radius: 4px; } .nav a:hover { background: rgba(255,255,255,0.1); }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".wrap {\n ",
|
"codePrefix": ".nav {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "display: flex;",
|
"solution": "display: flex;",
|
||||||
@@ -21,61 +21,41 @@
|
|||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "display", "expected": "flex" },
|
||||||
"property": "display",
|
"message": "Establece <kbd>display: flex</kbd>"
|
||||||
"expected": "flex"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>display: flex</kbd>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-2",
|
"id": "flexbox-2",
|
||||||
"title": "Direction & Wrap",
|
"title": "Gap",
|
||||||
"description": "Control the direction and wrapping of flex items within a container.",
|
"description": "La propiedad <kbd>gap</kbd> añade espaciado consistente entre elementos flex sin necesidad de márgenes. Solo crea espacio entre elementos, no en los bordes.",
|
||||||
"task": "Add <kbd>flex-direction: column</kbd> and <kbd>flex-wrap: wrap</kbd> to <kbd>.wrap</kbd>.",
|
"task": "Añade <kbd>gap: 1rem</kbd> para espaciar uniformemente los enlaces de navegación.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box'>2</div><div class='box'>3</div><div class='box'>4</div><div class='box'>5</div></div>",
|
"previewHTML": "<nav class=\"nav\"><a href=\"#\">Home</a><a href=\"#\">Products</a><a href=\"#\">About</a><a href=\"#\">Contact</a></nav>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; width: 3rem; height: 3rem; display: flex; align-items: center; justify-content: center; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .nav { background: #1a1a2e; padding: 1rem; display: flex; } .nav a { color: white; text-decoration: none; padding: 8px 1rem; border-radius: 4px; background: rgba(255,255,255,0.1); }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; height: 16rem; display: flex; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".wrap {\n ",
|
"codePrefix": ".nav {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "flex-direction: column;\n flex-wrap: wrap;",
|
"solution": "gap: 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "gap", "expected": "1rem" },
|
||||||
"property": "flex-direction",
|
"message": "Establece <kbd>gap: 1rem</kbd>"
|
||||||
"expected": "column"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>flex-direction: column</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "flex-wrap",
|
|
||||||
"expected": "wrap"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>flex-wrap: wrap</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-3",
|
"id": "flexbox-3",
|
||||||
"title": "Justify Content",
|
"title": "Justify Content",
|
||||||
"description": "Learn how to align flex items along the main axis of the flex container.",
|
"description": "<kbd>justify-content</kbd> distribuye elementos a lo largo del eje principal. Valores comunes:<br>• <kbd>flex-start</kbd> – agrupar al inicio<br>• <kbd>flex-end</kbd> – agrupar al final<br>• <kbd>center</kbd> – centrar elementos<br>• <kbd>space-between</kbd> – espacio igual entre elementos<br>• <kbd>space-around</kbd> – espacio igual alrededor de elementos",
|
||||||
"task": "Add <kbd>justify-content: space-between</kbd> to <kbd>.wrap</kbd> to distribute the boxes evenly.",
|
"task": "Empuja el botón \"Login\" hacia la derecha estableciendo <kbd>justify-content: space-between</kbd> en la navegación.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box'>2</div><div class='box'>3</div></div>",
|
"previewHTML": "<nav class=\"nav\"><div class=\"links\"><a href=\"#\">Home</a><a href=\"#\">Products</a><a href=\"#\">About</a></div><a href=\"#\" class=\"login\">Login</a></nav>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; text-align: center; font-weight: bold; width: 3rem; height: 3rem; display: flex; align-items: center; justify-content: center; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .nav { background: #1a1a2e; padding: 1rem; display: flex; } .links { display: flex; gap: 8px; } .nav a { color: white; text-decoration: none; padding: 8px 1rem; border-radius: 4px; } .nav a:hover { background: rgba(255,255,255,0.1); } .login { background: steelblue; }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".wrap {\n ",
|
"codePrefix": ".nav {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "justify-content: space-between;",
|
"solution": "justify-content: space-between;",
|
||||||
@@ -83,26 +63,20 @@
|
|||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "justify-content", "expected": "space-between" },
|
||||||
"property": "justify-content",
|
"message": "Establece <kbd>justify-content: space-between</kbd>"
|
||||||
"expected": "space-between"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>justify-content: space-between</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-4",
|
"id": "flexbox-4",
|
||||||
"title": "Align Items",
|
"title": "Align Items",
|
||||||
"description": "Control how flex items are aligned along the cross axis of the flex container.",
|
"description": "<kbd>align-items</kbd> controla la alineación en el eje transversal (vertical cuando flex-direction es row). Los valores incluyen:<br>• <kbd>stretch</kbd> – estirar para llenar (por defecto)<br>• <kbd>flex-start</kbd> – alinear arriba<br>• <kbd>flex-end</kbd> – alinear abajo<br>• <kbd>center</kbd> – centrar verticalmente",
|
||||||
"task": "Add <kbd>align-items: center</kbd> to <kbd>.wrap</kbd> to vertically center the boxes.",
|
"task": "El logo y los enlaces de navegación tienen diferentes alturas. Céntralos verticalmente con <kbd>align-items: center</kbd>.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box tall'>1</div><div class='box'>2</div><div class='box short'>3</div></div>",
|
"previewHTML": "<header class=\"header\"><div class=\"logo\">ACME</div><nav><a href=\"#\">Products</a><a href=\"#\">Pricing</a><a href=\"#\">Docs</a></nav></header>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; width: 3rem; display: flex; justify-content: center; } .tall { height: 6rem; } .short { height: 3rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .header { background: white; padding: 1rem 2rem; display: flex; justify-content: space-between; border-bottom: 1px solid #eee; } .logo { font-size: 1.5rem; font-weight: bold; color: steelblue; } nav { display: flex; gap: 1rem; } nav a { color: #333; text-decoration: none; font-size: 0.9rem; }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; height: 10rem; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".wrap {\n ",
|
"codePrefix": ".header {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "align-items: center;",
|
"solution": "align-items: center;",
|
||||||
@@ -110,62 +84,50 @@
|
|||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "align-items", "expected": "center" },
|
||||||
"property": "align-items",
|
"message": "Establece <kbd>align-items: center</kbd>"
|
||||||
"expected": "center"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>align-items: center</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-5",
|
"id": "flexbox-5",
|
||||||
"title": "Flex Grow",
|
"title": "Flex Wrap",
|
||||||
"description": "The <kbd>flex</kbd> property controls how much an item grows relative to others.",
|
"description": "Por defecto, los elementos flex se comprimen en una línea. <kbd>flex-wrap: wrap</kbd> permite que los elementos fluyan a múltiples líneas cuando se quedan sin espacio.",
|
||||||
"task": "Add <kbd>flex: 2</kbd> to <kbd>.box2</kbd> to make it grow twice as wide.",
|
"task": "Estas tarjetas desbordan el contenedor. Añade <kbd>flex-wrap: wrap</kbd> para permitir que pasen a nuevas filas.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box box1'>1</div><div class='box box2'>2</div><div class='box box3'>3</div></div>",
|
"previewHTML": "<div class=\"cards\"><article class=\"card\">Card 1</article><article class=\"card\">Card 2</article><article class=\"card\">Card 3</article><article class=\"card\">Card 4</article><article class=\"card\">Card 5</article><article class=\"card\">Card 6</article></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; display: flex; align-items: center; justify-content: center; } .box1 { background: coral; flex: 1; } .box2 { background: mediumseagreen; } .box3 { background: gold; flex: 1; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .cards { display: flex; gap: 1rem; } .card { background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); min-width: 120px; text-align: center; }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".box2 {\n ",
|
"codePrefix": ".cards {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "flex: 2;",
|
"solution": "flex-wrap: wrap;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "flex-wrap", "expected": "wrap" },
|
||||||
"property": "flex",
|
"message": "Establece <kbd>flex-wrap: wrap</kbd>"
|
||||||
"expected": "2"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>flex: 2</kbd>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-6",
|
"id": "flexbox-6",
|
||||||
"title": "Align Self",
|
"title": "Flex Grow",
|
||||||
"description": "Use <kbd>align-self</kbd> to override alignment for a single flex item.",
|
"description": "La propiedad <kbd>flex</kbd> en los elementos controla cómo crecen y se encogen. <kbd>flex: 1</kbd> hace que un elemento crezca para llenar el espacio disponible. Múltiples elementos con <kbd>flex: 1</kbd> comparten el espacio equitativamente.",
|
||||||
"task": "Add <kbd>align-self: flex-start</kbd> to <kbd>.middle</kbd> to move it to the top.",
|
"task": "Haz que el campo de búsqueda se expanda para llenar el espacio disponible estableciendo <kbd>flex: 1</kbd> en <kbd>.search</kbd>.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box middle'>2</div><div class='box'>3</div></div>",
|
"previewHTML": "<div class=\"toolbar\"><input class=\"search\" type=\"text\" placeholder=\"Search...\"><button class=\"btn\">Search</button><button class=\"btn\">Filters</button></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; width: 3rem; height: 3rem; display: flex; align-items: center; justify-content: center; } .middle { background: mediumseagreen; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .toolbar { display: flex; gap: 8px; padding: 1rem; background: #f5f5f5; border-radius: 8px; } .search { padding: 8px 1rem; border: 1px solid #ddd; border-radius: 4px; font-size: 1rem; } .btn { padding: 8px 1rem; background: steelblue; color: white; border: none; border-radius: 4px; cursor: pointer; }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; height: 12rem; align-items: center; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".middle {\n ",
|
"codePrefix": ".search {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "align-self: flex-start;",
|
"solution": "flex: 1;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "flex", "expected": "1" },
|
||||||
"property": "align-self",
|
"message": "Establece <kbd>flex: 1</kbd>"
|
||||||
"expected": "flex-start"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>align-self: flex-start</kbd>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,12 @@
|
|||||||
{
|
{
|
||||||
"id": "flexbox-1",
|
"id": "flexbox-1",
|
||||||
"title": "Container",
|
"title": "Container",
|
||||||
"description": "Learn how to create a flex container and understand the main and cross axes.<br><br><pre>.container {\n display: flex;\n justify-content: center;\n align-items: center;\n}</pre>",
|
"description": "Before flexbox, creating even simple layouts required floats, positioning hacks, or table-based layouts. Flexbox (Flexible Box Layout) revolutionized CSS by providing a one-dimensional layout system designed specifically for distributing space and aligning content.<br><br><strong>How it works:</strong> When you set <kbd>display: flex</kbd> on an element, it becomes a <em>flex container</em>. Its direct children automatically become <em>flex items</em> that flow along a main axis (horizontal by default). This single property transforms stacked block elements into a horizontal row.<br><br><strong>The two axes:</strong><br>• <em>Main axis</em> – The primary direction items flow (row = left→right)<br>• <em>Cross axis</em> – Perpendicular to main (row = top→bottom)<br><br><pre>.nav {\n display: flex;\n}</pre>",
|
||||||
"task": "Add <kbd>display: flex</kbd> to <kbd>.wrap</kbd> to create a flexbox layout.",
|
"task": "The navigation links are stacking vertically. Make them display side by side in a horizontal row.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box'>2</div><div class='box'>3</div></div>",
|
"previewHTML": "<nav class=\"nav\"><a href=\"#\">Home</a><a href=\"#\">Products</a><a href=\"#\">About</a><a href=\"#\">Contact</a></nav>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .nav { background: #1a1a2e; padding: 1rem; } .nav a { color: white; text-decoration: none; padding: 8px 1rem; border-radius: 4px; } .nav a:hover { background: rgba(255,255,255,0.1); }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".wrap {\n ",
|
"codePrefix": ".nav {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "display: flex;",
|
"solution": "display: flex;",
|
||||||
@@ -21,61 +21,41 @@
|
|||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "display", "expected": "flex" },
|
||||||
"property": "display",
|
"message": "Try changing the display mode to create a flex container"
|
||||||
"expected": "flex"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>display: flex</kbd>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-2",
|
"id": "flexbox-2",
|
||||||
"title": "Direction & Wrap",
|
"title": "Gap",
|
||||||
"description": "Control the direction and wrapping of flex items within a container.",
|
"description": "The <kbd>gap</kbd> property adds consistent spacing between flex items without needing margins. It only creates space between items, not around the edges.",
|
||||||
"task": "Add <kbd>flex-direction: column</kbd> and <kbd>flex-wrap: wrap</kbd> to <kbd>.wrap</kbd>.",
|
"task": "The navigation links are crammed together with no breathing room. Add 1rem of spacing between them.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box'>2</div><div class='box'>3</div><div class='box'>4</div><div class='box'>5</div></div>",
|
"previewHTML": "<nav class=\"nav\"><a href=\"#\">Home</a><a href=\"#\">Products</a><a href=\"#\">About</a><a href=\"#\">Contact</a></nav>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; width: 3rem; height: 3rem; display: flex; align-items: center; justify-content: center; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .nav { background: #1a1a2e; padding: 1rem; display: flex; } .nav a { color: white; text-decoration: none; padding: 8px 1rem; border-radius: 4px; background: rgba(255,255,255,0.1); }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; height: 16rem; display: flex; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".wrap {\n ",
|
"codePrefix": ".nav {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "flex-direction: column;\n flex-wrap: wrap;",
|
"solution": "gap: 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "gap", "expected": "1rem" },
|
||||||
"property": "flex-direction",
|
"message": "Use the property that adds spacing between flex items"
|
||||||
"expected": "column"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>flex-direction: column</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "flex-wrap",
|
|
||||||
"expected": "wrap"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>flex-wrap: wrap</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-3",
|
"id": "flexbox-3",
|
||||||
"title": "Justify Content",
|
"title": "Justify Content",
|
||||||
"description": "Learn how to align flex items along the main axis of the flex container.",
|
"description": "<kbd>justify-content</kbd> distributes items along the main axis. Common values:<br>• <kbd>flex-start</kbd> – pack items at the start<br>• <kbd>flex-end</kbd> – pack at the end<br>• <kbd>center</kbd> – center items<br>• <kbd>space-between</kbd> – equal space between items<br>• <kbd>space-around</kbd> – equal space around items",
|
||||||
"task": "Add <kbd>justify-content: space-between</kbd> to <kbd>.wrap</kbd> to distribute the boxes evenly.",
|
"task": "The Login button should sit on the far right, with the other links staying on the left. Distribute the space between them.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box'>2</div><div class='box'>3</div></div>",
|
"previewHTML": "<nav class=\"nav\"><div class=\"links\"><a href=\"#\">Home</a><a href=\"#\">Products</a><a href=\"#\">About</a></div><a href=\"#\" class=\"login\">Login</a></nav>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; text-align: center; font-weight: bold; width: 3rem; height: 3rem; display: flex; align-items: center; justify-content: center; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .nav { background: #1a1a2e; padding: 1rem; display: flex; } .links { display: flex; gap: 8px; } .nav a { color: white; text-decoration: none; padding: 8px 1rem; border-radius: 4px; } .nav a:hover { background: rgba(255,255,255,0.1); } .login { background: steelblue; }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".wrap {\n ",
|
"codePrefix": ".nav {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "justify-content: space-between;",
|
"solution": "justify-content: space-between;",
|
||||||
@@ -83,26 +63,20 @@
|
|||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "justify-content", "expected": "space-between" },
|
||||||
"property": "justify-content",
|
"message": "Use the property that distributes items along the main axis"
|
||||||
"expected": "space-between"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>justify-content: space-between</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-4",
|
"id": "flexbox-4",
|
||||||
"title": "Align Items",
|
"title": "Align Items",
|
||||||
"description": "Control how flex items are aligned along the cross axis of the flex container.",
|
"description": "<kbd>align-items</kbd> controls alignment on the cross axis (vertical when flex-direction is row). Values include:<br>• <kbd>stretch</kbd> – stretch to fill (default)<br>• <kbd>flex-start</kbd> – align to top<br>• <kbd>flex-end</kbd> – align to bottom<br>• <kbd>center</kbd> – center vertically",
|
||||||
"task": "Add <kbd>align-items: center</kbd> to <kbd>.wrap</kbd> to vertically center the boxes.",
|
"task": "The logo and nav links sit at different heights. Center them vertically so they line up.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box tall'>1</div><div class='box'>2</div><div class='box short'>3</div></div>",
|
"previewHTML": "<header class=\"header\"><div class=\"logo\">ACME</div><nav><a href=\"#\">Products</a><a href=\"#\">Pricing</a><a href=\"#\">Docs</a></nav></header>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; width: 3rem; display: flex; justify-content: center; } .tall { height: 6rem; } .short { height: 3rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .header { background: white; padding: 1rem 2rem; display: flex; justify-content: space-between; border-bottom: 1px solid #eee; } .logo { font-size: 1.5rem; font-weight: bold; color: steelblue; } nav { display: flex; gap: 1rem; } nav a { color: #333; text-decoration: none; font-size: 0.9rem; }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; height: 10rem; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".wrap {\n ",
|
"codePrefix": ".header {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "align-items: center;",
|
"solution": "align-items: center;",
|
||||||
@@ -110,62 +84,50 @@
|
|||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "align-items", "expected": "center" },
|
||||||
"property": "align-items",
|
"message": "Use the property that controls cross-axis alignment"
|
||||||
"expected": "center"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>align-items: center</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-5",
|
"id": "flexbox-5",
|
||||||
"title": "Flex Grow",
|
"title": "Flex Wrap",
|
||||||
"description": "The <kbd>flex</kbd> property controls how much an item grows relative to others.",
|
"description": "By default, flex items squeeze onto one line. <kbd>flex-wrap: wrap</kbd> allows items to flow onto multiple lines when they run out of space.",
|
||||||
"task": "Add <kbd>flex: 2</kbd> to <kbd>.box2</kbd> to make it grow twice as wide.",
|
"task": "The cards overflow the container instead of fitting within it. Allow the items to flow onto new rows when they run out of space.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box box1'>1</div><div class='box box2'>2</div><div class='box box3'>3</div></div>",
|
"previewHTML": "<div class=\"cards\"><article class=\"card\">Card 1</article><article class=\"card\">Card 2</article><article class=\"card\">Card 3</article><article class=\"card\">Card 4</article><article class=\"card\">Card 5</article><article class=\"card\">Card 6</article></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; display: flex; align-items: center; justify-content: center; } .box1 { background: coral; flex: 1; } .box2 { background: mediumseagreen; } .box3 { background: gold; flex: 1; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .cards { display: flex; gap: 1rem; } .card { background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); min-width: 120px; text-align: center; }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".box2 {\n ",
|
"codePrefix": ".cards {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "flex: 2;",
|
"solution": "flex-wrap: wrap;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "flex-wrap", "expected": "wrap" },
|
||||||
"property": "flex",
|
"message": "Use the property that allows flex items to wrap onto new lines"
|
||||||
"expected": "2"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>flex: 2</kbd>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-6",
|
"id": "flexbox-6",
|
||||||
"title": "Align Self",
|
"title": "Flex Grow",
|
||||||
"description": "Use <kbd>align-self</kbd> to override alignment for a single flex item.",
|
"description": "The <kbd>flex</kbd> property on items controls how they grow and shrink. <kbd>flex: 1</kbd> makes an item grow to fill available space. Multiple items with <kbd>flex: 1</kbd> share space equally.",
|
||||||
"task": "Add <kbd>align-self: flex-start</kbd> to <kbd>.middle</kbd> to move it to the top.",
|
"task": "The search input is too narrow. Make it stretch to fill all the remaining space in the toolbar.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box middle'>2</div><div class='box'>3</div></div>",
|
"previewHTML": "<div class=\"toolbar\"><input class=\"search\" type=\"text\" placeholder=\"Search...\"><button class=\"btn\">Search</button><button class=\"btn\">Filters</button></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; width: 3rem; height: 3rem; display: flex; align-items: center; justify-content: center; } .middle { background: mediumseagreen; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .toolbar { display: flex; gap: 8px; padding: 1rem; background: #f5f5f5; border-radius: 8px; } .search { padding: 8px 1rem; border: 1px solid #ddd; border-radius: 4px; font-size: 1rem; } .btn { padding: 8px 1rem; background: steelblue; color: white; border: none; border-radius: 4px; cursor: pointer; }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; height: 12rem; align-items: center; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".middle {\n ",
|
"codePrefix": ".search {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "align-self: flex-start;",
|
"solution": "flex: 1;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "regex",
|
||||||
"value": {
|
"value": "(flex\\s*:\\s*1|flex-grow\\s*:\\s*1)",
|
||||||
"property": "align-self",
|
"message": "Use the property that makes a flex item grow to fill available space"
|
||||||
"expected": "flex-start"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>align-self: flex-start</kbd>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,260 +1,155 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../schemas/code-crispies-module-schema.json",
|
"$schema": "../schemas/code-crispies-module-schema.json",
|
||||||
"id": "grid",
|
"id": "grid",
|
||||||
"title": "Grid",
|
"title": "CSS Grid",
|
||||||
"description": "Master the grid layout system for complex two-dimensional layouts",
|
"description": "Master the grid layout system for complex two-dimensional layouts",
|
||||||
"difficulty": "intermediate",
|
"difficulty": "intermediate",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "grid-1",
|
"id": "grid-1",
|
||||||
"title": "Grid Container Basics",
|
"title": "Grid Container",
|
||||||
"description": "Learn how to create a grid container and define basic grid structures.<br><br><pre>.container {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1rem;\n}</pre>",
|
"description": "CSS Grid is a two-dimensional layout system, meaning it can handle both columns AND rows simultaneously. While Flexbox excels at one-dimensional layouts (a single row or column), Grid shines when you need precise control over both dimensions—like photo galleries, dashboards, or page layouts.<br><br><strong>How it works:</strong> Set <kbd>display: grid</kbd> on a container, then define columns with <kbd>grid-template-columns</kbd>. The <kbd>fr</kbd> unit represents a fraction of available space—<kbd>1fr 1fr 1fr</kbd> creates three equal columns.<br><br><strong>Key properties:</strong><br>• <kbd>grid-template-columns</kbd> – Defines column sizes<br>• <kbd>repeat(3, 1fr)</kbd> – Shorthand for 3 equal columns<br>• <kbd>gap</kbd> – Adds spacing between grid cells<br><br><pre>.gallery {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1rem;\n}</pre>",
|
||||||
"task": "Create a <kbd>.grid</kbd> with <kbd>display: grid</kbd>, <kbd>grid-template-columns: repeat(3, 1fr)</kbd>, and <kbd>gap: 1rem</kbd>.",
|
"task": "This photo gallery displays images in a single column. Add <kbd>display: grid</kbd> to <kbd>.gallery</kbd> to enable grid layout.",
|
||||||
"previewHTML": "<div class='grid'><div class='item'>1</div><div class='item'>2</div><div class='item'>3</div><div class='item'>4</div><div class='item'>5</div><div class='item'>6</div></div>",
|
"previewHTML": "<section class=\"gallery\"><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%234a90a4' width='200' height='150'/%3E%3Cpath d='M60 100 L100 60 L140 100 L160 80 L200 120 L200 150 L0 150 L0 120 Z' fill='%232d5a4a'/%3E%3Ccircle cx='50' cy='40' r='15' fill='%23f4d03f'/%3E%3C/svg%3E\" alt=\"Mountains\"><figcaption>Mountain View</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%23e8c170' width='200' height='150'/%3E%3Crect fill='%2396ceb4' y='100' width='200' height='50'/%3E%3Ccircle cx='160' cy='35' r='20' fill='%23f4d03f'/%3E%3C/svg%3E\" alt=\"Beach\"><figcaption>Sunny Beach</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%232c3e50' width='200' height='150'/%3E%3Crect fill='%2334495e' x='20' y='60' width='40' height='90'/%3E%3Crect fill='%2334495e' x='80' y='40' width='50' height='110'/%3E%3Crect fill='%2334495e' x='150' y='70' width='35' height='80'/%3E%3C/svg%3E\" alt=\"City\"><figcaption>City Skyline</figcaption></figure></section>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1.25rem; } .item { background-color: #9b59b6; color: white; padding: 1.25rem; text-align: center; font-weight: bold; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .photo { margin: 0; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .photo img { width: 100%; display: block; } .photo figcaption { padding: 8px; text-align: center; font-size: 0.9rem; color: #666; }",
|
||||||
"sandboxCSS": ".grid { border: 0.125rem dashed #ccc; padding: 1rem; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Create a grid with 3 equal columns and gap */\n",
|
"codePrefix": ".gallery {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "\n}",
|
||||||
"solution": ".grid {\n display: grid;\n grid-template-columns: repeat(3, 1fr);\n gap: 1rem;\n}",
|
"solution": "display: grid;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": ".grid",
|
|
||||||
"message": "Use the <kbd>.grid</kbd> class selector",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "display", "expected": "grid" },
|
||||||
"property": "display",
|
"message": "Set <kbd>display: grid</kbd>"
|
||||||
"expected": "grid"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>display: grid</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "grid-template-columns:\\s*repeat\\(\\s*3\\s*,\\s*1fr\\s*\\)",
|
|
||||||
"message": "Set <kbd>grid-template-columns: repeat(3, 1fr)</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "gap",
|
|
||||||
"expected": "1rem"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>gap: 1rem</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "grid-2",
|
"id": "grid-2",
|
||||||
"title": "Grid Template Areas",
|
"title": "Grid Columns",
|
||||||
"description": "Use named grid areas to create visual layouts that are easy to understand.",
|
"description": "The <kbd>grid-template-columns</kbd> property defines how many columns your grid has and how wide each one should be. The <kbd>fr</kbd> unit divides available space into fractions—<kbd>1fr 1fr 1fr</kbd> creates three equal columns.",
|
||||||
"task": "Add <kbd>grid-template-areas</kbd> to create a layout with <kbd>header</kbd> spanning full width, <kbd>sidebar</kbd> and <kbd>content</kbd> in middle, and <kbd>footer</kbd> spanning full width.",
|
"task": "Add <kbd>grid-template-columns: repeat(3, 1fr)</kbd> to display the photos in three equal columns.",
|
||||||
"previewHTML": "<div class='page'><div class='header'>Header</div><div class='sidebar'>Sidebar</div><div class='content'>Main Content</div><div class='footer'>Footer</div></div>",
|
"previewHTML": "<section class=\"gallery\"><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%234a90a4' width='200' height='150'/%3E%3Cpath d='M60 100 L100 60 L140 100 L160 80 L200 120 L200 150 L0 150 L0 120 Z' fill='%232d5a4a'/%3E%3Ccircle cx='50' cy='40' r='15' fill='%23f4d03f'/%3E%3C/svg%3E\" alt=\"Mountains\"><figcaption>Mountain View</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%23e8c170' width='200' height='150'/%3E%3Crect fill='%2396ceb4' y='100' width='200' height='50'/%3E%3Ccircle cx='160' cy='35' r='20' fill='%23f4d03f'/%3E%3C/svg%3E\" alt=\"Beach\"><figcaption>Sunny Beach</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%232c3e50' width='200' height='150'/%3E%3Crect fill='%2334495e' x='20' y='60' width='40' height='90'/%3E%3Crect fill='%2334495e' x='80' y='40' width='50' height='110'/%3E%3Crect fill='%2334495e' x='150' y='70' width='35' height='80'/%3E%3C/svg%3E\" alt=\"City\"><figcaption>City Skyline</figcaption></figure></section>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1.25rem; } .page > div { padding: 1.25rem; color: white; text-align: center; font-weight: bold; } .header { background-color: #e74c3c; } .sidebar { background-color: #3498db; } .content { background-color: #2ecc71; } .footer { background-color: #f39c12; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .gallery { display: grid; } .photo { margin: 0; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .photo img { width: 100%; display: block; } .photo figcaption { padding: 8px; text-align: center; font-size: 0.9rem; color: #666; }",
|
||||||
"sandboxCSS": ".page { border: 0.125rem dashed #ccc; padding: 1rem; height: 25rem; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Create a layout using grid-template-areas */\n.page {\n display: grid;\n grid-template-columns: 12rem 1fr;\n grid-template-rows: auto 1fr auto;\n gap: 1rem;\n \n /* Add your grid-template-areas code below */\n",
|
"codePrefix": ".gallery {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}\n\n/* Define which element goes in which grid area */\n.header {\n grid-area: header;\n}\n\n.sidebar {\n grid-area: sidebar;\n}\n\n.content {\n grid-area: content;\n}\n\n.footer {\n grid-area: footer;\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "grid-template-areas:\n \"header header\"\n \"sidebar content\"\n \"footer footer\";",
|
"solution": "grid-template-columns: repeat(3, 1fr);",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "grid-template-columns:\\s*repeat\\(\\s*3\\s*,\\s*1fr\\s*\\)",
|
||||||
|
"message": "Set <kbd>grid-template-columns: repeat(3, 1fr)</kbd>",
|
||||||
|
"options": { "caseSensitive": false }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "grid-3",
|
||||||
|
"title": "Grid Gap",
|
||||||
|
"description": "Just like in Flexbox, the <kbd>gap</kbd> property adds consistent spacing between grid cells. It creates gutters between columns and rows without adding space around the edges.",
|
||||||
|
"task": "Add <kbd>gap: 1rem</kbd> to create breathing room between the photos.",
|
||||||
|
"previewHTML": "<section class=\"gallery\"><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%234a90a4' width='200' height='150'/%3E%3Cpath d='M60 100 L100 60 L140 100 L160 80 L200 120 L200 150 L0 150 L0 120 Z' fill='%232d5a4a'/%3E%3Ccircle cx='50' cy='40' r='15' fill='%23f4d03f'/%3E%3C/svg%3E\" alt=\"Mountains\"><figcaption>Mountain View</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%23e8c170' width='200' height='150'/%3E%3Crect fill='%2396ceb4' y='100' width='200' height='50'/%3E%3Ccircle cx='160' cy='35' r='20' fill='%23f4d03f'/%3E%3C/svg%3E\" alt=\"Beach\"><figcaption>Sunny Beach</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%232c3e50' width='200' height='150'/%3E%3Crect fill='%2334495e' x='20' y='60' width='40' height='90'/%3E%3Crect fill='%2334495e' x='80' y='40' width='50' height='110'/%3E%3Crect fill='%2334495e' x='150' y='70' width='35' height='80'/%3E%3C/svg%3E\" alt=\"City\"><figcaption>City Skyline</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%23a8d8ea' width='200' height='150'/%3E%3Cellipse cx='100' cy='130' rx='80' ry='30' fill='%2396ceb4'/%3E%3Crect fill='%238b4513' x='95' y='80' width='10' height='50'/%3E%3Ccircle cx='100' cy='70' r='30' fill='%232d5a4a'/%3E%3C/svg%3E\" alt=\"Tree\"><figcaption>Lone Tree</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%23ff6b6b' width='200' height='150'/%3E%3Ccircle cx='100' cy='75' r='40' fill='%23f4d03f'/%3E%3C/svg%3E\" alt=\"Sunset\"><figcaption>Golden Sunset</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%23dfe6e9' width='200' height='150'/%3E%3Cpath d='M0 100 Q50 60 100 100 T200 100 L200 150 L0 150 Z' fill='%23b2bec3'/%3E%3C/svg%3E\" alt=\"Clouds\"><figcaption>Cloudy Sky</figcaption></figure></section>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .gallery { display: grid; grid-template-columns: repeat(3, 1fr); } .photo { margin: 0; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .photo img { width: 100%; display: block; } .photo figcaption { padding: 8px; text-align: center; font-size: 0.9rem; color: #666; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".gallery {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "gap: 1rem;",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "gap", "expected": "1rem" },
|
||||||
|
"message": "Set <kbd>gap: 1rem</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "grid-4",
|
||||||
|
"title": "Spanning Columns",
|
||||||
|
"description": "Grid items can span multiple columns using <kbd>grid-column: span N</kbd>. This is perfect for featured content that needs more visual prominence, like a hero image in a gallery.",
|
||||||
|
"task": "Make the featured photo span two columns by adding <kbd>grid-column: span 2</kbd> to <kbd>.featured</kbd>.",
|
||||||
|
"previewHTML": "<section class=\"gallery\"><figure class=\"photo featured\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 400 200'%3E%3Crect fill='%234a90a4' width='400' height='200'/%3E%3Cpath d='M100 140 L200 80 L300 140 L350 100 L400 150 L400 200 L0 200 L0 160 Z' fill='%232d5a4a'/%3E%3Ccircle cx='80' cy='50' r='25' fill='%23f4d03f'/%3E%3Ctext x='200' y='180' text-anchor='middle' fill='white' font-size='16' font-weight='bold'>FEATURED</text>%3C/svg%3E\" alt=\"Featured\"><figcaption>Featured Landscape</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%23e8c170' width='200' height='150'/%3E%3Crect fill='%2396ceb4' y='100' width='200' height='50'/%3E%3Ccircle cx='160' cy='35' r='20' fill='%23f4d03f'/%3E%3C/svg%3E\" alt=\"Beach\"><figcaption>Beach</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%232c3e50' width='200' height='150'/%3E%3Crect fill='%2334495e' x='20' y='60' width='40' height='90'/%3E%3Crect fill='%2334495e' x='80' y='40' width='50' height='110'/%3E%3Crect fill='%2334495e' x='150' y='70' width='35' height='80'/%3E%3C/svg%3E\" alt=\"City\"><figcaption>City</figcaption></figure><figure class=\"photo\"><img src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 200 150'%3E%3Crect fill='%23a8d8ea' width='200' height='150'/%3E%3Cellipse cx='100' cy='130' rx='80' ry='30' fill='%2396ceb4'/%3E%3Crect fill='%238b4513' x='95' y='80' width='10' height='50'/%3E%3Ccircle cx='100' cy='70' r='30' fill='%232d5a4a'/%3E%3C/svg%3E\" alt=\"Tree\"><figcaption>Tree</figcaption></figure></section>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .gallery { display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; } .photo { margin: 0; background: white; border-radius: 8px; overflow: hidden; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .photo img { width: 100%; display: block; } .photo figcaption { padding: 8px; text-align: center; font-size: 0.9rem; color: #666; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".featured {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "grid-column: span 2;",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "grid-column:\\s*span\\s+2",
|
||||||
|
"message": "Set <kbd>grid-column: span 2</kbd>",
|
||||||
|
"options": { "caseSensitive": false }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "grid-5",
|
||||||
|
"title": "Auto-Fit Columns",
|
||||||
|
"description": "For truly responsive grids, use <kbd>auto-fit</kbd> with <kbd>minmax()</kbd>. This automatically creates as many columns as will fit, with each being at least a minimum width but growing to fill space.",
|
||||||
|
"task": "Add <kbd>grid-template-columns: repeat(auto-fit, minmax(150px, 1fr))</kbd> to make the product grid responsive.",
|
||||||
|
"previewHTML": "<section class=\"products\"><article class=\"product\"><div class=\"img\" style=\"background: steelblue;\"></div><h3>Wireless Headphones</h3><p>$79</p></article><article class=\"product\"><div class=\"img\" style=\"background: coral;\"></div><h3>Smart Watch</h3><p>$199</p></article><article class=\"product\"><div class=\"img\" style=\"background: mediumseagreen;\"></div><h3>Portable Speaker</h3><p>$49</p></article><article class=\"product\"><div class=\"img\" style=\"background: gold;\"></div><h3>USB-C Hub</h3><p>$35</p></article><article class=\"product\"><div class=\"img\" style=\"background: orchid;\"></div><h3>Webcam HD</h3><p>$89</p></article><article class=\"product\"><div class=\"img\" style=\"background: tomato;\"></div><h3>Keyboard</h3><p>$129</p></article></section>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .products { display: grid; gap: 1rem; } .product { background: white; border-radius: 8px; padding: 1rem; box-shadow: 0 2px 4px rgba(0,0,0,0.1); text-align: center; } .product .img { height: 80px; border-radius: 4px; margin-bottom: 8px; } .product h3 { margin: 0 0 4px; font-size: 0.95rem; } .product p { margin: 0; color: steelblue; font-weight: bold; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".products {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "grid-template-columns: repeat(auto-fit, minmax(150px, 1fr));",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "grid-template-columns:\\s*repeat\\(\\s*auto-fit\\s*,\\s*minmax\\(\\s*150px\\s*,\\s*1fr\\s*\\)\\s*\\)",
|
||||||
|
"message": "Set <kbd>grid-template-columns: repeat(auto-fit, minmax(150px, 1fr))</kbd>",
|
||||||
|
"options": { "caseSensitive": false }
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "grid-6",
|
||||||
|
"title": "Grid Template Areas",
|
||||||
|
"description": "For complex page layouts, <kbd>grid-template-areas</kbd> lets you name regions and place items visually. It's like drawing your layout with ASCII art—incredibly readable and maintainable.",
|
||||||
|
"task": "Complete the dashboard layout by adding the grid-template-areas. The header should span full width, then nav and main side by side, then footer spanning full width.",
|
||||||
|
"previewHTML": "<div class=\"dashboard\"><header class=\"header\">Dashboard Header</header><nav class=\"nav\">Navigation</nav><main class=\"main\">Main Content Area</main><footer class=\"footer\">Footer</footer></div>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .dashboard { display: grid; grid-template-columns: 180px 1fr; grid-template-rows: auto 1fr auto; gap: 1rem; min-height: 300px; } .header { grid-area: header; background: #1a1a2e; color: white; padding: 1rem; border-radius: 8px; } .nav { grid-area: nav; background: steelblue; color: white; padding: 1rem; border-radius: 8px; } .main { grid-area: main; background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .footer { grid-area: footer; background: #333; color: white; padding: 1rem; border-radius: 8px; text-align: center; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".dashboard {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"solution": "grid-template-areas:\n \"header header\"\n \"nav main\"\n \"footer footer\";",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "grid-template-areas",
|
"value": "grid-template-areas",
|
||||||
"message": "Use the <kbd>grid-template-areas</kbd> property",
|
"message": "Use the <kbd>grid-template-areas</kbd> property",
|
||||||
"options": {
|
"options": { "caseSensitive": false }
|
||||||
"caseSensitive": false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "grid-template-areas:\\s*['\"]header\\s+header['\"]\\s*['\"]sidebar\\s+content['\"]\\s*['\"]footer\\s+footer['\"]",
|
"value": "['\"]header\\s+header['\"]",
|
||||||
"message": "Create areas: <kbd>\"header header\" \"sidebar content\" \"footer footer\"</kbd>",
|
"message": "Header should span both columns: <kbd>\"header header\"</kbd>",
|
||||||
"options": {
|
"options": { "caseSensitive": false }
|
||||||
"caseSensitive": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "grid-3",
|
|
||||||
"title": "Spanning Grid Cells",
|
|
||||||
"description": "Make grid items span multiple grid cells horizontally or vertically.",
|
|
||||||
"task": "Add <kbd>grid-column: span 2</kbd> and <kbd>grid-row: span 2</kbd> to <kbd>.featured</kbd> to span 2x2 cells.",
|
|
||||||
"previewHTML": "<div class='grid'><div class='item'>1</div><div class='item'>2</div><div class='item featured'>Featured</div><div class='item'>4</div><div class='item'>5</div><div class='item'>6</div><div class='item'>7</div><div class='item'>8</div><div class='item'>9</div></div>",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1.25rem; } .item { background-color: #9b59b6; color: white; padding: 1.25rem; text-align: center; font-weight: bold; } .featured { background-color: #e74c3c; }",
|
|
||||||
"sandboxCSS": ".grid { border: 0.125rem dashed #ccc; padding: 1rem; display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; }",
|
|
||||||
"codePrefix": "/* Make the featured item span 2x2 cells */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"solution": ".featured {\n grid-column: span 2;\n grid-row: span 2;\n}",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": ".featured",
|
|
||||||
"message": "Use the <kbd>.featured</kbd> class selector",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "grid-column:\\s*span\\s+2",
|
"value": "['\"]nav\\s+main['\"]",
|
||||||
"message": "Set <kbd>grid-column: span 2</kbd>",
|
"message": "Nav and main should be side by side: <kbd>\"nav main\"</kbd>",
|
||||||
"options": {
|
"options": { "caseSensitive": false }
|
||||||
"caseSensitive": false
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "grid-row:\\s*span\\s+2",
|
"value": "['\"]footer\\s+footer['\"]",
|
||||||
"message": "Set <kbd>grid-row: span 2</kbd>",
|
"message": "Footer should span both columns: <kbd>\"footer footer\"</kbd>",
|
||||||
"options": {
|
"options": { "caseSensitive": false }
|
||||||
"caseSensitive": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "grid-4",
|
|
||||||
"title": "Automatic Grid Placement",
|
|
||||||
"description": "Learn how to use auto-placement and <kbd>auto-fit</kbd>/<kbd>auto-fill</kbd> for responsive grid layouts.",
|
|
||||||
"task": "Add <kbd>display: grid</kbd> and <kbd>grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr))</kbd> to <kbd>.cards</kbd>.",
|
|
||||||
"previewHTML": "<div class='cards'><div class='card'>Card 1</div><div class='card'>Card 2</div><div class='card'>Card 3</div><div class='card'>Card 4</div><div class='card'>Card 5</div><div class='card'>Card 6</div></div>",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1.25rem; } .card { background-color: #3498db; color: white; padding: 1.25rem; text-align: center; font-weight: bold; height: 6rem; display: flex; align-items: center; justify-content: center; }",
|
|
||||||
"sandboxCSS": ".cards { border: 0.125rem dashed #ccc; padding: 1rem; }",
|
|
||||||
"codePrefix": "/* Create a responsive grid with auto-fit columns */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"solution": ".cards {\n display: grid;\n grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr));\n}",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": ".cards",
|
|
||||||
"message": "Use the <kbd>.cards</kbd> class selector",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": false
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "display",
|
|
||||||
"expected": "grid"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>display: grid</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "grid-template-columns:\\s*repeat\\(\\s*auto-fit\\s*,\\s*minmax\\(\\s*10rem\\s*,\\s*1fr\\s*\\)\\s*\\)",
|
|
||||||
"message": "Set <kbd>grid-template-columns: repeat(auto-fit, minmax(10rem, 1fr))</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "grid-5",
|
|
||||||
"title": "Grid Alignment",
|
|
||||||
"description": "Control the alignment of grid items within their cells on both axes.",
|
|
||||||
"task": "Add <kbd>justify-items: center</kbd> and <kbd>align-items: center</kbd> to center items within their cells.",
|
|
||||||
"previewHTML": "<div class='cells'><div class='item'>1</div><div class='item tall'>2</div><div class='item'>3</div><div class='item'>4</div><div class='item'>5</div><div class='item wide'>6</div></div>",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1.25rem; } .item { background-color: #9b59b6; color: white; padding: 1.25rem; text-align: center; font-weight: bold; width: 3rem; height: 3rem; } .tall { height: 6rem; } .wide { width: 6rem; }",
|
|
||||||
"sandboxCSS": ".cells { border: 0.125rem dashed #ccc; padding: 1rem; display: grid; grid-template-columns: repeat(3, 1fr); gap: 1rem; height: 25rem; }",
|
|
||||||
"codePrefix": "/* Center grid items both horizontally and vertically */\n.cells {\n /* Add alignment properties below */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "\n}",
|
|
||||||
"solution": "justify-items: center;\n align-items: center;",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "justify-items",
|
|
||||||
"expected": "center"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>justify-items: center</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "align-items",
|
|
||||||
"expected": "center"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>align-items: center</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "grid-6",
|
|
||||||
"title": "Overlapping Grid Items",
|
|
||||||
"description": "Learn how to create overlapping layouts by using grid positioning and <kbd>z-index</kbd>.",
|
|
||||||
"task": "Add <kbd>grid-column: 1</kbd>, <kbd>grid-row: 1</kbd>, and <kbd>z-index: 1</kbd> to <kbd>.overlay</kbd> to position it above the base.",
|
|
||||||
"previewHTML": "<div class='stack'><div class='base'>Base Content</div><div class='overlay'>Overlay</div></div>",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui, -apple-system, sans-serif; padding: 1.25rem; } .stack { position: relative; height: 15rem; } .base { background-color: #3498db; color: white; padding: 1.25rem; display: flex; align-items: center; justify-content: center; font-weight: bold; } .overlay { background-color: rgba(231, 76, 60, 0.7); color: white; display: flex; align-items: center; justify-content: center; font-weight: bold; font-size: 1.5rem; }",
|
|
||||||
"sandboxCSS": ".stack { border: 0.125rem dashed #ccc; padding: 1rem; display: grid; grid-template-columns: 1fr; grid-template-rows: 1fr; }",
|
|
||||||
"codePrefix": "/* Position the overlay to cover the entire grid */\n.base {\n grid-column: 1;\n grid-row: 1;\n}\n\n.overlay {\n /* Add your code below to position the overlay */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "\n}",
|
|
||||||
"solution": "grid-column: 1;\n grid-row: 1;\n z-index: 1;",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "grid-column",
|
|
||||||
"expected": "1"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>grid-column: 1</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "grid-row",
|
|
||||||
"expected": "1"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>grid-row: 1</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "z-index:\\s*[1-9]\\d*",
|
|
||||||
"message": "Set <kbd>z-index</kbd> to a positive number",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,548 +1,257 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "css-basic-selectors",
|
"id": "css-basic-selectors",
|
||||||
"title": "CSS Selectors",
|
"title": "Podstawy CSS",
|
||||||
"description": "CSS selectors are the foundation of styling web pages, allowing you to target specific HTML elements for styling. This module introduces fundamental selector types including element type selectors, class selectors, ID selectors, and the universal selector.",
|
"description": "Poznaj podstawowe elementy CSS: właściwości, wartości i selektory. Ten moduł uczy zasad składni, których przestrzega każda deklaracja CSS.",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "introduction-to-selectors",
|
"id": "css-properties",
|
||||||
"title": "What's a Selector?",
|
"title": "Właściwości CSS",
|
||||||
"description": "A CSS selector is the first part of a CSS rule that tells the browser which HTML elements should receive the styles defined in the declaration block. Selectors are essentially patterns that match against elements in your HTML document. Understanding selectors is fundamental because they determine which elements your CSS rules will affect. The element or elements targeted by a selector are referred to as the 'subject of the selector.' When writing a CSS rule, you first specify the selector, followed by curly braces that contain the style declarations.<br/>For example, to change the text color of elements, you can use the <kbd>color</kbd> property within your declaration block.<br><br><pre>/* Element selector */\np {\n color: orangered;\n /* │ └─── Indicates the value of the expression\n │ \n └─────────── Indicates the property of the expression */\n}</pre>",
|
"description": "CSS stylizuje elementy za pomocą <strong>deklaracji</strong> - par właściwości i wartości. Każda deklaracja ma ten sam wzorzec:<br><br><pre>property: value;</pre><br><strong>Właściwość</strong> to co chcesz zmienić (jak <kbd>color</kbd> lub <kbd>background</kbd>). <strong>Wartość</strong> to na co to ustawiasz. Dwukropek je rozdziela, a średnik kończy linię.<br><br>Wartości występują w różnych typach:<br>• <strong>Słowa kluczowe:</strong> <kbd>red</kbd>, <kbd>bold</kbd>, <kbd>center</kbd><br>• <strong>Liczby z jednostkami:</strong> <kbd>16px</kbd>, <kbd>2rem</kbd>, <kbd>100%</kbd><br>• <strong>Kolory:</strong> <kbd>steelblue</kbd>, <kbd>#ff0000</kbd>",
|
||||||
"task": "Write a CSS rule using a type selector that targets all paragraph elements <kbd>p</kbd> in the document. Make the text blue by setting the <kbd>color</kbd> property to <kbd>blue</kbd>.",
|
"task": "Uzupełnij deklarację dodając <kbd>color: coral;</kbd> aby zmienić kolor tekstu.",
|
||||||
"previewHTML": "<h1>Introduction to CSS Selectors</h1>\n<p>This paragraph should turn blue.</p>\n<div>This div element should remain unchanged.</div>\n<p>This second paragraph should also turn blue.</p>",
|
"previewHTML": "<p class=\"text\">This text should turn coral.</p>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } .text { font-size: 1.25rem; }",
|
||||||
"sandboxCSS": "h1, p, div { padding: 8px; margin-bottom: 10px; border: 1px dashed #ccc; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Write a type selector to target all paragraph elements */\n",
|
"codePrefix": ".text {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "\n}",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"solution": "p { color: blue }",
|
"solution": "color: coral;",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "property_value",
|
||||||
"value": "^p\\s*{",
|
"value": { "property": "color", "expected": "coral" },
|
||||||
"message": "Start your rule with <kbd>p { … }</kbd> to select all paragraph elements",
|
"message": "Dodaj <kbd>color: coral;</kbd>"
|
||||||
"options": {
|
}
|
||||||
"caseSensitive": false
|
]
|
||||||
}
|
},
|
||||||
},
|
{
|
||||||
|
"id": "multiple-properties",
|
||||||
|
"title": "Wiele właściwości",
|
||||||
|
"description": "Reguła może mieć wiele deklaracji. Każda idzie w osobnej linii i każda potrzebuje średnika na końcu:<br><br><pre>.box {<br> background: gold;<br> color: navy;<br> padding: 1rem;<br>}</pre><br>Kolejność zwykle nie ma znaczenia - CSS stosuje wszystkie. Przy konfliktach wygrywa ostatnia.",
|
||||||
|
"task": "Dodaj dwie deklaracje: <kbd>background: lavender;</kbd> i <kbd>padding: 1rem;</kbd>",
|
||||||
|
"previewHTML": "<div class=\"card\">A styled card with background and padding.</div>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } .card { border-radius: 8px; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".card {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "background: lavender;\n padding: 1rem;",
|
||||||
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "color:",
|
"value": { "property": "background", "expected": "lavender" },
|
||||||
"message": "Include the <kbd>color:</kbd> property in your CSS rule"
|
"message": "Dodaj <kbd>background: lavender;</kbd>"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "blue",
|
|
||||||
"message": "Set the color value to <kbd>blue</kbd>"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"property": "color",
|
"message": "Dodaj <kbd>padding: 1rem;</kbd>"
|
||||||
"expected": "blue"
|
|
||||||
},
|
|
||||||
"message": "Use <kbd>color: blue</kbd> to set the text color"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "p\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "type-selectors",
|
"id": "type-selectors",
|
||||||
"title": "Type Selectors",
|
"title": "Selektory typu",
|
||||||
"description": "Type selectors (also called tag name selectors or element selectors) target HTML elements based on their tag name. For example, <kbd>p</kbd> selects all paragraph elements, <kbd>h1</kbd> selects all level-one headings, and <kbd>div</kbd> selects all division elements. Type selectors are the most fundamental way to select elements, applying styles consistently to all instances of a particular HTML element throughout your document. You can define a variety of CSS properties with type selectors, such as <kbd>color</kbd> for text color, <kbd>background-color</kbd> for the background, and <kbd>font-weight</kbd> for text emphasis. They provide a broad approach for styling your page and are often the starting point for more specific styling using other selector types.",
|
"description": "<strong>Selektor</strong> mówi przeglądarce, które elementy stylizować. Najprostszym selektorem jest <strong>selektor typu</strong> — po prostu nazwa tagu HTML.<br><br><pre>p {<br> color: steelblue;<br>}</pre><br>Ta reguła celuje w każdy element <kbd><p></kbd> na stronie. Selektory typu świetnie nadają się do ustawiania podstawowych stylów.",
|
||||||
"task": "Write three separate CSS rules using type selectors to target specific HTML elements: make <kbd>h2</kbd> headings <kbd>purple</kbd>, give <kbd>span</kbd> elements a <kbd>yellow</kbd> background, and make <kbd>strong</kbd> elements <kbd>red</kbd>.",
|
"task": "Ostyluj wszystkie akapity. Napisz regułę z <kbd>p</kbd> jako selektorem i ustaw <kbd>color: steelblue</kbd>.",
|
||||||
"previewHTML": "<h2>Type Selectors Example</h2>\n<p>Regular paragraph text <span>with a highlighted span</span> that should have a yellow background.</p>\n<p>Another paragraph with <strong>strong important text</strong> that should be red.</p>\n<h2>Another Heading</h2>",
|
"previewHTML": "<article>\n <h2>Fresh Roasted Coffee</h2>\n <p>Our beans are sourced from small farms in Colombia and Ethiopia.</p>\n <p>Each batch is roasted weekly to ensure peak freshness.</p>\n</article>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } h2 { margin: 0 0 1rem; }",
|
||||||
"sandboxCSS": "h2, p, span, strong { padding: 3px; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Write three separate type selectors below */\n\n",
|
"codePrefix": "",
|
||||||
"initialCode": "/* 1. Make h2 headings purple */\n\n\n/* 2. Give span elements a yellow background */\n\n\n/* 3. Make strong elements red */\n",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"solution": "/* 1. Make h2 headings purple */\nh2 {\n color: purple;\n}\n\n/* 2. Give span elements a yellow background */\nspan {\n background-color: yellow;\n}\n\n/* 3. Make strong elements red */\nstrong {\n color: red;\n}",
|
"solution": "p {\n color: steelblue;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^h2\\s*{",
|
"value": "p\\s*\\{",
|
||||||
"message": "Include an <kbd>h2 { … }</kbd> selector"
|
"message": "Zacznij od <kbd>p {</kbd> aby wybrać akapity"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "color", "expected": "steelblue" },
|
||||||
"property": "color",
|
"message": "Ustaw <kbd>color: steelblue</kbd>"
|
||||||
"expected": "purple"
|
}
|
||||||
},
|
]
|
||||||
"message": "Set the <kbd>color</kbd> property to <kbd>purple</kbd> for h2 elements"
|
},
|
||||||
},
|
{
|
||||||
|
"id": "styling-links",
|
||||||
|
"title": "Stylizowanie linków",
|
||||||
|
"description": "Selektory typu działają dla każdego elementu HTML. Selektor <kbd>a</kbd> celuje we wszystkie linki na stronie.<br><br>Linki domyślnie mają niebieski kolor i podkreślenie. Możesz zmienić oba z CSS — użyj <kbd>color</kbd> dla tekstu i <kbd>text-decoration: none</kbd> aby usunąć podkreślenie.",
|
||||||
|
"task": "Ostyluj linki nawigacji. Napisz regułę z <kbd>a</kbd> jako selektorem i ustaw <kbd>color: coral</kbd>.",
|
||||||
|
"previewHTML": "<nav>\n <a href=\"#\">Home</a>\n <a href=\"#\">Menu</a>\n <a href=\"#\">About</a>\n <a href=\"#\">Contact</a>\n</nav>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } nav { display: flex; gap: 1.5rem; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "a {\n color: coral;\n}",
|
||||||
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "h2\\s*{[^}]*}",
|
"value": "a\\s*\\{",
|
||||||
"message": "Make sure to close your h2 rule with a closing brace <kbd>}</kbd>"
|
"message": "Zacznij od <kbd>a {</kbd> aby wybrać linki"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^span\\s*{",
|
|
||||||
"message": "Include a <kbd>span { … }</kbd> selector"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "color", "expected": "coral" },
|
||||||
"property": "background-color",
|
"message": "Ustaw <kbd>color: coral</kbd>"
|
||||||
"expected": "yellow"
|
|
||||||
},
|
|
||||||
"message": "Set a <kbd>background-color: yellow</kbd> for span elements"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "span\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your span rule with a closing brace <kbd>}</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^strong\\s*{",
|
|
||||||
"message": "Include a <kbd>strong { … }</kbd> selector"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "strong\\s*{\\s*color:\\s*red;[^}]*}",
|
|
||||||
"message": "Set the <kbd>color: red</kbd> for strong elements"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "class-selectors",
|
"id": "class-selectors",
|
||||||
"title": "Class Selectors",
|
"title": "Selektory klas",
|
||||||
"description": "Class selectors target elements with a specific class attribute value. They begin with a dot (.) followed by the class name. Classes are powerful because they allow you to apply the same styles to multiple elements regardless of their type. An HTML element can have multiple classes (separated by spaces in the class attribute), and a class can be applied to any number of elements. When using class selectors, you can apply properties like <kbd>background-color</kbd> to set the background color of elements, and <kbd>font-weight</kbd> to control text thickness, making text bold or normal. This flexibility makes class selectors one of the most commonly used methods for applying styles in CSS, allowing for modular and reusable styling across your website.",
|
"description": "Selektory typu stylizują <em>wszystkie</em> elementy danego typu. Ale co jeśli chcesz ostylować tylko niektóre z nich?<br><br><strong>Selektory klas</strong> celują w elementy z określonym atrybutem <kbd>class</kbd>. Zaczynają się od kropki:<br><br><pre>.badge {<br> background: coral;<br>}</pre><br>To stylizuje tylko elementy z <kbd>class=\"badge\"</kbd>.",
|
||||||
"task": "Create a CSS rule using a class selector that targets elements with the class <kbd>highlight</kbd>. Give these elements a <kbd>yellow</kbd> background and <kbd>bold</kbd> text.",
|
"task": "Ostyluj badge powiadomień. Napisz regułę z <kbd>.badge</kbd> jako selektorem i ustaw <kbd>background: tomato</kbd>.",
|
||||||
"previewHTML": "<h2>Using Class Selectors</h2>\n<p>This is a regular paragraph, but <span class=\"highlight\">this span has the highlight class</span> applied to it.</p>\n<p class=\"highlight\">This entire paragraph has the highlight class.</p>\n<ul>\n <li>Regular list item</li>\n <li class=\"highlight\">This list item is highlighted</li>\n</ul>",
|
"previewHTML": "<header>\n <h1>Dashboard</h1>\n <span class=\"badge\">3</span>\n</header>\n<p>You have new notifications waiting.</p>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } header { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem; } h1 { margin: 0; font-size: 1.5rem; } .badge { color: white; padding: 0.25rem 0.5rem; border-radius: 999px; font-size: 0.875rem; } p { color: #555; margin: 0; }",
|
||||||
"sandboxCSS": "h2, p, li { padding: 5px; margin-bottom: 10px; }",
|
|
||||||
"codePrefix": "/* Create a class selector for elements with the 'highlight' class */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^\\.highlight\\s*{",
|
|
||||||
"message": "Start your rule with <kbd>.highlight { … }</kbd> to create a class selector",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Include the <kbd>background-color:</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "yellow"
|
|
||||||
},
|
|
||||||
"message": "Set the background color to <kbd>yellow</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "font-weight:",
|
|
||||||
"message": "Include the <kbd>font-weight:</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "font-weight",
|
|
||||||
"expected": "bold"
|
|
||||||
},
|
|
||||||
"message": "Set the font-weight to <kbd>bold</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "\\.highlight\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "multiple-classes",
|
|
||||||
"title": "Multiple Classes",
|
|
||||||
"description": "HTML elements can have multiple classes applied simultaneously, allowing for composable and modular CSS designs. When an element has multiple classes, it will receive styles from all matching class selectors. This approach enables you to build a library of reusable CSS classes that can be combined in different ways. You can also target elements that have a specific combination of classes by chaining class selectors together without spaces (e.g., <kbd>.class1.class2</kbd>). When styling these elements, you might use properties like <kbd>border-color</kbd> to change the color of element borders, and <kbd>background-color</kbd> to set the background color of elements. This technique lets you create conditional styles that only apply when certain classes appear together.",
|
|
||||||
"task": "Complete the CSS rule that targets elements with both <kbd>card</kbd> and <kbd>featured</kbd> classes by chaining the selectors. Set the border-color to gold and the background-color to lemonchiffon to make featured cards stand out.",
|
|
||||||
"previewHTML": "<h2>Multiple Class Combinations</h2>\n<div class=\"card\">Regular Card</div>\n<div class=\"card featured\">Featured Card</div>\n<div class=\"featured\">Just Featured (not a card)</div>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } .card { border: 2px solid gray; padding: 15px; margin-bottom: 10px; border-radius: 5px; }",
|
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* The .card class already has basic styling */\n/* Now target elements with BOTH classes: 'card' AND 'featured' */\n",
|
"codePrefix": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"solution": ".card.featured { border-color: gold; background-color: lemonchiffon }",
|
"solution": ".badge {\n background: tomato;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^\\.card\\.featured\\s*{",
|
"value": "\\.badge\\s*\\{",
|
||||||
"message": "Chain the selectors as <kbd>.card.featured</kbd> (no space between them)",
|
"message": "Zacznij od <kbd>.badge {</kbd> (nie zapomnij o kropce!)"
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "border-color:",
|
|
||||||
"message": "Include the <kbd>border-color</kbd> property"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "background", "expected": "tomato" },
|
||||||
"property": "border-color",
|
"message": "Ustaw <kbd>background: tomato</kbd>"
|
||||||
"expected": "gold"
|
|
||||||
},
|
|
||||||
"message": "Set the border color to <kbd>gold</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "\\.card\\.featured\\s*{[^}]*;",
|
|
||||||
"message": "Make sure to end your CSS rule with a semicolon <kbd>;</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Include the <kbd>background-color</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "lemonchiffon"
|
|
||||||
},
|
|
||||||
"message": "Set the background color to <kbd>lemonchiffon</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "\\.card\\.featured\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "class-with-type",
|
"id": "button-variants",
|
||||||
"title": "Combining Types",
|
"title": "Warianty przycisków",
|
||||||
"description": "You can combine type selectors with class selectors to target specific HTML elements that have a certain class. This creates a more specific selector that only matches when both conditions are true: the element is of the specified type AND it has the specified class. For example, <kbd>p.note</kbd> would select paragraph elements with the class <kbd>note</kbd>, but would not select divs or spans with that same class. You can style these combined selections using properties like <kbd>background-color</kbd> to set a colored background for your elements. This approach allows you to apply different styles to the same class when it appears on different element types.",
|
"description": "Elementy mogą mieć wiele klas. Gdy łączysz selektory klas bez spacji, celujesz w elementy które mają <em>wszystkie</em> te klasy:<br><br><pre>.btn.primary {<br> background: steelblue;<br>}</pre><br>To celuje w elementy z <kbd>class=\"btn primary\"</kbd>, nie tylko <kbd>.btn</kbd> lub tylko <kbd>.primary</kbd>.",
|
||||||
"task": "Create a CSS rule that specifically targets <kbd><span></kbd> elements with the class <kbd>highlight</kbd>. Make those elements have an orange background, while other elements with the highlight class remain untouched.",
|
"task": "Ostyluj główny przycisk. Napisz regułę z <kbd>.btn.primary</kbd> jako selektorem i ustaw <kbd>background: steelblue</kbd>.",
|
||||||
"previewHTML": "<h2>Type and Class Combinations</h2>\n<p>This paragraph has a <span class=\"highlight\">highlighted span</span> that should have an orange background.</p>\n<p class=\"highlight\">This paragraph has the highlight class but should NOT have an orange background.</p>",
|
"previewHTML": "<div class=\"actions\">\n <button class=\"btn\">Cancel</button>\n <button class=\"btn primary\">Save Changes</button>\n</div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } .highlight { font-weight: bold; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } .actions { display: flex; gap: 0.75rem; } .btn { padding: 0.5rem 1rem; border: none; border-radius: 6px; font-size: 1rem; cursor: pointer; background: #e0e0e0; color: #333; }",
|
||||||
"sandboxCSS": "h2, p, span { padding: 5px; }",
|
|
||||||
"codePrefix": "/* The .highlight class already sets font-weight to bold */\n/* Now target ONLY span elements with the highlight class */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^span\\.highlight\\s*{",
|
|
||||||
"message": "Use <kbd>span.highlight</kbd> selector (no space between element and class)",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Include the <kbd>background-color</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "orange"
|
|
||||||
},
|
|
||||||
"message": "Set the background color to <kbd>orange</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "span\\.highlight\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "id-selectors",
|
|
||||||
"title": "ID Selectors",
|
|
||||||
"description": "ID selectors target elements with a specific id attribute. They begin with a hash/pound sign (#) followed by the ID name. Unlike classes, IDs must be unique within a document—each ID value should be used only once per page. ID selectors have higher specificity than class or element selectors, meaning they override those selectors when conflicts arise. When styling with ID selectors, you can use properties like <kbd>color</kbd> to define text color, and <kbd>text-decoration</kbd> to control the appearance of text, such as adding underlines to elements. Because of their uniqueness requirement, IDs are best used for one-of-a-kind elements like page headers, main navigation, or specific unique components that appear only once on a page.",
|
|
||||||
"task": "Create a CSS rule with an ID selector that targets the element with the ID <kbd>main-title</kbd>. Set its color to purple and add an underline with <kbd>text-decoration: underline</kbd>.",
|
|
||||||
"previewHTML": "<h1 id=\"main-title\">Main Page Title</h1>\n<p>Regular paragraph content.</p>\n<h2>Secondary Heading</h2>\n<p id=\"intro\">Introduction paragraph (different ID).</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
|
||||||
"sandboxCSS": "h1, h2, p { padding: 8px; margin-bottom: 10px; border: 1px dashed #ccc; }",
|
|
||||||
"codePrefix": "/* Create an ID selector to target the element with id=\"main-title\" */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^#main-title\\s*{",
|
|
||||||
"message": "Start your rule with <kbd>#main-title</kbd> to create an ID selector",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "color:",
|
|
||||||
"message": "Include the <kbd>color</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "color",
|
|
||||||
"expected": "purple"
|
|
||||||
},
|
|
||||||
"message": "Set the color to <kbd>purple</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "text-decoration:",
|
|
||||||
"message": "Include the <kbd>text-decoration</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "text-decoration",
|
|
||||||
"expected": "underline"
|
|
||||||
},
|
|
||||||
"message": "Set the text-decoration to <kbd>underline</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "#main-title\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "id-with-type",
|
|
||||||
"title": "Type + ID",
|
|
||||||
"description": "Similar to how you can combine type and class selectors, you can also combine type selectors with ID selectors. For example, <kbd>h1#title</kbd> targets an h1 element with the ID 'title'. When using this combined approach, you can apply CSS properties like <kbd>font-style</kbd> to control the slant of the text, making it italic or normal. While this selector combination is more specific than using just the ID selector, it's often unnecessary since IDs should already be unique in a document. However, this technique can be useful for improving code readability or when you want to emphasize that a particular ID should only appear on a specific element type.",
|
|
||||||
"task": "Create a CSS rule that combines a type selector with an ID selector to target specifically a paragraph element with the ID <kbd>special</kbd>. Set its font style to italic.",
|
|
||||||
"previewHTML": "<h2 id=\"special\">Heading with ID \"special\" (should NOT be affected)</h2>\n<p id=\"special\">Paragraph with ID \"special\" (should become italic)</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
|
||||||
"sandboxCSS": "h2, p { padding: 8px; margin-bottom: 10px; border: 1px dashed #ccc; }",
|
|
||||||
"codePrefix": "/* Create a combined type+ID selector for a paragraph with id=\"special\" */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^p#special\\s*{",
|
|
||||||
"message": "Use <kbd>p#special</kbd> to target paragraphs with ID <kbd>special</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "font-style:",
|
|
||||||
"message": "Include the <kbd>font-style</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "font-style",
|
|
||||||
"expected": "italic"
|
|
||||||
},
|
|
||||||
"message": "Set the font-style to <kbd>italic</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "p#special\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "selector-lists",
|
|
||||||
"title": "Selector Lists",
|
|
||||||
"description": "When multiple elements need the same styling, you can group them together using a selector list (also known as grouping selectors). Selector lists are created by separating individual selectors with commas. This approach reduces repetition in your CSS, making it more maintainable and efficient. For example, <kbd>h1, h2, h3 { color: blue; }</kbd> applies the same blue color to all three heading levels. When styling multiple selectors at once, you can apply properties like <kbd>background-color</kbd> to set the background, <kbd>border-left</kbd> to create a left border with a specific thickness, style, and color, and <kbd>padding-left</kbd> to create space between the content and the left border. Whitespace around commas is optional, and each selector in the list can be any valid selector type-elements, classes, IDs, or even more complex selectors.",
|
|
||||||
"task": "Create a selector list that applies the same styles to three different elements: paragraphs with class <kbd>note</kbd>, list items with class <kbd>important</kbd>, and the element with ID <kbd>summary</kbd>. Give them a <kbd>lightyellow</kbd> background, a <kbd>gold</kbd> left border, and some left <kbd>padding</kbd>.",
|
|
||||||
"previewHTML": "<p class=\"note\">This is a note paragraph.</p>\n<ul>\n <li>Regular list item</li>\n <li class=\"important\">Important list item</li>\n</ul>\n<div id=\"summary\">Summary section</div>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
|
||||||
"sandboxCSS": "p, li, div { padding: 8px; margin-bottom: 8px; border: 1px dashed gray; }",
|
|
||||||
"codePrefix": "/* Create a selector list to apply the same styles to multiple different elements */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"solution": "p.note,\nli.important,\n#summary {\n background-color: lightyellow;\n border-left: 3px solid gold;\n padding-left: 10px\n}",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "p.note",
|
|
||||||
"message": "Include <kbd>p.note</kbd> in your selector list",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "li.important",
|
|
||||||
"message": "Include <kbd>li.important</kbd> in your selector list",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "#summary",
|
|
||||||
"message": "Include <kbd>#summary</kbd> in your selector list",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "(p\\.note|li\\.important|#summary)\\s*,\\s*(p\\.note|li\\.important|#summary)\\s*,\\s*(p\\.note|li\\.important|#summary)",
|
|
||||||
"message": "Create a comma-separated list with all three selectors",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Include the <kbd>background-color</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "lightyellow"
|
|
||||||
},
|
|
||||||
"message": "Set the background color to <kbd>lightyellow</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "border-left:",
|
|
||||||
"message": "Include the <kbd>border-left</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "border-left",
|
|
||||||
"expected": "3px solid gold"
|
|
||||||
},
|
|
||||||
"message": "Use <kbd>border-left: 3px solid gold</kbd> to create a left border"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "padding-left:",
|
|
||||||
"message": "Include the <kbd>padding-left</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "padding-left",
|
|
||||||
"expected": "10px"
|
|
||||||
},
|
|
||||||
"message": "Use <kbd>padding-left: 10px</kbd> to add left padding"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "universal-selector",
|
|
||||||
"title": "Universal (*)",
|
|
||||||
"description": "The universal selector is denoted by an asterisk (*) and matches any element of any type. It selects everything in the document or, when combined with other selectors, everything within a specific context. For example, <kbd>* { margin: 0; }</kbd> removes margins from all elements, while <kbd>article *</kbd> selects all elements inside article elements. When using the universal selector in combination with other selectors, you can apply properties like <kbd>margin</kbd> to control the spacing around elements. The universal selector is powerful but should be used carefully due to its broad impact. It's commonly used in CSS resets, to override default browser styling, or to target all children of a particular element.",
|
|
||||||
"task": "Use the universal selector to remove margins from all elements inside the container div. Create a rule using <kbd>div.container *</kbd> as the selector and set <kbd>margin: 0</kbd>.",
|
|
||||||
"previewHTML": "<div class=\"container\">\n <h2>Inside Container</h2>\n <p>This paragraph is inside the container.</p>\n <ul>\n <li>List item inside container</li>\n </ul>\n</div>\n<p>This paragraph is outside the container and should not be affected.</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } div.container { border: 2px solid navy; padding: 15px; background-color: lavender; } h2, p, ul, li { margin: 15px 0; }",
|
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Use the universal selector to target all elements inside the container */\n",
|
"codePrefix": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
|
"solution": ".btn.primary {\n background: steelblue;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^div\\.container\\s+\\*\\s*{",
|
"value": "\\.btn\\.primary\\s*\\{",
|
||||||
"message": "Use <kbd>div.container *</kbd> selector (with a space between container and *)",
|
"message": "Użyj <kbd>.btn.primary {</kbd> (bez spacji między klasami)"
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "margin:",
|
|
||||||
"message": "Include the <kbd>margin</kbd> property"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "background", "expected": "steelblue" },
|
||||||
"property": "margin",
|
"message": "Ustaw <kbd>background: steelblue</kbd>"
|
||||||
"expected": "0"
|
|
||||||
},
|
|
||||||
"message": "Set margin to <kbd>0</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "div\\.container\\s+\\*\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "specificity-basics",
|
"id": "specific-elements",
|
||||||
"title": "Specificity",
|
"title": "Celowanie w konkretne elementy",
|
||||||
"description": "CSS specificity determines which styles take precedence when multiple conflicting rules target the same element. Specificity follows a hierarchical system: inline styles have the highest specificity, followed by ID selectors, then class/attribute/pseudo-class selectors, and finally element/pseudo-element selectors. This can be conceptualized as a four-part score (inline, ID, class, element). When creating multiple rules that may target the same elements, you can use the <kbd>color</kbd> property to set text colors, and specificity will determine which color is actually applied. Understanding specificity is crucial for predictable styling and debugging CSS conflicts. When two selectors have equal specificity, the one that comes last in the stylesheet wins.",
|
"description": "Czasem chcesz, żeby klasa wyglądała inaczej na różnych elementach. Połącz selektor typu z selektorem klasy (bez spacji) aby być bardziej specyficznym:<br><br><pre>a.btn {<br> text-decoration: none;<br>}</pre><br>To stylizuje tylko elementy <kbd><a></kbd> z klasą <kbd>btn</kbd>, nie elementy <kbd><button></kbd> z tą klasą.",
|
||||||
"task": "Examine the existing CSS rules and add a new rule with higher specificity to override the text color of the paragraph. Create a rule using '.content p' as the selector and set color: green.",
|
"task": "Usuń podkreślenie z przycisków-linków. Napisz regułę z <kbd>a.btn</kbd> jako selektorem i ustaw <kbd>text-decoration: none</kbd>.",
|
||||||
"previewHTML": "<div class=\"content\">\n <p>What color will this paragraph be? Look at the CSS rules and their specificity.</p>\n</div>",
|
"previewHTML": "<div class=\"actions\">\n <button class=\"btn\">Regular Button</button>\n <a href=\"#\" class=\"btn\">Link Button</a>\n</div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } .actions { display: flex; gap: 0.75rem; align-items: center; } .btn { padding: 0.5rem 1rem; border: none; border-radius: 6px; font-size: 1rem; cursor: pointer; background: steelblue; color: white; }",
|
||||||
"sandboxCSS": "p { border: 1px dashed gray; padding: 10px; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* These CSS rules target the same paragraph but have different specificity */\n\n/* Rule 1: Element selector (lowest specificity) */\np {\n color: red;\n}\n\n/* Rule 2: Descendant selector (higher specificity than just 'p') */\n",
|
"codePrefix": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "a.btn {\n text-decoration: none;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^\\.content\\s+p\\s*{",
|
"value": "a\\.btn\\s*\\{",
|
||||||
"message": "Use <kbd>.content p</kbd> as your selector (note the space between)",
|
"message": "Użyj <kbd>a.btn {</kbd> (typ + klasa, bez spacji)"
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "color:",
|
"value": { "property": "text-decoration", "expected": "none" },
|
||||||
"message": "Include the <kbd>color</kbd> property"
|
"message": "Ustaw <kbd>text-decoration: none</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "grouping-selectors",
|
||||||
|
"title": "Grupowanie selektorów",
|
||||||
|
"description": "Gdy wiele elementów potrzebuje tych samych stylów, wymień je oddzielone przecinkami. To utrzymuje CSS czystym i łatwym w utrzymaniu.<br><br><pre>h1, h2, h3 {<br> color: steelblue;<br>}</pre><br>To stosuje ten sam kolor do wszystkich trzech poziomów nagłówków w jednej regule.",
|
||||||
|
"task": "Ostyluj wszystkie nagłówki jednolicie. Dodaj <kbd>color: steelblue</kbd> do zgrupowanego selektora <kbd>h1, h2, h3</kbd>.",
|
||||||
|
"previewHTML": "<article><h1>Main Title</h1><p>Introduction paragraph with some text.</p><h2>Section Heading</h2><p>More content here.</p><h3>Subsection</h3><p>Final paragraph.</p></article>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } article { max-width: 500px; } p { color: #555; line-height: 1.6; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "h1, h2, h3 {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "color: steelblue;",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "color", "expected": "steelblue" },
|
||||||
|
"message": "Ustaw <kbd>color: steelblue</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "descendant-selectors",
|
||||||
|
"title": "Selektory potomków",
|
||||||
|
"description": "Celuj w elementy wewnątrz innych elementów używając spacji między selektorami. To jeden z najbardziej użytecznych wzorców w CSS.<br><br><pre>.nav a {<br> color: white;<br>}</pre><br>To stylizuje tylko linki wewnątrz <kbd>.nav</kbd>, pozostawiając inne linki bez zmian.",
|
||||||
|
"task": "Ostyluj linki nawigacji inaczej. Napisz regułę z <kbd>.nav a</kbd> jako selektorem i ustaw <kbd>color: white</kbd>.",
|
||||||
|
"previewHTML": "<nav class=\"nav\"><a href=\"#\">Home</a><a href=\"#\">About</a><a href=\"#\">Contact</a></nav><p>Read more in our <a href=\"#\">documentation</a>.</p>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; margin: 0; } .nav { background: steelblue; padding: 1rem; display: flex; gap: 1rem; border-radius: 8px; margin-bottom: 1rem; } .nav a { text-decoration: none; } p a { color: steelblue; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": ".nav a {\n color: white;\n}",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "\\.nav\\s+a\\s*\\{",
|
||||||
|
"message": "Użyj <kbd>.nav a {</kbd> (spacja między .nav a a)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "green",
|
"value": { "property": "color", "expected": "white" },
|
||||||
"message": ""
|
"message": "Ustaw <kbd>color: white</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nested-styling",
|
||||||
|
"title": "Zagnieżdżone style",
|
||||||
|
"description": "Selektory potomków pozwalają tworzyć style kontekstowe. Ten sam element może wyglądać inaczej w zależności od tego gdzie się znajduje.<br><br>Na przykład, akapity w <kbd>.card</kbd> mogą być mniejsze niż akapity w <kbd>article</kbd>.",
|
||||||
|
"task": "Zmniejsz akapity wewnątrz karty. Napisz regułę z <kbd>.card p</kbd> jako selektorem i ustaw <kbd>font-size: 0.9rem</kbd>.",
|
||||||
|
"previewHTML": "<article><h2>Article Title</h2><p>This is a regular article paragraph with normal-sized text for comfortable reading.</p><div class=\"card\"><strong>Quick Tip</strong><p>Card paragraphs should be slightly smaller to fit the compact design.</p></div><p>Back to regular article text here.</p></article>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } article { max-width: 500px; } h2 { color: steelblue; margin-top: 0; } p { line-height: 1.6; color: #444; } .card { background: #f0f4f8; padding: 1rem; border-radius: 8px; border-left: 4px solid steelblue; } .card strong { color: steelblue; display: block; margin-bottom: 0.5rem; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": ".card p {\n font-size: 0.9rem;\n}",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "\\.card\\s+p\\s*\\{",
|
||||||
|
"message": "Użyj <kbd>.card p {</kbd> (spacja między .card a p)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "font-size", "expected": "0.9rem" },
|
||||||
|
"message": "Ustaw <kbd>font-size: 0.9rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "welcome",
|
"id": "welcome",
|
||||||
"title": "Code Crispies",
|
"title": "Witaj",
|
||||||
"description": "Welcome to Code Crispies - your interactive web development learning platform",
|
"description": "Rozpocznij przygodę z Code Crispies",
|
||||||
"mode": "html",
|
"mode": "css",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
|
"excludeFromProgress": true,
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "get-started",
|
"id": "hello",
|
||||||
"title": "Get Started",
|
"title": "Cześć!",
|
||||||
"description": "<strong>Code Crispies</strong> is a free, open-source platform for learning web development through hands-on exercises. No account required!<br><br><strong>What you'll learn:</strong><br>• <strong>HTML</strong> - Semantic elements, forms, tables, SVG (<em>HTML Block & Inline</em>, <em>HTML Forms</em>, <em>HTML Tables</em>)<br>• <strong>CSS</strong> - Selectors, box model, flexbox, animations (<em>CSS Selectors</em>, <em>CSS Box Model</em>, <em>CSS Flexbox</em>)<br>• <strong>Responsive Design</strong> - Media queries and mobile-first layouts<br><br><strong>How it works:</strong><br>1. Read the task in the left panel<br>2. Write code in the editor<br>3. See live results in the preview<br>4. Get instant feedback with hints<br><br><strong>Keyboard shortcuts:</strong> <kbd>Ctrl+Z</kbd> to undo, <kbd>Ctrl+Shift+Z</kbd> to redo<br><br><strong>More resources:</strong><br>• <a href=\"https://nextlevelshit.github.io/html-over-js/\" target=\"_blank\">HTML over JS</a> - Native HTML vs JavaScript solutions<br>• <a href=\"https://nextlevelshit.github.io/web-engineering-mandala/\" target=\"_blank\">Web Engineering Mandala</a> - JavaScript technology roadmap",
|
"description": "<strong>Witaj w Code Crispies!</strong> Naucz się CSS i HTML poprzez praktyczne ćwiczenia.<br><br><strong>Jak to działa:</strong><br>1. Przeczytaj zadanie po lewej<br>2. Napisz kod w edytorze<br>3. Zobacz wyniki na żywo w podglądzie<br><br><strong>Wskazówki:</strong> Użyj <kbd>Ctrl+Z</kbd> aby cofnąć. Otwórz menu (☰) aby przeglądać wszystkie moduły.",
|
||||||
"task": "Write <code>Hello World</code>",
|
"task": "Napisz <code>Hello World</code> aby rozpocząć",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 2rem; color: #6366f1; font-weight: bold; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 2rem; color: #6366f1; font-weight: bold; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,42 +22,9 @@
|
|||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "Hello World",
|
"value": "Hello World",
|
||||||
"message": "Write <code>Hello World</code>"
|
"message": "Napisz <code>Hello World</code>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "overview",
|
|
||||||
"title": "Overview",
|
|
||||||
"description": "<strong>You're ready!</strong> Open the menu (☰) to explore all modules.<br><br><strong>Recommended learning path:</strong><br>1. <em>HTML Block & Inline</em> - Understand container vs inline elements<br>2. <em>HTML Forms</em> - Build interactive forms with validation<br>3. <em>CSS Selectors</em> - Target elements precisely<br>4. <em>CSS Box Model</em> - Master padding, margin, borders<br>5. <em>CSS Flexbox</em> - Create flexible layouts<br>6. <em>CSS Animations</em> - Add motion and transitions<br><br><strong>Tips:</strong><br>• Use <em>Show Expected</em> to see the target result<br>• Your progress is saved automatically<br>• Try Emmet in HTML mode: <kbd>ul>li*3</kbd> + Tab<br><br><strong>Open Source:</strong><br>• <a href=\"https://git.librete.ch/libretech/code-crispies\" target=\"_blank\">Gitea (Source)</a> · <a href=\"https://github.com/nextlevelshit/code-crispies\" target=\"_blank\">GitHub (Mirror)</a><br>• Made by <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a> · <a href=\"https://www.linkedin.com/in/michael-werner-czechowski\" target=\"_blank\">Michael Czechowski</a>",
|
|
||||||
"task": "Click Next to continue",
|
|
||||||
"previewHTML": "<p>Hello World! 🌍</p><p>Hallo Welt!</p><p>Bonjour le monde!</p><p>¡Hola Mundo!</p><p>Ciao Mondo!</p><p>Olá Mundo!</p><p>こんにちは世界!</p><p>你好世界!</p><p>안녕 세상!</p><p>Привет мир!</p><p dir=\"rtl\">שלום עולם!</p><p dir=\"rtl\">مرحبا بالعالم!</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 12px; } p { margin: 6px 0; padding: 6px 12px; border-radius: 6px; font-weight: 600; font-size: 0.95em; } p:nth-child(1) { background: #fef3c7; color: #92400e; font-size: 1.2em; } p:nth-child(2) { background: #dcfce7; color: #166534; } p:nth-child(3) { background: #dbeafe; color: #1e40af; } p:nth-child(4) { background: #fce7f3; color: #9d174d; } p:nth-child(5) { background: #e0e7ff; color: #4338ca; } p:nth-child(6) { background: #fef9c3; color: #854d0e; } p:nth-child(7) { background: #fee2e2; color: #991b1b; } p:nth-child(8) { background: #f3e8ff; color: #7c3aed; } p:nth-child(9) { background: #ccfbf1; color: #0f766e; } p:nth-child(10) { background: #fae8ff; color: #86198f; } p:nth-child(11) { background: #fef3c7; color: #b45309; } p:nth-child(12) { background: #d1fae5; color: #047857; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<p>Hello World! 🌍</p>\n<p>Hallo Welt!</p>\n<p>Bonjour le monde!</p>\n<p>¡Hola Mundo!</p>\n<p>Ciao Mondo!</p>\n<p>Olá Mundo!</p>\n<p>こんにちは世界!</p>\n<p>你好世界!</p>\n<p>안녕 세상!</p>\n<p>Привет мир!</p>\n<p dir=\"rtl\">שלום עולם!</p>\n<p dir=\"rtl\">مرحبا بالعالم!</p>",
|
|
||||||
"solution": "<p>Hello World! 🌍</p>\n<p>Hallo Welt!</p>\n<p>Bonjour le monde!</p>\n<p>¡Hola Mundo!</p>\n<p>Ciao Mondo!</p>\n<p>Olá Mundo!</p>\n<p>こんにちは世界!</p>\n<p>你好世界!</p>\n<p>안녕 세상!</p>\n<p>Привет мир!</p>\n<p dir=\"rtl\">שלום עולם!</p>\n<p dir=\"rtl\">مرحبا بالعالم!</p>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "Hello World",
|
|
||||||
"message": "Click Next to continue"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "playground",
|
|
||||||
"title": "Playground",
|
|
||||||
"mode": "playground",
|
|
||||||
"description": "",
|
|
||||||
"task": "",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<style>\n body {\n font-family: system-ui, sans-serif;\n padding: 20px;\n }\n</style>\n\n<h1>Hello World</h1>\n<p>Start coding!</p>",
|
|
||||||
"solution": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,18 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "box-model",
|
"id": "box-model",
|
||||||
"title": "CSS Box Model",
|
"title": "CSS Box Model",
|
||||||
"description": "Master the fundamental principles of space management in web design through the CSS box model. This module explores how content, padding, borders, and margins combine to create layout structures that are both visually appealing and structurally sound.",
|
"description": "Opanuj podstawowe zasady zarządzania przestrzenią w projektowaniu stron poprzez model pudełkowy CSS. Ten moduł bada, jak treść, padding, ramki i marginesy łączą się, tworząc struktury układu.",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "box-model-1",
|
"id": "box-model-1",
|
||||||
"title": "Box Model Components",
|
"title": "Padding",
|
||||||
"description": "The CSS box model consists of four concentric layers: content area (innermost), padding, border, and margin (outermost). Understanding how these components interact is essential for precise layout control.",
|
"description": "Każdy element w CSS to pudełko z czterema warstwami: treść, padding, ramka i margines. <strong>Padding</strong> tworzy przestrzeń między treścią a krawędzią pudełka.<br><br>Bez paddingu tekst przylega niezręcznie do ramek. Padding sprawia, że treść jest czytelna i wizualnie zbalansowana.<br><br><pre>.card {\n padding: 1rem;\n}</pre>",
|
||||||
"task": "Set <kbd>padding</kbd> to <kbd>1rem</kbd> to create space between the content and border.",
|
"task": "Ta karta profilu wygląda na ciasną. Dodaj <kbd>padding: 1rem</kbd>, aby tekst miał miejsce do oddychania.",
|
||||||
"previewHTML": "<div class=\"box\">Box Model Components</div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: lavender; border: 2px dashed slategray; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".box {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "padding: 1rem;",
|
"solution": "padding: 1rem;",
|
||||||
@@ -22,62 +22,62 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "1rem" },
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"message": "Set <kbd>padding: 1rem</kbd>"
|
"message": "Ustaw <kbd>padding: 1rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-2",
|
"id": "box-model-2",
|
||||||
"title": "Adding Borders",
|
"title": "Borders",
|
||||||
"description": "Borders outline an element, creating visual separation from surrounding content. The border shorthand accepts three values: width, style, and color.",
|
"description": "Ramki tworzą wizualne granice wokół elementów. Skrót <kbd>border</kbd> przyjmuje trzy wartości: szerokość, styl i kolor.<br><br>Popularne style: <kbd>solid</kbd>, <kbd>dashed</kbd>, <kbd>dotted</kbd>, <kbd>none</kbd>",
|
||||||
"task": "Set <kbd>border</kbd> to <kbd>2px solid darkslategray</kbd>.",
|
"task": "Dodaj subtelny lewy akcent do karty za pomocą <kbd>border-left: 4px solid steelblue</kbd>.",
|
||||||
"previewHTML": "<div class=\"box\">This box needs a border</div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: mintcream; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".box {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "border: 2px solid darkslategray;",
|
"solution": "border-left: 4px solid steelblue;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border:\\s*2px\\s+solid\\s+darkslategray",
|
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
|
||||||
"message": "Set <kbd>border: 2px solid darkslategray</kbd>",
|
"message": "Ustaw <kbd>border-left: 4px solid steelblue</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-3",
|
"id": "box-model-3",
|
||||||
"title": "Adding Margins",
|
"title": "Margins",
|
||||||
"description": "Margins create space between elements, controlling how they relate to one another within a layout. Unlike padding (which affects internal spacing), margins exist outside the element's border.",
|
"description": "Marginesy tworzą przestrzeń <em>na zewnątrz</em> elementu, oddzielając go od sąsiadów. Podczas gdy padding przesuwa treść do wewnątrz, marginesy odpychają inne elementy.",
|
||||||
"task": "Set <kbd>margin</kbd> to <kbd>1rem</kbd> to create space between this element and its neighbors.",
|
"task": "Dodaj przestrzeń między tymi dwiema kartami profilu za pomocą <kbd>margin-bottom: 1rem</kbd> na <kbd>.card</kbd>.",
|
||||||
"previewHTML": "<div class=\"container\"><div class=\"outer\">This box needs margins</div><div class=\"neighbor\">Adjacent element</div></div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article><article class=\"card\"><h3>Alex Rivera</h3><p>UX Designer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .outer { background-color: plum; padding: 1rem; border: 2px solid orchid; } .neighbor { background-color: lightblue; padding: 1rem; border: 2px solid steelblue; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; border-left: 4px solid steelblue; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".outer {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "margin: 1rem;",
|
"solution": "margin-bottom: 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "margin", "expected": "1rem" },
|
"value": { "property": "margin-bottom", "expected": "1rem" },
|
||||||
"message": "Set <kbd>margin: 1rem</kbd>"
|
"message": "Ustaw <kbd>margin-bottom: 1rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-4",
|
"id": "box-model-4",
|
||||||
"title": "Box Sizing: Border-Box",
|
"title": "Box Sizing",
|
||||||
"description": "The <kbd>box-sizing</kbd> property determines how element dimensions are calculated. The default <kbd>content-box</kbd> excludes padding and border from width/height, while <kbd>border-box</kbd> includes them, making layout calculations more intuitive.",
|
"description": "Domyślnie <kbd>width</kbd> ustawia tylko szerokość treści. Padding i ramki są dodawane. To powoduje problemy z układem.<br><br><kbd>box-sizing: border-box</kbd> włącza padding i ramkę do szerokości, czyniąc rozmiary przewidywalnymi. Większość programistów stosuje to do wszystkich elementów.",
|
||||||
"task": "Set <kbd>box-sizing</kbd> to <kbd>border-box</kbd> so padding and border are included in the width.",
|
"task": "Obie karty mają <kbd>width: 200px</kbd>. Lewa używa domyślnego rozmiaru (content-box), stając się szersza niż oczekiwano. Napraw prawą kartę za pomocą <kbd>box-sizing: border-box</kbd>.",
|
||||||
"previewHTML": "<div class=\"sizing-demo\"><div class=\"box default\">Content-box (default)</div><div class=\"box sized\">Border-box</div></div>",
|
"previewHTML": "<div class=\"demo\"><article class=\"card\">Content-box</article><article class=\"card fix\">Border-box</article></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .sizing-demo { display: flex; gap: 1rem; } .box { width: 200px; padding: 1rem; border: 4px solid teal; background: lightcyan; } .default { box-sizing: content-box; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .demo { display: flex; gap: 1rem; } .card { width: 200px; padding: 1rem; border: 4px solid steelblue; background: white; border-radius: 8px; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".sized {\n ",
|
"codePrefix": ".fix {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "box-sizing: border-box;",
|
"solution": "box-sizing: border-box;",
|
||||||
@@ -86,93 +86,104 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "box-sizing", "expected": "border-box" },
|
"value": { "property": "box-sizing", "expected": "border-box" },
|
||||||
"message": "Set <kbd>box-sizing: border-box</kbd>"
|
"message": "Ustaw <kbd>box-sizing: border-box</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-5",
|
"id": "box-model-5",
|
||||||
"title": "Margin Collapse",
|
"title": "Padding Shorthand",
|
||||||
"description": "When two vertical margins meet, they collapse to the larger value instead of adding up. Understanding this behavior is crucial for consistent vertical spacing.",
|
"description": "Padding przyjmuje 1-4 wartości:<br>• 1 wartość: wszystkie strony<br>• 2 wartości: pionowo | poziomo<br>• 4 wartości: góra | prawo | dół | lewo",
|
||||||
"task": "Set <kbd>margin-bottom</kbd> to <kbd>2rem</kbd>. Notice the space between paragraphs equals 2rem (not 3rem) due to margin collapse.",
|
"task": "Ten przycisk potrzebuje więcej miejsca poziomego niż pionowego. Ustaw <kbd>padding: 8px 1rem</kbd> (8px góra/dół, 1rem lewo/prawo).",
|
||||||
"previewHTML": "<div class=\"collapse-demo\"><p class=\"first\">This paragraph has a bottom margin.</p><p class=\"second\">This paragraph has a top margin of 1rem.</p></div>",
|
"previewHTML": "<button class=\"btn\">Follow</button>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .collapse-demo { border: 1px solid silver; padding: 8px; background: ghostwhite; } .second { margin-top: 1rem; background: linen; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .btn { background: steelblue; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".first {\n ",
|
"codePrefix": ".btn {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "margin-bottom: 2rem;",
|
"solution": "padding: 8px 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "regex",
|
||||||
"value": { "property": "margin-bottom", "expected": "2rem" },
|
"value": "padding:\\s*8px\\s+1rem",
|
||||||
"message": "Set <kbd>margin-bottom: 2rem</kbd>"
|
"message": "Ustaw <kbd>padding: 8px 1rem</kbd>",
|
||||||
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-6",
|
"id": "box-model-6",
|
||||||
"title": "Margin Shorthand Notation",
|
"title": "Margin Shorthand",
|
||||||
"description": "The margin shorthand can set all four sides at once. Two values set vertical (top/bottom) and horizontal (left/right) margins respectively.",
|
"description": "Margines używa tego samego wzorca skrótu co padding. Typowym wzorcem jest poziome centrowanie elementów blokowych za pomocą <kbd>margin: 0 auto</kbd>.",
|
||||||
"task": "Set <kbd>margin</kbd> to <kbd>1rem 2rem</kbd> for 1rem top/bottom and 2rem left/right.",
|
"task": "Wycentruj tę kartę poziomo. Ustaw <kbd>margin: 0 auto</kbd>, aby automatycznie obliczyć równe marginesy lewo/prawo.",
|
||||||
"previewHTML": "<div class=\"container\"><div class=\"spaced\">This box needs margins: 1rem top/bottom, 2rem left/right</div></div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .spaced { background-color: honeydew; border: 2px solid mediumseagreen; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { width: 250px; background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; border-left: 4px solid steelblue; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".spaced {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "margin: 1rem 2rem;",
|
"solution": "margin: 0 auto;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "margin:\\s*1rem\\s+2rem",
|
"value": "margin:\\s*0\\s+auto",
|
||||||
"message": "Set <kbd>margin: 1rem 2rem</kbd>",
|
"message": "Ustaw <kbd>margin: 0 auto</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-7",
|
"id": "box-model-7",
|
||||||
"title": "Padding Shorthand Notation",
|
"title": "Border Radius",
|
||||||
"description": "Like margin, padding shorthand allows setting all sides at once. A single value applies to all four sides equally.",
|
"description": "Chociaż nie jest częścią klasycznego modelu pudełkowego, <kbd>border-radius</kbd> zaokrągla rogi ramki elementu. Użyj <kbd>50%</kbd> na kwadratowym elemencie, aby utworzyć koło.",
|
||||||
"task": "Set <kbd>padding</kbd> to <kbd>2rem</kbd> to add equal padding on all sides.",
|
"task": "Zrób okrągły obrazek awatara za pomocą <kbd>border-radius: 50%</kbd>.",
|
||||||
"previewHTML": "<div class=\"padded\">This box needs equal padding on all sides</div>",
|
"previewHTML": "<article class=\"card\"><img class=\"avatar\" src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='35' r='25' fill='%23666'/%3E%3Ccircle cx='50' cy='90' r='40' fill='%23666'/%3E%3C/svg%3E\" alt=\"Avatar\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .padded { background-color: papayawhip; border: 2px solid orange; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; text-align: center; } .avatar { width: 80px; height: 80px; background: #ddd; margin-bottom: 8px; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".padded {\n ",
|
"codePrefix": ".avatar {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "padding: 2rem;",
|
"solution": "border-radius: 50%;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "2rem" },
|
"value": { "property": "border-radius", "expected": "50%" },
|
||||||
"message": "Set <kbd>padding: 2rem</kbd>"
|
"message": "Ustaw <kbd>border-radius: 50%</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-8",
|
"id": "box-model-8",
|
||||||
"title": "Border on Specific Sides",
|
"title": "Complete Card",
|
||||||
"description": "For granular control, you can target specific sides with <kbd>border-top</kbd>, <kbd>border-right</kbd>, <kbd>border-bottom</kbd>, or <kbd>border-left</kbd>.",
|
"description": "Połączmy wszystko razem. Ta karta powiadomienia potrzebuje stylowania, żeby wyglądać profesjonalnie.",
|
||||||
"task": "Set <kbd>border-bottom</kbd> to <kbd>4px solid dodgerblue</kbd>.",
|
"task": "Ostyluj powiadomienie: dodaj <kbd>padding: 1rem</kbd>, <kbd>border-left: 4px solid coral</kbd> i <kbd>border-radius: 4px</kbd>.",
|
||||||
"previewHTML": "<div class=\"line\">This element needs only a bottom border</div>",
|
"previewHTML": "<div class=\"alert\"><strong>New message</strong><p>You have 3 unread notifications</p></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .line { padding: 1rem; background-color: aliceblue; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .alert { background: seashell; } .alert strong { color: coral; } .alert p { margin: 4px 0 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".line {\n ",
|
"codePrefix": ".alert {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "border-bottom: 4px solid dodgerblue;",
|
"solution": "padding: 1rem;\n border-left: 4px solid coral;\n border-radius: 4px;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
|
"message": "Ustaw <kbd>padding: 1rem</kbd>"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border-bottom:\\s*4px\\s+solid\\s+dodgerblue",
|
"value": "border-left:\\s*4px\\s+solid\\s+coral",
|
||||||
"message": "Set <kbd>border-bottom: 4px solid dodgerblue</kbd>",
|
"message": "Ustaw <kbd>border-left: 4px solid coral</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "border-radius", "expected": "4px" },
|
||||||
|
"message": "Ustaw <kbd>border-radius: 4px</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,115 +1,100 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "units-variables",
|
"id": "units-variables",
|
||||||
"title": "CSS Units & Variables",
|
"title": "Jednostki CSS i zmienne",
|
||||||
"description": "Understand the variety of CSS measurement units and how to define and use custom properties for maintainable styles.",
|
"description": "Poznaj różnorodność jednostek miar CSS oraz jak definiować i używać właściwości niestandardowych dla łatwych w utrzymaniu stylów.",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "units-1",
|
"id": "units-1",
|
||||||
"title": "Absolute vs. Relative Units",
|
"title": "Relative Units",
|
||||||
"description": "Learn the difference between px, rem, em, %, and vw/vh for flexible, responsive layouts.",
|
"description": "CSS oferuje dwa typy jednostek: <em>absolutne</em> (jak <kbd>px</kbd>) i <em>względne</em> (jak <kbd>%</kbd> i <kbd>rem</kbd>). Jednostki względne dostosowują się do kontekstu, czyniąc layouty elastycznymi i dostępnymi.<br><br><strong>Popularne jednostki względne:</strong><br>• <kbd>%</kbd> – Względem elementu nadrzędnego<br>• <kbd>rem</kbd> – Względem rozmiaru czcionki root (zwykle 16px)<br>• <kbd>em</kbd> – Względem rozmiaru czcionki elementu<br><br>Popularny wzorzec dla czytelnej treści: ustaw <kbd>width: 100%</kbd>, aby wypełnić dostępną przestrzeń, potem <kbd>max-width: 40rem</kbd> aby ograniczyć długość linii dla czytelności.",
|
||||||
"task": "Set the <kbd>width</kbd> of <kbd>.box</kbd> to <kbd>80%</kbd> and <kbd>max-width</kbd> to <kbd>37.5rem</kbd>.",
|
"task": "Ten tekst artykułu jest zbyt szeroki na dużych ekranach. Dodaj <kbd>max-width: 40rem</kbd> dla optymalnej szerokości czytania.",
|
||||||
"previewHTML": "<div class=\"box\">Resize me!</div>",
|
"previewHTML": "<article class=\"article\"><h2>The Art of Typography</h2><p>Good typography is invisible. When text is set well, readers absorb information without noticing the design decisions that make it comfortable to read. Line length is crucial—too wide and eyes get lost, too narrow and reading becomes choppy.</p><p>The ideal line length is 45-75 characters per line. At typical font sizes, this works out to roughly 40rem maximum width.</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { background: #f5f5f5; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: Georgia, serif; padding: 1rem; background: #f9f9f9; } .article { background: white; padding: 2rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .article h2 { margin: 0 0 1rem; color: #333; } .article p { margin: 0 0 1rem; line-height: 1.6; color: #444; } .article p:last-child { margin-bottom: 0; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Set flexible sizing */\n.box {",
|
"codePrefix": ".article {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " width: 80%;\n max-width: 37.5rem;",
|
"solution": "max-width: 40rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"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",
|
"type": "property_value",
|
||||||
"value": { "property": "max-width", "expected": "37.5rem" },
|
"value": { "property": "max-width", "expected": "40rem" },
|
||||||
"message": "Set max-width to <kbd>37.5rem</kbd>"
|
"message": "Ustaw <kbd>max-width: 40rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "units-2",
|
"id": "units-2",
|
||||||
"title": "CSS Custom Properties",
|
"title": "CSS Variables",
|
||||||
"description": "Define and reuse variables (--custom properties) to centralize your theme values.",
|
"description": "Właściwości niestandardowe CSS (zmienne) pozwalają definiować wartości wielokrotnego użytku. Definiuj je za pomocą <kbd>--nazwa</kbd> i używaj z <kbd>var(--nazwa)</kbd>. Zmienne zdefiniowane na <kbd>:root</kbd> są dostępne wszędzie.",
|
||||||
"task": "Create a <kbd>--main-color</kbd> variable in <kbd>:root</kbd> with <kbd>#6200ee</kbd> and apply it as the border color on <kbd>.themed</kbd>.",
|
"task": "Zdefiniuj <kbd>--brand: steelblue</kbd> w <kbd>:root</kbd>, następnie użyj jako koloru <kbd>background</kbd> dla <kbd>.btn</kbd>.",
|
||||||
"previewHTML": "<div class=\"themed\">Variable Box</div>",
|
"previewHTML": "<div class=\"actions\"><button class=\"btn\">Subscribe</button><button class=\"btn\">Learn More</button></div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .themed { padding: 1rem; border: 0.125rem solid #ddd; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .actions { display: flex; gap: 1rem; } .btn { color: white; border: none; padding: 12px 24px; border-radius: 6px; font-size: 1rem; cursor: pointer; background: #ccc; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Define and use a CSS variable */\n:root {",
|
"codePrefix": ":root {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}\n.themed { }",
|
"codeSuffix": "\n}\n\n.btn {\n background: var(--brand);\n}",
|
||||||
"solution": " --main-color: #6200ee;\n}\n.themed {\n border-color: var(--main-color);",
|
"solution": "--brand: steelblue;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "--main-color",
|
"value": "--brand",
|
||||||
"message": "Define <kbd>--main-color</kbd> in :root",
|
"message": "Zdefiniuj zmienną <kbd>--brand</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "var(--main-color)",
|
"value": "steelblue",
|
||||||
"message": "Use <kbd>var(--main-color)</kbd>",
|
"message": "Ustaw wartość na <kbd>steelblue</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"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",
|
"id": "units-3",
|
||||||
"title": "Unit Calculations (calc)",
|
"title": "calc() Function",
|
||||||
"description": "Use the <kbd>calc()</kbd> function to combine different units in one expression.",
|
"description": "Funkcja <kbd>calc()</kbd> pozwala mieszać różne jednostki w obliczeniach. Jest niezbędna dla layoutów łączących stałe i elastyczne rozmiary, jak układ z sidebar.",
|
||||||
"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>.",
|
"task": "Główna treść powinna wypełnić pozostałe miejsce po sidebarze 200px. Ustaw <kbd>width: calc(100% - 200px)</kbd> na <kbd>.main</kbd>.",
|
||||||
"previewHTML": "<div class=\"sized\">Calc Demo</div>",
|
"previewHTML": "<div class=\"layout\"><aside class=\"sidebar\">Sidebar<br>Navigation</aside><main class=\"main\"><h2>Main Content</h2><p>This area should fill the remaining width after accounting for the fixed-width sidebar.</p></main></div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sized { background: #e8f5e9; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .layout { display: flex; gap: 1rem; } .sidebar { width: 200px; background: #1a1a2e; color: white; padding: 1rem; border-radius: 8px; flex-shrink: 0; } .main { background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .main h2 { margin: 0 0 8px; } .main p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Use calc for dynamic sizing */\n.sized {",
|
"codePrefix": ".main {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " width: calc(100% - 2rem);\n min-height: calc(10vh + 1rem);",
|
"solution": "width: calc(100% - 200px);",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{ "type": "contains", "value": "calc", "message": "Use <kbd>calc()</kbd> function", "options": { "caseSensitive": false } },
|
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "width:\\s*calc\\(100% - 2rem\\)",
|
"value": "width:\\s*calc\\(\\s*100%\\s*-\\s*200px\\s*\\)",
|
||||||
"message": "Width should be <kbd>calc(100% - 2rem)</kbd>",
|
"message": "Ustaw <kbd>width: calc(100% - 200px)</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 }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "units-4",
|
"id": "units-4",
|
||||||
"title": "Viewport & Responsive Units",
|
"title": "Viewport Units",
|
||||||
"description": "Control layouts relative to viewport size with vw, vh, and vmin/vmax units.",
|
"description": "Jednostki viewport wymiarują elementy względem okna przeglądarki:<br>• <kbd>vw</kbd> – 1% szerokości viewport<br>• <kbd>vh</kbd> – 1% wysokości viewport<br><br>Są idealne dla sekcji pełnoekranowych jak banery hero.",
|
||||||
"task": "Give <kbd>.view</kbd> a <kbd>width</kbd> of <kbd>50vw</kbd> and <kbd>height</kbd> of <kbd>20vh</kbd>.",
|
"task": "Spraw, aby ta sekcja hero wypełniła wysokość viewport ustawiając <kbd>min-height: 100vh</kbd>.",
|
||||||
"previewHTML": "<div class=\"view\">Viewport Box</div>",
|
"previewHTML": "<section class=\"hero\"><h1>Welcome</h1><p>Scroll down to explore</p></section>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .view { background: #ffe0b2; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .hero { background: linear-gradient(135deg, #1a1a2e 0%, steelblue 100%); color: white; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 2rem; } .hero h1 { margin: 0 0 1rem; font-size: 2.5rem; } .hero p { margin: 0; opacity: 0.8; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Use viewport units */\n.view {",
|
"codePrefix": ".hero {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " width: 50vw;\n height: 20vh;",
|
"solution": "min-height: 100vh;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"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",
|
||||||
{ "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Set width to <kbd>50vw</kbd>" },
|
"value": { "property": "min-height", "expected": "100vh" },
|
||||||
{ "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Set height to <kbd>20vh</kbd>" }
|
"message": "Ustaw <kbd>min-height: 100vh</kbd>"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "transitions-animations",
|
"id": "transitions-animations",
|
||||||
"title": "CSS Animations",
|
"title": "Animacje CSS",
|
||||||
"description": "Bring interactivity to your UI by smoothly transitioning properties and creating keyframe-driven animations.",
|
"description": "Dodaj interaktywność do interfejsu poprzez płynne przejścia właściwości i animacje oparte na keyframes.",
|
||||||
"difficulty": "intermediate",
|
"difficulty": "intermediate",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "transitions-1",
|
"id": "transitions-1",
|
||||||
"title": "Transitions",
|
"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>",
|
"description": "Naucz się jak stosować <kbd>transition</kbd> do właściwości dla płynnych zmian przy zmianie stanu.<br><br><pre>transition: property duration;\n/* np. 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.",
|
"task": "Dodaj <kbd>transition: background-color 0.3s</kbd>, aby kolor płynnie zmieniał się przy hover.",
|
||||||
"previewHTML": "<button class=\"btn\">Hover Me</button>",
|
"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; }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
@@ -22,13 +22,13 @@
|
|||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "transition",
|
"value": "transition",
|
||||||
"message": "Use the <kbd>transition</kbd> property",
|
"message": "Użyj właściwości <kbd>transition</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "transition:\\s*background-color\\s*0\\.3s",
|
"value": "transition:\\s*background-color\\s*0\\.3s",
|
||||||
"message": "Set <kbd>transition: background-color 0.3s</kbd>",
|
"message": "Ustaw <kbd>transition: background-color 0.3s</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -36,8 +36,8 @@
|
|||||||
{
|
{
|
||||||
"id": "transitions-2",
|
"id": "transitions-2",
|
||||||
"title": "Timing Funcs",
|
"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.",
|
"description": "Poznaj funkcje easing jak <kbd>ease</kbd>, <kbd>linear</kbd>, <kbd>ease-in</kbd>, <kbd>ease-out</kbd> do kontrolowania tempa animacji.",
|
||||||
"task": "Set <kbd>transition-timing-function</kbd> to <kbd>ease-in-out</kbd> on <kbd>.btn</kbd>.",
|
"task": "Ustaw <kbd>transition-timing-function</kbd> na <kbd>ease-in-out</kbd>.",
|
||||||
"previewHTML": "<button class=\"btn\">Timing</button>",
|
"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); }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
@@ -50,21 +50,21 @@
|
|||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "transition-timing-function",
|
"value": "transition-timing-function",
|
||||||
"message": "Use <kbd>transition-timing-function</kbd>",
|
"message": "Użyj <kbd>transition-timing-function</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "transition-timing-function", "expected": "ease-in-out" },
|
"value": { "property": "transition-timing-function", "expected": "ease-in-out" },
|
||||||
"message": "Set timing to <kbd>ease-in-out</kbd>"
|
"message": "Ustaw timing na <kbd>ease-in-out</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "transitions-3",
|
"id": "transitions-3",
|
||||||
"title": "Keyframes",
|
"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>",
|
"description": "Twórz nazwane animacje używając <kbd>@keyframes</kbd> i stosuj je przez skrót <kbd>animation</kbd>.<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>.",
|
"task": "Zdefiniuj keyframe przy <kbd>50%</kbd> z <kbd>transform: translateY(-20px)</kbd> i zastosuj <kbd>animation: bounce 1s infinite</kbd> na <kbd>.ball</kbd>.",
|
||||||
"previewHTML": "<div class=\"ball\"></div>",
|
"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; }",
|
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: crimson; border-radius: 50%; margin: 2rem auto; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -77,25 +77,25 @@
|
|||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "@keyframes bounce",
|
"value": "@keyframes bounce",
|
||||||
"message": "Define <kbd>@keyframes bounce</kbd>",
|
"message": "Zdefiniuj <kbd>@keyframes bounce</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "50%.*transform: translateY\\(-20px\\)",
|
"value": "50%.*transform: translateY\\(-20px\\)",
|
||||||
"message": "At <kbd>50%</kbd>, use <kbd>transform: translateY(-20px)</kbd>",
|
"message": "Przy <kbd>50%</kbd>, użyj <kbd>transform: translateY(-20px)</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "animation",
|
"value": "animation",
|
||||||
"message": "Use <kbd>animation</kbd> property on <kbd>.ball</kbd>",
|
"message": "Użyj właściwości <kbd>animation</kbd> na <kbd>.ball</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "animation:.*bounce.*1s.*infinite",
|
"value": "animation:.*bounce.*1s.*infinite",
|
||||||
"message": "Apply <kbd>animation: bounce 1s infinite</kbd>",
|
"message": "Zastosuj <kbd>animation: bounce 1s infinite</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -103,8 +103,8 @@
|
|||||||
{
|
{
|
||||||
"id": "transitions-4",
|
"id": "transitions-4",
|
||||||
"title": "Animation Properties",
|
"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>.",
|
"description": "Dostosuj animacje za pomocą <kbd>animation-delay</kbd>, <kbd>animation-iteration-count</kbd>, <kbd>animation-direction</kbd> i <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>.",
|
"task": "Zastosuj animację <kbd>pulse</kbd> na <kbd>.box</kbd> z <kbd>animation-name: pulse</kbd>, <kbd>animation-duration: 2s</kbd>, <kbd>animation-delay: 1s</kbd>, <kbd>animation-iteration-count: 2</kbd> i <kbd>animation-fill-mode: forwards</kbd>.",
|
||||||
"previewHTML": "<div class=\"box\">Pulse</div>",
|
"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); } }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
@@ -117,27 +117,27 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "animation-name", "expected": "pulse" },
|
"value": { "property": "animation-name", "expected": "pulse" },
|
||||||
"message": "Set <kbd>animation-name: pulse</kbd>"
|
"message": "Ustaw <kbd>animation-name: pulse</kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "animation-duration", "expected": "2s" },
|
"value": { "property": "animation-duration", "expected": "2s" },
|
||||||
"message": "Set <kbd>animation-duration: 2s</kbd>"
|
"message": "Ustaw <kbd>animation-duration: 2s</kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "animation-delay", "expected": "1s" },
|
"value": { "property": "animation-delay", "expected": "1s" },
|
||||||
"message": "Set <kbd>animation-delay: 1s</kbd>"
|
"message": "Ustaw <kbd>animation-delay: 1s</kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "animation-iteration-count", "expected": "2" },
|
"value": { "property": "animation-iteration-count", "expected": "2" },
|
||||||
"message": "Set <kbd>animation-iteration-count: 2</kbd>"
|
"message": "Ustaw <kbd>animation-iteration-count: 2</kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "animation-fill-mode", "expected": "forwards" },
|
"value": { "property": "animation-fill-mode", "expected": "forwards" },
|
||||||
"message": "Set <kbd>animation-fill-mode: forwards</kbd>"
|
"message": "Ustaw <kbd>animation-fill-mode: forwards</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,14 +2,14 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "responsive-design",
|
"id": "responsive-design",
|
||||||
"title": "CSS Responsive Design",
|
"title": "CSS Responsive Design",
|
||||||
"description": "Make your layouts adapt to different screen sizes using media queries and fluid design techniques.",
|
"description": "Dostosuj swoje layouty do różnych rozmiarów ekranów używając media queries i technik płynnego designu.",
|
||||||
"difficulty": "intermediate",
|
"difficulty": "intermediate",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "responsive-1",
|
"id": "responsive-1",
|
||||||
"title": "Media Queries",
|
"title": "Media Queries",
|
||||||
"description": "Understand the syntax and use cases for CSS media queries to apply styles conditionally based on viewport characteristics.",
|
"description": "Poznaj składnię i przypadki użycia CSS media queries do warunkowego stosowania stylów na podstawie cech viewport.<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>.",
|
"task": "Napisz media query z <kbd>@media (max-width: 600px)</kbd>, która zmienia tło <kbd>.panel</kbd> na <kbd>lightcoral</kbd>.",
|
||||||
"previewHTML": "<div class=\"panel\">Resize the window</div>",
|
"previewHTML": "<div class=\"panel\">Resize the window</div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .panel { padding: 1rem; background: lightblue; }",
|
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .panel { padding: 1rem; background: lightblue; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -22,19 +22,19 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "@media\\s*\\(max-width:\\s*600px\\)",
|
"value": "@media\\s*\\(max-width:\\s*600px\\)",
|
||||||
"message": "Use <kbd>@media (max-width: 600px)</kbd>",
|
"message": "Użyj <kbd>@media (max-width: 600px)</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": ".panel",
|
"value": ".panel",
|
||||||
"message": "Target <kbd>.panel</kbd> inside the media query",
|
"message": "Zaadresuj <kbd>.panel</kbd> wewnątrz media query",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "background", "expected": "lightcoral" },
|
"value": { "property": "background", "expected": "lightcoral" },
|
||||||
"message": "Set <kbd>background: lightcoral</kbd>",
|
"message": "Ustaw <kbd>background: lightcoral</kbd>",
|
||||||
"options": { "exact": false }
|
"options": { "exact": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -42,8 +42,8 @@
|
|||||||
{
|
{
|
||||||
"id": "responsive-2",
|
"id": "responsive-2",
|
||||||
"title": "Fluid Type",
|
"title": "Fluid Type",
|
||||||
"description": "Use relative units like <kbd>vw</kbd> to make font sizes scale with the viewport width.",
|
"description": "Używaj jednostek względnych jak <kbd>vw</kbd>, aby rozmiary czcionek skalowały się z szerokością viewport.",
|
||||||
"task": "Set <kbd>font-size: 5vw</kbd> on <kbd>.text</kbd> so it scales as the viewport changes.",
|
"task": "Ustaw <kbd>font-size: 5vw</kbd>, aby skalowała się ze zmianą viewport.",
|
||||||
"previewHTML": "<p class=\"text\">Fluid Typography</p>",
|
"previewHTML": "<p class=\"text\">Fluid Typography</p>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -53,46 +53,46 @@
|
|||||||
"solution": " font-size: 5vw;",
|
"solution": " font-size: 5vw;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{ "type": "property_value", "value": { "property": "font-size", "expected": "5vw" }, "message": "Set <kbd>font-size: 5vw</kbd>" }
|
{ "type": "property_value", "value": { "property": "font-size", "expected": "5vw" }, "message": "Ustaw <kbd>font-size: 5vw</kbd>" }
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "responsive-3",
|
"id": "responsive-3",
|
||||||
"title": "Flex Grids",
|
"title": "Responsive Grid",
|
||||||
"description": "Combine CSS Grid with <kbd>auto-fit</kbd> or <kbd>auto-fill</kbd> for responsive column layouts.",
|
"description": "Połącz CSS Grid z <kbd>auto-fit</kbd> lub <kbd>auto-fill</kbd> dla responsywnych układów kolumnowych, które automatycznie dostosowują liczbę kolumn na podstawie dostępnej przestrzeni.",
|
||||||
"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>.",
|
"task": "Dodaj <kbd>display: grid</kbd>, <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd> i <kbd>gap: 1rem</kbd>.",
|
||||||
"previewHTML": "<div class=\"cards\"><div>1</div><div>2</div><div>3</div><div>4</div></div>",
|
"previewHTML": "<section class=\"features\"><article class=\"feature\"><h3>Fast</h3><p>Lightning quick load times</p></article><article class=\"feature\"><h3>Secure</h3><p>Enterprise-grade security</p></article><article class=\"feature\"><h3>Reliable</h3><p>99.9% uptime guaranteed</p></article><article class=\"feature\"><h3>Support</h3><p>24/7 customer service</p></article></section>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .cards > div { background: #d1c4e9; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .feature { background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .feature h3 { margin: 0 0 8px; color: steelblue; } .feature p { margin: 0; color: #666; font-size: 0.9rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Create a responsive grid */\n.cards {",
|
"codePrefix": ".features {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
|
"solution": "display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "display", "expected": "grid" },
|
"value": { "property": "display", "expected": "grid" },
|
||||||
"message": "Set <kbd>display: grid</kbd>"
|
"message": "Ustaw <kbd>display: grid</kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "repeat\\(auto-fit,\\s*minmax\\(200px,\\s*1fr\\)\\)",
|
"value": "repeat\\(auto-fit,\\s*minmax\\(200px,\\s*1fr\\)\\)",
|
||||||
"message": "Use <kbd>repeat(auto-fit, minmax(200px, 1fr))</kbd>",
|
"message": "Użyj <kbd>repeat(auto-fit, minmax(200px, 1fr))</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "gap", "expected": "1rem" },
|
"value": { "property": "gap", "expected": "1rem" },
|
||||||
"message": "Set <kbd>gap: 1rem</kbd>"
|
"message": "Ustaw <kbd>gap: 1rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "responsive-4",
|
"id": "responsive-4",
|
||||||
"title": "Mobile-First",
|
"title": "Mobile-First",
|
||||||
"description": "Adopt a mobile-first approach by writing base styles for small screens and enhancing for larger viewports.",
|
"description": "Zastosuj podejście mobile-first: pisz bazowe style dla małych ekranów i rozszerzaj dla większych viewport.",
|
||||||
"task": "Write a media query with <kbd>@media (min-width: 768px)</kbd> that sets <kbd>.sidebar</kbd> width to <kbd>250px</kbd>.",
|
"task": "Napisz media query z <kbd>@media (min-width: 768px)</kbd>, która ustawia szerokość <kbd>.sidebar</kbd> na <kbd>250px</kbd>.",
|
||||||
"previewHTML": "<aside class=\"sidebar\">Sidebar</aside>",
|
"previewHTML": "<aside class=\"sidebar\">Sidebar</aside>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sidebar { background: #c8e6c9; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sidebar { background: #c8e6c9; padding: 1rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -105,19 +105,19 @@
|
|||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "@media\\s*\\(min-width:\\s*768px\\)",
|
"value": "@media\\s*\\(min-width:\\s*768px\\)",
|
||||||
"message": "Use <kbd>@media (min-width: 768px)</kbd>",
|
"message": "Użyj <kbd>@media (min-width: 768px)</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": ".sidebar",
|
"value": ".sidebar",
|
||||||
"message": "Target <kbd>.sidebar</kbd> inside media query",
|
"message": "Zaadresuj <kbd>.sidebar</kbd> w media query",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "width", "expected": "250px" },
|
"value": { "property": "width", "expected": "250px" },
|
||||||
"message": "Set <kbd>width: 250px</kbd>",
|
"message": "Ustaw <kbd>width: 250px</kbd>",
|
||||||
"options": { "exact": false }
|
"options": { "exact": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -2,94 +2,65 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-elements",
|
"id": "html-elements",
|
||||||
"title": "HTML Block & Inline",
|
"title": "HTML Block & Inline",
|
||||||
"description": "Understanding the fundamental difference between container (block) and inline elements",
|
"description": "Zrozum podstawową różnicę między elementami kontenerowymi (blokowymi) a elementami liniowymi",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "block-vs-inline-intro",
|
"id": "block-vs-inline-intro",
|
||||||
"title": "Block vs Inline Elements",
|
"title": "Elementy blokowe vs liniowe",
|
||||||
"description": "HTML elements fall into two main categories:<br><br><strong>Block elements</strong> (containers) start on a new line and take full width. Examples: <kbd><div></kbd>, <kbd><p></kbd>, <kbd><h1></kbd>, <kbd><section></kbd><br><br><strong>Inline elements</strong> flow within text and only take needed width. Examples: <kbd><span></kbd>, <kbd><a></kbd>, <kbd><strong></kbd>, <kbd><em></kbd>",
|
"description": "Elementy HTML dzielą się na dwie główne kategorie:<br><br><strong>Elementy blokowe</strong> (kontenery) zaczynają się w nowej linii i zajmują pełną szerokość. Przykłady: <kbd><div></kbd>, <kbd><p></kbd>, <kbd><h1></kbd>, <kbd><section></kbd><br><br><strong>Elementy liniowe</strong> płyną wewnątrz tekstu i zajmują tylko potrzebną szerokość. Przykłady: <kbd><span></kbd>, <kbd><a></kbd>, <kbd><strong></kbd>, <kbd><em></kbd>",
|
||||||
"task": "Wrap the word <kbd>important</kbd> with <kbd><strong></kbd> tags to make it bold. Notice how the paragraph (block) takes full width while strong (inline) flows with text.",
|
"task": "Otocz słowo <kbd>ważne</kbd> tagami <kbd><strong></kbd>, aby je pogrubić. Zauważ, jak akapit (blok) zajmuje pełną szerokość, podczas gdy strong (liniowy) płynie z tekstem.",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; } p { background: #e3f2fd; padding: 10px; } strong { background: #ffecb3; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; } p { background: #e3f2fd; padding: 10px; } strong { background: #ffecb3; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "<p>This is a paragraph with an important word.</p>",
|
"initialCode": "<p>To jest akapit z ważne słowem.</p>",
|
||||||
"solution": "<p>This is a paragraph with an <strong>important</strong> word.</p>",
|
"solution": "<p>To jest akapit z <strong>ważne</strong> słowem.</p>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "p",
|
"value": "p",
|
||||||
"message": "Add a <kbd><p></kbd> paragraph element"
|
"message": "Dodaj element akapitu <kbd><p></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "parent_child",
|
"type": "parent_child",
|
||||||
"value": { "parent": "p", "child": "strong" },
|
"value": { "parent": "p", "child": "strong" },
|
||||||
"message": "Wrap the word <kbd>important</kbd> with <kbd><strong></kbd> tags"
|
"message": "Otocz słowo <kbd>ważne</kbd> tagami <kbd><strong></kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "semantic-containers",
|
"id": "semantic-containers",
|
||||||
"title": "Semantic Tags",
|
"title": "Tagi semantyczne",
|
||||||
"description": "Modern HTML uses semantic containers that describe their content:<br><br><kbd><header></kbd> - Page or section header<br><kbd><nav></kbd> - Navigation links<br><kbd><main></kbd> - Main content area<br><kbd><section></kbd> - Thematic grouping<br><kbd><article></kbd> - Self-contained content<br><kbd><footer></kbd> - Page or section footer",
|
"description": "Nowoczesny HTML używa semantycznych kontenerów, które opisują swoją zawartość:<br><br><kbd><header></kbd> - Nagłówek strony lub sekcji<br><kbd><nav></kbd> - Linki nawigacyjne<br><kbd><main></kbd> - Główna treść<br><kbd><section></kbd> - Grupa tematyczna<br><kbd><article></kbd> - Samodzielna treść<br><kbd><footer></kbd> - Stopka strony lub sekcji",
|
||||||
"task": "Create a basic page structure:<br>1. Add a <kbd><header></kbd> with an <kbd><h1></kbd> containing the text <code>My Website</code><br>2. Add a <kbd><main></kbd> element with a paragraph saying <code>Welcome to my site!</code><br>3. Add a <kbd><footer></kbd> with a paragraph saying <code>Copyright 2026</code>",
|
"task": "Stwórz podstawową strukturę strony:<br>1. Dodaj <kbd><header></kbd> z <kbd><h1></kbd> zawierającym tekst <code>Moja Strona</code><br>2. Dodaj element <kbd><main></kbd> z akapitem mówiącym <code>Witaj na mojej stronie!</code><br>3. Dodaj <kbd><footer></kbd> z akapitem mówiącym <code>Copyright 2026</code>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; margin: 0; } header { background: #1976d2; color: white; padding: 15px; } main { padding: 20px; min-height: 100px; } footer { background: #424242; color: white; padding: 10px; text-align: center; }",
|
"previewBaseCSS": "body { font-family: system-ui; margin: 0; } header { background: #1976d2; color: white; padding: 15px; } main { padding: 20px; min-height: 100px; } footer { background: #424242; color: white; padding: 10px; text-align: center; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<header>\n <h1>My Website</h1>\n</header>\n<main>\n <p>Welcome to my site!</p>\n</main>\n<footer>\n <p>Copyright 2026</p>\n</footer>",
|
"solution": "<header>\n <h1>Moja Strona</h1>\n</header>\n<main>\n <p>Witaj na mojej stronie!</p>\n</main>\n<footer>\n <p>Copyright 2026</p>\n</footer>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "header",
|
"value": "header",
|
||||||
"message": "Add a <kbd><header></kbd> element"
|
"message": "Dodaj element <kbd><header></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "main",
|
"value": "main",
|
||||||
"message": "Add a <kbd><main></kbd> element"
|
"message": "Dodaj element <kbd><main></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "footer",
|
"value": "footer",
|
||||||
"message": "Add a <kbd><footer></kbd> element"
|
"message": "Dodaj element <kbd><footer></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "parent_child",
|
"type": "parent_child",
|
||||||
"value": { "parent": "header", "child": "h1" },
|
"value": { "parent": "header", "child": "h1" },
|
||||||
"message": "Add an <kbd><h1></kbd> heading inside your header"
|
"message": "Dodaj nagłówek <kbd><h1></kbd> wewnątrz header"
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "div-vs-span",
|
|
||||||
"title": "div & span",
|
|
||||||
"description": "When you need a container without semantic meaning:<br><br><kbd><div></kbd> - Generic block container (for layout/grouping)<br><kbd><span></kbd> - Generic inline container (for styling text portions)<br><br>Use semantic elements when possible, div/span when no semantic element fits.",
|
|
||||||
"task": "Wrap the word 'highlighted' in a <kbd><span></kbd> to style it differently. Wrap the whole quote in a <kbd><div></kbd>.",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: Georgia, serif; padding: 20px; } div { background: #f5f5f5; padding: 15px; border-left: 4px solid #1976d2; } span { background: #fff59d; padding: 2px 4px; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "The most highlighted moment was unforgettable.",
|
|
||||||
"solution": "<div>The most <span>highlighted</span> moment was unforgettable.</div>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "div",
|
|
||||||
"message": "Wrap everything in a <kbd><div></kbd> element"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "span",
|
|
||||||
"message": "Add a <kbd><span></kbd> around the word <kbd>highlighted</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_text",
|
|
||||||
"value": { "selector": "span", "text": "highlighted" },
|
|
||||||
"message": "The <kbd><span></kbd> should contain the word <kbd>highlighted</kbd>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,100 +1,100 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-forms-basic",
|
"id": "html-forms-basic",
|
||||||
"title": "HTML Forms",
|
"title": "Formularze HTML",
|
||||||
"description": "Learn to create forms with various input types",
|
"description": "Naucz się tworzyć formularze z różnymi typami pól",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "form-structure",
|
"id": "form-structure",
|
||||||
"title": "Form Structure",
|
"title": "Struktura formularza",
|
||||||
"description": "Every form needs a <kbd><form></kbd> wrapper. Inside, use <kbd><label></kbd> to describe inputs and <kbd><input></kbd> for user data entry.<br><br>The <kbd>for</kbd> attribute on labels should match the <kbd>id</kbd> on inputs for accessibility.",
|
"description": "Każdy formularz potrzebuje wrappera <kbd><form></kbd>. Wewnątrz używaj <kbd><label></kbd> do opisywania pól i <kbd><input></kbd> do wprowadzania danych.<br><br>Atrybut <kbd>for</kbd> w labelach powinien odpowiadać <kbd>id</kbd> pól dla dostępności.",
|
||||||
"task": "Create a form with:<br>1. A <kbd><label></kbd> with the text <code>Name:</code> and <kbd>for=\"name\"</kbd> attribute<br>2. A text <kbd><input></kbd> with <kbd>id=\"name\"</kbd> and <kbd>name=\"name\"</kbd> attributes",
|
"task": "Stwórz formularz z:<br>1. <kbd><label></kbd> z tekstem <code>Imię:</code> i atrybutem <kbd>for=\"name\"</kbd><br>2. Tekstowym <kbd><input></kbd> z atrybutami <kbd>id=\"name\"</kbd> i <kbd>name=\"name\"</kbd>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-bottom: 5px; font-weight: 500; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-bottom: 5px; font-weight: 500; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<form>\n <label for=\"name\">Name:</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n</form>",
|
"solution": "<form>\n <label for=\"name\">Imię:</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n</form>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "form",
|
"value": "form",
|
||||||
"message": "Wrap everything in a <kbd><form></kbd> element"
|
"message": "Otocz wszystko elementem <kbd><form></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "label",
|
"value": "label",
|
||||||
"message": "Add a <kbd><label></kbd> for your input"
|
"message": "Dodaj <kbd><label></kbd> dla swojego pola"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "input",
|
"value": "input",
|
||||||
"message": "Add an <kbd><input></kbd> element"
|
"message": "Dodaj element <kbd><input></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "label", "attr": "for", "value": null },
|
"value": { "selector": "label", "attr": "for", "value": null },
|
||||||
"message": "Add a <kbd>for</kbd> attribute to your label"
|
"message": "Dodaj atrybut <kbd>for</kbd> do swojego labela"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "input", "attr": "id", "value": null },
|
"value": { "selector": "input", "attr": "id", "value": null },
|
||||||
"message": "Add an <kbd>id</kbd> attribute to your input"
|
"message": "Dodaj atrybut <kbd>id</kbd> do swojego pola"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "input-types",
|
"id": "input-types",
|
||||||
"title": "Input Types",
|
"title": "Typy pól",
|
||||||
"description": "Different input types provide appropriate keyboards and validation:<br><br><kbd>type=\"text\"</kbd> - General text<br><kbd>type=\"email\"</kbd> - Email with @ validation<br><kbd>type=\"password\"</kbd> - Hidden characters<br><kbd>type=\"number\"</kbd> - Numeric keyboard<br><kbd>type=\"tel\"</kbd> - Phone keyboard",
|
"description": "Różne typy pól zapewniają odpowiednie klawiatury i walidację:<br><br><kbd>type=\"text\"</kbd> - Ogólny tekst<br><kbd>type=\"email\"</kbd> - Email z walidacją @<br><kbd>type=\"password\"</kbd> - Ukryte znaki<br><kbd>type=\"number\"</kbd> - Klawiatura numeryczna<br><kbd>type=\"tel\"</kbd> - Klawiatura telefoniczna",
|
||||||
"task": "Create a login form with two fields:<br>1. An email field: <kbd><label for=\"email\">Email:</label></kbd> and <kbd><input type=\"email\" id=\"email\"></kbd><br>2. A password field: <kbd><label for=\"password\">Password:</label></kbd> and <kbd><input type=\"password\" id=\"password\"></kbd>",
|
"task": "Stwórz formularz logowania z dwoma polami:<br>1. Pole email: <kbd><label for=\"email\">Email:</label></kbd> i <kbd><input type=\"email\" id=\"email\"></kbd><br>2. Pole hasła: <kbd><label for=\"password\">Hasło:</label></kbd> i <kbd><input type=\"password\" id=\"password\"></kbd>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "<form>\n \n</form>",
|
"initialCode": "<form>\n \n</form>",
|
||||||
"solution": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\">\n</form>",
|
"solution": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <label for=\"password\">Hasło:</label>\n <input type=\"password\" id=\"password\" name=\"password\">\n</form>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "input[type='email']",
|
"value": "input[type='email']",
|
||||||
"message": "Add an input with type=\"email\""
|
"message": "Dodaj pole z type=\"email\""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "input[type='password']",
|
"value": "input[type='password']",
|
||||||
"message": "Add an input with type=\"password\""
|
"message": "Dodaj pole z type=\"password\""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "label", "min": 2 },
|
"value": { "selector": "label", "min": 2 },
|
||||||
"message": "Add labels for both inputs"
|
"message": "Dodaj labele dla obu pól"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "submit-button",
|
"id": "submit-button",
|
||||||
"title": "Submit Button",
|
"title": "Przycisk wysyłania",
|
||||||
"description": "Forms need a way to submit data. Use:<br><br><kbd><button type=\"submit\"></kbd> - Preferred, flexible content<br><kbd><input type=\"submit\"></kbd> - Simple text-only button<br><br>The button text should be action-oriented (e.g., <code>Sign In</code>, 'Register', 'Send').",
|
"description": "Formularze potrzebują sposobu na wysłanie danych. Użyj:<br><br><kbd><button type=\"submit\"></kbd> - Preferowany, elastyczna zawartość<br><kbd><input type=\"submit\"></kbd> - Prosty przycisk tekstowy<br><br>Tekst przycisku powinien być zorientowany na akcję (np. <code>Zaloguj</code>, 'Zarejestruj', 'Wyślij').",
|
||||||
"task": "Add a submit button to the form with the text <code>Sign In</code>.",
|
"task": "Dodaj przycisk wysyłania do formularza z tekstem <code>Zaloguj</code>.",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } button { width: 100%; margin-top: 20px; padding: 10px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; } button:hover { background: #1565c0; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } button { width: 100%; margin-top: 20px; padding: 10px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; } button:hover { background: #1565c0; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\">\n \n</form>",
|
"initialCode": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">Hasło:</label>\n <input type=\"password\" id=\"password\">\n \n</form>",
|
||||||
"solution": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\">\n \n <button type=\"submit\">Sign In</button>\n</form>",
|
"solution": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">Hasło:</label>\n <input type=\"password\" id=\"password\">\n \n <button type=\"submit\">Zaloguj</button>\n</form>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "button[type='submit'], input[type='submit']",
|
"value": "button[type='submit'], input[type='submit']",
|
||||||
"message": "Add a submit button to your form"
|
"message": "Dodaj przycisk wysyłania do formularza"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_text",
|
"type": "element_text",
|
||||||
"value": { "selector": "button", "text": "Sign In" },
|
"value": { "selector": "button", "text": "Zaloguj" },
|
||||||
"message": "The button should say <kbd>Sign In</kbd>"
|
"message": "Przycisk powinien wyświetlać <kbd>Zaloguj</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,110 +1,32 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-forms-validation",
|
"id": "html-forms-validation",
|
||||||
"title": "HTML Validation",
|
"title": "Walidacja formularzy",
|
||||||
"description": "Learn HTML5 built-in form validation attributes",
|
"description": "Użyj wbudowanej walidacji HTML5 dla lepszego doświadczenia użytkownika",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "intermediate",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "required-fields",
|
"id": "required-fields",
|
||||||
"title": "Required Fields",
|
"title": "Pola wymagane",
|
||||||
"description": "The <kbd>required</kbd> attribute prevents form submission if the field is empty.<br><br>Add it to any input that must be filled:<br><kbd><input type=\"text\" required></kbd><br><br>The browser shows a validation message automatically.",
|
"description": "Atrybut <kbd>required</kbd> zapobiega wysłaniu formularza, jeśli pole jest puste. Przeglądarka automatycznie pokazuje komunikat walidacji - bez JavaScript!<br><br>Dodaj go do każdego pola, które musi być wypełnione:<br><kbd><input type=\"text\" required></kbd>",
|
||||||
"task": "Make both the name and email fields required by adding the <kbd>required</kbd> attribute.",
|
"task": "Uczyń oba pola (imię i email) wymaganymi, dodając atrybut <kbd>required</kbd> do każdego pola.",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 350px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-of-type { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } input:invalid { border-color: #d32f2f; } button { margin-top: 20px; padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 320px; } label { display: block; margin-top: 15px; margin-bottom: 5px; font-weight: 500; } label:first-of-type { margin-top: 0; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; box-sizing: border-box; } input:focus { outline: 2px solid steelblue; border-color: transparent; } button { margin-top: 20px; padding: 10px 20px; background: steelblue; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 500; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "<form>\n <label for=\"name\">Name: *</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n \n <label for=\"email\">Email: *</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <button type=\"submit\">Submit</button>\n</form>",
|
"initialCode": "<form>\n <label for=\"name\">Imię *</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n \n <label for=\"email\">Email *</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <button type=\"submit\">Wyślij</button>\n</form>",
|
||||||
"solution": "<form>\n <label for=\"name\">Name: *</label>\n <input type=\"text\" id=\"name\" name=\"name\" required>\n \n <label for=\"email\">Email: *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <button type=\"submit\">Submit</button>\n</form>",
|
"solution": "<form>\n <label for=\"name\">Imię *</label>\n <input type=\"text\" id=\"name\" name=\"name\" required>\n \n <label for=\"email\">Email *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <button type=\"submit\">Wyślij</button>\n</form>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "input[name='name']", "attr": "required", "value": true },
|
"value": { "selector": "input[name='name']", "attr": "required", "value": true },
|
||||||
"message": "Add the <kbd>required</kbd> attribute to the name input"
|
"message": "Dodaj <kbd>required</kbd> do pola imienia"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "input[name='email']", "attr": "required", "value": true },
|
"value": { "selector": "input[name='email']", "attr": "required", "value": true },
|
||||||
"message": "Add the <kbd>required</kbd> attribute to the email input"
|
"message": "Dodaj <kbd>required</kbd> do pola email"
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "input-constraints",
|
|
||||||
"title": "Constraints",
|
|
||||||
"description": "Control what users can enter:<br><br><kbd>minlength</kbd> / <kbd>maxlength</kbd> - Text length limits<br><kbd>min</kbd> / <kbd>max</kbd> - Number range<br><kbd>pattern</kbd> - Regex pattern matching<br><kbd>placeholder</kbd> - Hint text (not a label!)",
|
|
||||||
"task": "Add validation to the password input:<br>1. Add <kbd>minlength=\"8\"</kbd> for minimum length<br>2. Add <kbd>maxlength=\"20\"</kbd> for maximum length<br>3. Add <kbd>placeholder=\"Enter password\"</kbd> as a hint",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 350px; } label { display: block; margin-bottom: 5px; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } input:invalid:not(:placeholder-shown) { border-color: #d32f2f; } small { display: block; font-size: 12px; color: #666; margin-top: 4px; } button { margin-top: 20px; padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<form>\n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required aria-describedby=\"password-hint\">\n <small id=\"password-hint\">Must be 8-20 characters</small>\n \n <button type=\"submit\">Create Account</button>\n</form>",
|
|
||||||
"solution": "<form>\n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required minlength=\"8\" maxlength=\"20\" placeholder=\"Enter password\" aria-describedby=\"password-hint\">\n <small id=\"password-hint\">Must be 8-20 characters</small>\n \n <button type=\"submit\">Create Account</button>\n</form>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "input[type='password']", "attr": "minlength", "value": "8" },
|
|
||||||
"message": "Add <kbd>minlength</kbd>=\"8\" to the password input"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "input[type='password']", "attr": "maxlength", "value": "20" },
|
|
||||||
"message": "Add <kbd>maxlength</kbd>=\"20\" to the password input"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "input[type='password']", "attr": "placeholder", "value": null },
|
|
||||||
"message": "Add a <kbd>placeholder</kbd> to hint what to enter"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "complete-registration",
|
|
||||||
"title": "Full Form",
|
|
||||||
"description": "Build a complete registration form with all validation concepts:<br><br>- Required fields marked with *<br>- Email validation (use type=\"email\")<br>- Password with length constraints<br>- Terms checkbox (required)<br>- Submit button",
|
|
||||||
"task": "Complete the registration form. Add required attributes, proper input types, and validation constraints.",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 400px; background: #f5f5f5; padding: 25px; border-radius: 8px; } h2 { margin-top: 0; margin-bottom: 20px; } label { display: block; margin-top: 15px; margin-bottom: 5px; font-weight: 500; } input[type='text'], input[type='email'], input[type='password'] { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; font-size: 14px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; } input[type='checkbox'] { width: auto; margin-right: 8px; vertical-align: middle; } label:has(input[type='checkbox']) { display: flex; align-items: center; font-weight: normal; } button { width: 100%; margin-top: 20px; padding: 12px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: 500; } button:hover { background: #1565c0; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<form>\n <h2>Create Account</h2>\n \n <label for=\"fullname\">Full Name *</label>\n <input type=\"text\" id=\"fullname\" name=\"fullname\">\n \n <label for=\"email\">Email *</label>\n <input id=\"email\" name=\"email\">\n \n <label for=\"password\">Password *</label>\n <input id=\"password\" name=\"password\">\n \n <label>\n <input type=\"checkbox\" id=\"terms\" name=\"terms\">\n I agree to the Terms of Service *\n </label>\n \n <button type=\"submit\">Register</button>\n</form>",
|
|
||||||
"solution": "<form>\n <h2>Create Account</h2>\n \n <label for=\"fullname\">Full Name *</label>\n <input type=\"text\" id=\"fullname\" name=\"fullname\" required>\n \n <label for=\"email\">Email *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <label for=\"password\">Password *</label>\n <input type=\"password\" id=\"password\" name=\"password\" required minlength=\"8\">\n \n <label>\n <input type=\"checkbox\" id=\"terms\" name=\"terms\" required>\n I agree to the Terms of Service *\n </label>\n \n <button type=\"submit\">Register</button>\n</form>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#fullname", "attr": "required", "value": true },
|
|
||||||
"message": "Make the full name field <kbd>required</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#email", "attr": "type", "value": "email" },
|
|
||||||
"message": "Set the email input <kbd>type=\"email\"</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#email", "attr": "required", "value": true },
|
|
||||||
"message": "Make the email field <kbd>required</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#password", "attr": "type", "value": "password" },
|
|
||||||
"message": "Set the password input <kbd>type=\"password\"</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#password", "attr": "required", "value": true },
|
|
||||||
"message": "Make the password field <kbd>required</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#password", "attr": "minlength", "value": "8" },
|
|
||||||
"message": "Add <kbd>minlength</kbd>=\"8\" to password"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "attribute_value",
|
|
||||||
"value": { "selector": "#terms", "attr": "required", "value": true },
|
|
||||||
"message": "Make the terms checkbox <kbd>required</kbd>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-details-summary",
|
"id": "html-details-summary",
|
||||||
"title": "HTML Details & Summary",
|
"title": "HTML Details & Summary",
|
||||||
"description": "Create expandable content sections without JavaScript",
|
"description": "Twórz rozwijane sekcje bez JavaScript",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "details-summary-basic",
|
"id": "details-summary-basic",
|
||||||
"title": "First Widget",
|
"title": "Pierwszy widget",
|
||||||
"description": "The <kbd><details></kbd> element creates a collapsible section. The <kbd><summary></kbd> provides the clickable label.<br><br>Click the summary to toggle the hidden content - no JavaScript needed!",
|
"description": "Element <kbd><details></kbd> tworzy zwijany sekcję. Element <kbd><summary></kbd> zapewnia klikalną etykietę.<br><br>Kliknij podsumowanie, aby przełączyć ukrytą treść - bez JavaScript!",
|
||||||
"task": "Create a <kbd><details></kbd> element with:<br>1. A <kbd><summary></kbd> saying <code>Click to reveal</code><br>2. A <kbd><p></kbd> with the text <code>This content was hidden!</code>",
|
"task": "Utwórz element <kbd><details></kbd> z:<br>1. Elementem <kbd><summary></kbd> z tekstem <code>Click to reveal</code><br>2. Elementem <kbd><p></kbd> z tekstem <code>This content was hidden!</code>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; } summary { font-weight: 600; cursor: pointer; } summary:hover { color: #1976d2; } details p { margin-top: 15px; color: #666; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; } summary { font-weight: 600; cursor: pointer; } summary:hover { color: #1976d2; } details p { margin-top: 15px; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,30 +21,30 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "details",
|
"value": "details",
|
||||||
"message": "Add a <kbd><details></kbd> element"
|
"message": "Dodaj element <kbd><details></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "summary",
|
"value": "summary",
|
||||||
"message": "Add a <kbd><summary></kbd> inside the details"
|
"message": "Dodaj <kbd><summary></kbd> wewnątrz details"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "parent_child",
|
"type": "parent_child",
|
||||||
"value": { "parent": "details", "child": "summary" },
|
"value": { "parent": "details", "child": "summary" },
|
||||||
"message": "The <kbd><summary></kbd> must be inside <kbd><details></kbd>"
|
"message": "Element <kbd><summary></kbd> musi być wewnątrz <kbd><details></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "parent_child",
|
"type": "parent_child",
|
||||||
"value": { "parent": "details", "child": "p" },
|
"value": { "parent": "details", "child": "p" },
|
||||||
"message": "Add a <kbd><p></kbd> inside <kbd><details></kbd> for the hidden content"
|
"message": "Dodaj <kbd><p></kbd> wewnątrz <kbd><details></kbd> dla ukrytej treści"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "details-open-attribute",
|
"id": "details-open-attribute",
|
||||||
"title": "Pre-expanded Details",
|
"title": "Domyślnie rozwinięte",
|
||||||
"description": "By default, <kbd><details></kbd> is closed. Add the <kbd>open</kbd> attribute to show the content initially.<br><br>This is a boolean attribute - just add <kbd>open</kbd> without a value.",
|
"description": "Domyślnie <kbd><details></kbd> jest zamknięte. Dodaj atrybut <kbd>open</kbd>, aby pokazać treść na początku.<br><br>To jest atrybut logiczny - po prostu dodaj <kbd>open</kbd> bez wartości.",
|
||||||
"task": "Add the <kbd>open</kbd> attribute to the <kbd><details></kbd> element to show the content by default.",
|
"task": "Dodaj atrybut <kbd>open</kbd> do elementu <kbd><details></kbd>, aby domyślnie pokazać treść.",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; background: #f9f9f9; } summary { font-weight: 600; cursor: pointer; } details p { margin-top: 15px; color: #666; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; background: #f9f9f9; } summary { font-weight: 600; cursor: pointer; } details p { margin-top: 15px; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -55,15 +55,15 @@
|
|||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "details", "attr": "open", "value": true },
|
"value": { "selector": "details", "attr": "open", "value": true },
|
||||||
"message": "Add the <kbd>open</kbd> attribute to <kbd><details></kbd>"
|
"message": "Dodaj atrybut <kbd>open</kbd> do <kbd><details></kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "faq-accordion",
|
"id": "faq-accordion",
|
||||||
"title": "FAQ Accordion",
|
"title": "Akordeon FAQ",
|
||||||
"description": "Multiple <kbd><details></kbd> elements create an accordion-style FAQ. Each question can be expanded independently.<br><br><b>Pro tip:</b> Type <kbd>details*3>summary+p</kbd> and press Tab for Emmet expansion. <kbd>*3</kbd> creates 3 elements, <kbd>></kbd> nests inside, <kbd>+</kbd> adds siblings.",
|
"description": "Wiele elementów <kbd><details></kbd> tworzy FAQ w stylu akordeonu. Każde pytanie może być rozwijane niezależnie.<br><br><b>Pro tip:</b> Wpisz <kbd>details*3>summary+p</kbd> i naciśnij Tab dla rozwinięcia Emmet. <kbd>*3</kbd> tworzy 3 elementy, <kbd>></kbd> zagnieżdża wewnątrz, <kbd>+</kbd> dodaje rodzeństwo.",
|
||||||
"task": "Create an FAQ section with:<br>1. An <kbd><h1></kbd> saying <code>Frequently Asked Questions</code><br>2. Three <kbd><details></kbd> elements, each with a question in <kbd><summary></kbd> and an answer in <kbd><p></kbd>",
|
"task": "Utwórz sekcję FAQ z:<br>1. Nagłówkiem <kbd><h1></kbd> z tekstem <code>Frequently Asked Questions</code><br>2. Trzema elementami <kbd><details></kbd>, każdy z pytaniem w <kbd><summary></kbd> i odpowiedzią w <kbd><p></kbd>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; min-height: 100vh; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); display: flex; flex-direction: column; justify-content: center; padding: 40px; margin: 0; box-sizing: border-box; } h1 { font-size: 2.5rem; color: #4a4a4a; text-align: center; margin: 0 0 30px 0; } details { background: white; border-radius: 12px; margin-bottom: 15px; padding: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } summary { font-size: 1.3rem; font-weight: 600; color: #5c5c5c; cursor: pointer; list-style: none; } summary::before { content: '▸ '; color: #fcb69f; } details[open] summary::before { content: '▾ '; } details p { margin: 15px 0 0 0; color: #666; line-height: 1.6; }",
|
"previewBaseCSS": "body { font-family: system-ui; min-height: 100vh; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); display: flex; flex-direction: column; justify-content: center; padding: 40px; margin: 0; box-sizing: border-box; } h1 { font-size: 2.5rem; color: #4a4a4a; text-align: center; margin: 0 0 30px 0; } details { background: white; border-radius: 12px; margin-bottom: 15px; padding: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } summary { font-size: 1.3rem; font-weight: 600; color: #5c5c5c; cursor: pointer; list-style: none; } summary::before { content: '▸ '; color: #fcb69f; } details[open] summary::before { content: '▾ '; } details p { margin: 15px 0 0 0; color: #666; line-height: 1.6; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -74,22 +74,22 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "h1",
|
"value": "h1",
|
||||||
"message": "Add an <kbd><h1></kbd> heading for the FAQ title"
|
"message": "Dodaj nagłówek <kbd><h1></kbd> dla tytułu FAQ"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "details", "min": 3 },
|
"value": { "selector": "details", "min": 3 },
|
||||||
"message": "Create at least 3 <kbd><details></kbd> elements for the FAQ"
|
"message": "Utwórz co najmniej 3 elementy <kbd><details></kbd> dla FAQ"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "summary", "min": 3 },
|
"value": { "selector": "summary", "min": 3 },
|
||||||
"message": "Each <kbd><details></kbd> needs a <kbd><summary></kbd> for the question"
|
"message": "Każdy <kbd><details></kbd> potrzebuje <kbd><summary></kbd> dla pytania"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "details p", "min": 3 },
|
"value": { "selector": "details p", "min": 3 },
|
||||||
"message": "Each <kbd><details></kbd> needs a <kbd><p></kbd> for the answer"
|
"message": "Każdy <kbd><details></kbd> potrzebuje <kbd><p></kbd> dla odpowiedzi"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-progress-meter",
|
"id": "html-progress-meter",
|
||||||
"title": "HTML Progress & Meter",
|
"title": "HTML Progress & Meter",
|
||||||
"description": "Display completion status and scalar measurements natively",
|
"description": "Wyświetlaj postęp i pomiary natywnie",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "progress-basic",
|
"id": "progress-basic",
|
||||||
"title": "Progress Bars",
|
"title": "Paski postępu",
|
||||||
"description": "The <kbd><progress></kbd> element shows task completion. Use <kbd>value</kbd> for current progress and <kbd>max</kbd> for the total.<br><br><b>Note:</b> This is not a self-closing tag! Write <kbd><progress>...</progress></kbd> with fallback text inside for older browsers.",
|
"description": "Element <kbd><progress></kbd> pokazuje postęp zadania. Użyj <kbd>value</kbd> dla aktualnego postępu i <kbd>max</kbd> dla całości.<br><br><b>Uwaga:</b> To nie jest samozamykający się tag! Pisz <kbd><progress>...</progress></kbd> z tekstem zastępczym w środku dla starszych przeglądarek.",
|
||||||
"task": "Create a progress bar showing 70% completion:<br>1. Add a <kbd><label></kbd> saying <code>Download:</code><br>2. Add a <kbd><progress></kbd> with <kbd>value=\"70\"</kbd> and <kbd>max=\"100\"</kbd>",
|
"task": "Utwórz pasek postępu pokazujący 70% ukończenia:<br>1. Dodaj <kbd><label></kbd> z tekstem <code>Download:</code><br>2. Dodaj <kbd><progress></kbd> z <kbd>value=\"70\"</kbd> i <kbd>max=\"100\"</kbd>",
|
||||||
"previewHTML": "",
|
"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; }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,30 +21,30 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "progress",
|
"value": "progress",
|
||||||
"message": "Add a <kbd><progress></kbd> element"
|
"message": "Dodaj element <kbd><progress></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "progress", "attr": "value", "value": "70" },
|
"value": { "selector": "progress", "attr": "value", "value": "70" },
|
||||||
"message": "Set <kbd>value=</kbd>\"70\" on the progress element"
|
"message": "Ustaw <kbd>value=</kbd>\"70\" w elemencie progress"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "progress", "attr": "max", "value": "100" },
|
"value": { "selector": "progress", "attr": "max", "value": "100" },
|
||||||
"message": "Set <kbd>max=</kbd>\"100\" on the progress element"
|
"message": "Ustaw <kbd>max=</kbd>\"100\" w elemencie progress"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "label",
|
"value": "label",
|
||||||
"message": "Add a <kbd><label></kbd> for the progress bar"
|
"message": "Dodaj <kbd><label></kbd> dla paska postępu"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "progress-indeterminate",
|
"id": "progress-indeterminate",
|
||||||
"title": "Indeterminate Progress",
|
"title": "Nieokreślony postęp",
|
||||||
"description": "When progress is unknown (like loading), omit the <kbd>value</kbd> attribute. This creates an animated indeterminate state.<br><br>Useful for network requests or processes with unknown duration.",
|
"description": "Gdy postęp jest nieznany (jak przy ładowaniu), pomiń atrybut <kbd>value</kbd>. To tworzy animowany stan nieokreślony.<br><br>Przydatne dla żądań sieciowych lub procesów o nieznanym czasie trwania.",
|
||||||
"task": "Create a loading indicator:<br>1. Add a <kbd><p></kbd> saying <code>Loading...</code><br>2. Add a <kbd><progress></kbd> without a value attribute",
|
"task": "Utwórz wskaźnik ładowania:<br>1. Dodaj <kbd><p></kbd> z tekstem <code>Loading...</code><br>2. Dodaj <kbd><progress></kbd> bez atrybutu value",
|
||||||
"previewHTML": "",
|
"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; }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
@@ -55,20 +55,20 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "progress",
|
"value": "progress",
|
||||||
"message": "Add a <kbd><progress></kbd> element"
|
"message": "Dodaj element <kbd><progress></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "p",
|
"value": "p",
|
||||||
"message": "Add a <kbd><p></kbd> with loading text"
|
"message": "Dodaj <kbd><p></kbd> z tekstem ładowania"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "meter-gauge",
|
"id": "meter-gauge",
|
||||||
"title": "Meter Gauges",
|
"title": "Wskaźniki meter",
|
||||||
"description": "The <kbd><meter></kbd> element displays a scalar value within a range. Use it for measurements like disk space, battery, or ratings.<br><br>Set <kbd>low</kbd>, <kbd>high</kbd>, and <kbd>optimum</kbd> to define good/bad ranges - the browser colors it accordingly!",
|
"description": "Element <kbd><meter></kbd> wyświetla wartość skalarną w zakresie. Używaj go do pomiarów jak przestrzeń dyskowa, bateria lub oceny.<br><br>Ustaw <kbd>low</kbd>, <kbd>high</kbd> i <kbd>optimum</kbd>, aby zdefiniować dobre/złe zakresy - przeglądarka odpowiednio je koloruje!",
|
||||||
"task": "Create a battery level meter:<br>1. Add a <kbd><label></kbd> saying <code>Battery:</code><br>2. Add a <kbd><meter></kbd> with:<br> - <kbd>value=\"0.8\"</kbd><br> - <kbd>min=\"0\"</kbd> and <kbd>max=\"1\"</kbd><br> - <kbd>low=\"0.2\"</kbd> and <kbd>high=\"0.8\"</kbd><br> - <kbd>optimum=\"1\"</kbd>",
|
"task": "Utwórz wskaźnik poziomu baterii:<br>1. Dodaj <kbd><label></kbd> z tekstem <code>Battery:</code><br>2. Dodaj <kbd><meter></kbd> z:<br> - <kbd>value=\"0.8\"</kbd><br> - <kbd>min=\"0\"</kbd> i <kbd>max=\"1\"</kbd><br> - <kbd>low=\"0.2\"</kbd> i <kbd>high=\"0.8\"</kbd><br> - <kbd>optimum=\"1\"</kbd>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -79,22 +79,42 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "meter",
|
"value": "meter",
|
||||||
"message": "Add a <kbd><meter></kbd> element"
|
"message": "Dodaj element <kbd><meter></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "meter", "attr": "value", "value": "0.8" },
|
"value": { "selector": "meter", "attr": "value", "value": "0.8" },
|
||||||
"message": "Set <kbd>value=</kbd>\"0.8\" on the meter"
|
"message": "Ustaw <kbd>value=</kbd>\"0.8\" w meter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "meter", "attr": "min", "value": "0" },
|
||||||
|
"message": "Ustaw <kbd>min=</kbd>\"0\" w meter"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "meter", "attr": "max", "value": "1" },
|
||||||
|
"message": "Ustaw <kbd>max=</kbd>\"1\" w meter"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "meter", "attr": "low", "value": "0.2" },
|
"value": { "selector": "meter", "attr": "low", "value": "0.2" },
|
||||||
"message": "Set <kbd>low=</kbd>\"0.2\" to define the low threshold"
|
"message": "Ustaw <kbd>low=</kbd>\"0.2\", aby zdefiniować niski próg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "meter", "attr": "high", "value": "0.8" },
|
||||||
|
"message": "Ustaw <kbd>high=</kbd>\"0.8\", aby zdefiniować wysoki próg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "meter", "attr": "optimum", "value": "1" },
|
||||||
|
"message": "Ustaw <kbd>optimum=</kbd>\"1\", aby wskazać optymalną wartość"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "label",
|
"value": "label",
|
||||||
"message": "Add a <kbd><label></kbd> for the meter"
|
"message": "Dodaj <kbd><label></kbd> dla meter"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-datalist",
|
"id": "html-datalist",
|
||||||
"title": "Datalist",
|
"title": "Datalist",
|
||||||
"description": "Provide suggestions for text inputs without JavaScript",
|
"description": "Dodaj sugestie do pól tekstowych bez JavaScript",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "datalist-basic",
|
"id": "datalist-basic",
|
||||||
"title": "Input with Suggestions",
|
"title": "Pole z sugestiami",
|
||||||
"description": "The <kbd><datalist></kbd> element provides autocomplete suggestions for inputs. Connect it using the <kbd>list</kbd> attribute on the input matching the datalist's <kbd>id</kbd>.<br><br>Users can still type freely - suggestions are just helpers!",
|
"description": "Element <kbd><datalist></kbd> dostarcza sugestie autouzupełniania dla pól. Połącz go używając atrybutu <kbd>list</kbd> na polu, pasującego do <kbd>id</kbd> datalist.<br><br>Użytkownicy mogą nadal pisać dowolnie - sugestie to tylko pomocnicy!",
|
||||||
"task": "Create a browser selector:<br>1. Add a <kbd><label></kbd> saying <code>Browser:</code><br>2. Add an <kbd><input></kbd> with <kbd>list=\"browsers\"</kbd><br>3. Add a <kbd><datalist id=\"browsers\"></kbd> with options for Chrome, Firefox, and Safari",
|
"task": "Utwórz selektor przeglądarki:<br>1. Dodaj <kbd><label></kbd> z tekstem <code>Przeglądarka:</code><br>2. Dodaj <kbd><input></kbd> z <kbd>list=\"browsers\"</kbd><br>3. Dodaj <kbd><datalist id=\"browsers\"></kbd> z opcjami Chrome, Firefox i Safari",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; font-size: 16px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; font-size: 16px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,30 +21,30 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "datalist",
|
"value": "datalist",
|
||||||
"message": "Add a <kbd><datalist></kbd> element"
|
"message": "Dodaj element <kbd><datalist></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "input", "attr": "list", "value": "browsers" },
|
"value": { "selector": "input", "attr": "list", "value": "browsers" },
|
||||||
"message": "Connect the input to datalist using <kbd>list=</kbd>\"browsers\""
|
"message": "Połącz pole z datalist używając <kbd>list=</kbd>\"browsers\""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "option", "min": 3 },
|
"value": { "selector": "option", "min": 3 },
|
||||||
"message": "Add at least 3 <kbd><option></kbd> elements inside <kbd><datalist></kbd>"
|
"message": "Dodaj co najmniej 3 elementy <kbd><option></kbd> wewnątrz <kbd><datalist></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "label",
|
"value": "label",
|
||||||
"message": "Add a <kbd><label></kbd> for the input"
|
"message": "Dodaj <kbd><label></kbd> dla pola"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "datalist-countries",
|
"id": "datalist-countries",
|
||||||
"title": "Country Selector",
|
"title": "Selektor krajów",
|
||||||
"description": "Datalists work great for long lists like countries. Users can type to filter suggestions instantly.<br><br>The <kbd>value</kbd> attribute is what gets entered, and you can add display text after it.",
|
"description": "Datalist świetnie sprawdza się przy długich listach jak kraje. Użytkownicy mogą pisać, aby natychmiast filtrować sugestie.<br><br>Atrybut <kbd>value</kbd> określa, co zostanie wpisane, a po nim możesz dodać tekst wyświetlany.",
|
||||||
"task": "Create a country input:<br>1. Add a <kbd><label></kbd> saying <code>Country:</code><br>2. Add an <kbd><input></kbd> with <kbd>list=\"countries\"</kbd><br>3. Add a <kbd><datalist id=\"countries\"></kbd> with at least 4 country options",
|
"task": "Utwórz pole kraju:<br>1. Dodaj <kbd><label></kbd> z tekstem <code>Kraj:</code><br>2. Dodaj <kbd><input></kbd> z <kbd>list=\"countries\"</kbd><br>3. Dodaj <kbd><datalist id=\"countries\"></kbd> z co najmniej 4 opcjami krajów",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 30px; background: linear-gradient(135deg, #e0f7fa 0%, #b2ebf2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } label { display: block; margin-bottom: 10px; font-weight: 600; color: #00695c; } input { width: 100%; padding: 12px 15px; border: 2px solid #26a69a; border-radius: 8px; font-size: 16px; background: white; } input:focus { outline: none; border-color: #00695c; box-shadow: 0 0 0 3px rgba(38,166,154,0.2); }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 30px; background: linear-gradient(135deg, #e0f7fa 0%, #b2ebf2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } label { display: block; margin-bottom: 10px; font-weight: 600; color: #00695c; } input { width: 100%; padding: 12px 15px; border: 2px solid #26a69a; border-radius: 8px; font-size: 16px; background: white; } input:focus { outline: none; border-color: #00695c; box-shadow: 0 0 0 3px rgba(38,166,154,0.2); }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -55,22 +55,22 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "datalist",
|
"value": "datalist",
|
||||||
"message": "Add a <kbd><datalist></kbd> element"
|
"message": "Dodaj element <kbd><datalist></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "datalist", "attr": "id", "value": "countries" },
|
"value": { "selector": "datalist", "attr": "id", "value": "countries" },
|
||||||
"message": "Set <kbd>id=</kbd>\"countries\" on the datalist"
|
"message": "Ustaw <kbd>id=</kbd>\"countries\" w datalist"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "input", "attr": "list", "value": "countries" },
|
"value": { "selector": "input", "attr": "list", "value": "countries" },
|
||||||
"message": "Connect the input using <kbd>list=</kbd>\"countries\""
|
"message": "Połącz pole używając <kbd>list=</kbd>\"countries\""
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "option", "min": 4 },
|
"value": { "selector": "option", "min": 4 },
|
||||||
"message": "Add at least 4 country options"
|
"message": "Dodaj co najmniej 4 opcje krajów"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-dialog",
|
"id": "html-dialog",
|
||||||
"title": "Dialogs",
|
"title": "Dialogi",
|
||||||
"description": "Create modal dialogs without JavaScript libraries",
|
"description": "Twórz okna dialogowe bez bibliotek JavaScript",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "dialog-basic",
|
"id": "dialog-basic",
|
||||||
"title": "Open Dialog",
|
"title": "Otwórz dialog",
|
||||||
"description": "The <kbd><dialog></kbd> element creates a native modal. Add the <kbd>open</kbd> attribute to show it.<br><br>Use <kbd><form method=\"dialog\"></kbd> inside to close it when the form submits - no JavaScript needed!",
|
"description": "Element <kbd><dialog></kbd> tworzy natywne okno modalne. Dodaj atrybut <kbd>open</kbd>, aby je wyświetlić.<br><br>Użyj <kbd><form method=\"dialog\"></kbd> wewnątrz, aby zamknąć je przy wysłaniu formularza - bez JavaScript!",
|
||||||
"task": "Create a dialog with:<br>1. The <kbd>open</kbd> attribute to show it<br>2. An <kbd><h2></kbd> saying <code>Welcome!</code><br>3. A <kbd><p></kbd> with a greeting message<br>4. A <kbd><form method=\"dialog\"></kbd> with a close button",
|
"task": "Utwórz dialog z:<br>1. Atrybutem <kbd>open</kbd> aby go wyświetlić<br>2. Elementem <kbd><h2></kbd> z tekstem <code>Witaj!</code><br>3. Elementem <kbd><p></kbd> z wiadomością powitalną<br>4. Elementem <kbd><form method=\"dialog\"></kbd> z przyciskiem zamknięcia",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: #f5f5f5; } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 400px; } dialog::backdrop { background: rgba(0,0,0,0.5); } dialog h2 { margin: 0 0 15px 0; color: #1976d2; } dialog p { color: #666; margin: 0 0 20px 0; } dialog button { background: #1976d2; color: white; border: none; padding: 10px 25px; border-radius: 6px; cursor: pointer; font-size: 16px; } dialog button:hover { background: #1565c0; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: #f5f5f5; } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 400px; } dialog::backdrop { background: rgba(0,0,0,0.5); } dialog h2 { margin: 0 0 15px 0; color: #1976d2; } dialog p { color: #666; margin: 0 0 20px 0; } dialog button { background: #1976d2; color: white; border: none; padding: 10px 25px; border-radius: 6px; cursor: pointer; font-size: 16px; } dialog button:hover { background: #1565c0; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,35 +21,35 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "dialog",
|
"value": "dialog",
|
||||||
"message": "Add a <kbd><dialog></kbd> element"
|
"message": "Dodaj element <kbd><dialog></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "dialog", "attr": "open", "value": true },
|
"value": { "selector": "dialog", "attr": "open", "value": true },
|
||||||
"message": "Add the <kbd>open</kbd> attribute to show the dialog"
|
"message": "Dodaj atrybut <kbd>open</kbd> aby wyświetlić dialog"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "dialog h2",
|
"value": "dialog h2",
|
||||||
"message": "Add an <kbd><h2></kbd> heading inside the dialog"
|
"message": "Dodaj nagłówek <kbd><h2></kbd> wewnątrz dialogu"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "form[method='dialog']",
|
"value": "form[method='dialog']",
|
||||||
"message": "Add a <kbd><form method=\"dialog\"></kbd> for closing"
|
"message": "Dodaj <kbd><form method=\"dialog\"></kbd> do zamknięcia"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "dialog button",
|
"value": "dialog button",
|
||||||
"message": "Add a close button inside the form"
|
"message": "Dodaj przycisk zamknięcia wewnątrz formularza"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "dialog-form",
|
"id": "dialog-form",
|
||||||
"title": "Dialog + Form",
|
"title": "Dialog z formularzem",
|
||||||
"description": "Dialogs can contain full forms. The <kbd>method=\"dialog\"</kbd> makes the form close the dialog on submit instead of sending data.<br><br>This pattern is perfect for confirmation dialogs, quick inputs, or settings panels.",
|
"description": "Dialogi mogą zawierać pełne formularze. <kbd>method=\"dialog\"</kbd> sprawia, że formularz zamyka dialog przy wysłaniu zamiast wysyłać dane.<br><br>Ten wzorzec jest idealny dla dialogów potwierdzenia, szybkich wejść lub paneli ustawień.",
|
||||||
"task": "Create a confirmation dialog:<br>1. Add <kbd>open</kbd> to show it<br>2. An <kbd><h2></kbd> saying <code>Confirm Delete</code><br>3. A <kbd><p></kbd> asking <code>Are you sure?</code><br>4. A <kbd><form method=\"dialog\"></kbd> with Cancel and Delete buttons",
|
"task": "Utwórz dialog potwierdzenia:<br>1. Dodaj <kbd>open</kbd> aby go wyświetlić<br>2. Element <kbd><h2></kbd> z tekstem <code>Potwierdź usunięcie</code><br>3. Element <kbd><p></kbd> z pytaniem <code>Czy jesteś pewien?</code><br>4. Element <kbd><form method=\"dialog\"></kbd> z przyciskami Anuluj i Usuń",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: linear-gradient(135deg, #fce4ec 0%, #f8bbd9 100%); } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 350px; text-align: center; } dialog h2 { margin: 0 0 10px 0; color: #c62828; } dialog p { color: #666; margin: 0 0 20px 0; } dialog form { display: flex; gap: 10px; justify-content: center; } dialog button { padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; border: none; } dialog button:first-child { background: #e0e0e0; color: #333; } dialog button:last-child { background: #c62828; color: white; } dialog button:hover { opacity: 0.9; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: linear-gradient(135deg, #fce4ec 0%, #f8bbd9 100%); } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 350px; text-align: center; } dialog h2 { margin: 0 0 10px 0; color: #c62828; } dialog p { color: #666; margin: 0 0 20px 0; } dialog form { display: flex; gap: 10px; justify-content: center; } dialog button { padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; border: none; } dialog button:first-child { background: #e0e0e0; color: #333; } dialog button:last-child { background: #c62828; color: white; } dialog button:hover { opacity: 0.9; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -60,22 +60,22 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "dialog[open]",
|
"value": "dialog[open]",
|
||||||
"message": "Add a <kbd><dialog></kbd> with the open attribute"
|
"message": "Dodaj <kbd><dialog></kbd> z atrybutem open"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "dialog h2",
|
"value": "dialog h2",
|
||||||
"message": "Add a heading to the dialog"
|
"message": "Dodaj nagłówek do dialogu"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "form[method='dialog']",
|
"value": "form[method='dialog']",
|
||||||
"message": "Add a <kbd><form method=\"dialog\"></kbd> for the buttons"
|
"message": "Dodaj <kbd><form method=\"dialog\"></kbd> dla przycisków"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "dialog button", "min": 2 },
|
"value": { "selector": "dialog button", "min": 2 },
|
||||||
"message": "Add at least 2 buttons (Cancel and Confirm)"
|
"message": "Dodaj co najmniej 2 przyciski (Anuluj i Potwierdź)"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,16 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-forms-fieldset",
|
"id": "html-forms-fieldset",
|
||||||
"title": "Fieldsets",
|
"title": "Fieldset",
|
||||||
"description": "Group form controls with fieldset and legend elements",
|
"description": "Grupuj elementy formularza za pomocą fieldset i legend",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "fieldset-basic",
|
"id": "fieldset-basic",
|
||||||
"title": "Grouping with Fieldset",
|
"title": "Grupowanie z Fieldset",
|
||||||
"description": "The <kbd><fieldset></kbd> element groups related form controls together. Add a <kbd><legend></kbd> as the first child to give the group a title.<br><br>This helps with accessibility and visual organization of complex forms.",
|
"description": "Element <kbd><fieldset></kbd> grupuje powiązane elementy formularza. Dodaj <kbd><legend></kbd> jako pierwsze dziecko, aby nadać grupie tytuł.<br><br>To pomaga w dostępności i wizualnej organizacji złożonych formularzy.",
|
||||||
"task": "Create a form with a fieldset:<br>1. A <kbd><form></kbd> element<br>2. A <kbd><fieldset></kbd> inside<br>3. A <kbd><legend></kbd> saying <code>Personal Info</code><br>4. Two labeled inputs for name and email",
|
"task": "Utwórz formularz z fieldset:<br>1. Element <kbd><form></kbd><br>2. Element <kbd><fieldset></kbd> wewnątrz<br>3. Element <kbd><legend></kbd> z tekstem <code>Dane osobowe</code><br>4. Dwa opisane pola dla imienia i e-maila",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f0f4f8; } form { max-width: 400px; } fieldset { border: 2px solid #3498db; border-radius: 10px; padding: 20px; background: white; } legend { color: #3498db; font-weight: 600; padding: 0 10px; font-size: 1.1rem; } label { display: block; margin: 15px 0 5px; color: #333; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; } input:focus { outline: 2px solid #3498db; border-color: transparent; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f0f4f8; } form { max-width: 400px; } fieldset { border: 2px solid #3498db; border-radius: 10px; padding: 20px; background: white; } legend { color: #3498db; font-weight: 600; padding: 0 10px; font-size: 1.1rem; } label { display: block; margin: 15px 0 5px; color: #333; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; } input:focus { outline: 2px solid #3498db; border-color: transparent; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,35 +21,35 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "form",
|
"value": "form",
|
||||||
"message": "Add a <kbd><form></kbd> element"
|
"message": "Dodaj element <kbd><form></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "fieldset",
|
"value": "fieldset",
|
||||||
"message": "Add a <kbd><fieldset></kbd> inside the form"
|
"message": "Dodaj <kbd><fieldset></kbd> wewnątrz formularza"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "legend",
|
"value": "legend",
|
||||||
"message": "Add a <kbd><legend></kbd> to title your fieldset"
|
"message": "Dodaj <kbd><legend></kbd> jako tytuł fieldset"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "label", "min": 2 },
|
"value": { "selector": "label", "min": 2 },
|
||||||
"message": "Add at least 2 labels"
|
"message": "Dodaj co najmniej 2 etykiety"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "input", "min": 2 },
|
"value": { "selector": "input", "min": 2 },
|
||||||
"message": "Add at least 2 input fields"
|
"message": "Dodaj co najmniej 2 pola wprowadzania"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "fieldset-textarea",
|
"id": "fieldset-textarea",
|
||||||
"title": "Adding Textarea",
|
"title": "Dodawanie Textarea",
|
||||||
"description": "The <kbd><textarea></kbd> element creates a multi-line text input, perfect for longer content like messages or descriptions.<br><br>Use <kbd>rows</kbd> and <kbd>cols</kbd> attributes to set default size.",
|
"description": "Element <kbd><textarea></kbd> tworzy wieloliniowe pole tekstowe, idealne dla dłuższych treści jak wiadomości lub opisy.<br><br>Użyj atrybutów <kbd>rows</kbd> i <kbd>cols</kbd> aby ustawić domyślny rozmiar.",
|
||||||
"task": "Create a contact form:<br>1. A <kbd><fieldset></kbd> with <kbd><legend></kbd> <code>Contact Us</code><br>2. A labeled <kbd><input></kbd> for email<br>3. A labeled <kbd><textarea></kbd> for the message<br>4. A submit <kbd><button></kbd>",
|
"task": "Utwórz formularz kontaktowy:<br>1. Element <kbd><fieldset></kbd> z <kbd><legend></kbd> <code>Kontakt</code><br>2. Opisane <kbd><input></kbd> dla e-maila<br>3. Opisane <kbd><textarea></kbd> dla wiadomości<br>4. Przycisk <kbd><button></kbd> wysyłania",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } form { max-width: 450px; margin: 0 auto; } fieldset { border: none; border-radius: 15px; padding: 30px; background: white; box-shadow: 0 10px 40px rgba(0,0,0,0.2); } legend { color: #667eea; font-weight: 700; padding: 0; font-size: 1.5rem; margin-bottom: 10px; } label { display: block; margin: 20px 0 8px; color: #333; font-weight: 500; } input, textarea { width: 100%; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: none; border-color: #667eea; } textarea { resize: vertical; min-height: 100px; } button { margin-top: 20px; width: 100%; padding: 14px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; } button:hover { opacity: 0.9; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } form { max-width: 450px; margin: 0 auto; } fieldset { border: none; border-radius: 15px; padding: 30px; background: white; box-shadow: 0 10px 40px rgba(0,0,0,0.2); } legend { color: #667eea; font-weight: 700; padding: 0; font-size: 1.5rem; margin-bottom: 10px; } label { display: block; margin: 20px 0 8px; color: #333; font-weight: 500; } input, textarea { width: 100%; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: none; border-color: #667eea; } textarea { resize: vertical; min-height: 100px; } button { margin-top: 20px; width: 100%; padding: 14px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; } button:hover { opacity: 0.9; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -60,35 +60,35 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "fieldset",
|
"value": "fieldset",
|
||||||
"message": "Add a <kbd><fieldset></kbd> element"
|
"message": "Dodaj element <kbd><fieldset></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "legend",
|
"value": "legend",
|
||||||
"message": "Add a <kbd><legend></kbd> element"
|
"message": "Dodaj element <kbd><legend></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "textarea",
|
"value": "textarea",
|
||||||
"message": "Add a <kbd><textarea></kbd> for the message"
|
"message": "Dodaj <kbd><textarea></kbd> dla wiadomości"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "button",
|
"value": "button",
|
||||||
"message": "Add a submit button"
|
"message": "Dodaj przycisk wysyłania"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "input",
|
"value": "input",
|
||||||
"message": "Add an input field for email"
|
"message": "Dodaj pole dla e-maila"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "fieldset-multiple",
|
"id": "fieldset-multiple",
|
||||||
"title": "Multiple Fieldsets",
|
"title": "Wiele Fieldsetów",
|
||||||
"description": "Complex forms can use multiple <kbd><fieldset></kbd> elements to organize different sections.<br><br>This improves usability for long forms like registration or checkout pages.",
|
"description": "Złożone formularze mogą używać wielu elementów <kbd><fieldset></kbd> do organizacji różnych sekcji.<br><br>To poprawia użyteczność długich formularzy jak rejestracja czy koszyk.",
|
||||||
"task": "Create a registration form with 2 fieldsets:<br>1. <code>Account Info</code> with username and password inputs<br>2. <code>Preferences</code> with a textarea for bio<br>3. A submit button outside the fieldsets",
|
"task": "Utwórz formularz rejestracji z 2 fieldsetami:<br>1. <code>Dane konta</code> z polami nazwa użytkownika i hasło<br>2. <code>Preferencje</code> z textarea dla bio<br>3. Przycisk wysyłania poza fieldsetami",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } form { max-width: 500px; } fieldset { border: 1px solid #ddd; border-radius: 10px; padding: 20px; margin-bottom: 20px; background: white; } legend { color: #2c3e50; font-weight: 600; padding: 0 10px; } label { display: block; margin: 15px 0 5px; color: #555; } input, textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: 2px solid #3498db; border-color: transparent; } textarea { resize: vertical; min-height: 80px; } button { width: 100%; padding: 14px; background: #2c3e50; color: white; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; } button:hover { background: #34495e; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } form { max-width: 500px; } fieldset { border: 1px solid #ddd; border-radius: 10px; padding: 20px; margin-bottom: 20px; background: white; } legend { color: #2c3e50; font-weight: 600; padding: 0 10px; } label { display: block; margin: 15px 0 5px; color: #555; } input, textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: 2px solid #3498db; border-color: transparent; } textarea { resize: vertical; min-height: 80px; } button { width: 100%; padding: 14px; background: #2c3e50; color: white; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; } button:hover { background: #34495e; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -99,27 +99,27 @@
|
|||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "fieldset", "min": 2 },
|
"value": { "selector": "fieldset", "min": 2 },
|
||||||
"message": "Create at least 2 fieldsets"
|
"message": "Utwórz co najmniej 2 fieldsety"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "legend", "min": 2 },
|
"value": { "selector": "legend", "min": 2 },
|
||||||
"message": "Add a legend to each fieldset"
|
"message": "Dodaj legend do każdego fieldseta"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "textarea",
|
"value": "textarea",
|
||||||
"message": "Add a textarea for the bio"
|
"message": "Dodaj textarea dla bio"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "button",
|
"value": "button",
|
||||||
"message": "Add a submit button"
|
"message": "Dodaj przycisk wysyłania"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "input", "min": 2 },
|
"value": { "selector": "input", "min": 2 },
|
||||||
"message": "Add at least 2 input fields"
|
"message": "Dodaj co najmniej 2 pola wprowadzania"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,125 +1,42 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-tables",
|
"id": "html-tables",
|
||||||
"title": "HTML Tables",
|
"title": "Tabele HTML",
|
||||||
"description": "Create structured data tables with headers and captions",
|
"description": "Twórz strukturalne tabele danych z semantycznym znacznikiem",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "table-basic",
|
"id": "table-basic",
|
||||||
"title": "Basic Table Structure",
|
"title": "Tabele danych",
|
||||||
"description": "Tables use <kbd><table></kbd> with <kbd><tr></kbd> for rows. Inside rows, use <kbd><th></kbd> for headers and <kbd><td></kbd> for data cells.<br><br>The <kbd><caption></kbd> element provides an accessible title for the table.",
|
"description": "Tabele wyświetlają strukturalne dane w wierszach i kolumnach. Użyj <kbd><table></kbd> jako kontenera, <kbd><tr></kbd> dla wierszy, <kbd><th></kbd> dla komórek nagłówka i <kbd><td></kbd> dla komórek danych.<br><br>Dodaj <kbd><caption></kbd> dla dostępnego tytułu opisującego zawartość tabeli.",
|
||||||
"task": "Create a simple table with:<br>1. A <kbd><caption></kbd> saying <code>Fruit Prices</code><br>2. A header row with <code>Fruit</code> and <code>Price</code> columns<br>3. At least 2 data rows",
|
"task": "Utwórz tabelę cenową:<br>1. Element <kbd><caption></kbd> z tekstem <code>Pricing</code><br>2. Wiersz nagłówka z <code>Plan</code> i <code>Price</code><br>3. Dwa wiersze danych dla Basic ($9) i Pro ($29)",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px; font-weight: 600; font-size: 1.2rem; color: #333; background: #f8f9fa; } th, td { padding: 12px 20px; text-align: left; border-bottom: 1px solid #eee; } th { background: #3498db; color: white; font-weight: 500; } tr:hover { background: #f8f9fa; } tr:last-child td { border-bottom: none; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; } table { border-collapse: collapse; width: 100%; max-width: 350px; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 16px; font-weight: 600; font-size: 1.1rem; color: #333; background: #f8f9fa; } th, td { padding: 14px 20px; text-align: left; border-bottom: 1px solid #eee; } th { background: steelblue; color: white; font-weight: 500; } tr:last-child td { border-bottom: none; } tr:hover td { background: #f8f9fa; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<table>\n <caption>Fruit Prices</caption>\n <tr>\n <th>Fruit</th>\n <th>Price</th>\n </tr>\n <tr>\n <td>Apple</td>\n <td>$1.50</td>\n </tr>\n <tr>\n <td>Banana</td>\n <td>$0.75</td>\n </tr>\n</table>",
|
"solution": "<table>\n <caption>Pricing</caption>\n <tr>\n <th>Plan</th>\n <th>Price</th>\n </tr>\n <tr>\n <td>Basic</td>\n <td>$9</td>\n </tr>\n <tr>\n <td>Pro</td>\n <td>$29</td>\n </tr>\n</table>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "table",
|
"value": "table",
|
||||||
"message": "Add a <kbd><table></kbd> element"
|
"message": "Dodaj element <kbd><table></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "caption",
|
"value": "caption",
|
||||||
"message": "Add a <kbd><caption></kbd> for the table title"
|
"message": "Dodaj <kbd><caption></kbd> dla tytułu tabeli"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "th", "min": 2 },
|
"value": { "selector": "th", "min": 2 },
|
||||||
"message": "Add at least 2 header cells (th)"
|
"message": "Dodaj komórki nagłówka (<kbd><th></kbd>) dla Plan i Price"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "tr", "min": 3 },
|
"value": { "selector": "tr", "min": 3 },
|
||||||
"message": "Add at least 3 rows (1 header + 2 data rows)"
|
"message": "Dodaj 3 wiersze (1 nagłówek + 2 wiersze danych)"
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "table-thead-tbody",
|
|
||||||
"title": "Table Head & Body",
|
|
||||||
"description": "Use <kbd><thead></kbd> to group header rows and <kbd><tbody></kbd> to group data rows. This helps browsers and assistive technology understand the table structure.<br><br>You can also use <kbd><tfoot></kbd> for footer rows like totals.",
|
|
||||||
"task": "Create a structured table:<br>1. A <kbd><caption></kbd> with <code>Monthly Sales</code><br>2. A <kbd><thead></kbd> with Month and Revenue headers<br>3. A <kbd><tbody></kbd> with at least 2 data rows",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } table { border-collapse: collapse; width: 100%; max-width: 450px; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.2); } caption { padding: 20px; font-weight: 700; font-size: 1.3rem; color: white; background: transparent; text-shadow: 0 2px 4px rgba(0,0,0,0.2); caption-side: top; } thead { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } th { padding: 15px 20px; text-align: left; color: white; font-weight: 500; } tbody tr { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } td { padding: 15px 20px; color: #333; } tbody tr:last-child { border-bottom: none; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "",
|
|
||||||
"solution": "<table>\n <caption>Monthly Sales</caption>\n <thead>\n <tr>\n <th>Month</th>\n <th>Revenue</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>January</td>\n <td>$12,500</td>\n </tr>\n <tr>\n <td>February</td>\n <td>$14,200</td>\n </tr>\n </tbody>\n</table>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "table",
|
|
||||||
"message": "Add a <kbd><table></kbd> element"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "caption",
|
|
||||||
"message": "Add a <kbd><caption></kbd> element"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "thead",
|
|
||||||
"message": "Add a <kbd><thead></kbd> for the header section"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "tbody",
|
|
||||||
"message": "Add a <kbd><tbody></kbd> for the data rows"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_count",
|
|
||||||
"value": { "selector": "tbody tr", "min": 2 },
|
|
||||||
"message": "Add at least 2 data rows in tbody"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "table-complete",
|
|
||||||
"title": "Complete Table with Footer",
|
|
||||||
"description": "Add <kbd><tfoot></kbd> to create a footer section for totals or summary data. The footer stays at the bottom even if tbody has many rows.<br><br>Combine all sections for a fully structured, accessible table.",
|
|
||||||
"task": "Create a complete table:<br>1. A <kbd><caption></kbd> with <code>Order Summary</code><br>2. A <kbd><thead></kbd> with Item and Price headers<br>3. A <kbd><tbody></kbd> with 2 items<br>4. A <kbd><tfoot></kbd> with a Total row",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px 20px; font-weight: 600; font-size: 1.1rem; color: #333; text-align: left; background: #f8f9fa; border-bottom: 2px solid #eee; } th, td { padding: 12px 20px; text-align: left; } thead th { background: #2c3e50; color: white; } tbody td { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } tfoot { background: #ecf0f1; font-weight: 600; } tfoot td { border-top: 2px solid #2c3e50; color: #2c3e50; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "",
|
|
||||||
"solution": "<table>\n <caption>Order Summary</caption>\n <thead>\n <tr>\n <th>Item</th>\n <th>Price</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Widget</td>\n <td>$25.00</td>\n </tr>\n <tr>\n <td>Gadget</td>\n <td>$35.00</td>\n </tr>\n </tbody>\n <tfoot>\n <tr>\n <td>Total</td>\n <td>$60.00</td>\n </tr>\n </tfoot>\n</table>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "table",
|
|
||||||
"message": "Add a <kbd><table></kbd> element"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "caption",
|
|
||||||
"message": "Add a <kbd><caption></kbd> element"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "thead",
|
|
||||||
"message": "Add a <kbd><thead></kbd> section"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "tbody",
|
|
||||||
"message": "Add a <kbd><tbody></kbd> section"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_exists",
|
|
||||||
"value": "tfoot",
|
|
||||||
"message": "Add a <kbd><tfoot></kbd> section for the total"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "element_count",
|
|
||||||
"value": { "selector": "tbody tr", "min": 2 },
|
|
||||||
"message": "Add at least 2 item rows in tbody"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,15 +2,15 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-marquee",
|
"id": "html-marquee",
|
||||||
"title": "HTML Marquee",
|
"title": "HTML Marquee",
|
||||||
"description": "Create scrolling text with the classic (deprecated but fun!) marquee element",
|
"description": "Twórz przewijający się tekst za pomocą klasycznego (przestarzałego ale fajnego!) elementu marquee",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "marquee-basic",
|
"id": "marquee-basic",
|
||||||
"title": "Scrolling Text",
|
"title": "Przewijający się tekst",
|
||||||
"description": "The <kbd><marquee></kbd> element creates scrolling text - a classic from the early web! While deprecated, it still works in most browsers.<br><br>Note: For production, use CSS animations instead. But for learning and fun, marquee is great!",
|
"description": "Element <kbd><marquee></kbd> tworzy przewijający się tekst - klasyk z wczesnego internetu! Choć przestarzały, nadal działa w większości przeglądarek.<br><br>Uwaga: W produkcji używaj animacji CSS. Ale do nauki i zabawy marquee jest świetne!",
|
||||||
"task": "Create a simple marquee:<br>1. Add a <kbd><marquee></kbd> element<br>2. Put some text inside like <code>Welcome to my website!</code>",
|
"task": "Utwórz prosty marquee:<br>1. Dodaj element <kbd><marquee></kbd><br>2. Umieść w nim tekst jak <code>Witaj na mojej stronie!</code>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2rem; color: #00ff00; text-shadow: 0 0 10px #00ff00, 0 0 20px #00ff00; font-family: 'Courier New', monospace; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2rem; color: #00ff00; text-shadow: 0 0 10px #00ff00, 0 0 20px #00ff00; font-family: 'Courier New', monospace; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,15 +21,15 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "marquee",
|
"value": "marquee",
|
||||||
"message": "Add a <kbd><marquee></kbd> element"
|
"message": "Dodaj element <kbd><marquee></kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "marquee-direction",
|
"id": "marquee-direction",
|
||||||
"title": "Direction & Behavior",
|
"title": "Kierunek i zachowanie",
|
||||||
"description": "Control the marquee with attributes:<br>• <kbd>direction</kbd>: left, right, up, down<br>• <kbd>behavior</kbd>: scroll (default), slide (stops at edge), alternate (bounces)<br>• <kbd>scrollamount</kbd>: speed (default is 6)",
|
"description": "Kontroluj marquee za pomocą atrybutów:<br>• <kbd>direction</kbd>: left, right, up, down<br>• <kbd>behavior</kbd>: scroll (domyślnie), slide (zatrzymuje się na krawędzi), alternate (odbija się)<br>• <kbd>scrollamount</kbd>: prędkość (domyślnie 6)",
|
||||||
"task": "Create a bouncing marquee:<br>1. Add a <kbd><marquee></kbd> element<br>2. Set <kbd>behavior=\"alternate\"</kbd> to make it bounce<br>3. Add some fun text",
|
"task": "Utwórz odbijający się marquee:<br>1. Dodaj element <kbd><marquee></kbd><br>2. Ustaw <kbd>behavior=\"alternate\"</kbd> aby się odbijał<br>3. Dodaj jakiś fajny tekst",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2.5rem; color: white; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); font-weight: bold; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2.5rem; color: white; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); font-weight: bold; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -40,20 +40,20 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "marquee",
|
"value": "marquee",
|
||||||
"message": "Add a <kbd><marquee></kbd> element"
|
"message": "Dodaj element <kbd><marquee></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "marquee", "attr": "behavior", "value": "alternate" },
|
"value": { "selector": "marquee", "attr": "behavior", "value": "alternate" },
|
||||||
"message": "Add <kbd>behavior=</kbd>\"alternate\" to make it bounce"
|
"message": "Dodaj <kbd>behavior=</kbd>\"alternate\" aby się odbijał"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "marquee-retro",
|
"id": "marquee-retro",
|
||||||
"title": "Retro News Ticker",
|
"title": "Retro pasek wiadomości",
|
||||||
"description": "Combine multiple marquee attributes for a classic news ticker effect. You can even put multiple elements inside!<br><br>Remember: This is deprecated HTML. Modern sites use CSS animations, but marquee is great for understanding web history.",
|
"description": "Połącz wiele atrybutów marquee dla klasycznego efektu paska wiadomości. Możesz nawet umieścić wiele elementów w środku!<br><br>Pamiętaj: To przestarzały HTML. Nowoczesne strony używają animacji CSS, ale marquee jest świetne do zrozumienia historii internetu.",
|
||||||
"task": "Create a news ticker:<br>1. A <kbd><marquee></kbd> with <kbd>direction=\"left\"</kbd><br>2. Set <kbd>scrollamount=\"5\"</kbd> for smooth scrolling<br>3. Add a breaking news headline inside",
|
"task": "Utwórz pasek wiadomości:<br>1. Element <kbd><marquee></kbd> z <kbd>direction=\"left\"</kbd><br>2. Ustaw <kbd>scrollamount=\"5\"</kbd> dla płynnego przewijania<br>3. Dodaj w środku nagłówek z ostatniej chwili",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 0; margin: 0; background: #1a1a2e; } marquee { background: linear-gradient(90deg, #c0392b 0%, #e74c3c 50%, #c0392b 100%); padding: 15px 0; font-size: 1.3rem; color: white; font-weight: 500; text-transform: uppercase; letter-spacing: 1px; border-top: 3px solid #f1c40f; border-bottom: 3px solid #f1c40f; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 0; margin: 0; background: #1a1a2e; } marquee { background: linear-gradient(90deg, #c0392b 0%, #e74c3c 50%, #c0392b 100%); padding: 15px 0; font-size: 1.3rem; color: white; font-weight: 500; text-transform: uppercase; letter-spacing: 1px; border-top: 3px solid #f1c40f; border-bottom: 3px solid #f1c40f; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -64,17 +64,17 @@
|
|||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "marquee",
|
"value": "marquee",
|
||||||
"message": "Add a <kbd><marquee></kbd> element"
|
"message": "Dodaj element <kbd><marquee></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "marquee", "attr": "direction", "value": "left" },
|
"value": { "selector": "marquee", "attr": "direction", "value": "left" },
|
||||||
"message": "Add <kbd>direction=</kbd>\"left\" for horizontal scrolling"
|
"message": "Dodaj <kbd>direction=</kbd>\"left\" dla poziomego przewijania"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "marquee", "attr": "scrollamount", "value": "5" },
|
"value": { "selector": "marquee", "attr": "scrollamount", "value": "5" },
|
||||||
"message": "Add <kbd>scrollamount=</kbd>\"5\" for smooth speed"
|
"message": "Dodaj <kbd>scrollamount=</kbd>\"5\" dla płynnej prędkości"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,99 +2,169 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "html-svg",
|
"id": "html-svg",
|
||||||
"title": "HTML SVG",
|
"title": "HTML SVG",
|
||||||
"description": "Draw scalable vector graphics directly in HTML",
|
"description": "Rysuj skalowalne grafiki wektorowe bezpośrednio w HTML",
|
||||||
"mode": "html",
|
"mode": "html",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "svg-circle",
|
"id": "svg-circle",
|
||||||
"title": "Drawing Circles",
|
"title": "Rysowanie kół",
|
||||||
"description": "SVG (Scalable Vector Graphics) lets you draw shapes directly in HTML. The <kbd><svg></kbd> element is the container, with <kbd>width</kbd> and <kbd>height</kbd> attributes.<br><br>Use <kbd><circle></kbd> with <kbd>cx</kbd>, <kbd>cy</kbd> (center) and <kbd>r</kbd> (radius) to draw circles.",
|
"description": "SVG (Scalable Vector Graphics) pozwala rysować kształty bezpośrednio w HTML. Element <kbd><svg></kbd> jest kontenerem z atrybutami <kbd>width</kbd> i <kbd>height</kbd>.<br><br>Użyj <kbd><circle></kbd> z <kbd>cx</kbd>, <kbd>cy</kbd> (środek) i <kbd>r</kbd> (promień) do rysowania kół.",
|
||||||
"task": "Create an SVG with a circle:<br>1. An <kbd><svg></kbd> with width=\"200\" and height=\"200\"<br>2. A <kbd><circle></kbd> centered at (100,100) with radius 50<br>3. Add a <kbd>fill</kbd> color",
|
"task": "Utwórz SVG z kołem:<br>1. Element <kbd><svg></kbd> z width=\"200\" i height=\"200\"<br>2. Element <kbd><circle></kbd> wyśrodkowany w (100,100) z promieniem 50<br>3. Dodaj kolor <kbd>fill</kbd>",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; display: flex; justify-content: center; } svg { background: white; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; display: flex; justify-content: center; } svg { background: white; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"50\" fill=\"#3498db\" />\n</svg>",
|
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"50\" fill=\"steelblue\" />\n</svg>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "svg",
|
"value": "svg",
|
||||||
"message": "Add an <kbd><svg></kbd> element"
|
"message": "Dodaj element <kbd><svg></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "circle",
|
"value": "circle",
|
||||||
"message": "Add a <kbd><circle></kbd> element inside the SVG"
|
"message": "Dodaj element <kbd><circle></kbd> wewnątrz SVG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "svg", "attr": "width", "value": "200" },
|
||||||
|
"message": "Ustaw <kbd>width=</kbd>\"200\" w SVG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "svg", "attr": "height", "value": "200" },
|
||||||
|
"message": "Ustaw <kbd>height=</kbd>\"200\" w SVG"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "circle", "attr": "cx", "value": "100" },
|
"value": { "selector": "circle", "attr": "cx", "value": "100" },
|
||||||
"message": "Set <kbd>cx=</kbd>\"100\" for the circle's horizontal center"
|
"message": "Ustaw <kbd>cx=</kbd>\"100\" dla poziomego środka koła"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "attribute_value",
|
"type": "attribute_value",
|
||||||
"value": { "selector": "circle", "attr": "cy", "value": "100" },
|
"value": { "selector": "circle", "attr": "cy", "value": "100" },
|
||||||
"message": "Set <kbd>cy=</kbd>\"100\" for the circle's vertical center"
|
"message": "Ustaw <kbd>cy=</kbd>\"100\" dla pionowego środka koła"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "circle", "attr": "r", "value": "50" },
|
||||||
|
"message": "Ustaw <kbd>r=</kbd>\"50\" dla promienia koła"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "svg-rect-line",
|
"id": "svg-rect-line",
|
||||||
"title": "Rectangles & Lines",
|
"title": "Prostokąty i linie",
|
||||||
"description": "Draw rectangles with <kbd><rect></kbd> using <kbd>x</kbd>, <kbd>y</kbd>, <kbd>width</kbd>, <kbd>height</kbd>.<br><br>Draw lines with <kbd><line></kbd> using <kbd>x1</kbd>, <kbd>y1</kbd> (start) and <kbd>x2</kbd>, <kbd>y2</kbd> (end). Lines need a <kbd>stroke</kbd> color!",
|
"description": "Rysuj prostokąty za pomocą <kbd><rect></kbd> używając <kbd>x</kbd>, <kbd>y</kbd>, <kbd>width</kbd>, <kbd>height</kbd>.<br><br>Rysuj linie za pomocą <kbd><line></kbd> używając <kbd>x1</kbd>, <kbd>y1</kbd> (start) i <kbd>x2</kbd>, <kbd>y2</kbd> (koniec). Linie potrzebują koloru <kbd>stroke</kbd>!",
|
||||||
"task": "Create an SVG with:<br>1. An <kbd><svg></kbd> (200x150)<br>2. A <kbd><rect></kbd> at position (20,20) with size 80x60<br>3. A <kbd><line></kbd> from (120,30) to (180,90) with a stroke color",
|
"task": "Utwórz SVG z:<br>1. Elementem <kbd><svg></kbd> (200x150)<br>2. Elementem <kbd><rect></kbd> na pozycji (20,20) o rozmiarze 80x60<br>3. Elementem <kbd><line></kbd> od (120,30) do (180,90) z kolorem stroke",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 200px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 200px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<svg width=\"200\" height=\"150\">\n <rect x=\"20\" y=\"20\" width=\"80\" height=\"60\" fill=\"#e74c3c\" />\n <line x1=\"120\" y1=\"30\" x2=\"180\" y2=\"90\" stroke=\"#2c3e50\" stroke-width=\"3\" />\n</svg>",
|
"solution": "<svg width=\"200\" height=\"150\">\n <rect x=\"20\" y=\"20\" width=\"80\" height=\"60\" fill=\"tomato\" />\n <line x1=\"120\" y1=\"30\" x2=\"180\" y2=\"90\" stroke=\"slategray\" stroke-width=\"3\" />\n</svg>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "svg",
|
"value": "svg",
|
||||||
"message": "Add an <kbd><svg></kbd> element"
|
"message": "Dodaj element <kbd><svg></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "rect",
|
"value": "rect",
|
||||||
"message": "Add a <kbd><rect></kbd> element"
|
"message": "Dodaj element <kbd><rect></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "line",
|
"value": "line",
|
||||||
"message": "Add a <kbd><line></kbd> element"
|
"message": "Dodaj element <kbd><line></kbd>"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "svg", "attr": "width", "value": "200" },
|
||||||
|
"message": "Ustaw <kbd>width=</kbd>\"200\" w SVG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "svg", "attr": "height", "value": "150" },
|
||||||
|
"message": "Ustaw <kbd>height=</kbd>\"150\" w SVG"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "rect", "attr": "x", "value": "20" },
|
||||||
|
"message": "Ustaw <kbd>x=</kbd>\"20\" w rect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "rect", "attr": "y", "value": "20" },
|
||||||
|
"message": "Ustaw <kbd>y=</kbd>\"20\" w rect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "rect", "attr": "width", "value": "80" },
|
||||||
|
"message": "Ustaw <kbd>width=</kbd>\"80\" w rect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "rect", "attr": "height", "value": "60" },
|
||||||
|
"message": "Ustaw <kbd>height=</kbd>\"60\" w rect"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "line", "attr": "x1", "value": "120" },
|
||||||
|
"message": "Ustaw <kbd>x1=</kbd>\"120\" w line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "line", "attr": "y1", "value": "30" },
|
||||||
|
"message": "Ustaw <kbd>y1=</kbd>\"30\" w line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "line", "attr": "x2", "value": "180" },
|
||||||
|
"message": "Ustaw <kbd>x2=</kbd>\"180\" w line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "attribute_value",
|
||||||
|
"value": { "selector": "line", "attr": "y2", "value": "90" },
|
||||||
|
"message": "Ustaw <kbd>y2=</kbd>\"90\" w line"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "contains",
|
||||||
|
"value": "stroke",
|
||||||
|
"message": "Dodaj kolor <kbd>stroke</kbd> do line"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "svg-shapes",
|
"id": "svg-shapes",
|
||||||
"title": "Multiple Shapes",
|
"title": "Wiele kształtów",
|
||||||
"description": "Combine shapes to create simple graphics! Add <kbd>stroke</kbd> for outlines and <kbd>stroke-width</kbd> for thickness.<br><br>Use <kbd>fill=\"none\"</kbd> for hollow shapes. Shapes stack in order - later elements appear on top.",
|
"description": "Łącz kształty, aby tworzyć proste grafiki! Dodaj <kbd>stroke</kbd> dla konturów i <kbd>stroke-width</kbd> dla grubości.<br><br>Użyj <kbd>fill=\"none\"</kbd> dla pustych kształtów. Kształty nakładają się w kolejności - późniejsze elementy są na wierzchu.",
|
||||||
"task": "Create a simple face:<br>1. An <kbd><svg></kbd> (200x200)<br>2. A large <kbd><circle></kbd> for the face<br>3. Two small <kbd><circle></kbd> elements for eyes<br>4. A <kbd><line></kbd> for the smile",
|
"task": "Utwórz prostą twarz:<br>1. Element <kbd><svg></kbd> (200x200)<br>2. Duże <kbd><circle></kbd> dla twarzy<br>3. Dwa małe <kbd><circle></kbd> dla oczu<br>4. Element <kbd><line></kbd> dla uśmiechu",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); min-height: 250px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); min-height: 250px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"80\" fill=\"#f1c40f\" stroke=\"#e67e22\" stroke-width=\"4\" />\n <circle cx=\"70\" cy=\"80\" r=\"10\" fill=\"#2c3e50\" />\n <circle cx=\"130\" cy=\"80\" r=\"10\" fill=\"#2c3e50\" />\n <line x1=\"60\" y1=\"130\" x2=\"140\" y2=\"130\" stroke=\"#2c3e50\" stroke-width=\"4\" stroke-linecap=\"round\" />\n</svg>",
|
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"80\" fill=\"gold\" stroke=\"orange\" stroke-width=\"4\" />\n <circle cx=\"70\" cy=\"80\" r=\"10\" fill=\"darkslategray\" />\n <circle cx=\"130\" cy=\"80\" r=\"10\" fill=\"darkslategray\" />\n <line x1=\"60\" y1=\"130\" x2=\"140\" y2=\"130\" stroke=\"darkslategray\" stroke-width=\"4\" stroke-linecap=\"round\" />\n</svg>",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "svg",
|
"value": "svg",
|
||||||
"message": "Add an <kbd><svg></kbd> element"
|
"message": "Dodaj element <kbd><svg></kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_count",
|
"type": "element_count",
|
||||||
"value": { "selector": "circle", "min": 3 },
|
"value": { "selector": "circle", "min": 3 },
|
||||||
"message": "Add at least 3 circles (1 face + 2 eyes)"
|
"message": "Dodaj co najmniej 3 koła (1 twarz + 2 oczy)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "element_exists",
|
"type": "element_exists",
|
||||||
"value": "line",
|
"value": "line",
|
||||||
"message": "Add a <kbd><line></kbd> for the smile"
|
"message": "Dodaj <kbd><line></kbd> dla uśmiechu"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,18 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "flexbox",
|
"id": "flexbox",
|
||||||
"title": "CSS Flexbox",
|
"title": "CSS Flexbox",
|
||||||
"description": "Master the flexible box layout model for modern responsive designs",
|
"description": "Opanuj model elastycznego układu pudełkowego dla nowoczesnych responsywnych projektów",
|
||||||
"difficulty": "intermediate",
|
"difficulty": "intermediate",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "flexbox-1",
|
"id": "flexbox-1",
|
||||||
"title": "Container",
|
"title": "Container",
|
||||||
"description": "Learn how to create a flex container and understand the main and cross axes.",
|
"description": "Przed flexboxem nawet proste układy wymagały floatów, hacków pozycjonowania lub układów tabelarycznych. Flexbox (Flexible Box Layout) zrewolucjonizował CSS, dostarczając jednowymiarowy system układu zaprojektowany specjalnie do dystrybucji przestrzeni i wyrównywania zawartości.<br><br><strong>Jak to działa:</strong> Gdy ustawisz <kbd>display: flex</kbd> na elemencie, staje się on <em>kontenerem flex</em>. Jego bezpośrednie dzieci automatycznie stają się <em>elementami flex</em>, które płyną wzdłuż osi głównej (domyślnie poziomej). Ta jedna właściwość przekształca ułożone elementy blokowe w poziomy rząd.<br><br><strong>Dwie osie:</strong><br>• <em>Oś główna</em> – Główny kierunek przepływu elementów (row = lewo→prawo)<br>• <em>Oś poprzeczna</em> – Prostopadła do głównej (row = góra→dół)<br><br><pre>.nav {\n display: flex;\n}</pre>",
|
||||||
"task": "Add <kbd>display: flex</kbd> to <kbd>.wrap</kbd> to create a flexbox layout.",
|
"task": "To menu nawigacyjne układa się pionowo. Dodaj <kbd>display: flex</kbd>, aby ułożyć linki poziomo.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box'>2</div><div class='box'>3</div></div>",
|
"previewHTML": "<nav class=\"nav\"><a href=\"#\">Home</a><a href=\"#\">Products</a><a href=\"#\">About</a><a href=\"#\">Contact</a></nav>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .nav { background: #1a1a2e; padding: 1rem; } .nav a { color: white; text-decoration: none; padding: 8px 1rem; border-radius: 4px; } .nav a:hover { background: rgba(255,255,255,0.1); }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".wrap {\n ",
|
"codePrefix": ".nav {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "display: flex;",
|
"solution": "display: flex;",
|
||||||
@@ -21,61 +21,41 @@
|
|||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "display", "expected": "flex" },
|
||||||
"property": "display",
|
"message": "Ustaw <kbd>display: flex</kbd>"
|
||||||
"expected": "flex"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>display: flex</kbd>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-2",
|
"id": "flexbox-2",
|
||||||
"title": "Direction & Wrap",
|
"title": "Gap",
|
||||||
"description": "Control the direction and wrapping of flex items within a container.",
|
"description": "Właściwość <kbd>gap</kbd> dodaje równomierne odstępy między elementami flex bez potrzeby używania marginesów. Tworzy przestrzeń tylko między elementami, nie na krawędziach.",
|
||||||
"task": "Add <kbd>flex-direction: column</kbd> and <kbd>flex-wrap: wrap</kbd> to <kbd>.wrap</kbd>.",
|
"task": "Dodaj <kbd>gap: 1rem</kbd>, aby równomiernie rozmieścić linki nawigacyjne.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box'>2</div><div class='box'>3</div><div class='box'>4</div><div class='box'>5</div></div>",
|
"previewHTML": "<nav class=\"nav\"><a href=\"#\">Home</a><a href=\"#\">Products</a><a href=\"#\">About</a><a href=\"#\">Contact</a></nav>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; width: 3rem; height: 3rem; display: flex; align-items: center; justify-content: center; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .nav { background: #1a1a2e; padding: 1rem; display: flex; } .nav a { color: white; text-decoration: none; padding: 8px 1rem; border-radius: 4px; background: rgba(255,255,255,0.1); }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; height: 16rem; display: flex; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".wrap {\n ",
|
"codePrefix": ".nav {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "flex-direction: column;\n flex-wrap: wrap;",
|
"solution": "gap: 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "gap", "expected": "1rem" },
|
||||||
"property": "flex-direction",
|
"message": "Ustaw <kbd>gap: 1rem</kbd>"
|
||||||
"expected": "column"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>flex-direction: column</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "flex-wrap",
|
|
||||||
"expected": "wrap"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>flex-wrap: wrap</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-3",
|
"id": "flexbox-3",
|
||||||
"title": "Justify Content",
|
"title": "Justify Content",
|
||||||
"description": "Learn how to align flex items along the main axis of the flex container.",
|
"description": "<kbd>justify-content</kbd> rozmieszcza elementy wzdłuż osi głównej. Popularne wartości:<br>• <kbd>flex-start</kbd> – pakuj elementy na początku<br>• <kbd>flex-end</kbd> – pakuj na końcu<br>• <kbd>center</kbd> – wyśrodkuj elementy<br>• <kbd>space-between</kbd> – równa przestrzeń między elementami<br>• <kbd>space-around</kbd> – równa przestrzeń wokół elementów",
|
||||||
"task": "Add <kbd>justify-content: space-between</kbd> to <kbd>.wrap</kbd> to distribute the boxes evenly.",
|
"task": "Przesuń przycisk \"Login\" na prawą stronę, ustawiając <kbd>justify-content: space-between</kbd> na nawigacji.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box'>2</div><div class='box'>3</div></div>",
|
"previewHTML": "<nav class=\"nav\"><div class=\"links\"><a href=\"#\">Home</a><a href=\"#\">Products</a><a href=\"#\">About</a></div><a href=\"#\" class=\"login\">Login</a></nav>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; text-align: center; font-weight: bold; width: 3rem; height: 3rem; display: flex; align-items: center; justify-content: center; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .nav { background: #1a1a2e; padding: 1rem; display: flex; } .links { display: flex; gap: 8px; } .nav a { color: white; text-decoration: none; padding: 8px 1rem; border-radius: 4px; } .nav a:hover { background: rgba(255,255,255,0.1); } .login { background: steelblue; }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".wrap {\n ",
|
"codePrefix": ".nav {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "justify-content: space-between;",
|
"solution": "justify-content: space-between;",
|
||||||
@@ -83,26 +63,20 @@
|
|||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "justify-content", "expected": "space-between" },
|
||||||
"property": "justify-content",
|
"message": "Ustaw <kbd>justify-content: space-between</kbd>"
|
||||||
"expected": "space-between"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>justify-content: space-between</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-4",
|
"id": "flexbox-4",
|
||||||
"title": "Align Items",
|
"title": "Align Items",
|
||||||
"description": "Control how flex items are aligned along the cross axis of the flex container.",
|
"description": "<kbd>align-items</kbd> kontroluje wyrównanie na osi poprzecznej (pionowo gdy flex-direction to row). Wartości to:<br>• <kbd>stretch</kbd> – rozciągnij do wypełnienia (domyślnie)<br>• <kbd>flex-start</kbd> – wyrównaj do góry<br>• <kbd>flex-end</kbd> – wyrównaj do dołu<br>• <kbd>center</kbd> – wyśrodkuj pionowo",
|
||||||
"task": "Add <kbd>align-items: center</kbd> to <kbd>.wrap</kbd> to vertically center the boxes.",
|
"task": "Logo i linki nawigacyjne mają różne wysokości. Wyśrodkuj je pionowo za pomocą <kbd>align-items: center</kbd>.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box tall'>1</div><div class='box'>2</div><div class='box short'>3</div></div>",
|
"previewHTML": "<header class=\"header\"><div class=\"logo\">ACME</div><nav><a href=\"#\">Products</a><a href=\"#\">Pricing</a><a href=\"#\">Docs</a></nav></header>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; width: 3rem; display: flex; justify-content: center; } .tall { height: 6rem; } .short { height: 3rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .header { background: white; padding: 1rem 2rem; display: flex; justify-content: space-between; border-bottom: 1px solid #eee; } .logo { font-size: 1.5rem; font-weight: bold; color: steelblue; } nav { display: flex; gap: 1rem; } nav a { color: #333; text-decoration: none; font-size: 0.9rem; }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; height: 10rem; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".wrap {\n ",
|
"codePrefix": ".header {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "align-items: center;",
|
"solution": "align-items: center;",
|
||||||
@@ -110,62 +84,50 @@
|
|||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "align-items", "expected": "center" },
|
||||||
"property": "align-items",
|
"message": "Ustaw <kbd>align-items: center</kbd>"
|
||||||
"expected": "center"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>align-items: center</kbd>",
|
|
||||||
"options": {
|
|
||||||
"exact": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-5",
|
"id": "flexbox-5",
|
||||||
"title": "Flex Grow",
|
"title": "Flex Wrap",
|
||||||
"description": "The <kbd>flex</kbd> property controls how much an item grows relative to others.",
|
"description": "Domyślnie elementy flex ściskają się w jednej linii. <kbd>flex-wrap: wrap</kbd> pozwala elementom przenosić się na kolejne linie, gdy zabraknie miejsca.",
|
||||||
"task": "Add <kbd>flex: 2</kbd> to <kbd>.box2</kbd> to make it grow twice as wide.",
|
"task": "Te karty wychodzą poza kontener. Dodaj <kbd>flex-wrap: wrap</kbd>, aby mogły przenosić się do nowych wierszy.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box box1'>1</div><div class='box box2'>2</div><div class='box box3'>3</div></div>",
|
"previewHTML": "<div class=\"cards\"><article class=\"card\">Card 1</article><article class=\"card\">Card 2</article><article class=\"card\">Card 3</article><article class=\"card\">Card 4</article><article class=\"card\">Card 5</article><article class=\"card\">Card 6</article></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; display: flex; align-items: center; justify-content: center; } .box1 { background: coral; flex: 1; } .box2 { background: mediumseagreen; } .box3 { background: gold; flex: 1; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .cards { display: flex; gap: 1rem; } .card { background: white; padding: 2rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); min-width: 120px; text-align: center; }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".box2 {\n ",
|
"codePrefix": ".cards {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "flex: 2;",
|
"solution": "flex-wrap: wrap;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "flex-wrap", "expected": "wrap" },
|
||||||
"property": "flex",
|
"message": "Ustaw <kbd>flex-wrap: wrap</kbd>"
|
||||||
"expected": "2"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>flex: 2</kbd>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "flexbox-6",
|
"id": "flexbox-6",
|
||||||
"title": "Align Self",
|
"title": "Flex Grow",
|
||||||
"description": "Use <kbd>align-self</kbd> to override alignment for a single flex item.",
|
"description": "Właściwość <kbd>flex</kbd> na elementach kontroluje ich rozrastanie i kurczenie się. <kbd>flex: 1</kbd> sprawia, że element rozrasta się, aby wypełnić dostępną przestrzeń. Wiele elementów z <kbd>flex: 1</kbd> dzieli przestrzeń równomiernie.",
|
||||||
"task": "Add <kbd>align-self: flex-start</kbd> to <kbd>.middle</kbd> to move it to the top.",
|
"task": "Spraw, aby pole wyszukiwania rozszerzyło się i wypełniło dostępną przestrzeń, ustawiając <kbd>flex: 1</kbd> na <kbd>.search</kbd>.",
|
||||||
"previewHTML": "<div class='wrap'><div class='box'>1</div><div class='box middle'>2</div><div class='box'>3</div></div>",
|
"previewHTML": "<div class=\"toolbar\"><input class=\"search\" type=\"text\" placeholder=\"Search...\"><button class=\"btn\">Search</button><button class=\"btn\">Filters</button></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background: steelblue; color: white; padding: 1rem; margin: 8px; text-align: center; font-weight: bold; width: 3rem; height: 3rem; display: flex; align-items: center; justify-content: center; } .middle { background: mediumseagreen; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .toolbar { display: flex; gap: 8px; padding: 1rem; background: #f5f5f5; border-radius: 8px; } .search { padding: 8px 1rem; border: 1px solid #ddd; border-radius: 4px; font-size: 1rem; } .btn { padding: 8px 1rem; background: steelblue; color: white; border: none; border-radius: 4px; cursor: pointer; }",
|
||||||
"sandboxCSS": ".wrap { border: 2px dashed #aaa; padding: 1rem; display: flex; height: 12rem; align-items: center; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".middle {\n ",
|
"codePrefix": ".search {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "align-self: flex-start;",
|
"solution": "flex: 1;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "flex", "expected": "1" },
|
||||||
"property": "align-self",
|
"message": "Ustaw <kbd>flex: 1</kbd>"
|
||||||
"expected": "flex-start"
|
|
||||||
},
|
|
||||||
"message": "Set <kbd>align-self: flex-start</kbd>"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,548 +1,257 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "css-basic-selectors",
|
"id": "css-basic-selectors",
|
||||||
"title": "CSS Selectors",
|
"title": "Основи CSS",
|
||||||
"description": "CSS selectors are the foundation of styling web pages, allowing you to target specific HTML elements for styling. This module introduces fundamental selector types including element type selectors, class selectors, ID selectors, and the universal selector.",
|
"description": "Вивчіть основні будівельні блоки CSS: властивості, значення та селектори. Цей модуль навчає правилам синтаксису, яких дотримується кожна декларація CSS.",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "introduction-to-selectors",
|
"id": "css-properties",
|
||||||
"title": "What's a Selector?",
|
"title": "Властивості CSS",
|
||||||
"description": "A CSS selector is the first part of a CSS rule that tells the browser which HTML elements should receive the styles defined in the declaration block. Selectors are essentially patterns that match against elements in your HTML document. Understanding selectors is fundamental because they determine which elements your CSS rules will affect. The element or elements targeted by a selector are referred to as the 'subject of the selector.' When writing a CSS rule, you first specify the selector, followed by curly braces that contain the style declarations.<br/>For example, to change the text color of elements, you can use the <kbd>color</kbd> property within your declaration block.<br><br><pre>/* Element selector */\np {\n color: orangered;\n /* │ └─── Indicates the value of the expression\n │ \n └─────────── Indicates the property of the expression */\n}</pre>",
|
"description": "CSS стилізує елементи за допомогою <strong>декларацій</strong> - пар властивостей і значень. Кожна декларація має той самий шаблон:<br><br><pre>property: value;</pre><br><strong>Властивість</strong> - це те, що ви хочете змінити (як <kbd>color</kbd> або <kbd>background</kbd>). <strong>Значення</strong> - це те, на що ви це встановлюєте. Двокрапка їх розділяє, а крапка з комою закінчує рядок.<br><br>Значення бувають різних типів:<br>• <strong>Ключові слова:</strong> <kbd>red</kbd>, <kbd>bold</kbd>, <kbd>center</kbd><br>• <strong>Числа з одиницями:</strong> <kbd>16px</kbd>, <kbd>2rem</kbd>, <kbd>100%</kbd><br>• <strong>Кольори:</strong> <kbd>steelblue</kbd>, <kbd>#ff0000</kbd>",
|
||||||
"task": "Write a CSS rule using a type selector that targets all paragraph elements <kbd>p</kbd> in the document. Make the text blue by setting the <kbd>color</kbd> property to <kbd>blue</kbd>.",
|
"task": "Завершіть декларацію, додавши <kbd>color: coral;</kbd> щоб змінити колір тексту.",
|
||||||
"previewHTML": "<h1>Introduction to CSS Selectors</h1>\n<p>This paragraph should turn blue.</p>\n<div>This div element should remain unchanged.</div>\n<p>This second paragraph should also turn blue.</p>",
|
"previewHTML": "<p class=\"text\">This text should turn coral.</p>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } .text { font-size: 1.25rem; }",
|
||||||
"sandboxCSS": "h1, p, div { padding: 8px; margin-bottom: 10px; border: 1px dashed #ccc; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Write a type selector to target all paragraph elements */\n",
|
"codePrefix": ".text {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "\n}",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"solution": "p { color: blue }",
|
"solution": "color: coral;",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "property_value",
|
||||||
"value": "^p\\s*{",
|
"value": { "property": "color", "expected": "coral" },
|
||||||
"message": "Start your rule with <kbd>p { … }</kbd> to select all paragraph elements",
|
"message": "Додайте <kbd>color: coral;</kbd>"
|
||||||
"options": {
|
}
|
||||||
"caseSensitive": false
|
]
|
||||||
}
|
},
|
||||||
},
|
{
|
||||||
|
"id": "multiple-properties",
|
||||||
|
"title": "Кілька властивостей",
|
||||||
|
"description": "Правило може мати кілька декларацій. Кожна йде в окремому рядку, і кожна потребує крапки з комою в кінці:<br><br><pre>.box {<br> background: gold;<br> color: navy;<br> padding: 1rem;<br>}</pre><br>Порядок зазвичай не має значення - CSS застосовує всі. При конфліктах перемагає остання.",
|
||||||
|
"task": "Додайте дві декларації: <kbd>background: lavender;</kbd> та <kbd>padding: 1rem;</kbd>",
|
||||||
|
"previewHTML": "<div class=\"card\">A styled card with background and padding.</div>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } .card { border-radius: 8px; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": ".card {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "background: lavender;\n padding: 1rem;",
|
||||||
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "color:",
|
"value": { "property": "background", "expected": "lavender" },
|
||||||
"message": "Include the <kbd>color:</kbd> property in your CSS rule"
|
"message": "Додайте <kbd>background: lavender;</kbd>"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "blue",
|
|
||||||
"message": "Set the color value to <kbd>blue</kbd>"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"property": "color",
|
"message": "Додайте <kbd>padding: 1rem;</kbd>"
|
||||||
"expected": "blue"
|
|
||||||
},
|
|
||||||
"message": "Use <kbd>color: blue</kbd> to set the text color"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "p\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": false
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "type-selectors",
|
"id": "type-selectors",
|
||||||
"title": "Type Selectors",
|
"title": "Селектори типу",
|
||||||
"description": "Type selectors (also called tag name selectors or element selectors) target HTML elements based on their tag name. For example, <kbd>p</kbd> selects all paragraph elements, <kbd>h1</kbd> selects all level-one headings, and <kbd>div</kbd> selects all division elements. Type selectors are the most fundamental way to select elements, applying styles consistently to all instances of a particular HTML element throughout your document. You can define a variety of CSS properties with type selectors, such as <kbd>color</kbd> for text color, <kbd>background-color</kbd> for the background, and <kbd>font-weight</kbd> for text emphasis. They provide a broad approach for styling your page and are often the starting point for more specific styling using other selector types.",
|
"description": "<strong>Селектор</strong> говорить браузеру, які елементи стилізувати. Найпростіший селектор - це <strong>селектор типу</strong> — просто назва HTML-тегу.<br><br><pre>p {<br> color: steelblue;<br>}</pre><br>Це правило націлюється на кожен елемент <kbd><p></kbd> на сторінці. Селектори типу чудово підходять для встановлення базових стилів.",
|
||||||
"task": "Write three separate CSS rules using type selectors to target specific HTML elements: make <kbd>h2</kbd> headings <kbd>purple</kbd>, give <kbd>span</kbd> elements a <kbd>yellow</kbd> background, and make <kbd>strong</kbd> elements <kbd>red</kbd>.",
|
"task": "Стилізуйте всі абзаци. Напишіть правило з <kbd>p</kbd> як селектором і встановіть <kbd>color: steelblue</kbd>.",
|
||||||
"previewHTML": "<h2>Type Selectors Example</h2>\n<p>Regular paragraph text <span>with a highlighted span</span> that should have a yellow background.</p>\n<p>Another paragraph with <strong>strong important text</strong> that should be red.</p>\n<h2>Another Heading</h2>",
|
"previewHTML": "<article>\n <h2>Fresh Roasted Coffee</h2>\n <p>Our beans are sourced from small farms in Colombia and Ethiopia.</p>\n <p>Each batch is roasted weekly to ensure peak freshness.</p>\n</article>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } h2 { margin: 0 0 1rem; }",
|
||||||
"sandboxCSS": "h2, p, span, strong { padding: 3px; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Write three separate type selectors below */\n\n",
|
"codePrefix": "",
|
||||||
"initialCode": "/* 1. Make h2 headings purple */\n\n\n/* 2. Give span elements a yellow background */\n\n\n/* 3. Make strong elements red */\n",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"solution": "/* 1. Make h2 headings purple */\nh2 {\n color: purple;\n}\n\n/* 2. Give span elements a yellow background */\nspan {\n background-color: yellow;\n}\n\n/* 3. Make strong elements red */\nstrong {\n color: red;\n}",
|
"solution": "p {\n color: steelblue;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^h2\\s*{",
|
"value": "p\\s*\\{",
|
||||||
"message": "Include an <kbd>h2 { … }</kbd> selector"
|
"message": "Почніть з <kbd>p {</kbd> щоб вибрати абзаци"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "color", "expected": "steelblue" },
|
||||||
"property": "color",
|
"message": "Встановіть <kbd>color: steelblue</kbd>"
|
||||||
"expected": "purple"
|
}
|
||||||
},
|
]
|
||||||
"message": "Set the <kbd>color</kbd> property to <kbd>purple</kbd> for h2 elements"
|
},
|
||||||
},
|
{
|
||||||
|
"id": "styling-links",
|
||||||
|
"title": "Стилізація посилань",
|
||||||
|
"description": "Селектори типу працюють для будь-якого HTML-елемента. Селектор <kbd>a</kbd> націлюється на всі посилання на сторінці.<br><br>Посилання мають синій колір і підкреслення за замовчуванням. Ви можете змінити обидва з CSS — використовуйте <kbd>color</kbd> для тексту і <kbd>text-decoration: none</kbd> щоб прибрати підкреслення.",
|
||||||
|
"task": "Стилізуйте навігаційні посилання. Напишіть правило з <kbd>a</kbd> як селектором і встановіть <kbd>color: coral</kbd>.",
|
||||||
|
"previewHTML": "<nav>\n <a href=\"#\">Home</a>\n <a href=\"#\">Menu</a>\n <a href=\"#\">About</a>\n <a href=\"#\">Contact</a>\n</nav>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } nav { display: flex; gap: 1.5rem; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "a {\n color: coral;\n}",
|
||||||
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "h2\\s*{[^}]*}",
|
"value": "a\\s*\\{",
|
||||||
"message": "Make sure to close your h2 rule with a closing brace <kbd>}</kbd>"
|
"message": "Почніть з <kbd>a {</kbd> щоб вибрати посилання"
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^span\\s*{",
|
|
||||||
"message": "Include a <kbd>span { … }</kbd> selector"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "color", "expected": "coral" },
|
||||||
"property": "background-color",
|
"message": "Встановіть <kbd>color: coral</kbd>"
|
||||||
"expected": "yellow"
|
|
||||||
},
|
|
||||||
"message": "Set a <kbd>background-color: yellow</kbd> for span elements"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "span\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your span rule with a closing brace <kbd>}</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^strong\\s*{",
|
|
||||||
"message": "Include a <kbd>strong { … }</kbd> selector"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "strong\\s*{\\s*color:\\s*red;[^}]*}",
|
|
||||||
"message": "Set the <kbd>color: red</kbd> for strong elements"
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "class-selectors",
|
"id": "class-selectors",
|
||||||
"title": "Class Selectors",
|
"title": "Селектори класів",
|
||||||
"description": "Class selectors target elements with a specific class attribute value. They begin with a dot (.) followed by the class name. Classes are powerful because they allow you to apply the same styles to multiple elements regardless of their type. An HTML element can have multiple classes (separated by spaces in the class attribute), and a class can be applied to any number of elements. When using class selectors, you can apply properties like <kbd>background-color</kbd> to set the background color of elements, and <kbd>font-weight</kbd> to control text thickness, making text bold or normal. This flexibility makes class selectors one of the most commonly used methods for applying styles in CSS, allowing for modular and reusable styling across your website.",
|
"description": "Селектори типу стилізують <em>усі</em> елементи цього типу. Але що якщо ви хочете стилізувати лише деякі з них?<br><br><strong>Селектори класів</strong> націлюються на елементи з певним атрибутом <kbd>class</kbd>. Вони починаються з крапки:<br><br><pre>.badge {<br> background: coral;<br>}</pre><br>Це стилізує лише елементи з <kbd>class=\"badge\"</kbd>.",
|
||||||
"task": "Create a CSS rule using a class selector that targets elements with the class <kbd>highlight</kbd>. Give these elements a <kbd>yellow</kbd> background and <kbd>bold</kbd> text.",
|
"task": "Стилізуйте значок сповіщень. Напишіть правило з <kbd>.badge</kbd> як селектором і встановіть <kbd>background: tomato</kbd>.",
|
||||||
"previewHTML": "<h2>Using Class Selectors</h2>\n<p>This is a regular paragraph, but <span class=\"highlight\">this span has the highlight class</span> applied to it.</p>\n<p class=\"highlight\">This entire paragraph has the highlight class.</p>\n<ul>\n <li>Regular list item</li>\n <li class=\"highlight\">This list item is highlighted</li>\n</ul>",
|
"previewHTML": "<header>\n <h1>Dashboard</h1>\n <span class=\"badge\">3</span>\n</header>\n<p>You have new notifications waiting.</p>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } header { display: flex; align-items: center; gap: 0.75rem; margin-bottom: 1rem; } h1 { margin: 0; font-size: 1.5rem; } .badge { color: white; padding: 0.25rem 0.5rem; border-radius: 999px; font-size: 0.875rem; } p { color: #555; margin: 0; }",
|
||||||
"sandboxCSS": "h2, p, li { padding: 5px; margin-bottom: 10px; }",
|
|
||||||
"codePrefix": "/* Create a class selector for elements with the 'highlight' class */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^\\.highlight\\s*{",
|
|
||||||
"message": "Start your rule with <kbd>.highlight { … }</kbd> to create a class selector",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Include the <kbd>background-color:</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "yellow"
|
|
||||||
},
|
|
||||||
"message": "Set the background color to <kbd>yellow</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "font-weight:",
|
|
||||||
"message": "Include the <kbd>font-weight:</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "font-weight",
|
|
||||||
"expected": "bold"
|
|
||||||
},
|
|
||||||
"message": "Set the font-weight to <kbd>bold</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "\\.highlight\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "multiple-classes",
|
|
||||||
"title": "Multiple Classes",
|
|
||||||
"description": "HTML elements can have multiple classes applied simultaneously, allowing for composable and modular CSS designs. When an element has multiple classes, it will receive styles from all matching class selectors. This approach enables you to build a library of reusable CSS classes that can be combined in different ways. You can also target elements that have a specific combination of classes by chaining class selectors together without spaces (e.g., <kbd>.class1.class2</kbd>). When styling these elements, you might use properties like <kbd>border-color</kbd> to change the color of element borders, and <kbd>background-color</kbd> to set the background color of elements. This technique lets you create conditional styles that only apply when certain classes appear together.",
|
|
||||||
"task": "Complete the CSS rule that targets elements with both <kbd>card</kbd> and <kbd>featured</kbd> classes by chaining the selectors. Set the border-color to gold and the background-color to lemonchiffon to make featured cards stand out.",
|
|
||||||
"previewHTML": "<h2>Multiple Class Combinations</h2>\n<div class=\"card\">Regular Card</div>\n<div class=\"card featured\">Featured Card</div>\n<div class=\"featured\">Just Featured (not a card)</div>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } .card { border: 2px solid gray; padding: 15px; margin-bottom: 10px; border-radius: 5px; }",
|
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* The .card class already has basic styling */\n/* Now target elements with BOTH classes: 'card' AND 'featured' */\n",
|
"codePrefix": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"solution": ".card.featured { border-color: gold; background-color: lemonchiffon }",
|
"solution": ".badge {\n background: tomato;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^\\.card\\.featured\\s*{",
|
"value": "\\.badge\\s*\\{",
|
||||||
"message": "Chain the selectors as <kbd>.card.featured</kbd> (no space between them)",
|
"message": "Почніть з <kbd>.badge {</kbd> (не забудьте крапку!)"
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "border-color:",
|
|
||||||
"message": "Include the <kbd>border-color</kbd> property"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "background", "expected": "tomato" },
|
||||||
"property": "border-color",
|
"message": "Встановіть <kbd>background: tomato</kbd>"
|
||||||
"expected": "gold"
|
|
||||||
},
|
|
||||||
"message": "Set the border color to <kbd>gold</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "\\.card\\.featured\\s*{[^}]*;",
|
|
||||||
"message": "Make sure to end your CSS rule with a semicolon <kbd>;</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Include the <kbd>background-color</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "lemonchiffon"
|
|
||||||
},
|
|
||||||
"message": "Set the background color to <kbd>lemonchiffon</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "\\.card\\.featured\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "class-with-type",
|
"id": "button-variants",
|
||||||
"title": "Combining Types",
|
"title": "Варіанти кнопок",
|
||||||
"description": "You can combine type selectors with class selectors to target specific HTML elements that have a certain class. This creates a more specific selector that only matches when both conditions are true: the element is of the specified type AND it has the specified class. For example, <kbd>p.note</kbd> would select paragraph elements with the class <kbd>note</kbd>, but would not select divs or spans with that same class. You can style these combined selections using properties like <kbd>background-color</kbd> to set a colored background for your elements. This approach allows you to apply different styles to the same class when it appears on different element types.",
|
"description": "Елементи можуть мати кілька класів. Коли ви з'єднуєте селектори класів без пробілів, ви націлюєтесь на елементи, які мають <em>усі</em> ці класи:<br><br><pre>.btn.primary {<br> background: steelblue;<br>}</pre><br>Це націлюється на елементи з <kbd>class=\"btn primary\"</kbd>, а не лише <kbd>.btn</kbd> або лише <kbd>.primary</kbd>.",
|
||||||
"task": "Create a CSS rule that specifically targets <kbd><span></kbd> elements with the class <kbd>highlight</kbd>. Make those elements have an orange background, while other elements with the highlight class remain untouched.",
|
"task": "Стилізуйте основну кнопку. Напишіть правило з <kbd>.btn.primary</kbd> як селектором і встановіть <kbd>background: steelblue</kbd>.",
|
||||||
"previewHTML": "<h2>Type and Class Combinations</h2>\n<p>This paragraph has a <span class=\"highlight\">highlighted span</span> that should have an orange background.</p>\n<p class=\"highlight\">This paragraph has the highlight class but should NOT have an orange background.</p>",
|
"previewHTML": "<div class=\"actions\">\n <button class=\"btn\">Cancel</button>\n <button class=\"btn primary\">Save Changes</button>\n</div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } .highlight { font-weight: bold; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } .actions { display: flex; gap: 0.75rem; } .btn { padding: 0.5rem 1rem; border: none; border-radius: 6px; font-size: 1rem; cursor: pointer; background: #e0e0e0; color: #333; }",
|
||||||
"sandboxCSS": "h2, p, span { padding: 5px; }",
|
|
||||||
"codePrefix": "/* The .highlight class already sets font-weight to bold */\n/* Now target ONLY span elements with the highlight class */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^span\\.highlight\\s*{",
|
|
||||||
"message": "Use <kbd>span.highlight</kbd> selector (no space between element and class)",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Include the <kbd>background-color</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "orange"
|
|
||||||
},
|
|
||||||
"message": "Set the background color to <kbd>orange</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "span\\.highlight\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "id-selectors",
|
|
||||||
"title": "ID Selectors",
|
|
||||||
"description": "ID selectors target elements with a specific id attribute. They begin with a hash/pound sign (#) followed by the ID name. Unlike classes, IDs must be unique within a document—each ID value should be used only once per page. ID selectors have higher specificity than class or element selectors, meaning they override those selectors when conflicts arise. When styling with ID selectors, you can use properties like <kbd>color</kbd> to define text color, and <kbd>text-decoration</kbd> to control the appearance of text, such as adding underlines to elements. Because of their uniqueness requirement, IDs are best used for one-of-a-kind elements like page headers, main navigation, or specific unique components that appear only once on a page.",
|
|
||||||
"task": "Create a CSS rule with an ID selector that targets the element with the ID <kbd>main-title</kbd>. Set its color to purple and add an underline with <kbd>text-decoration: underline</kbd>.",
|
|
||||||
"previewHTML": "<h1 id=\"main-title\">Main Page Title</h1>\n<p>Regular paragraph content.</p>\n<h2>Secondary Heading</h2>\n<p id=\"intro\">Introduction paragraph (different ID).</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
|
||||||
"sandboxCSS": "h1, h2, p { padding: 8px; margin-bottom: 10px; border: 1px dashed #ccc; }",
|
|
||||||
"codePrefix": "/* Create an ID selector to target the element with id=\"main-title\" */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^#main-title\\s*{",
|
|
||||||
"message": "Start your rule with <kbd>#main-title</kbd> to create an ID selector",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "color:",
|
|
||||||
"message": "Include the <kbd>color</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "color",
|
|
||||||
"expected": "purple"
|
|
||||||
},
|
|
||||||
"message": "Set the color to <kbd>purple</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "text-decoration:",
|
|
||||||
"message": "Include the <kbd>text-decoration</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "text-decoration",
|
|
||||||
"expected": "underline"
|
|
||||||
},
|
|
||||||
"message": "Set the text-decoration to <kbd>underline</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "#main-title\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "id-with-type",
|
|
||||||
"title": "Type + ID",
|
|
||||||
"description": "Similar to how you can combine type and class selectors, you can also combine type selectors with ID selectors. For example, <kbd>h1#title</kbd> targets an h1 element with the ID 'title'. When using this combined approach, you can apply CSS properties like <kbd>font-style</kbd> to control the slant of the text, making it italic or normal. While this selector combination is more specific than using just the ID selector, it's often unnecessary since IDs should already be unique in a document. However, this technique can be useful for improving code readability or when you want to emphasize that a particular ID should only appear on a specific element type.",
|
|
||||||
"task": "Create a CSS rule that combines a type selector with an ID selector to target specifically a paragraph element with the ID <kbd>special</kbd>. Set its font style to italic.",
|
|
||||||
"previewHTML": "<h2 id=\"special\">Heading with ID \"special\" (should NOT be affected)</h2>\n<p id=\"special\">Paragraph with ID \"special\" (should become italic)</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
|
||||||
"sandboxCSS": "h2, p { padding: 8px; margin-bottom: 10px; border: 1px dashed #ccc; }",
|
|
||||||
"codePrefix": "/* Create a combined type+ID selector for a paragraph with id=\"special\" */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "^p#special\\s*{",
|
|
||||||
"message": "Use <kbd>p#special</kbd> to target paragraphs with ID <kbd>special</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "font-style:",
|
|
||||||
"message": "Include the <kbd>font-style</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "font-style",
|
|
||||||
"expected": "italic"
|
|
||||||
},
|
|
||||||
"message": "Set the font-style to <kbd>italic</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "p#special\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "selector-lists",
|
|
||||||
"title": "Selector Lists",
|
|
||||||
"description": "When multiple elements need the same styling, you can group them together using a selector list (also known as grouping selectors). Selector lists are created by separating individual selectors with commas. This approach reduces repetition in your CSS, making it more maintainable and efficient. For example, <kbd>h1, h2, h3 { color: blue; }</kbd> applies the same blue color to all three heading levels. When styling multiple selectors at once, you can apply properties like <kbd>background-color</kbd> to set the background, <kbd>border-left</kbd> to create a left border with a specific thickness, style, and color, and <kbd>padding-left</kbd> to create space between the content and the left border. Whitespace around commas is optional, and each selector in the list can be any valid selector type-elements, classes, IDs, or even more complex selectors.",
|
|
||||||
"task": "Create a selector list that applies the same styles to three different elements: paragraphs with class <kbd>note</kbd>, list items with class <kbd>important</kbd>, and the element with ID <kbd>summary</kbd>. Give them a <kbd>lightyellow</kbd> background, a <kbd>gold</kbd> left border, and some left <kbd>padding</kbd>.",
|
|
||||||
"previewHTML": "<p class=\"note\">This is a note paragraph.</p>\n<ul>\n <li>Regular list item</li>\n <li class=\"important\">Important list item</li>\n</ul>\n<div id=\"summary\">Summary section</div>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
|
||||||
"sandboxCSS": "p, li, div { padding: 8px; margin-bottom: 8px; border: 1px dashed gray; }",
|
|
||||||
"codePrefix": "/* Create a selector list to apply the same styles to multiple different elements */\n",
|
|
||||||
"initialCode": "",
|
|
||||||
"codeSuffix": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"solution": "p.note,\nli.important,\n#summary {\n background-color: lightyellow;\n border-left: 3px solid gold;\n padding-left: 10px\n}",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "p.note",
|
|
||||||
"message": "Include <kbd>p.note</kbd> in your selector list",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "li.important",
|
|
||||||
"message": "Include <kbd>li.important</kbd> in your selector list",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "#summary",
|
|
||||||
"message": "Include <kbd>#summary</kbd> in your selector list",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "(p\\.note|li\\.important|#summary)\\s*,\\s*(p\\.note|li\\.important|#summary)\\s*,\\s*(p\\.note|li\\.important|#summary)",
|
|
||||||
"message": "Create a comma-separated list with all three selectors",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "background-color:",
|
|
||||||
"message": "Include the <kbd>background-color</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "background-color",
|
|
||||||
"expected": "lightyellow"
|
|
||||||
},
|
|
||||||
"message": "Set the background color to <kbd>lightyellow</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "border-left:",
|
|
||||||
"message": "Include the <kbd>border-left</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "border-left",
|
|
||||||
"expected": "3px solid gold"
|
|
||||||
},
|
|
||||||
"message": "Use <kbd>border-left: 3px solid gold</kbd> to create a left border"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "padding-left:",
|
|
||||||
"message": "Include the <kbd>padding-left</kbd> property"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "property_value",
|
|
||||||
"value": {
|
|
||||||
"property": "padding-left",
|
|
||||||
"expected": "10px"
|
|
||||||
},
|
|
||||||
"message": "Use <kbd>padding-left: 10px</kbd> to add left padding"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "universal-selector",
|
|
||||||
"title": "Universal (*)",
|
|
||||||
"description": "The universal selector is denoted by an asterisk (*) and matches any element of any type. It selects everything in the document or, when combined with other selectors, everything within a specific context. For example, <kbd>* { margin: 0; }</kbd> removes margins from all elements, while <kbd>article *</kbd> selects all elements inside article elements. When using the universal selector in combination with other selectors, you can apply properties like <kbd>margin</kbd> to control the spacing around elements. The universal selector is powerful but should be used carefully due to its broad impact. It's commonly used in CSS resets, to override default browser styling, or to target all children of a particular element.",
|
|
||||||
"task": "Use the universal selector to remove margins from all elements inside the container div. Create a rule using <kbd>div.container *</kbd> as the selector and set <kbd>margin: 0</kbd>.",
|
|
||||||
"previewHTML": "<div class=\"container\">\n <h2>Inside Container</h2>\n <p>This paragraph is inside the container.</p>\n <ul>\n <li>List item inside container</li>\n </ul>\n</div>\n<p>This paragraph is outside the container and should not be affected.</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } div.container { border: 2px solid navy; padding: 15px; background-color: lavender; } h2, p, ul, li { margin: 15px 0; }",
|
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Use the universal selector to target all elements inside the container */\n",
|
"codePrefix": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
|
"solution": ".btn.primary {\n background: steelblue;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^div\\.container\\s+\\*\\s*{",
|
"value": "\\.btn\\.primary\\s*\\{",
|
||||||
"message": "Use <kbd>div.container *</kbd> selector (with a space between container and *)",
|
"message": "Використовуйте <kbd>.btn.primary {</kbd> (без пробілу між класами)"
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "margin:",
|
|
||||||
"message": "Include the <kbd>margin</kbd> property"
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": {
|
"value": { "property": "background", "expected": "steelblue" },
|
||||||
"property": "margin",
|
"message": "Встановіть <kbd>background: steelblue</kbd>"
|
||||||
"expected": "0"
|
|
||||||
},
|
|
||||||
"message": "Set margin to <kbd>0</kbd>"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"type": "regex",
|
|
||||||
"value": "div\\.container\\s+\\*\\s*{[^}]*}",
|
|
||||||
"message": "Make sure to close your CSS rule with a closing brace <kbd>}</kbd>",
|
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "specificity-basics",
|
"id": "specific-elements",
|
||||||
"title": "Specificity",
|
"title": "Націлювання на конкретні елементи",
|
||||||
"description": "CSS specificity determines which styles take precedence when multiple conflicting rules target the same element. Specificity follows a hierarchical system: inline styles have the highest specificity, followed by ID selectors, then class/attribute/pseudo-class selectors, and finally element/pseudo-element selectors. This can be conceptualized as a four-part score (inline, ID, class, element). When creating multiple rules that may target the same elements, you can use the <kbd>color</kbd> property to set text colors, and specificity will determine which color is actually applied. Understanding specificity is crucial for predictable styling and debugging CSS conflicts. When two selectors have equal specificity, the one that comes last in the stylesheet wins.",
|
"description": "Іноді ви хочете, щоб клас виглядав по-різному на різних елементах. Поєднайте селектор типу з селектором класу (без пробілу) щоб бути більш специфічним:<br><br><pre>a.btn {<br> text-decoration: none;<br>}</pre><br>Це стилізує лише елементи <kbd><a></kbd> з класом <kbd>btn</kbd>, а не елементи <kbd><button></kbd> з цим класом.",
|
||||||
"task": "Examine the existing CSS rules and add a new rule with higher specificity to override the text color of the paragraph. Create a rule using '.content p' as the selector and set color: green.",
|
"task": "Приберіть підкреслення з кнопок-посилань. Напишіть правило з <kbd>a.btn</kbd> як селектором і встановіть <kbd>text-decoration: none</kbd>.",
|
||||||
"previewHTML": "<div class=\"content\">\n <p>What color will this paragraph be? Look at the CSS rules and their specificity.</p>\n</div>",
|
"previewHTML": "<div class=\"actions\">\n <button class=\"btn\">Regular Button</button>\n <a href=\"#\" class=\"btn\">Link Button</a>\n</div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }",
|
"previewBaseCSS": "body { font-family: system-ui; padding: 1rem; } .actions { display: flex; gap: 0.75rem; align-items: center; } .btn { padding: 0.5rem 1rem; border: none; border-radius: 6px; font-size: 1rem; cursor: pointer; background: steelblue; color: white; }",
|
||||||
"sandboxCSS": "p { border: 1px dashed gray; padding: 10px; }",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* These CSS rules target the same paragraph but have different specificity */\n\n/* Rule 1: Element selector (lowest specificity) */\np {\n color: red;\n}\n\n/* Rule 2: Descendant selector (higher specificity than just 'p') */\n",
|
"codePrefix": "",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "",
|
"codeSuffix": "",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "a.btn {\n text-decoration: none;\n}",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "^\\.content\\s+p\\s*{",
|
"value": "a\\.btn\\s*\\{",
|
||||||
"message": "Use <kbd>.content p</kbd> as your selector (note the space between)",
|
"message": "Використовуйте <kbd>a.btn {</kbd> (тип + клас, без пробілу)"
|
||||||
"options": {
|
|
||||||
"caseSensitive": true
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "color:",
|
"value": { "property": "text-decoration", "expected": "none" },
|
||||||
"message": "Include the <kbd>color</kbd> property"
|
"message": "Встановіть <kbd>text-decoration: none</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "grouping-selectors",
|
||||||
|
"title": "Групування селекторів",
|
||||||
|
"description": "Коли кільком елементам потрібні однакові стилі, перелічіть їх через кому. Це тримає ваш CSS чистим і підтримуваним.<br><br><pre>h1, h2, h3 {<br> color: steelblue;<br>}</pre><br>Це застосовує однаковий колір до всіх трьох рівнів заголовків в одному правилі.",
|
||||||
|
"task": "Стилізуйте всі заголовки однаково. Додайте <kbd>color: steelblue</kbd> до згрупованого селектора <kbd>h1, h2, h3</kbd>.",
|
||||||
|
"previewHTML": "<article><h1>Main Title</h1><p>Introduction paragraph with some text.</p><h2>Section Heading</h2><p>More content here.</p><h3>Subsection</h3><p>Final paragraph.</p></article>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } article { max-width: 500px; } p { color: #555; line-height: 1.6; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "h1, h2, h3 {\n ",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "\n}",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": "color: steelblue;",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "color", "expected": "steelblue" },
|
||||||
|
"message": "Встановіть <kbd>color: steelblue</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "descendant-selectors",
|
||||||
|
"title": "Селектори нащадків",
|
||||||
|
"description": "Націлюйтесь на елементи всередині інших елементів використовуючи пробіл між селекторами. Це один з найкорисніших патернів у CSS.<br><br><pre>.nav a {<br> color: white;<br>}</pre><br>Це стилізує лише посилання всередині <kbd>.nav</kbd>, залишаючи інші посилання без змін.",
|
||||||
|
"task": "Стилізуйте навігаційні посилання по-іншому. Напишіть правило з <kbd>.nav a</kbd> як селектором і встановіть <kbd>color: white</kbd>.",
|
||||||
|
"previewHTML": "<nav class=\"nav\"><a href=\"#\">Home</a><a href=\"#\">About</a><a href=\"#\">Contact</a></nav><p>Read more in our <a href=\"#\">documentation</a>.</p>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; margin: 0; } .nav { background: steelblue; padding: 1rem; display: flex; gap: 1rem; border-radius: 8px; margin-bottom: 1rem; } .nav a { text-decoration: none; } p a { color: steelblue; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": ".nav a {\n color: white;\n}",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "\\.nav\\s+a\\s*\\{",
|
||||||
|
"message": "Використовуйте <kbd>.nav a {</kbd> (пробіл між .nav і a)"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "property_value",
|
||||||
"value": "green",
|
"value": { "property": "color", "expected": "white" },
|
||||||
"message": ""
|
"message": "Встановіть <kbd>color: white</kbd>"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "nested-styling",
|
||||||
|
"title": "Вкладені стилі",
|
||||||
|
"description": "Селектори нащадків дозволяють створювати контекстні стилі. Один і той самий елемент може виглядати по-різному залежно від того, де він з'являється.<br><br>Наприклад, абзаци в <kbd>.card</kbd> можуть бути меншими ніж абзаци в <kbd>article</kbd>.",
|
||||||
|
"task": "Зробіть абзаци всередині картки меншими. Напишіть правило з <kbd>.card p</kbd> як селектором і встановіть <kbd>font-size: 0.9rem</kbd>.",
|
||||||
|
"previewHTML": "<article><h2>Article Title</h2><p>This is a regular article paragraph with normal-sized text for comfortable reading.</p><div class=\"card\"><strong>Quick Tip</strong><p>Card paragraphs should be slightly smaller to fit the compact design.</p></div><p>Back to regular article text here.</p></article>",
|
||||||
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } article { max-width: 500px; } h2 { color: steelblue; margin-top: 0; } p { line-height: 1.6; color: #444; } .card { background: #f0f4f8; padding: 1rem; border-radius: 8px; border-left: 4px solid steelblue; } .card strong { color: steelblue; display: block; margin-bottom: 0.5rem; }",
|
||||||
|
"sandboxCSS": "",
|
||||||
|
"codePrefix": "",
|
||||||
|
"initialCode": "",
|
||||||
|
"codeSuffix": "",
|
||||||
|
"previewContainer": "preview-area",
|
||||||
|
"solution": ".card p {\n font-size: 0.9rem;\n}",
|
||||||
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "regex",
|
||||||
|
"value": "\\.card\\s+p\\s*\\{",
|
||||||
|
"message": "Використовуйте <kbd>.card p {</kbd> (пробіл між .card і p)"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "font-size", "expected": "0.9rem" },
|
||||||
|
"message": "Встановіть <kbd>font-size: 0.9rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,16 +1,17 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "welcome",
|
"id": "welcome",
|
||||||
"title": "Code Crispies",
|
"title": "Ласкаво просимо",
|
||||||
"description": "Welcome to Code Crispies - your interactive web development learning platform",
|
"description": "Почніть з Code Crispies",
|
||||||
"mode": "html",
|
"mode": "css",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
|
"excludeFromProgress": true,
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "get-started",
|
"id": "hello",
|
||||||
"title": "Get Started",
|
"title": "Привіт!",
|
||||||
"description": "<strong>Code Crispies</strong> is a free, open-source platform for learning web development through hands-on exercises. No account required!<br><br><strong>What you'll learn:</strong><br>• <strong>HTML</strong> - Semantic elements, forms, tables, SVG (<em>HTML Block & Inline</em>, <em>HTML Forms</em>, <em>HTML Tables</em>)<br>• <strong>CSS</strong> - Selectors, box model, flexbox, animations (<em>CSS Selectors</em>, <em>CSS Box Model</em>, <em>CSS Flexbox</em>)<br>• <strong>Responsive Design</strong> - Media queries and mobile-first layouts<br><br><strong>How it works:</strong><br>1. Read the task in the left panel<br>2. Write code in the editor<br>3. See live results in the preview<br>4. Get instant feedback with hints<br><br><strong>Keyboard shortcuts:</strong> <kbd>Ctrl+Z</kbd> to undo, <kbd>Ctrl+Shift+Z</kbd> to redo<br><br><strong>More resources:</strong><br>• <a href=\"https://nextlevelshit.github.io/html-over-js/\" target=\"_blank\">HTML over JS</a> - Native HTML vs JavaScript solutions<br>• <a href=\"https://nextlevelshit.github.io/web-engineering-mandala/\" target=\"_blank\">Web Engineering Mandala</a> - JavaScript technology roadmap",
|
"description": "<strong>Ласкаво просимо до Code Crispies!</strong> Вивчайте CSS та HTML через практичні вправи.<br><br><strong>Як це працює:</strong><br>1. Прочитайте завдання зліва<br>2. Напишіть код в редакторі<br>3. Побачте результати в попередньому перегляді<br><br><strong>Поради:</strong> Використовуйте <kbd>Ctrl+Z</kbd> для скасування. Відкрийте меню (☰) щоб переглянути всі модулі.",
|
||||||
"task": "Write <code>Hello World</code>",
|
"task": "Напишіть <code>Hello World</code> щоб почати",
|
||||||
"previewHTML": "",
|
"previewHTML": "",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 2rem; color: #6366f1; font-weight: bold; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 2rem; color: #6366f1; font-weight: bold; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -21,42 +22,9 @@
|
|||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "Hello World",
|
"value": "Hello World",
|
||||||
"message": "Write <code>Hello World</code>"
|
"message": "Напишіть <code>Hello World</code>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "overview",
|
|
||||||
"title": "Overview",
|
|
||||||
"description": "<strong>You're ready!</strong> Open the menu (☰) to explore all modules.<br><br><strong>Recommended learning path:</strong><br>1. <em>HTML Block & Inline</em> - Understand container vs inline elements<br>2. <em>HTML Forms</em> - Build interactive forms with validation<br>3. <em>CSS Selectors</em> - Target elements precisely<br>4. <em>CSS Box Model</em> - Master padding, margin, borders<br>5. <em>CSS Flexbox</em> - Create flexible layouts<br>6. <em>CSS Animations</em> - Add motion and transitions<br><br><strong>Tips:</strong><br>• Use <em>Show Expected</em> to see the target result<br>• Your progress is saved automatically<br>• Try Emmet in HTML mode: <kbd>ul>li*3</kbd> + Tab<br><br><strong>Open Source:</strong><br>• <a href=\"https://git.librete.ch/libretech/code-crispies\" target=\"_blank\">Gitea (Source)</a> · <a href=\"https://github.com/nextlevelshit/code-crispies\" target=\"_blank\">GitHub (Mirror)</a><br>• Made by <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a> · <a href=\"https://www.linkedin.com/in/michael-werner-czechowski\" target=\"_blank\">Michael Czechowski</a>",
|
|
||||||
"task": "Click Next to continue",
|
|
||||||
"previewHTML": "<p>Hello World! 🌍</p><p>Hallo Welt!</p><p>Bonjour le monde!</p><p>¡Hola Mundo!</p><p>Ciao Mondo!</p><p>Olá Mundo!</p><p>こんにちは世界!</p><p>你好世界!</p><p>안녕 세상!</p><p>Привет мир!</p><p dir=\"rtl\">שלום עולם!</p><p dir=\"rtl\">مرحبا بالعالم!</p>",
|
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 12px; } p { margin: 6px 0; padding: 6px 12px; border-radius: 6px; font-weight: 600; font-size: 0.95em; } p:nth-child(1) { background: #fef3c7; color: #92400e; font-size: 1.2em; } p:nth-child(2) { background: #dcfce7; color: #166534; } p:nth-child(3) { background: #dbeafe; color: #1e40af; } p:nth-child(4) { background: #fce7f3; color: #9d174d; } p:nth-child(5) { background: #e0e7ff; color: #4338ca; } p:nth-child(6) { background: #fef9c3; color: #854d0e; } p:nth-child(7) { background: #fee2e2; color: #991b1b; } p:nth-child(8) { background: #f3e8ff; color: #7c3aed; } p:nth-child(9) { background: #ccfbf1; color: #0f766e; } p:nth-child(10) { background: #fae8ff; color: #86198f; } p:nth-child(11) { background: #fef3c7; color: #b45309; } p:nth-child(12) { background: #d1fae5; color: #047857; }",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<p>Hello World! 🌍</p>\n<p>Hallo Welt!</p>\n<p>Bonjour le monde!</p>\n<p>¡Hola Mundo!</p>\n<p>Ciao Mondo!</p>\n<p>Olá Mundo!</p>\n<p>こんにちは世界!</p>\n<p>你好世界!</p>\n<p>안녕 세상!</p>\n<p>Привет мир!</p>\n<p dir=\"rtl\">שלום עולם!</p>\n<p dir=\"rtl\">مرحبا بالعالم!</p>",
|
|
||||||
"solution": "<p>Hello World! 🌍</p>\n<p>Hallo Welt!</p>\n<p>Bonjour le monde!</p>\n<p>¡Hola Mundo!</p>\n<p>Ciao Mondo!</p>\n<p>Olá Mundo!</p>\n<p>こんにちは世界!</p>\n<p>你好世界!</p>\n<p>안녕 세상!</p>\n<p>Привет мир!</p>\n<p dir=\"rtl\">שלום עולם!</p>\n<p dir=\"rtl\">مرحبا بالعالم!</p>",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": [
|
|
||||||
{
|
|
||||||
"type": "contains",
|
|
||||||
"value": "Hello World",
|
|
||||||
"message": "Click Next to continue"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"id": "playground",
|
|
||||||
"title": "Playground",
|
|
||||||
"mode": "playground",
|
|
||||||
"description": "",
|
|
||||||
"task": "",
|
|
||||||
"previewHTML": "",
|
|
||||||
"previewBaseCSS": "",
|
|
||||||
"sandboxCSS": "",
|
|
||||||
"initialCode": "<style>\n body {\n font-family: system-ui, sans-serif;\n padding: 20px;\n }\n</style>\n\n<h1>Hello World</h1>\n<p>Start coding!</p>",
|
|
||||||
"solution": "",
|
|
||||||
"previewContainer": "preview-area",
|
|
||||||
"validations": []
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,18 +2,18 @@
|
|||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "box-model",
|
"id": "box-model",
|
||||||
"title": "CSS Box Model",
|
"title": "CSS Box Model",
|
||||||
"description": "Master the fundamental principles of space management in web design through the CSS box model. This module explores how content, padding, borders, and margins combine to create layout structures that are both visually appealing and structurally sound.",
|
"description": "Опануйте фундаментальні принципи управління простором у веб-дизайні через блокову модель CSS. Цей модуль досліджує, як контент, відступи, межі та поля поєднуються для створення структур макету.",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "box-model-1",
|
"id": "box-model-1",
|
||||||
"title": "Box Model Components",
|
"title": "Padding",
|
||||||
"description": "The CSS box model consists of four concentric layers: content area (innermost), padding, border, and margin (outermost). Understanding how these components interact is essential for precise layout control.",
|
"description": "Кожен елемент у CSS - це блок з чотирма шарами: контент, відступ (padding), межа та поле. <strong>Padding</strong> створює простір для дихання між вашим контентом і краєм блоку.<br><br>Без padding текст незручно притискається до меж. Padding робить контент читабельним і візуально збалансованим.<br><br><pre>.card {\n padding: 1rem;\n}</pre>",
|
||||||
"task": "Set <kbd>padding</kbd> to <kbd>1rem</kbd> to create space between the content and border.",
|
"task": "Ця картка профілю виглядає тісною. Додайте <kbd>padding: 1rem</kbd>, щоб текст мав простір для дихання.",
|
||||||
"previewHTML": "<div class=\"box\">Box Model Components</div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: lavender; border: 2px dashed slategray; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".box {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "padding: 1rem;",
|
"solution": "padding: 1rem;",
|
||||||
@@ -22,62 +22,62 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "1rem" },
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
"message": "Set <kbd>padding: 1rem</kbd>"
|
"message": "Встановіть <kbd>padding: 1rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-2",
|
"id": "box-model-2",
|
||||||
"title": "Adding Borders",
|
"title": "Borders",
|
||||||
"description": "Borders outline an element, creating visual separation from surrounding content. The border shorthand accepts three values: width, style, and color.",
|
"description": "Межі створюють візуальні границі навколо елементів. Скорочення <kbd>border</kbd> приймає три значення: ширину, стиль і колір.<br><br>Поширені стилі: <kbd>solid</kbd>, <kbd>dashed</kbd>, <kbd>dotted</kbd>, <kbd>none</kbd>",
|
||||||
"task": "Set <kbd>border</kbd> to <kbd>2px solid darkslategray</kbd>.",
|
"task": "Додайте тонкий лівий акцент до картки за допомогою <kbd>border-left: 4px solid steelblue</kbd>.",
|
||||||
"previewHTML": "<div class=\"box\">This box needs a border</div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: mintcream; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".box {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "border: 2px solid darkslategray;",
|
"solution": "border-left: 4px solid steelblue;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border:\\s*2px\\s+solid\\s+darkslategray",
|
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
|
||||||
"message": "Set <kbd>border: 2px solid darkslategray</kbd>",
|
"message": "Встановіть <kbd>border-left: 4px solid steelblue</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-3",
|
"id": "box-model-3",
|
||||||
"title": "Adding Margins",
|
"title": "Margins",
|
||||||
"description": "Margins create space between elements, controlling how they relate to one another within a layout. Unlike padding (which affects internal spacing), margins exist outside the element's border.",
|
"description": "Поля створюють простір <em>зовні</em> елемента, відділяючи його від сусідів. Тоді як padding штовхає контент всередину, поля відштовхують інші елементи.",
|
||||||
"task": "Set <kbd>margin</kbd> to <kbd>1rem</kbd> to create space between this element and its neighbors.",
|
"task": "Додайте простір між цими двома картками профілю за допомогою <kbd>margin-bottom: 1rem</kbd> на <kbd>.card</kbd>.",
|
||||||
"previewHTML": "<div class=\"container\"><div class=\"outer\">This box needs margins</div><div class=\"neighbor\">Adjacent element</div></div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article><article class=\"card\"><h3>Alex Rivera</h3><p>UX Designer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .outer { background-color: plum; padding: 1rem; border: 2px solid orchid; } .neighbor { background-color: lightblue; padding: 1rem; border: 2px solid steelblue; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; border-left: 4px solid steelblue; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".outer {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "margin: 1rem;",
|
"solution": "margin-bottom: 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "margin", "expected": "1rem" },
|
"value": { "property": "margin-bottom", "expected": "1rem" },
|
||||||
"message": "Set <kbd>margin: 1rem</kbd>"
|
"message": "Встановіть <kbd>margin-bottom: 1rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-4",
|
"id": "box-model-4",
|
||||||
"title": "Box Sizing: Border-Box",
|
"title": "Box Sizing",
|
||||||
"description": "The <kbd>box-sizing</kbd> property determines how element dimensions are calculated. The default <kbd>content-box</kbd> excludes padding and border from width/height, while <kbd>border-box</kbd> includes them, making layout calculations more intuitive.",
|
"description": "За замовчуванням <kbd>width</kbd> встановлює лише ширину контенту. Padding і межі додаються до загальної суми. Це спричиняє проблеми з макетом.<br><br><kbd>box-sizing: border-box</kbd> включає padding і межу у ширину, роблячи розмір передбачуваним. Більшість розробників застосовують це до всіх елементів.",
|
||||||
"task": "Set <kbd>box-sizing</kbd> to <kbd>border-box</kbd> so padding and border are included in the width.",
|
"task": "Обидві картки мають <kbd>width: 200px</kbd>. Ліва використовує стандартний розмір (content-box), стаючи ширшою за очікуване. Виправте праву картку за допомогою <kbd>box-sizing: border-box</kbd>.",
|
||||||
"previewHTML": "<div class=\"sizing-demo\"><div class=\"box default\">Content-box (default)</div><div class=\"box sized\">Border-box</div></div>",
|
"previewHTML": "<div class=\"demo\"><article class=\"card\">Content-box</article><article class=\"card fix\">Border-box</article></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .sizing-demo { display: flex; gap: 1rem; } .box { width: 200px; padding: 1rem; border: 4px solid teal; background: lightcyan; } .default { box-sizing: content-box; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .demo { display: flex; gap: 1rem; } .card { width: 200px; padding: 1rem; border: 4px solid steelblue; background: white; border-radius: 8px; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".sized {\n ",
|
"codePrefix": ".fix {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "box-sizing: border-box;",
|
"solution": "box-sizing: border-box;",
|
||||||
@@ -86,93 +86,104 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "box-sizing", "expected": "border-box" },
|
"value": { "property": "box-sizing", "expected": "border-box" },
|
||||||
"message": "Set <kbd>box-sizing: border-box</kbd>"
|
"message": "Встановіть <kbd>box-sizing: border-box</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-5",
|
"id": "box-model-5",
|
||||||
"title": "Margin Collapse",
|
"title": "Padding Shorthand",
|
||||||
"description": "When two vertical margins meet, they collapse to the larger value instead of adding up. Understanding this behavior is crucial for consistent vertical spacing.",
|
"description": "Padding приймає 1-4 значення:<br>• 1 значення: всі сторони<br>• 2 значення: вертикально | горизонтально<br>• 4 значення: верх | право | низ | ліво",
|
||||||
"task": "Set <kbd>margin-bottom</kbd> to <kbd>2rem</kbd>. Notice the space between paragraphs equals 2rem (not 3rem) due to margin collapse.",
|
"task": "Ця кнопка потребує більше горизонтального простору, ніж вертикального. Встановіть <kbd>padding: 8px 1rem</kbd> (8px верх/низ, 1rem ліво/право).",
|
||||||
"previewHTML": "<div class=\"collapse-demo\"><p class=\"first\">This paragraph has a bottom margin.</p><p class=\"second\">This paragraph has a top margin of 1rem.</p></div>",
|
"previewHTML": "<button class=\"btn\">Follow</button>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .collapse-demo { border: 1px solid silver; padding: 8px; background: ghostwhite; } .second { margin-top: 1rem; background: linen; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .btn { background: steelblue; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".first {\n ",
|
"codePrefix": ".btn {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "margin-bottom: 2rem;",
|
"solution": "padding: 8px 1rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "regex",
|
||||||
"value": { "property": "margin-bottom", "expected": "2rem" },
|
"value": "padding:\\s*8px\\s+1rem",
|
||||||
"message": "Set <kbd>margin-bottom: 2rem</kbd>"
|
"message": "Встановіть <kbd>padding: 8px 1rem</kbd>",
|
||||||
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-6",
|
"id": "box-model-6",
|
||||||
"title": "Margin Shorthand Notation",
|
"title": "Margin Shorthand",
|
||||||
"description": "The margin shorthand can set all four sides at once. Two values set vertical (top/bottom) and horizontal (left/right) margins respectively.",
|
"description": "Margin використовує той самий шаблон скорочення, що й padding. Поширений шаблон - горизонтальне центрування блокових елементів за допомогою <kbd>margin: 0 auto</kbd>.",
|
||||||
"task": "Set <kbd>margin</kbd> to <kbd>1rem 2rem</kbd> for 1rem top/bottom and 2rem left/right.",
|
"task": "Відцентруйте цю картку горизонтально. Встановіть <kbd>margin: 0 auto</kbd>, щоб автоматично обчислити рівні ліві/праві поля.",
|
||||||
"previewHTML": "<div class=\"container\"><div class=\"spaced\">This box needs margins: 1rem top/bottom, 2rem left/right</div></div>",
|
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .spaced { background-color: honeydew; border: 2px solid mediumseagreen; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { width: 250px; background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; border-left: 4px solid steelblue; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".spaced {\n ",
|
"codePrefix": ".card {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "margin: 1rem 2rem;",
|
"solution": "margin: 0 auto;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "margin:\\s*1rem\\s+2rem",
|
"value": "margin:\\s*0\\s+auto",
|
||||||
"message": "Set <kbd>margin: 1rem 2rem</kbd>",
|
"message": "Встановіть <kbd>margin: 0 auto</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-7",
|
"id": "box-model-7",
|
||||||
"title": "Padding Shorthand Notation",
|
"title": "Border Radius",
|
||||||
"description": "Like margin, padding shorthand allows setting all sides at once. A single value applies to all four sides equally.",
|
"description": "Хоча не є частиною класичної блокової моделі, <kbd>border-radius</kbd> заокруглює кути межі елемента. Використовуйте <kbd>50%</kbd> на квадратному елементі, щоб створити коло.",
|
||||||
"task": "Set <kbd>padding</kbd> to <kbd>2rem</kbd> to add equal padding on all sides.",
|
"task": "Зробіть зображення аватара круглим за допомогою <kbd>border-radius: 50%</kbd>.",
|
||||||
"previewHTML": "<div class=\"padded\">This box needs equal padding on all sides</div>",
|
"previewHTML": "<article class=\"card\"><img class=\"avatar\" src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='35' r='25' fill='%23666'/%3E%3Ccircle cx='50' cy='90' r='40' fill='%23666'/%3E%3C/svg%3E\" alt=\"Avatar\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .padded { background-color: papayawhip; border: 2px solid orange; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; text-align: center; } .avatar { width: 80px; height: 80px; background: #ddd; margin-bottom: 8px; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".padded {\n ",
|
"codePrefix": ".avatar {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "padding: 2rem;",
|
"solution": "border-radius: 50%;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "padding", "expected": "2rem" },
|
"value": { "property": "border-radius", "expected": "50%" },
|
||||||
"message": "Set <kbd>padding: 2rem</kbd>"
|
"message": "Встановіть <kbd>border-radius: 50%</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "box-model-8",
|
"id": "box-model-8",
|
||||||
"title": "Border on Specific Sides",
|
"title": "Complete Card",
|
||||||
"description": "For granular control, you can target specific sides with <kbd>border-top</kbd>, <kbd>border-right</kbd>, <kbd>border-bottom</kbd>, or <kbd>border-left</kbd>.",
|
"description": "Об'єднаймо все разом. Ця картка сповіщення потребує стилізації, щоб виглядати професійно.",
|
||||||
"task": "Set <kbd>border-bottom</kbd> to <kbd>4px solid dodgerblue</kbd>.",
|
"task": "Стилізуйте сповіщення: додайте <kbd>padding: 1rem</kbd>, <kbd>border-left: 4px solid coral</kbd> та <kbd>border-radius: 4px</kbd>.",
|
||||||
"previewHTML": "<div class=\"line\">This element needs only a bottom border</div>",
|
"previewHTML": "<div class=\"alert\"><strong>New message</strong><p>You have 3 unread notifications</p></div>",
|
||||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .line { padding: 1rem; background-color: aliceblue; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .alert { background: seashell; } .alert strong { color: coral; } .alert p { margin: 4px 0 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": ".line {\n ",
|
"codePrefix": ".alert {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "\n}",
|
"codeSuffix": "\n}",
|
||||||
"solution": "border-bottom: 4px solid dodgerblue;",
|
"solution": "padding: 1rem;\n border-left: 4px solid coral;\n border-radius: 4px;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "padding", "expected": "1rem" },
|
||||||
|
"message": "Встановіть <kbd>padding: 1rem</kbd>"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "border-bottom:\\s*4px\\s+solid\\s+dodgerblue",
|
"value": "border-left:\\s*4px\\s+solid\\s+coral",
|
||||||
"message": "Set <kbd>border-bottom: 4px solid dodgerblue</kbd>",
|
"message": "Встановіть <kbd>border-left: 4px solid coral</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"type": "property_value",
|
||||||
|
"value": { "property": "border-radius", "expected": "4px" },
|
||||||
|
"message": "Встановіть <kbd>border-radius: 4px</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,115 +1,100 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "units-variables",
|
"id": "units-variables",
|
||||||
"title": "CSS Units & Variables",
|
"title": "Одиниці CSS та змінні",
|
||||||
"description": "Understand the variety of CSS measurement units and how to define and use custom properties for maintainable styles.",
|
"description": "Зрозумійте різноманітність одиниць вимірювання CSS та як визначати й використовувати кастомні властивості для стилів, які легко підтримувати.",
|
||||||
"difficulty": "beginner",
|
"difficulty": "beginner",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "units-1",
|
"id": "units-1",
|
||||||
"title": "Absolute vs. Relative Units",
|
"title": "Relative Units",
|
||||||
"description": "Learn the difference between px, rem, em, %, and vw/vh for flexible, responsive layouts.",
|
"description": "CSS пропонує два типи одиниць: <em>абсолютні</em> (як <kbd>px</kbd>) та <em>відносні</em> (як <kbd>%</kbd> і <kbd>rem</kbd>). Відносні одиниці адаптуються до контексту, роблячи макети гнучкими та доступними.<br><br><strong>Поширені відносні одиниці:</strong><br>• <kbd>%</kbd> – Відносно батьківського елемента<br>• <kbd>rem</kbd> – Відносно кореневого розміру шрифту (зазвичай 16px)<br>• <kbd>em</kbd> – Відносно розміру шрифту елемента<br><br>Поширений патерн для читабельного контенту: встановіть <kbd>width: 100%</kbd>, щоб заповнити доступний простір, потім <kbd>max-width: 40rem</kbd> для обмеження довжини рядка.",
|
||||||
"task": "Set the <kbd>width</kbd> of <kbd>.box</kbd> to <kbd>80%</kbd> and <kbd>max-width</kbd> to <kbd>37.5rem</kbd>.",
|
"task": "Цей текст статті занадто широкий на великих екранах. Додайте <kbd>max-width: 40rem</kbd> для оптимальної ширини читання.",
|
||||||
"previewHTML": "<div class=\"box\">Resize me!</div>",
|
"previewHTML": "<article class=\"article\"><h2>The Art of Typography</h2><p>Good typography is invisible. When text is set well, readers absorb information without noticing the design decisions that make it comfortable to read. Line length is crucial—too wide and eyes get lost, too narrow and reading becomes choppy.</p><p>The ideal line length is 45-75 characters per line. At typical font sizes, this works out to roughly 40rem maximum width.</p></article>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { background: #f5f5f5; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: Georgia, serif; padding: 1rem; background: #f9f9f9; } .article { background: white; padding: 2rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .article h2 { margin: 0 0 1rem; color: #333; } .article p { margin: 0 0 1rem; line-height: 1.6; color: #444; } .article p:last-child { margin-bottom: 0; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Set flexible sizing */\n.box {",
|
"codePrefix": ".article {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " width: 80%;\n max-width: 37.5rem;",
|
"solution": "max-width: 40rem;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"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",
|
"type": "property_value",
|
||||||
"value": { "property": "max-width", "expected": "37.5rem" },
|
"value": { "property": "max-width", "expected": "40rem" },
|
||||||
"message": "Set max-width to <kbd>37.5rem</kbd>"
|
"message": "Встановіть <kbd>max-width: 40rem</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "units-2",
|
"id": "units-2",
|
||||||
"title": "CSS Custom Properties",
|
"title": "CSS Variables",
|
||||||
"description": "Define and reuse variables (--custom properties) to centralize your theme values.",
|
"description": "Кастомні властивості CSS (змінні) дозволяють визначати значення для повторного використання. Визначайте їх за допомогою <kbd>--назва</kbd> та використовуйте з <kbd>var(--назва)</kbd>. Змінні, визначені на <kbd>:root</kbd>, доступні всюди.",
|
||||||
"task": "Create a <kbd>--main-color</kbd> variable in <kbd>:root</kbd> with <kbd>#6200ee</kbd> and apply it as the border color on <kbd>.themed</kbd>.",
|
"task": "Визначте <kbd>--brand: steelblue</kbd> в <kbd>:root</kbd>, потім використайте як колір <kbd>background</kbd> для <kbd>.btn</kbd>.",
|
||||||
"previewHTML": "<div class=\"themed\">Variable Box</div>",
|
"previewHTML": "<div class=\"actions\"><button class=\"btn\">Subscribe</button><button class=\"btn\">Learn More</button></div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .themed { padding: 1rem; border: 0.125rem solid #ddd; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .actions { display: flex; gap: 1rem; } .btn { color: white; border: none; padding: 12px 24px; border-radius: 6px; font-size: 1rem; cursor: pointer; background: #ccc; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Define and use a CSS variable */\n:root {",
|
"codePrefix": ":root {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}\n.themed { }",
|
"codeSuffix": "\n}\n\n.btn {\n background: var(--brand);\n}",
|
||||||
"solution": " --main-color: #6200ee;\n}\n.themed {\n border-color: var(--main-color);",
|
"solution": "--brand: steelblue;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "--main-color",
|
"value": "--brand",
|
||||||
"message": "Define <kbd>--main-color</kbd> in :root",
|
"message": "Визначте змінну <kbd>--brand</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "var(--main-color)",
|
"value": "steelblue",
|
||||||
"message": "Use <kbd>var(--main-color)</kbd>",
|
"message": "Встановіть значення <kbd>steelblue</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"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",
|
"id": "units-3",
|
||||||
"title": "Unit Calculations (calc)",
|
"title": "calc() Function",
|
||||||
"description": "Use the <kbd>calc()</kbd> function to combine different units in one expression.",
|
"description": "Функція <kbd>calc()</kbd> дозволяє змішувати різні одиниці в обчисленнях. Це важливо для макетів, що поєднують фіксовані та гнучкі розміри, як макет із сайдбаром.",
|
||||||
"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>.",
|
"task": "Основний контент повинен заповнити залишок місця після сайдбару 200px. Встановіть <kbd>width: calc(100% - 200px)</kbd> на <kbd>.main</kbd>.",
|
||||||
"previewHTML": "<div class=\"sized\">Calc Demo</div>",
|
"previewHTML": "<div class=\"layout\"><aside class=\"sidebar\">Sidebar<br>Navigation</aside><main class=\"main\"><h2>Main Content</h2><p>This area should fill the remaining width after accounting for the fixed-width sidebar.</p></main></div>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sized { background: #e8f5e9; padding: 1rem; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .layout { display: flex; gap: 1rem; } .sidebar { width: 200px; background: #1a1a2e; color: white; padding: 1rem; border-radius: 8px; flex-shrink: 0; } .main { background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .main h2 { margin: 0 0 8px; } .main p { margin: 0; color: #666; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Use calc for dynamic sizing */\n.sized {",
|
"codePrefix": ".main {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " width: calc(100% - 2rem);\n min-height: calc(10vh + 1rem);",
|
"solution": "width: calc(100% - 200px);",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"validations": [
|
||||||
{ "type": "contains", "value": "calc", "message": "Use <kbd>calc()</kbd> function", "options": { "caseSensitive": false } },
|
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "width:\\s*calc\\(100% - 2rem\\)",
|
"value": "width:\\s*calc\\(\\s*100%\\s*-\\s*200px\\s*\\)",
|
||||||
"message": "Width should be <kbd>calc(100% - 2rem)</kbd>",
|
"message": "Встановіть <kbd>width: calc(100% - 200px)</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 }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "units-4",
|
"id": "units-4",
|
||||||
"title": "Viewport & Responsive Units",
|
"title": "Viewport Units",
|
||||||
"description": "Control layouts relative to viewport size with vw, vh, and vmin/vmax units.",
|
"description": "Одиниці viewport розміряють елементи відносно вікна браузера:<br>• <kbd>vw</kbd> – 1% ширини viewport<br>• <kbd>vh</kbd> – 1% висоти viewport<br><br>Вони ідеальні для повноекранних секцій як hero-банери.",
|
||||||
"task": "Give <kbd>.view</kbd> a <kbd>width</kbd> of <kbd>50vw</kbd> and <kbd>height</kbd> of <kbd>20vh</kbd>.",
|
"task": "Зробіть цю hero-секцію висотою з viewport, встановивши <kbd>min-height: 100vh</kbd>.",
|
||||||
"previewHTML": "<div class=\"view\">Viewport Box</div>",
|
"previewHTML": "<section class=\"hero\"><h1>Welcome</h1><p>Scroll down to explore</p></section>",
|
||||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .view { background: #ffe0b2; }",
|
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .hero { background: linear-gradient(135deg, #1a1a2e 0%, steelblue 100%); color: white; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 2rem; } .hero h1 { margin: 0 0 1rem; font-size: 2.5rem; } .hero p { margin: 0; opacity: 0.8; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
"codePrefix": "/* Use viewport units */\n.view {",
|
"codePrefix": ".hero {\n ",
|
||||||
"initialCode": "",
|
"initialCode": "",
|
||||||
"codeSuffix": "}",
|
"codeSuffix": "\n}",
|
||||||
"solution": " width: 50vw;\n height: 20vh;",
|
"solution": "min-height: 100vh;",
|
||||||
"previewContainer": "preview-area",
|
"previewContainer": "preview-area",
|
||||||
"validations": [
|
"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",
|
||||||
{ "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Set width to <kbd>50vw</kbd>" },
|
"value": { "property": "min-height", "expected": "100vh" },
|
||||||
{ "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Set height to <kbd>20vh</kbd>" }
|
"message": "Встановіть <kbd>min-height: 100vh</kbd>"
|
||||||
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
|
|||||||
@@ -1,15 +1,15 @@
|
|||||||
{
|
{
|
||||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||||
"id": "transitions-animations",
|
"id": "transitions-animations",
|
||||||
"title": "CSS Animations",
|
"title": "CSS Анімації",
|
||||||
"description": "Bring interactivity to your UI by smoothly transitioning properties and creating keyframe-driven animations.",
|
"description": "Додайте інтерактивність до інтерфейсу через плавні переходи властивостей та анімації на основі keyframes.",
|
||||||
"difficulty": "intermediate",
|
"difficulty": "intermediate",
|
||||||
"lessons": [
|
"lessons": [
|
||||||
{
|
{
|
||||||
"id": "transitions-1",
|
"id": "transitions-1",
|
||||||
"title": "Transitions",
|
"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>",
|
"description": "Навчіться застосовувати <kbd>transition</kbd> до властивостей для плавних змін при зміні стану.<br><br><pre>transition: property duration;\n/* напр. 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.",
|
"task": "Додайте <kbd>transition: background-color 0.3s</kbd>, щоб колір плавно змінювався при наведенні.",
|
||||||
"previewHTML": "<button class=\"btn\">Hover Me</button>",
|
"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; }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
@@ -22,13 +22,13 @@
|
|||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "transition",
|
"value": "transition",
|
||||||
"message": "Use the <kbd>transition</kbd> property",
|
"message": "Використайте властивість <kbd>transition</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "transition:\\s*background-color\\s*0\\.3s",
|
"value": "transition:\\s*background-color\\s*0\\.3s",
|
||||||
"message": "Set <kbd>transition: background-color 0.3s</kbd>",
|
"message": "Встановіть <kbd>transition: background-color 0.3s</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -36,8 +36,8 @@
|
|||||||
{
|
{
|
||||||
"id": "transitions-2",
|
"id": "transitions-2",
|
||||||
"title": "Timing Funcs",
|
"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.",
|
"description": "Дослідіть функції пом'якшення як <kbd>ease</kbd>, <kbd>linear</kbd>, <kbd>ease-in</kbd>, <kbd>ease-out</kbd> для контролю темпу анімації.",
|
||||||
"task": "Set <kbd>transition-timing-function</kbd> to <kbd>ease-in-out</kbd> on <kbd>.btn</kbd>.",
|
"task": "Встановіть <kbd>transition-timing-function</kbd> на <kbd>ease-in-out</kbd>.",
|
||||||
"previewHTML": "<button class=\"btn\">Timing</button>",
|
"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); }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
@@ -50,21 +50,21 @@
|
|||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "transition-timing-function",
|
"value": "transition-timing-function",
|
||||||
"message": "Use <kbd>transition-timing-function</kbd>",
|
"message": "Використайте <kbd>transition-timing-function</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "transition-timing-function", "expected": "ease-in-out" },
|
"value": { "property": "transition-timing-function", "expected": "ease-in-out" },
|
||||||
"message": "Set timing to <kbd>ease-in-out</kbd>"
|
"message": "Встановіть timing на <kbd>ease-in-out</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"id": "transitions-3",
|
"id": "transitions-3",
|
||||||
"title": "Keyframes",
|
"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>",
|
"description": "Створюйте іменовані анімації використовуючи <kbd>@keyframes</kbd> та застосовуйте їх через скорочення <kbd>animation</kbd>.<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>.",
|
"task": "Визначте keyframe при <kbd>50%</kbd> з <kbd>transform: translateY(-20px)</kbd> та застосуйте <kbd>animation: bounce 1s infinite</kbd> на <kbd>.ball</kbd>.",
|
||||||
"previewHTML": "<div class=\"ball\"></div>",
|
"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; }",
|
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: crimson; border-radius: 50%; margin: 2rem auto; }",
|
||||||
"sandboxCSS": "",
|
"sandboxCSS": "",
|
||||||
@@ -77,25 +77,25 @@
|
|||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "@keyframes bounce",
|
"value": "@keyframes bounce",
|
||||||
"message": "Define <kbd>@keyframes bounce</kbd>",
|
"message": "Визначте <kbd>@keyframes bounce</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "50%.*transform: translateY\\(-20px\\)",
|
"value": "50%.*transform: translateY\\(-20px\\)",
|
||||||
"message": "At <kbd>50%</kbd>, use <kbd>transform: translateY(-20px)</kbd>",
|
"message": "При <kbd>50%</kbd>, використайте <kbd>transform: translateY(-20px)</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "contains",
|
"type": "contains",
|
||||||
"value": "animation",
|
"value": "animation",
|
||||||
"message": "Use <kbd>animation</kbd> property on <kbd>.ball</kbd>",
|
"message": "Використайте властивість <kbd>animation</kbd> на <kbd>.ball</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "regex",
|
"type": "regex",
|
||||||
"value": "animation:.*bounce.*1s.*infinite",
|
"value": "animation:.*bounce.*1s.*infinite",
|
||||||
"message": "Apply <kbd>animation: bounce 1s infinite</kbd>",
|
"message": "Застосуйте <kbd>animation: bounce 1s infinite</kbd>",
|
||||||
"options": { "caseSensitive": false }
|
"options": { "caseSensitive": false }
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
@@ -103,8 +103,8 @@
|
|||||||
{
|
{
|
||||||
"id": "transitions-4",
|
"id": "transitions-4",
|
||||||
"title": "Animation Properties",
|
"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>.",
|
"description": "Налаштуйте анімації за допомогою <kbd>animation-delay</kbd>, <kbd>animation-iteration-count</kbd>, <kbd>animation-direction</kbd> та <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>.",
|
"task": "Застосуйте анімацію <kbd>pulse</kbd> до <kbd>.box</kbd> з <kbd>animation-name: pulse</kbd>, <kbd>animation-duration: 2s</kbd>, <kbd>animation-delay: 1s</kbd>, <kbd>animation-iteration-count: 2</kbd> та <kbd>animation-fill-mode: forwards</kbd>.",
|
||||||
"previewHTML": "<div class=\"box\">Pulse</div>",
|
"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); } }",
|
"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": "",
|
"sandboxCSS": "",
|
||||||
@@ -117,27 +117,27 @@
|
|||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "animation-name", "expected": "pulse" },
|
"value": { "property": "animation-name", "expected": "pulse" },
|
||||||
"message": "Set <kbd>animation-name: pulse</kbd>"
|
"message": "Встановіть <kbd>animation-name: pulse</kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "animation-duration", "expected": "2s" },
|
"value": { "property": "animation-duration", "expected": "2s" },
|
||||||
"message": "Set <kbd>animation-duration: 2s</kbd>"
|
"message": "Встановіть <kbd>animation-duration: 2s</kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "animation-delay", "expected": "1s" },
|
"value": { "property": "animation-delay", "expected": "1s" },
|
||||||
"message": "Set <kbd>animation-delay: 1s</kbd>"
|
"message": "Встановіть <kbd>animation-delay: 1s</kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "animation-iteration-count", "expected": "2" },
|
"value": { "property": "animation-iteration-count", "expected": "2" },
|
||||||
"message": "Set <kbd>animation-iteration-count: 2</kbd>"
|
"message": "Встановіть <kbd>animation-iteration-count: 2</kbd>"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"type": "property_value",
|
"type": "property_value",
|
||||||
"value": { "property": "animation-fill-mode", "expected": "forwards" },
|
"value": { "property": "animation-fill-mode", "expected": "forwards" },
|
||||||
"message": "Set <kbd>animation-fill-mode: forwards</kbd>"
|
"message": "Встановіть <kbd>animation-fill-mode: forwards</kbd>"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user