fix(i18n): sync all lesson translations with English source

Synchronizes 72 lesson files across 5 languages (de, pl, es, ar, uk) to match
the English source. This ensures code, solutions, and validations are identical
while only title, description, task, and message fields are translated.

Changes include:
- Box model lessons (01-box-model.json)
- Units and variables (05-units-variables.json)
- Transitions and animations (06-transitions-animations.json)
- Responsive design (08-responsive.json)
- HTML elements (20-html-elements.json)
- HTML forms basic and validation (21, 22)
- HTML details/summary, progress/meter (23, 24)
- HTML datalist, dialog, fieldset (25, 27, 28)
- HTML tables and SVG (30, 32)
- HTML marquee (31)
- Welcome module (00-welcome.json)

Fixes validation inconsistencies and removes extra content that exceeded
English source. German translations were largely correct; Polish, Spanish,
Arabic, and Ukrainian required full translations.
This commit is contained in:
2026-01-14 15:39:22 +01:00
parent 617906acb9
commit 1a5c09b750
72 changed files with 2206 additions and 2611 deletions

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "welcome",
"title": "Code Crispies",
"description": "Welcome to Code Crispies - your interactive web development learning platform",
"description": "مرحباً بك في Code Crispies - منصتك التفاعلية لتعلم تطوير الويب",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "get-started",
"title": "Get 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",
"task": "Write <code>Hello World</code>",
"title": "ابدأ",
"description": "<strong>Code Crispies</strong> منصة مجانية ومفتوحة المصدر لتعلم تطوير الويب من خلال التمارين العملية. لا حاجة لحساب!<br><br><strong>ما ستتعلمه:</strong><br>• <strong>HTML</strong> - العناصر الدلالية، النماذج، الجداول، SVG (<em>HTML كتلي وسطري</em>، <em>HTML النماذج</em>، <em>HTML الجداول</em>)<br>• <strong>CSS</strong> - المحددات، نموذج الصندوق، flexbox، الحركات (<em>CSS المحددات</em>، <em>CSS نموذج الصندوق</em>، <em>CSS Flexbox</em>)<br>• <strong>التصميم المتجاوب</strong> - استعلامات الوسائط وتخطيطات mobile-first<br><br><strong>كيف يعمل:</strong><br>1. اقرأ المهمة في اللوحة اليسرى<br>2. اكتب الكود في المحرر<br>3. شاهد النتائج مباشرة في المعاينة<br>4. احصل على ملاحظات فورية مع تلميحات<br><br><strong>اختصارات لوحة المفاتيح:</strong> <kbd>Ctrl+Z</kbd> تراجع، <kbd>Ctrl+Shift+Z</kbd> إعادة<br><br><strong>المزيد من الموارد:</strong><br>• <a href=\"https://nextlevelshit.github.io/html-over-js/\" target=\"_blank\">HTML over JS</a> - HTML الأصلي مقابل حلول JavaScript<br>• <a href=\"https://nextlevelshit.github.io/web-engineering-mandala/\" target=\"_blank\">Web Engineering Mandala</a> - خارطة تقنيات JavaScript",
"task": "اكتب <code>Hello World</code>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 2rem; color: #6366f1; font-weight: bold; }",
"sandboxCSS": "",
@@ -21,15 +21,15 @@
{
"type": "contains",
"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",
"title": "نظرة عامة",
"description": "<strong>أنت جاهز!</strong> افتح القائمة (☰) لاستكشاف جميع الوحدات.<br><br><strong>مسار التعلم الموصى به:</strong><br>1. <em>HTML كتلي وسطري</em> - افهم عناصر الحاوية مقابل السطرية<br>2. <em>HTML النماذج</em> - أنشئ نماذج تفاعلية مع التحقق<br>3. <em>CSS المحددات</em> - استهدف العناصر بدقة<br>4. <em>CSS نموذج الصندوق</em> - أتقن padding، margin، borders<br>5. <em>CSS Flexbox</em> - أنشئ تخطيطات مرنة<br>6. <em>CSS الحركات</em> - أضف الحركة والانتقالات<br><br><strong>نصائح:</strong><br>• استخدم <em>إظهار المتوقع</em> لرؤية النتيجة المستهدفة<br>• يُحفظ تقدمك تلقائياً<br>• جرب Emmet في وضع HTML: <kbd>ul>li*3</kbd> + Tab<br><br><strong>مفتوح المصدر:</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>• صُنع بواسطة <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": "انقر التالي للمتابعة",
"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": "",
@@ -39,8 +39,8 @@
"validations": [
{
"type": "contains",
"value": "Hello World",
"message": "Click Next to continue"
"value": "Hello",
"message": "انقر التالي للمتابعة"
}
]
},

View File

@@ -2,18 +2,18 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "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",
"lessons": [
{
"id": "box-model-1",
"title": "Box Model Components",
"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.",
"task": "Set <kbd>padding</kbd> to <kbd>1rem</kbd> to create space between the content and border.",
"previewHTML": "<div class=\"box\">Box Model Components</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: lavender; border: 2px dashed slategray; }",
"title": "Padding",
"description": "كل عنصر في CSS هو صندوق بأربع طبقات: المحتوى، الحشو (padding)، الحدود، والهامش. <strong>Padding</strong> يخلق مساحة تنفس بين محتواك وحافة الصندوق.<br><br>بدون padding، يضغط النص بشكل محرج على الحدود. Padding يجعل المحتوى قابلاً للقراءة ومتوازناً بصرياً.<br><br><pre>.card {\n padding: 1rem;\n}</pre>",
"task": "بطاقة الملف الشخصي هذه تبدو ضيقة. أضف <kbd>padding: 1rem</kbd> ليكون للنص مجال للتنفس.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"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": "",
"codePrefix": ".box {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "padding: 1rem;",
@@ -22,62 +22,62 @@
{
"type": "property_value",
"value": { "property": "padding", "expected": "1rem" },
"message": "Set <kbd>padding: 1rem</kbd>"
"message": "اضبط <kbd>padding: 1rem</kbd>"
}
]
},
{
"id": "box-model-2",
"title": "Adding Borders",
"description": "Borders outline an element, creating visual separation from surrounding content. The border shorthand accepts three values: width, style, and color.",
"task": "Set <kbd>border</kbd> to <kbd>2px solid darkslategray</kbd>.",
"previewHTML": "<div class=\"box\">This box needs a border</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: mintcream; padding: 1rem; }",
"title": "Borders",
"description": "الحدود تنشئ حدوداً مرئية حول العناصر. اختصار <kbd>border</kbd> يقبل ثلاث قيم: العرض، النمط، واللون.<br><br>الأنماط الشائعة: <kbd>solid</kbd>، <kbd>dashed</kbd>، <kbd>dotted</kbd>، <kbd>none</kbd>",
"task": "أضف لمسة يسارية خفيفة للبطاقة باستخدام <kbd>border-left: 4px solid steelblue</kbd>.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"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": "",
"codePrefix": ".box {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "border: 2px solid darkslategray;",
"solution": "border-left: 4px solid steelblue;",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "border:\\s*2px\\s+solid\\s+darkslategray",
"message": "Set <kbd>border: 2px solid darkslategray</kbd>",
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
"message": "اضبط <kbd>border-left: 4px solid steelblue</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-3",
"title": "Adding 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.",
"task": "Set <kbd>margin</kbd> to <kbd>1rem</kbd> to create space between this element and its neighbors.",
"previewHTML": "<div class=\"container\"><div class=\"outer\">This box needs margins</div><div class=\"neighbor\">Adjacent element</div></div>",
"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; }",
"title": "Margins",
"description": "الهوامش تنشئ مساحة <em>خارج</em> العنصر، تفصله عن جيرانه. بينما يدفع padding المحتوى للداخل، الهوامش تدفع العناصر الأخرى بعيداً.",
"task": "أضف مساحة بين بطاقتي الملف الشخصي هاتين باستخدام <kbd>margin-bottom: 1rem</kbd> على <kbd>.card</kbd>.",
"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; 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": "",
"codePrefix": ".outer {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "margin: 1rem;",
"solution": "margin-bottom: 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "margin", "expected": "1rem" },
"message": "Set <kbd>margin: 1rem</kbd>"
"value": { "property": "margin-bottom", "expected": "1rem" },
"message": "اضبط <kbd>margin-bottom: 1rem</kbd>"
}
]
},
{
"id": "box-model-4",
"title": "Box Sizing: Border-Box",
"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.",
"task": "Set <kbd>box-sizing</kbd> to <kbd>border-box</kbd> so padding and border are included in the width.",
"previewHTML": "<div class=\"sizing-demo\"><div class=\"box default\">Content-box (default)</div><div class=\"box sized\">Border-box</div></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; }",
"title": "Box Sizing",
"description": "افتراضياً، <kbd>width</kbd> يحدد فقط عرض المحتوى. Padding والحدود تُضاف للمجموع. هذا يسبب مشاكل في التخطيط.<br><br><kbd>box-sizing: border-box</kbd> يشمل padding والحدود في العرض، مما يجعل التحجيم متوقعاً. معظم المطورين يطبقون هذا على جميع العناصر.",
"task": "كلا البطاقتين لهما <kbd>width: 200px</kbd>. اليسرى تستخدم التحجيم الافتراضي (content-box)، مما يجعلها أعرض من المتوقع. أصلح البطاقة اليمنى باستخدام <kbd>box-sizing: border-box</kbd>.",
"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; background: #f5f5f5; } .demo { display: flex; gap: 1rem; } .card { width: 200px; padding: 1rem; border: 4px solid steelblue; background: white; border-radius: 8px; }",
"sandboxCSS": "",
"codePrefix": ".sized {\n ",
"codePrefix": ".fix {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "box-sizing: border-box;",
@@ -86,93 +86,104 @@
{
"type": "property_value",
"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",
"title": "Margin Collapse",
"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.",
"task": "Set <kbd>margin-bottom</kbd> to <kbd>2rem</kbd>. Notice the space between paragraphs equals 2rem (not 3rem) due to margin collapse.",
"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>",
"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; }",
"title": "Padding Shorthand",
"description": "Padding يقبل 1-4 قيم:<br>• قيمة واحدة: جميع الجوانب<br>• قيمتان: عمودي | أفقي<br>• 4 قيم: أعلى | يمين | أسفل | يسار",
"task": "هذا الزر يحتاج مساحة أفقية أكثر من العمودية. اضبط <kbd>padding: 8px 1rem</kbd> (8px أعلى/أسفل، 1rem يسار/يمين).",
"previewHTML": "<button class=\"btn\">Follow</button>",
"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": "",
"codePrefix": ".first {\n ",
"codePrefix": ".btn {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "margin-bottom: 2rem;",
"solution": "padding: 8px 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "margin-bottom", "expected": "2rem" },
"message": "Set <kbd>margin-bottom: 2rem</kbd>"
"type": "regex",
"value": "padding:\\s*8px\\s+1rem",
"message": "اضبط <kbd>padding: 8px 1rem</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-6",
"title": "Margin Shorthand Notation",
"description": "The margin shorthand can set all four sides at once. Two values set vertical (top/bottom) and horizontal (left/right) margins respectively.",
"task": "Set <kbd>margin</kbd> to <kbd>1rem 2rem</kbd> for 1rem top/bottom and 2rem left/right.",
"previewHTML": "<div class=\"container\"><div class=\"spaced\">This box needs margins: 1rem top/bottom, 2rem left/right</div></div>",
"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; }",
"title": "Margin Shorthand",
"description": "Margin يستخدم نفس نمط الاختصار مثل padding. نمط شائع هو توسيط عناصر الكتلة أفقياً باستخدام <kbd>margin: 0 auto</kbd>.",
"task": "وسّط هذه البطاقة أفقياً. اضبط <kbd>margin: 0 auto</kbd> لحساب هوامش يسار/يمين متساوية تلقائياً.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"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": "",
"codePrefix": ".spaced {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "margin: 1rem 2rem;",
"solution": "margin: 0 auto;",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "margin:\\s*1rem\\s+2rem",
"message": "Set <kbd>margin: 1rem 2rem</kbd>",
"value": "margin:\\s*0\\s+auto",
"message": "اضبط <kbd>margin: 0 auto</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-7",
"title": "Padding Shorthand Notation",
"description": "Like margin, padding shorthand allows setting all sides at once. A single value applies to all four sides equally.",
"task": "Set <kbd>padding</kbd> to <kbd>2rem</kbd> to add equal padding on all sides.",
"previewHTML": "<div class=\"padded\">This box needs equal padding on all sides</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .padded { background-color: papayawhip; border: 2px solid orange; }",
"title": "Border Radius",
"description": "على الرغم من أنه ليس جزءاً من نموذج الصندوق الكلاسيكي، <kbd>border-radius</kbd> يُدوّر زوايا صندوق حدود العنصر. استخدم <kbd>50%</kbd> على عنصر مربع لإنشاء دائرة.",
"task": "اجعل صورة الأفاتار دائرية باستخدام <kbd>border-radius: 50%</kbd>.",
"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; 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": "",
"codePrefix": ".padded {\n ",
"codePrefix": ".avatar {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "padding: 2rem;",
"solution": "border-radius: 50%;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "padding", "expected": "2rem" },
"message": "Set <kbd>padding: 2rem</kbd>"
"value": { "property": "border-radius", "expected": "50%" },
"message": "اضبط <kbd>border-radius: 50%</kbd>"
}
]
},
{
"id": "box-model-8",
"title": "Border on Specific Sides",
"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>.",
"task": "Set <kbd>border-bottom</kbd> to <kbd>4px solid dodgerblue</kbd>.",
"previewHTML": "<div class=\"line\">This element needs only a bottom border</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .line { padding: 1rem; background-color: aliceblue; }",
"title": "Complete Card",
"description": "لنجمع كل شيء معاً. بطاقة الإشعار هذه تحتاج تنسيقاً لتبدو احترافية.",
"task": "نسّق الإشعار: أضف <kbd>padding: 1rem</kbd>، <kbd>border-left: 4px solid coral</kbd>، و<kbd>border-radius: 4px</kbd>.",
"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; } .alert { background: seashell; } .alert strong { color: coral; } .alert p { margin: 4px 0 0; color: #666; }",
"sandboxCSS": "",
"codePrefix": ".line {\n ",
"codePrefix": ".alert {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "border-bottom: 4px solid dodgerblue;",
"solution": "padding: 1rem;\n border-left: 4px solid coral;\n border-radius: 4px;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "padding", "expected": "1rem" },
"message": "اضبط <kbd>padding: 1rem</kbd>"
},
{
"type": "regex",
"value": "border-bottom:\\s*4px\\s+solid\\s+dodgerblue",
"message": "Set <kbd>border-bottom: 4px solid dodgerblue</kbd>",
"value": "border-left:\\s*4px\\s+solid\\s+coral",
"message": "اضبط <kbd>border-left: 4px solid coral</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "border-radius", "expected": "4px" },
"message": "اضبط <kbd>border-radius: 4px</kbd>"
}
]
}

View File

@@ -1,115 +1,100 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "units-variables",
"title": "CSS Units & Variables",
"description": "Understand the variety of CSS measurement units and how to define and use custom properties for maintainable styles.",
"title": "وحدات CSS والمتغيرات",
"description": "افهم تنوع وحدات القياس في CSS وكيفية تعريف واستخدام الخصائص المخصصة لأنماط قابلة للصيانة.",
"difficulty": "beginner",
"lessons": [
{
"id": "units-1",
"title": "Absolute vs. Relative Units",
"description": "Learn the difference between px, rem, em, %, and vw/vh for flexible, responsive layouts.",
"task": "Set the <kbd>width</kbd> of <kbd>.box</kbd> to <kbd>80%</kbd> and <kbd>max-width</kbd> to <kbd>37.5rem</kbd>.",
"previewHTML": "<div class=\"box\">Resize me!</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { background: #f5f5f5; padding: 1rem; }",
"title": "Relative Units",
"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": "نص هذه المقالة عريض جداً على الشاشات الكبيرة. أضف <kbd>max-width: 40rem</kbd> للحصول على عرض قراءة مثالي.",
"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: 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": "",
"codePrefix": "/* Set flexible sizing */\n.box {",
"codePrefix": ".article {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: 80%;\n max-width: 37.5rem;",
"codeSuffix": "\n}",
"solution": "max-width: 40rem;",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "width", "message": "Use <kbd>width</kbd> property", "options": { "caseSensitive": false } },
{ "type": "property_value", "value": { "property": "width", "expected": "80%" }, "message": "Set width to <kbd>80%</kbd>" },
{ "type": "contains", "value": "max-width", "message": "Use <kbd>max-width</kbd> property", "options": { "caseSensitive": false } },
{
"type": "property_value",
"value": { "property": "max-width", "expected": "37.5rem" },
"message": "Set max-width to <kbd>37.5rem</kbd>"
"value": { "property": "max-width", "expected": "40rem" },
"message": "اضبط <kbd>max-width: 40rem</kbd>"
}
]
},
{
"id": "units-2",
"title": "CSS Custom Properties",
"description": "Define and reuse variables (--custom properties) to centralize your theme values.",
"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>.",
"previewHTML": "<div class=\"themed\">Variable Box</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .themed { padding: 1rem; border: 0.125rem solid #ddd; }",
"title": "CSS Variables",
"description": "الخصائص المخصصة في CSS (المتغيرات) تتيح لك تعريف قيم قابلة لإعادة الاستخدام. عرّفها بـ <kbd>--اسم</kbd> واستخدمها بـ <kbd>var(--اسم)</kbd>. المتغيرات المعرّفة على <kbd>:root</kbd> متاحة في كل مكان.",
"task": "عرّف <kbd>--brand: steelblue</kbd> في <kbd>:root</kbd>، ثم استخدمها كلون <kbd>background</kbd> لـ <kbd>.btn</kbd>.",
"previewHTML": "<div class=\"actions\"><button class=\"btn\">Subscribe</button><button class=\"btn\">Learn More</button></div>",
"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": "",
"codePrefix": "/* Define and use a CSS variable */\n:root {",
"codePrefix": ":root {\n ",
"initialCode": "",
"codeSuffix": "}\n.themed { }",
"solution": " --main-color: #6200ee;\n}\n.themed {\n border-color: var(--main-color);",
"codeSuffix": "\n}\n\n.btn {\n background: var(--brand);\n}",
"solution": "--brand: steelblue;",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "--main-color",
"message": "Define <kbd>--main-color</kbd> in :root",
"value": "--brand",
"message": "عرّف المتغير <kbd>--brand</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "var(--main-color)",
"message": "Use <kbd>var(--main-color)</kbd>",
"value": "steelblue",
"message": "اضبط القيمة على <kbd>steelblue</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "border", "expected": "var(--main-color)" },
"message": "Apply variable to border color",
"options": { "exact": false }
}
]
},
{
"id": "units-3",
"title": "Unit Calculations (calc)",
"description": "Use the <kbd>calc()</kbd> function to combine different units in one expression.",
"task": "Set the <kbd>width</kbd> of <kbd>.sized</kbd> to <kbd>calc(100% - 2rem)</kbd> and <kbd>min-height</kbd> to <kbd>calc(10vh + 1rem)</kbd>.",
"previewHTML": "<div class=\"sized\">Calc Demo</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sized { background: #e8f5e9; padding: 1rem; }",
"title": "calc() Function",
"description": "دالة <kbd>calc()</kbd> تتيح لك خلط وحدات مختلفة في الحسابات. هذا ضروري للتخطيطات التي تجمع بين الأحجام الثابتة والمرنة، مثل تخطيط الشريط الجانبي.",
"task": "المحتوى الرئيسي يجب أن يملأ المساحة المتبقية بعد الشريط الجانبي 200px. اضبط <kbd>width: calc(100% - 200px)</kbd> على <kbd>.main</kbd>.",
"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: 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": "",
"codePrefix": "/* Use calc for dynamic sizing */\n.sized {",
"codePrefix": ".main {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: calc(100% - 2rem);\n min-height: calc(10vh + 1rem);",
"codeSuffix": "\n}",
"solution": "width: calc(100% - 200px);",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "calc", "message": "Use <kbd>calc()</kbd> function", "options": { "caseSensitive": false } },
{
"type": "regex",
"value": "width:\\s*calc\\(100% - 2rem\\)",
"message": "Width should be <kbd>calc(100% - 2rem)</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "min-height:\\s*calc\\(10vh \\+ 1rem\\)",
"message": "Min-height should be <kbd>calc(10vh + 1rem)</kbd>",
"value": "width:\\s*calc\\(\\s*100%\\s*-\\s*200px\\s*\\)",
"message": "اضبط <kbd>width: calc(100% - 200px)</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "units-4",
"title": "Viewport & Responsive Units",
"description": "Control layouts relative to viewport size with vw, vh, and vmin/vmax units.",
"task": "Give <kbd>.view</kbd> a <kbd>width</kbd> of <kbd>50vw</kbd> and <kbd>height</kbd> of <kbd>20vh</kbd>.",
"previewHTML": "<div class=\"view\">Viewport Box</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .view { background: #ffe0b2; }",
"title": "Viewport Units",
"description": "وحدات العرض تحدد حجم العناصر نسبة لنافذة المتصفح:<br>• <kbd>vw</kbd> 1% من عرض العرض<br>• <kbd>vh</kbd> 1% من ارتفاع العرض<br><br>هذه مثالية للأقسام بملء الشاشة مثل لافتات hero.",
"task": "اجعل قسم hero هذا يملأ ارتفاع العرض بضبط <kbd>min-height: 100vh</kbd>.",
"previewHTML": "<section class=\"hero\"><h1>Welcome</h1><p>Scroll down to explore</p></section>",
"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": "",
"codePrefix": "/* Use viewport units */\n.view {",
"codePrefix": ".hero {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: 50vw;\n height: 20vh;",
"codeSuffix": "\n}",
"solution": "min-height: 100vh;",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "vw", "message": "Use <kbd>vw</kbd> unit", "options": { "caseSensitive": false } },
{ "type": "contains", "value": "vh", "message": "Use <kbd>vh</kbd> unit", "options": { "caseSensitive": false } },
{ "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Set width to <kbd>50vw</kbd>" },
{ "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Set height to <kbd>20vh</kbd>" }
{
"type": "property_value",
"value": { "property": "min-height", "expected": "100vh" },
"message": "اضبط <kbd>min-height: 100vh</kbd>"
}
]
}
]

View File

@@ -1,15 +1,15 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "transitions-animations",
"title": "CSS Animations",
"description": "Bring interactivity to your UI by smoothly transitioning properties and creating keyframe-driven animations.",
"title": "حركات CSS",
"description": "أضف التفاعل لواجهتك من خلال انتقالات الخصائص السلسة والحركات المبنية على keyframes.",
"difficulty": "intermediate",
"lessons": [
{
"id": "transitions-1",
"title": "Transitions",
"description": "Learn how to apply <kbd>transition</kbd> to properties for smooth changes on state changes.<br><br><pre>transition: property duration;\n/* e.g. transition: background-color 0.3s; */</pre>",
"task": "Add <kbd>transition: background-color 0.3s</kbd> to <kbd>.btn</kbd> so the color fades smoothly on hover.",
"description": "تعلم كيفية تطبيق <kbd>transition</kbd> على الخصائص للتغييرات السلسة عند تغيير الحالة.<br><br><pre>transition: property duration;\n/* مثال: transition: background-color 0.3s; */</pre>",
"task": "أضف <kbd>transition: background-color 0.3s</kbd> ليتغير اللون بسلاسة عند التمرير.",
"previewHTML": "<button class=\"btn\">Hover Me</button>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: black; color: white; padding: 0.5rem 1rem; border: none; cursor: pointer; } .btn:hover { background: white; color: black; }",
"sandboxCSS": "",
@@ -22,13 +22,13 @@
{
"type": "contains",
"value": "transition",
"message": "Use the <kbd>transition</kbd> property",
"message": "استخدم خاصية <kbd>transition</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"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 }
}
]
@@ -36,8 +36,8 @@
{
"id": "transitions-2",
"title": "Timing Funcs",
"description": "Explore easing functions like <kbd>ease</kbd>, <kbd>linear</kbd>, <kbd>ease-in</kbd>, <kbd>ease-out</kbd> to control animation pacing.",
"task": "Set <kbd>transition-timing-function</kbd> to <kbd>ease-in-out</kbd> on <kbd>.btn</kbd>.",
"description": "استكشف دوال التسهيل مثل <kbd>ease</kbd>، <kbd>linear</kbd>، <kbd>ease-in</kbd>، <kbd>ease-out</kbd> للتحكم في إيقاع الحركة.",
"task": "اضبط <kbd>transition-timing-function</kbd> على <kbd>ease-in-out</kbd>.",
"previewHTML": "<button class=\"btn\">Timing</button>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: navy; color: gold; padding: 0.5rem 1rem; border: none; cursor: pointer; transition: all 0.5s; } .btn:hover { background: gold; color: navy; transform: scale(1.1); }",
"sandboxCSS": "",
@@ -50,21 +50,21 @@
{
"type": "contains",
"value": "transition-timing-function",
"message": "Use <kbd>transition-timing-function</kbd>",
"message": "استخدم <kbd>transition-timing-function</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "transition-timing-function", "expected": "ease-in-out" },
"message": "Set timing to <kbd>ease-in-out</kbd>"
"message": "اضبط التوقيت على <kbd>ease-in-out</kbd>"
}
]
},
{
"id": "transitions-3",
"title": "Keyframes",
"description": "Create named animations using <kbd>@keyframes</kbd> and apply them via the <kbd>animation</kbd> shorthand.<br><br><pre>@keyframes bounce {\n 50% { transform: translateY(-20px); }\n}\n.ball {\n animation: bounce 1s infinite;\n}</pre>",
"task": "Define a keyframe at <kbd>50%</kbd> with <kbd>transform: translateY(-20px)</kbd> and apply <kbd>animation: bounce 1s infinite</kbd> to <kbd>.ball</kbd>.",
"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": "عرّف keyframe عند <kbd>50%</kbd> مع <kbd>transform: translateY(-20px)</kbd> وطبّق <kbd>animation: bounce 1s infinite</kbd> على <kbd>.ball</kbd>.",
"previewHTML": "<div class=\"ball\"></div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: crimson; border-radius: 50%; margin: 2rem auto; }",
"sandboxCSS": "",
@@ -77,25 +77,25 @@
{
"type": "contains",
"value": "@keyframes bounce",
"message": "Define <kbd>@keyframes bounce</kbd>",
"message": "عرّف <kbd>@keyframes bounce</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "50%.*transform: translateY\\(-20px\\)",
"message": "At <kbd>50%</kbd>, use <kbd>transform: translateY(-20px)</kbd>",
"message": "عند <kbd>50%</kbd>، استخدم <kbd>transform: translateY(-20px)</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "animation",
"message": "Use <kbd>animation</kbd> property on <kbd>.ball</kbd>",
"message": "استخدم خاصية <kbd>animation</kbd> على <kbd>.ball</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "animation:.*bounce.*1s.*infinite",
"message": "Apply <kbd>animation: bounce 1s infinite</kbd>",
"message": "طبّق <kbd>animation: bounce 1s infinite</kbd>",
"options": { "caseSensitive": false }
}
]
@@ -103,8 +103,8 @@
{
"id": "transitions-4",
"title": "Animation Properties",
"description": "Fine-tune animations with <kbd>animation-delay</kbd>, <kbd>animation-iteration-count</kbd>, <kbd>animation-direction</kbd>, and <kbd>animation-fill-mode</kbd>.",
"task": "Apply the <kbd>pulse</kbd> animation to <kbd>.box</kbd> with <kbd>animation-name: pulse</kbd>, <kbd>animation-duration: 2s</kbd>, <kbd>animation-delay: 1s</kbd>, <kbd>animation-iteration-count: 2</kbd>, and <kbd>animation-fill-mode: forwards</kbd>.",
"description": "اضبط الحركات بـ <kbd>animation-delay</kbd>، <kbd>animation-iteration-count</kbd>، <kbd>animation-direction</kbd>، و <kbd>animation-fill-mode</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>",
"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": "",
@@ -117,27 +117,27 @@
{
"type": "property_value",
"value": { "property": "animation-name", "expected": "pulse" },
"message": "Set <kbd>animation-name: pulse</kbd>"
"message": "اضبط <kbd>animation-name: pulse</kbd>"
},
{
"type": "property_value",
"value": { "property": "animation-duration", "expected": "2s" },
"message": "Set <kbd>animation-duration: 2s</kbd>"
"message": "اضبط <kbd>animation-duration: 2s</kbd>"
},
{
"type": "property_value",
"value": { "property": "animation-delay", "expected": "1s" },
"message": "Set <kbd>animation-delay: 1s</kbd>"
"message": "اضبط <kbd>animation-delay: 1s</kbd>"
},
{
"type": "property_value",
"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",
"value": { "property": "animation-fill-mode", "expected": "forwards" },
"message": "Set <kbd>animation-fill-mode: forwards</kbd>"
"message": "اضبط <kbd>animation-fill-mode: forwards</kbd>"
}
]
}

View File

@@ -2,14 +2,14 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "responsive-design",
"title": "CSS Responsive Design",
"description": "Make your layouts adapt to different screen sizes using media queries and fluid design techniques.",
"description": "اجعل تخطيطاتك تتكيف مع أحجام الشاشات المختلفة باستخدام media queries وتقنيات التصميم المرن.",
"difficulty": "intermediate",
"lessons": [
{
"id": "responsive-1",
"title": "Media Queries",
"description": "Understand the syntax and use cases for CSS media queries to apply styles conditionally based on viewport characteristics.",
"task": "Write a media query with <kbd>@media (max-width: 600px)</kbd> that changes <kbd>.panel</kbd> background to <kbd>lightcoral</kbd>.",
"description": "افهم صياغة واستخدامات CSS media queries لتطبيق الأنماط شرطياً بناءً على خصائص viewport.<br><br><pre>@media (max-width: 600px) {\n .panel {\n background: lightcoral;\n }\n}</pre>",
"task": "اكتب media query باستخدام <kbd>@media (max-width: 600px)</kbd> لتغيير خلفية <kbd>.panel</kbd> إلى <kbd>lightcoral</kbd>.",
"previewHTML": "<div class=\"panel\">Resize the window</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .panel { padding: 1rem; background: lightblue; }",
"sandboxCSS": "",
@@ -22,19 +22,19 @@
{
"type": "regex",
"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 }
},
{
"type": "contains",
"value": ".panel",
"message": "Target <kbd>.panel</kbd> inside the media query",
"message": "استهدف <kbd>.panel</kbd> داخل media query",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "background", "expected": "lightcoral" },
"message": "Set <kbd>background: lightcoral</kbd>",
"message": "اضبط <kbd>background: lightcoral</kbd>",
"options": { "exact": false }
}
]
@@ -42,8 +42,8 @@
{
"id": "responsive-2",
"title": "Fluid Type",
"description": "Use relative units like <kbd>vw</kbd> to make font sizes scale with the viewport width.",
"task": "Set <kbd>font-size: 5vw</kbd> on <kbd>.text</kbd> so it scales as the viewport changes.",
"description": "استخدم وحدات نسبية مثل <kbd>vw</kbd> لجعل أحجام الخطوط تتناسب مع عرض viewport.",
"task": "اضبط <kbd>font-size: 5vw</kbd> لتتغير مع viewport.",
"previewHTML": "<p class=\"text\">Fluid Typography</p>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; }",
"sandboxCSS": "",
@@ -53,46 +53,46 @@
"solution": " font-size: 5vw;",
"previewContainer": "preview-area",
"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",
"title": "Flex Grids",
"description": "Combine CSS Grid with <kbd>auto-fit</kbd> or <kbd>auto-fill</kbd> for responsive column layouts.",
"task": "Add <kbd>display: grid</kbd>, <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd>, and <kbd>gap: 1rem</kbd> to <kbd>.cards</kbd>.",
"previewHTML": "<div class=\"cards\"><div>1</div><div>2</div><div>3</div><div>4</div></div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .cards > div { background: #d1c4e9; padding: 1rem; }",
"title": "Responsive Grid",
"description": "ادمج CSS Grid مع <kbd>auto-fit</kbd> أو <kbd>auto-fill</kbd> لتخطيطات أعمدة متجاوبة تضبط عدد الأعمدة تلقائياً بناءً على المساحة المتاحة.",
"task": "أضف <kbd>display: grid</kbd> و <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd> و <kbd>gap: 1rem</kbd>.",
"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: 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": "",
"codePrefix": "/* Create a responsive grid */\n.cards {",
"codePrefix": ".features {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
"codeSuffix": "\n}",
"solution": "display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "display", "expected": "grid" },
"message": "Set <kbd>display: grid</kbd>"
"message": "اضبط <kbd>display: grid</kbd>"
},
{
"type": "regex",
"value": "repeat\\(auto-fit,\\s*minmax\\(200px,\\s*1fr\\)\\)",
"message": "Use <kbd>repeat(auto-fit, minmax(200px, 1fr))</kbd>",
"message": "استخدم <kbd>repeat(auto-fit, minmax(200px, 1fr))</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "gap", "expected": "1rem" },
"message": "Set <kbd>gap: 1rem</kbd>"
"message": "اضبط <kbd>gap: 1rem</kbd>"
}
]
},
{
"id": "responsive-4",
"title": "Mobile-First",
"description": "Adopt a mobile-first approach by writing base styles for small screens and enhancing for larger viewports.",
"task": "Write a media query with <kbd>@media (min-width: 768px)</kbd> that sets <kbd>.sidebar</kbd> width to <kbd>250px</kbd>.",
"description": "اتبع نهج mobile-first بكتابة أنماط أساسية للشاشات الصغيرة وتحسينها لـ viewports أكبر.",
"task": "اكتب media query باستخدام <kbd>@media (min-width: 768px)</kbd> لضبط عرض <kbd>.sidebar</kbd> إلى <kbd>250px</kbd>.",
"previewHTML": "<aside class=\"sidebar\">Sidebar</aside>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sidebar { background: #c8e6c9; padding: 1rem; }",
"sandboxCSS": "",
@@ -105,19 +105,19 @@
{
"type": "regex",
"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 }
},
{
"type": "contains",
"value": ".sidebar",
"message": "Target <kbd>.sidebar</kbd> inside media query",
"message": "استهدف <kbd>.sidebar</kbd> في media query",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "width", "expected": "250px" },
"message": "Set <kbd>width: 250px</kbd>",
"message": "اضبط <kbd>width: 250px</kbd>",
"options": { "exact": false }
}
]

View File

@@ -2,65 +2,65 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-elements",
"title": "HTML Block & Inline",
"description": "Understanding the fundamental difference between container (block) and inline elements",
"description": "فهم الفرق الأساسي بين عناصر الحاويات (الكتلية) والعناصر السطرية",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "block-vs-inline-intro",
"title": "Block vs Inline Elements",
"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>&lt;div&gt;</kbd>, <kbd>&lt;p&gt;</kbd>, <kbd>&lt;h1&gt;</kbd>, <kbd>&lt;section&gt;</kbd><br><br><strong>Inline elements</strong> flow within text and only take needed width. Examples: <kbd>&lt;span&gt;</kbd>, <kbd>&lt;a&gt;</kbd>, <kbd>&lt;strong&gt;</kbd>, <kbd>&lt;em&gt;</kbd>",
"task": "Wrap the word <kbd>important</kbd> with <kbd>&lt;strong&gt;</kbd> tags to make it bold. Notice how the paragraph (block) takes full width while strong (inline) flows with text.",
"title": "العناصر الكتلية vs السطرية",
"description": "تنقسم عناصر HTML إلى فئتين رئيسيتين:<br><br><strong>العناصر الكتلية</strong> (الحاويات) تبدأ في سطر جديد وتأخذ العرض الكامل. أمثلة: <kbd>&lt;div&gt;</kbd>, <kbd>&lt;p&gt;</kbd>, <kbd>&lt;h1&gt;</kbd>, <kbd>&lt;section&gt;</kbd><br><br><strong>العناصر السطرية</strong> تتدفق داخل النص وتأخذ العرض المطلوب فقط. أمثلة: <kbd>&lt;span&gt;</kbd>, <kbd>&lt;a&gt;</kbd>, <kbd>&lt;strong&gt;</kbd>, <kbd>&lt;em&gt;</kbd>",
"task": "أحط الكلمة <kbd>مهمة</kbd> بوسوم <kbd>&lt;strong&gt;</kbd> لجعلها عريضة. لاحظ كيف يأخذ الفقرة (كتلي) العرض الكامل بينما strong (سطري) يتدفق مع النص.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; } p { background: #e3f2fd; padding: 10px; } strong { background: #ffecb3; }",
"sandboxCSS": "",
"initialCode": "<p>This is a paragraph with an important word.</p>",
"solution": "<p>This is a paragraph with an <strong>important</strong> word.</p>",
"initialCode": "<p>هذه فقرة تحتوي على كلمة مهمة.</p>",
"solution": "<p>هذه فقرة تحتوي على كلمة <strong>مهمة</strong>.</p>",
"previewContainer": "preview-area",
"validations": [
{
"type": "element_exists",
"value": "p",
"message": "Add a <kbd>&lt;p&gt;</kbd> paragraph element"
"message": "أضف عنصر فقرة <kbd>&lt;p&gt;</kbd>"
},
{
"type": "parent_child",
"value": { "parent": "p", "child": "strong" },
"message": "Wrap the word <kbd>important</kbd> with <kbd>&lt;strong&gt;</kbd> tags"
"message": "أحط الكلمة <kbd>مهمة</kbd> بوسوم <kbd>&lt;strong&gt;</kbd>"
}
]
},
{
"id": "semantic-containers",
"title": "Semantic Tags",
"description": "Modern HTML uses semantic containers that describe their content:<br><br><kbd>&lt;header&gt;</kbd> - Page or section header<br><kbd>&lt;nav&gt;</kbd> - Navigation links<br><kbd>&lt;main&gt;</kbd> - Main content area<br><kbd>&lt;section&gt;</kbd> - Thematic grouping<br><kbd>&lt;article&gt;</kbd> - Self-contained content<br><kbd>&lt;footer&gt;</kbd> - Page or section footer",
"task": "Create a basic page structure:<br>1. Add a <kbd>&lt;header&gt;</kbd> with an <kbd>&lt;h1&gt;</kbd> containing the text <code>My Website</code><br>2. Add a <kbd>&lt;main&gt;</kbd> element with a paragraph saying <code>Welcome to my site!</code><br>3. Add a <kbd>&lt;footer&gt;</kbd> with a paragraph saying <code>Copyright 2026</code>",
"title": "الوسوم الدلالية",
"description": "يستخدم HTML الحديث حاويات دلالية تصف محتواها:<br><br><kbd>&lt;header&gt;</kbd> - رأس الصفحة أو القسم<br><kbd>&lt;nav&gt;</kbd> - روابط التنقل<br><kbd>&lt;main&gt;</kbd> - منطقة المحتوى الرئيسي<br><kbd>&lt;section&gt;</kbd> - تجميع موضوعي<br><kbd>&lt;article&gt;</kbd> - محتوى مستقل<br><kbd>&lt;footer&gt;</kbd> - تذييل الصفحة أو القسم",
"task": "أنشئ هيكل صفحة أساسي:<br>1. أضف <kbd>&lt;header&gt;</kbd> مع <kbd>&lt;h1&gt;</kbd> يحتوي على النص <code>موقعي</code><br>2. أضف عنصر <kbd>&lt;main&gt;</kbd> مع فقرة تقول <code>مرحباً بك في موقعي!</code><br>3. أضف <kbd>&lt;footer&gt;</kbd> مع فقرة تقول <code>Copyright 2026</code>",
"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; }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "header",
"message": "Add a <kbd>&lt;header&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;header&gt;</kbd>"
},
{
"type": "element_exists",
"value": "main",
"message": "Add a <kbd>&lt;main&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;main&gt;</kbd>"
},
{
"type": "element_exists",
"value": "footer",
"message": "Add a <kbd>&lt;footer&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;footer&gt;</kbd>"
},
{
"type": "parent_child",
"value": { "parent": "header", "child": "h1" },
"message": "Add an <kbd>&lt;h1&gt;</kbd> heading inside your header"
"message": "أضف عنوان <kbd>&lt;h1&gt;</kbd> داخل header"
}
]
}

View File

@@ -1,100 +1,100 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-forms-basic",
"title": "HTML Forms",
"description": "Learn to create forms with various input types",
"title": "نماذج HTML",
"description": "تعلم إنشاء النماذج بأنواع حقول مختلفة",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "form-structure",
"title": "Form Structure",
"description": "Every form needs a <kbd>&lt;form&gt;</kbd> wrapper. Inside, use <kbd>&lt;label&gt;</kbd> to describe inputs and <kbd>&lt;input&gt;</kbd> for user data entry.<br><br>The <kbd>for</kbd> attribute on labels should match the <kbd>id</kbd> on inputs for accessibility.",
"task": "Create a form with:<br>1. A <kbd>&lt;label&gt;</kbd> with the text <code>Name:</code> and <kbd>for=\"name\"</kbd> attribute<br>2. A text <kbd>&lt;input&gt;</kbd> with <kbd>id=\"name\"</kbd> and <kbd>name=\"name\"</kbd> attributes",
"title": "هيكل النموذج",
"description": "كل نموذج يحتاج غلاف <kbd>&lt;form&gt;</kbd>. بداخله، استخدم <kbd>&lt;label&gt;</kbd> لوصف الحقول و <kbd>&lt;input&gt;</kbd> لإدخال البيانات.<br><br>سمة <kbd>for</kbd> في التسميات يجب أن تطابق <kbd>id</kbd> في الحقول للوصولية.",
"task": "أنشئ نموذجاً مع:<br>1. <kbd>&lt;label&gt;</kbd> بالنص <code>الاسم:</code> وسمة <kbd>for=\"name\"</kbd><br>2. <kbd>&lt;input&gt;</kbd> نصي بسمات <kbd>id=\"name\"</kbd> و <kbd>name=\"name\"</kbd>",
"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; }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "form",
"message": "Wrap everything in a <kbd>&lt;form&gt;</kbd> element"
"message": "أحط كل شيء بعنصر <kbd>&lt;form&gt;</kbd>"
},
{
"type": "element_exists",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for your input"
"message": "أضف <kbd>&lt;label&gt;</kbd> لحقلك"
},
{
"type": "element_exists",
"value": "input",
"message": "Add an <kbd>&lt;input&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;input&gt;</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "label", "attr": "for", "value": null },
"message": "Add a <kbd>for</kbd> attribute to your label"
"message": "أضف سمة <kbd>for</kbd> للتسمية"
},
{
"type": "attribute_value",
"value": { "selector": "input", "attr": "id", "value": null },
"message": "Add an <kbd>id</kbd> attribute to your input"
"message": "أضف سمة <kbd>id</kbd> لحقلك"
}
]
},
{
"id": "input-types",
"title": "Input Types",
"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",
"task": "Create a login form with two fields:<br>1. An email field: <kbd>&lt;label for=\"email\"&gt;Email:&lt;/label&gt;</kbd> and <kbd>&lt;input type=\"email\" id=\"email\"&gt;</kbd><br>2. A password field: <kbd>&lt;label for=\"password\"&gt;Password:&lt;/label&gt;</kbd> and <kbd>&lt;input type=\"password\" id=\"password\"&gt;</kbd>",
"title": "أنواع الحقول",
"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": "أنشئ نموذج تسجيل دخول بحقلين:<br>1. حقل بريد: <kbd>&lt;label for=\"email\"&gt;البريد:&lt;/label&gt;</kbd> و <kbd>&lt;input type=\"email\" id=\"email\"&gt;</kbd><br>2. حقل كلمة مرور: <kbd>&lt;label for=\"password\"&gt;كلمة المرور:&lt;/label&gt;</kbd> و <kbd>&lt;input type=\"password\" id=\"password\"&gt;</kbd>",
"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; }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "input[type='email']",
"message": "Add an input with type=\"email\""
"message": "أضف حقل بـ type=\"email\""
},
{
"type": "element_exists",
"value": "input[type='password']",
"message": "Add an input with type=\"password\""
"message": "أضف حقل بـ type=\"password\""
},
{
"type": "element_count",
"value": { "selector": "label", "min": 2 },
"message": "Add labels for both inputs"
"message": "أضف تسميات لكلا الحقلين"
}
]
},
{
"id": "submit-button",
"title": "Submit Button",
"description": "Forms need a way to submit data. Use:<br><br><kbd>&lt;button type=\"submit\"&gt;</kbd> - Preferred, flexible content<br><kbd>&lt;input type=\"submit\"&gt;</kbd> - Simple text-only button<br><br>The button text should be action-oriented (e.g., <code>Sign In</code>, 'Register', 'Send').",
"task": "Add a submit button to the form with the text <code>Sign In</code>.",
"title": "زر الإرسال",
"description": "النماذج تحتاج طريقة لإرسال البيانات. استخدم:<br><br><kbd>&lt;button type=\"submit\"&gt;</kbd> - مفضل، محتوى مرن<br><kbd>&lt;input type=\"submit\"&gt;</kbd> - زر نص بسيط<br><br>نص الزر يجب أن يكون موجه للعمل (مثل <code>تسجيل الدخول</code>، 'التسجيل'، 'إرسال').",
"task": "أضف زر إرسال للنموذج بالنص <code>تسجيل الدخول</code>.",
"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; }",
"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>",
"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>",
"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\">البريد:</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",
"validations": [
{
"type": "element_exists",
"value": "button[type='submit'], input[type='submit']",
"message": "Add a submit button to your form"
"message": "أضف زر إرسال لنموذجك"
},
{
"type": "element_text",
"value": { "selector": "button", "text": "Sign In" },
"message": "The button should say <kbd>Sign In</kbd>"
"value": { "selector": "button", "text": "تسجيل الدخول" },
"message": "يجب أن يعرض الزر <kbd>تسجيل الدخول</kbd>"
}
]
}

View File

@@ -1,110 +1,32 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-forms-validation",
"title": "HTML Validation",
"description": "Learn HTML5 built-in form validation attributes",
"title": "تحقق النماذج",
"description": "استخدم تحقق HTML5 المدمج لتجربة مستخدم أفضل",
"mode": "html",
"difficulty": "intermediate",
"difficulty": "beginner",
"lessons": [
{
"id": "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>&lt;input type=\"text\" required&gt;</kbd><br><br>The browser shows a validation message automatically.",
"task": "Make both the name and email fields required by adding the <kbd>required</kbd> attribute.",
"title": "الحقول المطلوبة",
"description": "سمة <kbd>required</kbd> تمنع إرسال النموذج إذا كان الحقل فارغاً. يعرض المتصفح رسالة تحقق تلقائياً - بدون JavaScript!<br><br>أضفها لأي حقل يجب ملؤه:<br><kbd>&lt;input type=\"text\" required&gt;</kbd>",
"task": "اجعل كلا الحقلين (الاسم والبريد) مطلوبين بإضافة سمة <kbd>required</kbd> لكل حقل.",
"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": "",
"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>",
"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\">الاسم *</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",
"validations": [
{
"type": "attribute_value",
"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",
"value": { "selector": "input[name='email']", "attr": "required", "value": true },
"message": "Add the <kbd>required</kbd> attribute 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>"
"message": "أضف <kbd>required</kbd> لحقل البريد"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-details-summary",
"title": "HTML Details & Summary",
"description": "Create expandable content sections without JavaScript",
"description": "أنشئ أقسام قابلة للتوسيع بدون JavaScript",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "details-summary-basic",
"title": "First Widget",
"description": "The <kbd>&lt;details&gt;</kbd> element creates a collapsible section. The <kbd>&lt;summary&gt;</kbd> provides the clickable label.<br><br>Click the summary to toggle the hidden content - no JavaScript needed!",
"task": "Create a <kbd>&lt;details&gt;</kbd> element with:<br>1. A <kbd>&lt;summary&gt;</kbd> saying <code>Click to reveal</code><br>2. A <kbd>&lt;p&gt;</kbd> with the text <code>This content was hidden!</code>",
"title": "أول عنصر تفاعلي",
"description": "عنصر <kbd>&lt;details&gt;</kbd> ينشئ قسماً قابلاً للطي. عنصر <kbd>&lt;summary&gt;</kbd> يوفر التسمية القابلة للنقر.<br><br>انقر على الملخص لإظهار المحتوى المخفي - بدون JavaScript!",
"task": "أنشئ عنصر <kbd>&lt;details&gt;</kbd> مع:<br>1. عنصر <kbd>&lt;summary&gt;</kbd> يقول <code>Click to reveal</code><br>2. عنصر <kbd>&lt;p&gt;</kbd> بالنص <code>This content was hidden!</code>",
"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; }",
"sandboxCSS": "",
@@ -21,30 +21,30 @@
{
"type": "element_exists",
"value": "details",
"message": "Add a <kbd>&lt;details&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;details&gt;</kbd>"
},
{
"type": "element_exists",
"value": "summary",
"message": "Add a <kbd>&lt;summary&gt;</kbd> inside the details"
"message": "أضف <kbd>&lt;summary&gt;</kbd> داخل details"
},
{
"type": "parent_child",
"value": { "parent": "details", "child": "summary" },
"message": "The <kbd>&lt;summary&gt;</kbd> must be inside <kbd>&lt;details&gt;</kbd>"
"message": "يجب أن يكون <kbd>&lt;summary&gt;</kbd> داخل <kbd>&lt;details&gt;</kbd>"
},
{
"type": "parent_child",
"value": { "parent": "details", "child": "p" },
"message": "Add a <kbd>&lt;p&gt;</kbd> inside <kbd>&lt;details&gt;</kbd> for the hidden content"
"message": "أضف <kbd>&lt;p&gt;</kbd> داخل <kbd>&lt;details&gt;</kbd> للمحتوى المخفي"
}
]
},
{
"id": "details-open-attribute",
"title": "Pre-expanded Details",
"description": "By default, <kbd>&lt;details&gt;</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.",
"task": "Add the <kbd>open</kbd> attribute to the <kbd>&lt;details&gt;</kbd> element to show the content by default.",
"title": "موسع افتراضياً",
"description": "افتراضياً، <kbd>&lt;details&gt;</kbd> مغلق. أضف سمة <kbd>open</kbd> لإظهار المحتوى في البداية.<br><br>هذه سمة منطقية - فقط أضف <kbd>open</kbd> بدون قيمة.",
"task": "أضف سمة <kbd>open</kbd> لعنصر <kbd>&lt;details&gt;</kbd> لإظهار المحتوى افتراضياً.",
"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; }",
"sandboxCSS": "",
@@ -55,15 +55,15 @@
{
"type": "attribute_value",
"value": { "selector": "details", "attr": "open", "value": true },
"message": "Add the <kbd>open</kbd> attribute to <kbd>&lt;details&gt;</kbd>"
"message": "أضف سمة <kbd>open</kbd> إلى <kbd>&lt;details&gt;</kbd>"
}
]
},
{
"id": "faq-accordion",
"title": "FAQ Accordion",
"description": "Multiple <kbd>&lt;details&gt;</kbd> elements create an accordion-style FAQ. Each question can be expanded independently.<br><br><b>Pro tip:</b> Type <kbd>details*3&gt;summary+p</kbd> and press Tab for Emmet expansion. <kbd>*3</kbd> creates 3 elements, <kbd>&gt;</kbd> nests inside, <kbd>+</kbd> adds siblings.",
"task": "Create an FAQ section with:<br>1. An <kbd>&lt;h1&gt;</kbd> saying <code>Frequently Asked Questions</code><br>2. Three <kbd>&lt;details&gt;</kbd> elements, each with a question in <kbd>&lt;summary&gt;</kbd> and an answer in <kbd>&lt;p&gt;</kbd>",
"title": "أكورديون FAQ",
"description": "عناصر <kbd>&lt;details&gt;</kbd> المتعددة تنشئ FAQ بأسلوب الأكورديون. كل سؤال يمكن توسيعه بشكل مستقل.<br><br><b>نصيحة:</b> اكتب <kbd>details*3&gt;summary+p</kbd> واضغط Tab لتوسيع Emmet. <kbd>*3</kbd> ينشئ 3 عناصر، <kbd>&gt;</kbd> يضع بالداخل، <kbd>+</kbd> يضيف أشقاء.",
"task": "أنشئ قسم FAQ مع:<br>1. عنصر <kbd>&lt;h1&gt;</kbd> يقول <code>Frequently Asked Questions</code><br>2. ثلاثة عناصر <kbd>&lt;details&gt;</kbd>، كل واحد بسؤال في <kbd>&lt;summary&gt;</kbd> وإجابة في <kbd>&lt;p&gt;</kbd>",
"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; }",
"sandboxCSS": "",
@@ -74,22 +74,22 @@
{
"type": "element_exists",
"value": "h1",
"message": "Add an <kbd>&lt;h1&gt;</kbd> heading for the FAQ title"
"message": "أضف عنوان <kbd>&lt;h1&gt;</kbd> لعنوان FAQ"
},
{
"type": "element_count",
"value": { "selector": "details", "min": 3 },
"message": "Create at least 3 <kbd>&lt;details&gt;</kbd> elements for the FAQ"
"message": "أنشئ على الأقل 3 عناصر <kbd>&lt;details&gt;</kbd> للـ FAQ"
},
{
"type": "element_count",
"value": { "selector": "summary", "min": 3 },
"message": "Each <kbd>&lt;details&gt;</kbd> needs a <kbd>&lt;summary&gt;</kbd> for the question"
"message": "كل <kbd>&lt;details&gt;</kbd> يحتاج <kbd>&lt;summary&gt;</kbd> للسؤال"
},
{
"type": "element_count",
"value": { "selector": "details p", "min": 3 },
"message": "Each <kbd>&lt;details&gt;</kbd> needs a <kbd>&lt;p&gt;</kbd> for the answer"
"message": "كل <kbd>&lt;details&gt;</kbd> يحتاج <kbd>&lt;p&gt;</kbd> للإجابة"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-progress-meter",
"title": "HTML Progress & Meter",
"description": "Display completion status and scalar measurements natively",
"description": "اعرض حالة الإكمال والقياسات بشكل أصلي",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "progress-basic",
"title": "Progress Bars",
"description": "The <kbd>&lt;progress&gt;</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>&lt;progress&gt;...&lt;/progress&gt;</kbd> with fallback text inside for older browsers.",
"task": "Create a progress bar showing 70% completion:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Download:</code><br>2. Add a <kbd>&lt;progress&gt;</kbd> with <kbd>value=\"70\"</kbd> and <kbd>max=\"100\"</kbd>",
"title": "أشرطة التقدم",
"description": "عنصر <kbd>&lt;progress&gt;</kbd> يُظهر إكمال المهمة. استخدم <kbd>value</kbd> للتقدم الحالي و <kbd>max</kbd> للإجمالي.<br><br><b>ملاحظة:</b> هذا ليس وسماً ذاتي الإغلاق! اكتب <kbd>&lt;progress&gt;...&lt;/progress&gt;</kbd> مع نص بديل بالداخل للمتصفحات القديمة.",
"task": "أنشئ شريط تقدم يُظهر 70% إكمال:<br>1. أضف <kbd>&lt;label&gt;</kbd> يقول <code>Download:</code><br>2. أضف <kbd>&lt;progress&gt;</kbd> مع <kbd>value=\"70\"</kbd> و <kbd>max=\"100\"</kbd>",
"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; }",
"sandboxCSS": "",
@@ -21,30 +21,30 @@
{
"type": "element_exists",
"value": "progress",
"message": "Add a <kbd>&lt;progress&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;progress&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"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",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for the progress bar"
"message": "أضف <kbd>&lt;label&gt;</kbd> لشريط التقدم"
}
]
},
{
"id": "progress-indeterminate",
"title": "Indeterminate Progress",
"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.",
"task": "Create a loading indicator:<br>1. Add a <kbd>&lt;p&gt;</kbd> saying <code>Loading...</code><br>2. Add a <kbd>&lt;progress&gt;</kbd> without a value attribute",
"title": "تقدم غير محدد",
"description": "عندما يكون التقدم غير معروف (مثل التحميل)، احذف سمة <kbd>value</kbd>. هذا ينشئ حالة متحركة غير محددة.<br><br>مفيد لطلبات الشبكة أو العمليات ذات المدة غير المعروفة.",
"task": "أنشئ مؤشر تحميل:<br>1. أضف <kbd>&lt;p&gt;</kbd> يقول <code>Loading...</code><br>2. أضف <kbd>&lt;progress&gt;</kbd> بدون سمة value",
"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; }",
"sandboxCSS": "",
@@ -55,20 +55,20 @@
{
"type": "element_exists",
"value": "progress",
"message": "Add a <kbd>&lt;progress&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;progress&gt;</kbd>"
},
{
"type": "element_exists",
"value": "p",
"message": "Add a <kbd>&lt;p&gt;</kbd> with loading text"
"message": "أضف <kbd>&lt;p&gt;</kbd> مع نص التحميل"
}
]
},
{
"id": "meter-gauge",
"title": "Meter Gauges",
"description": "The <kbd>&lt;meter&gt;</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!",
"task": "Create a battery level meter:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Battery:</code><br>2. Add a <kbd>&lt;meter&gt;</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>",
"title": "مقاييس meter",
"description": "عنصر <kbd>&lt;meter&gt;</kbd> يعرض قيمة قياسية ضمن نطاق. استخدمه للقياسات مثل مساحة القرص، البطارية، أو التقييمات.<br><br>عيّن <kbd>low</kbd> و <kbd>high</kbd> و <kbd>optimum</kbd> لتحديد النطاقات الجيدة/السيئة - المتصفح يلونها وفقاً لذلك!",
"task": "أنشئ مقياس مستوى البطارية:<br>1. أضف <kbd>&lt;label&gt;</kbd> يقول <code>Battery:</code><br>2. أضف <kbd>&lt;meter&gt;</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": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
"sandboxCSS": "",
@@ -79,22 +79,42 @@
{
"type": "element_exists",
"value": "meter",
"message": "Add a <kbd>&lt;meter&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;meter&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"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",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for the meter"
"message": "أضف <kbd>&lt;label&gt;</kbd> للـ meter"
}
]
}

View File

@@ -1,16 +1,16 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-datalist",
"title": "Datalist",
"description": "Provide suggestions for text inputs without JavaScript",
"title": "قائمة البيانات",
"description": "قدم اقتراحات لحقول النص بدون JavaScript",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "datalist-basic",
"title": "Input with Suggestions",
"description": "The <kbd>&lt;datalist&gt;</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!",
"task": "Create a browser selector:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Browser:</code><br>2. Add an <kbd>&lt;input&gt;</kbd> with <kbd>list=\"browsers\"</kbd><br>3. Add a <kbd>&lt;datalist id=\"browsers\"&gt;</kbd> with options for Chrome, Firefox, and Safari",
"title": "حقل مع اقتراحات",
"description": "عنصر <kbd>&lt;datalist&gt;</kbd> يوفر اقتراحات الإكمال التلقائي للحقول. اربطه باستخدام سمة <kbd>list</kbd> في الحقل بما يتطابق مع <kbd>id</kbd> قائمة البيانات.<br><br>يمكن للمستخدمين الكتابة بحرية - الاقتراحات مجرد مساعدات!",
"task": "أنشئ محدد متصفح:<br>1. أضف <kbd>&lt;label&gt;</kbd> يقول <code>المتصفح:</code><br>2. أضف <kbd>&lt;input&gt;</kbd> مع <kbd>list=\"browsers\"</kbd><br>3. أضف <kbd>&lt;datalist id=\"browsers\"&gt;</kbd> مع خيارات Chrome و Firefox و Safari",
"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; }",
"sandboxCSS": "",
@@ -21,30 +21,30 @@
{
"type": "element_exists",
"value": "datalist",
"message": "Add a <kbd>&lt;datalist&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;datalist&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"value": { "selector": "option", "min": 3 },
"message": "Add at least 3 <kbd>&lt;option&gt;</kbd> elements inside <kbd>&lt;datalist&gt;</kbd>"
"message": "أضف على الأقل 3 عناصر <kbd>&lt;option&gt;</kbd> داخل <kbd>&lt;datalist&gt;</kbd>"
},
{
"type": "element_exists",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for the input"
"message": "أضف <kbd>&lt;label&gt;</kbd> للحقل"
}
]
},
{
"id": "datalist-countries",
"title": "Country Selector",
"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.",
"task": "Create a country input:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Country:</code><br>2. Add an <kbd>&lt;input&gt;</kbd> with <kbd>list=\"countries\"</kbd><br>3. Add a <kbd>&lt;datalist id=\"countries\"&gt;</kbd> with at least 4 country options",
"title": "محدد الدول",
"description": "قوائم البيانات تعمل بشكل رائع للقوائم الطويلة مثل الدول. يمكن للمستخدمين الكتابة لتصفية الاقتراحات فوراً.<br><br>سمة <kbd>value</kbd> هي ما يتم إدخاله، ويمكنك إضافة نص عرض بعدها.",
"task": "أنشئ حقل دولة:<br>1. أضف <kbd>&lt;label&gt;</kbd> يقول <code>الدولة:</code><br>2. أضف <kbd>&lt;input&gt;</kbd> مع <kbd>list=\"countries\"</kbd><br>3. أضف <kbd>&lt;datalist id=\"countries\"&gt;</kbd> مع 4 خيارات دول على الأقل",
"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); }",
"sandboxCSS": "",
@@ -55,22 +55,22 @@
{
"type": "element_exists",
"value": "datalist",
"message": "Add a <kbd>&lt;datalist&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;datalist&gt;</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "datalist", "attr": "id", "value": "countries" },
"message": "Set <kbd>id=</kbd>\"countries\" on the datalist"
"message": "عيّن <kbd>id=</kbd>\"countries\" في قائمة البيانات"
},
{
"type": "attribute_value",
"value": { "selector": "input", "attr": "list", "value": "countries" },
"message": "Connect the input using <kbd>list=</kbd>\"countries\""
"message": "اربط الحقل باستخدام <kbd>list=</kbd>\"countries\""
},
{
"type": "element_count",
"value": { "selector": "option", "min": 4 },
"message": "Add at least 4 country options"
"message": "أضف على الأقل 4 خيارات دول"
}
]
}

View File

@@ -1,16 +1,16 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-dialog",
"title": "Dialogs",
"description": "Create modal dialogs without JavaScript libraries",
"title": "مربعات الحوار",
"description": "أنشئ مربعات حوار نموذجية بدون مكتبات JavaScript",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "dialog-basic",
"title": "Open Dialog",
"description": "The <kbd>&lt;dialog&gt;</kbd> element creates a native modal. Add the <kbd>open</kbd> attribute to show it.<br><br>Use <kbd>&lt;form method=\"dialog\"&gt;</kbd> inside to close it when the form submits - no JavaScript needed!",
"task": "Create a dialog with:<br>1. The <kbd>open</kbd> attribute to show it<br>2. An <kbd>&lt;h2&gt;</kbd> saying <code>Welcome!</code><br>3. A <kbd>&lt;p&gt;</kbd> with a greeting message<br>4. A <kbd>&lt;form method=\"dialog\"&gt;</kbd> with a close button",
"title": "فتح مربع الحوار",
"description": "عنصر <kbd>&lt;dialog&gt;</kbd> ينشئ نافذة نموذجية أصلية. أضف سمة <kbd>open</kbd> لعرضها.<br><br>استخدم <kbd>&lt;form method=\"dialog\"&gt;</kbd> بداخلها لإغلاقها عند إرسال النموذج - بدون JavaScript!",
"task": "أنشئ مربع حوار مع:<br>1. سمة <kbd>open</kbd> لعرضه<br>2. عنصر <kbd>&lt;h2&gt;</kbd> يقول <code>أهلاً!</code><br>3. عنصر <kbd>&lt;p&gt;</kbd> مع رسالة ترحيب<br>4. عنصر <kbd>&lt;form method=\"dialog\"&gt;</kbd> مع زر إغلاق",
"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; }",
"sandboxCSS": "",
@@ -21,35 +21,35 @@
{
"type": "element_exists",
"value": "dialog",
"message": "Add a <kbd>&lt;dialog&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;dialog&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"value": "dialog h2",
"message": "Add an <kbd>&lt;h2&gt;</kbd> heading inside the dialog"
"message": "أضف عنوان <kbd>&lt;h2&gt;</kbd> داخل مربع الحوار"
},
{
"type": "element_exists",
"value": "form[method='dialog']",
"message": "Add a <kbd>&lt;form method=\"dialog\"&gt;</kbd> for closing"
"message": "أضف <kbd>&lt;form method=\"dialog\"&gt;</kbd> للإغلاق"
},
{
"type": "element_exists",
"value": "dialog button",
"message": "Add a close button inside the form"
"message": "أضف زر إغلاق داخل النموذج"
}
]
},
{
"id": "dialog-form",
"title": "Dialog + Form",
"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.",
"task": "Create a confirmation dialog:<br>1. Add <kbd>open</kbd> to show it<br>2. An <kbd>&lt;h2&gt;</kbd> saying <code>Confirm Delete</code><br>3. A <kbd>&lt;p&gt;</kbd> asking <code>Are you sure?</code><br>4. A <kbd>&lt;form method=\"dialog\"&gt;</kbd> with Cancel and Delete buttons",
"title": "حوار + نموذج",
"description": "مربعات الحوار يمكن أن تحتوي على نماذج كاملة. <kbd>method=\"dialog\"</kbd> يجعل النموذج يغلق مربع الحوار عند الإرسال بدلاً من إرسال البيانات.<br><br>هذا النمط مثالي لحوارات التأكيد، المدخلات السريعة، أو لوحات الإعدادات.",
"task": "أنشئ مربع حوار تأكيد:<br>1. أضف <kbd>open</kbd> لعرضه<br>2. عنصر <kbd>&lt;h2&gt;</kbd> يقول <code>تأكيد الحذف</code><br>3. عنصر <kbd>&lt;p&gt;</kbd> يسأل <code>هل أنت متأكد؟</code><br>4. عنصر <kbd>&lt;form method=\"dialog\"&gt;</kbd> مع أزرار إلغاء وحذف",
"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; }",
"sandboxCSS": "",
@@ -60,22 +60,22 @@
{
"type": "element_exists",
"value": "dialog[open]",
"message": "Add a <kbd>&lt;dialog&gt;</kbd> with the open attribute"
"message": "أضف <kbd>&lt;dialog&gt;</kbd> مع سمة open"
},
{
"type": "element_exists",
"value": "dialog h2",
"message": "Add a heading to the dialog"
"message": "أضف عنواناً لمربع الحوار"
},
{
"type": "element_exists",
"value": "form[method='dialog']",
"message": "Add a <kbd>&lt;form method=\"dialog\"&gt;</kbd> for the buttons"
"message": "أضف <kbd>&lt;form method=\"dialog\"&gt;</kbd> للأزرار"
},
{
"type": "element_count",
"value": { "selector": "dialog button", "min": 2 },
"message": "Add at least 2 buttons (Cancel and Confirm)"
"message": "أضف على الأقل زرين (إلغاء وتأكيد)"
}
]
}

View File

@@ -1,16 +1,16 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-forms-fieldset",
"title": "Fieldsets",
"description": "Group form controls with fieldset and legend elements",
"title": "مجموعة الحقول",
"description": "جمّع عناصر التحكم في النموذج باستخدام fieldset و legend",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "fieldset-basic",
"title": "Grouping with Fieldset",
"description": "The <kbd>&lt;fieldset&gt;</kbd> element groups related form controls together. Add a <kbd>&lt;legend&gt;</kbd> as the first child to give the group a title.<br><br>This helps with accessibility and visual organization of complex forms.",
"task": "Create a form with a fieldset:<br>1. A <kbd>&lt;form&gt;</kbd> element<br>2. A <kbd>&lt;fieldset&gt;</kbd> inside<br>3. A <kbd>&lt;legend&gt;</kbd> saying <code>Personal Info</code><br>4. Two labeled inputs for name and email",
"title": "التجميع مع Fieldset",
"description": "عنصر <kbd>&lt;fieldset&gt;</kbd> يجمع عناصر التحكم المرتبطة في النموذج معاً. أضف <kbd>&lt;legend&gt;</kbd> كأول عنصر فرعي لإعطاء المجموعة عنواناً.<br><br>هذا يساعد في إمكانية الوصول والتنظيم البصري للنماذج المعقدة.",
"task": "أنشئ نموذجاً مع fieldset:<br>1. عنصر <kbd>&lt;form&gt;</kbd><br>2. عنصر <kbd>&lt;fieldset&gt;</kbd> بداخله<br>3. عنصر <kbd>&lt;legend&gt;</kbd> يقول <code>المعلومات الشخصية</code><br>4. حقلين مُعنونين للاسم والبريد الإلكتروني",
"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; }",
"sandboxCSS": "",
@@ -21,35 +21,35 @@
{
"type": "element_exists",
"value": "form",
"message": "Add a <kbd>&lt;form&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;form&gt;</kbd>"
},
{
"type": "element_exists",
"value": "fieldset",
"message": "Add a <kbd>&lt;fieldset&gt;</kbd> inside the form"
"message": "أضف <kbd>&lt;fieldset&gt;</kbd> داخل النموذج"
},
{
"type": "element_exists",
"value": "legend",
"message": "Add a <kbd>&lt;legend&gt;</kbd> to title your fieldset"
"message": "أضف <kbd>&lt;legend&gt;</kbd> لعنونة مجموعة الحقول"
},
{
"type": "element_count",
"value": { "selector": "label", "min": 2 },
"message": "Add at least 2 labels"
"message": "أضف على الأقل عنوانين"
},
{
"type": "element_count",
"value": { "selector": "input", "min": 2 },
"message": "Add at least 2 input fields"
"message": "أضف على الأقل حقلي إدخال"
}
]
},
{
"id": "fieldset-textarea",
"title": "Adding Textarea",
"description": "The <kbd>&lt;textarea&gt;</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.",
"task": "Create a contact form:<br>1. A <kbd>&lt;fieldset&gt;</kbd> with <kbd>&lt;legend&gt;</kbd> <code>Contact Us</code><br>2. A labeled <kbd>&lt;input&gt;</kbd> for email<br>3. A labeled <kbd>&lt;textarea&gt;</kbd> for the message<br>4. A submit <kbd>&lt;button&gt;</kbd>",
"title": "إضافة Textarea",
"description": "عنصر <kbd>&lt;textarea&gt;</kbd> ينشئ حقل إدخال نص متعدد الأسطر، مثالي للمحتوى الطويل مثل الرسائل أو الأوصاف.<br><br>استخدم سمات <kbd>rows</kbd> و <kbd>cols</kbd> لتعيين الحجم الافتراضي.",
"task": "أنشئ نموذج اتصال:<br>1. عنصر <kbd>&lt;fieldset&gt;</kbd> مع <kbd>&lt;legend&gt;</kbd> <code>تواصل معنا</code><br>2. حقل <kbd>&lt;input&gt;</kbd> مُعنون للبريد الإلكتروني<br>3. حقل <kbd>&lt;textarea&gt;</kbd> مُعنون للرسالة<br>4. زر <kbd>&lt;button&gt;</kbd> للإرسال",
"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; }",
"sandboxCSS": "",
@@ -60,35 +60,35 @@
{
"type": "element_exists",
"value": "fieldset",
"message": "Add a <kbd>&lt;fieldset&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;fieldset&gt;</kbd>"
},
{
"type": "element_exists",
"value": "legend",
"message": "Add a <kbd>&lt;legend&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;legend&gt;</kbd>"
},
{
"type": "element_exists",
"value": "textarea",
"message": "Add a <kbd>&lt;textarea&gt;</kbd> for the message"
"message": "أضف <kbd>&lt;textarea&gt;</kbd> للرسالة"
},
{
"type": "element_exists",
"value": "button",
"message": "Add a submit button"
"message": "أضف زر إرسال"
},
{
"type": "element_exists",
"value": "input",
"message": "Add an input field for email"
"message": "أضف حقل للبريد الإلكتروني"
}
]
},
{
"id": "fieldset-multiple",
"title": "Multiple Fieldsets",
"description": "Complex forms can use multiple <kbd>&lt;fieldset&gt;</kbd> elements to organize different sections.<br><br>This improves usability for long forms like registration or checkout pages.",
"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",
"title": "مجموعات حقول متعددة",
"description": "النماذج المعقدة يمكنها استخدام عناصر <kbd>&lt;fieldset&gt;</kbd> متعددة لتنظيم أقسام مختلفة.<br><br>هذا يحسن قابلية الاستخدام للنماذج الطويلة مثل التسجيل أو الدفع.",
"task": "أنشئ نموذج تسجيل مع 2 مجموعات حقول:<br>1. <code>معلومات الحساب</code> مع حقول اسم المستخدم وكلمة المرور<br>2. <code>التفضيلات</code> مع textarea للسيرة الذاتية<br>3. زر إرسال خارج مجموعات الحقول",
"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; }",
"sandboxCSS": "",
@@ -99,27 +99,27 @@
{
"type": "element_count",
"value": { "selector": "fieldset", "min": 2 },
"message": "Create at least 2 fieldsets"
"message": "أنشئ على الأقل 2 مجموعات حقول"
},
{
"type": "element_count",
"value": { "selector": "legend", "min": 2 },
"message": "Add a legend to each fieldset"
"message": "أضف legend لكل مجموعة حقول"
},
{
"type": "element_exists",
"value": "textarea",
"message": "Add a textarea for the bio"
"message": "أضف textarea للسيرة الذاتية"
},
{
"type": "element_exists",
"value": "button",
"message": "Add a submit button"
"message": "أضف زر إرسال"
},
{
"type": "element_count",
"value": { "selector": "input", "min": 2 },
"message": "Add at least 2 input fields"
"message": "أضف على الأقل حقلي إدخال"
}
]
}

View File

@@ -1,125 +1,42 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-tables",
"title": "HTML Tables",
"description": "Create structured data tables with headers and captions",
"title": "جداول HTML",
"description": "أنشئ جداول بيانات منظمة بترميز دلالي",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "table-basic",
"title": "Basic Table Structure",
"description": "Tables use <kbd>&lt;table&gt;</kbd> with <kbd>&lt;tr&gt;</kbd> for rows. Inside rows, use <kbd>&lt;th&gt;</kbd> for headers and <kbd>&lt;td&gt;</kbd> for data cells.<br><br>The <kbd>&lt;caption&gt;</kbd> element provides an accessible title for the table.",
"task": "Create a simple table with:<br>1. A <kbd>&lt;caption&gt;</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",
"title": "جداول البيانات",
"description": "الجداول تعرض البيانات المنظمة في صفوف وأعمدة. استخدم <kbd>&lt;table&gt;</kbd> كحاوية، <kbd>&lt;tr&gt;</kbd> للصفوف، <kbd>&lt;th&gt;</kbd> لخلايا الرأس و <kbd>&lt;td&gt;</kbd> لخلايا البيانات.<br><br>أضف <kbd>&lt;caption&gt;</kbd> لعنوان قابل للوصول يصف محتوى الجدول.",
"task": "أنشئ جدول أسعار:<br>1. عنصر <kbd>&lt;caption&gt;</kbd> يقول <code>Pricing</code><br>2. صف رأس مع <code>Plan</code> و <code>Price</code><br>3. صفين بيانات لـ Basic ($9) و Pro ($29)",
"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": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "table",
"message": "Add a <kbd>&lt;table&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;table&gt;</kbd>"
},
{
"type": "element_exists",
"value": "caption",
"message": "Add a <kbd>&lt;caption&gt;</kbd> for the table title"
"message": "أضف <kbd>&lt;caption&gt;</kbd> لعنوان الجدول"
},
{
"type": "element_count",
"value": { "selector": "th", "min": 2 },
"message": "Add at least 2 header cells (th)"
"message": "أضف خلايا رأس (<kbd>&lt;th&gt;</kbd>) لـ Plan و Price"
},
{
"type": "element_count",
"value": { "selector": "tr", "min": 3 },
"message": "Add at least 3 rows (1 header + 2 data rows)"
}
]
},
{
"id": "table-thead-tbody",
"title": "Table Head & Body",
"description": "Use <kbd>&lt;thead&gt;</kbd> to group header rows and <kbd>&lt;tbody&gt;</kbd> to group data rows. This helps browsers and assistive technology understand the table structure.<br><br>You can also use <kbd>&lt;tfoot&gt;</kbd> for footer rows like totals.",
"task": "Create a structured table:<br>1. A <kbd>&lt;caption&gt;</kbd> with <code>Monthly Sales</code><br>2. A <kbd>&lt;thead&gt;</kbd> with Month and Revenue headers<br>3. A <kbd>&lt;tbody&gt;</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>&lt;table&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "caption",
"message": "Add a <kbd>&lt;caption&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "thead",
"message": "Add a <kbd>&lt;thead&gt;</kbd> for the header section"
},
{
"type": "element_exists",
"value": "tbody",
"message": "Add a <kbd>&lt;tbody&gt;</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>&lt;tfoot&gt;</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>&lt;caption&gt;</kbd> with <code>Order Summary</code><br>2. A <kbd>&lt;thead&gt;</kbd> with Item and Price headers<br>3. A <kbd>&lt;tbody&gt;</kbd> with 2 items<br>4. A <kbd>&lt;tfoot&gt;</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>&lt;table&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "caption",
"message": "Add a <kbd>&lt;caption&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "thead",
"message": "Add a <kbd>&lt;thead&gt;</kbd> section"
},
{
"type": "element_exists",
"value": "tbody",
"message": "Add a <kbd>&lt;tbody&gt;</kbd> section"
},
{
"type": "element_exists",
"value": "tfoot",
"message": "Add a <kbd>&lt;tfoot&gt;</kbd> section for the total"
},
{
"type": "element_count",
"value": { "selector": "tbody tr", "min": 2 },
"message": "Add at least 2 item rows in tbody"
"message": "أضف 3 صفوف (1 رأس + 2 صفوف بيانات)"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-marquee",
"title": "HTML Marquee",
"description": "Create scrolling text with the classic (deprecated but fun!) marquee element",
"description": "أنشئ نصاً متحركاً باستخدام عنصر marquee الكلاسيكي (قديم لكنه ممتع!)",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "marquee-basic",
"title": "Scrolling Text",
"description": "The <kbd>&lt;marquee&gt;</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!",
"task": "Create a simple marquee:<br>1. Add a <kbd>&lt;marquee&gt;</kbd> element<br>2. Put some text inside like <code>Welcome to my website!</code>",
"title": "النص المتحرك",
"description": "عنصر <kbd>&lt;marquee&gt;</kbd> ينشئ نصاً متحركاً - كلاسيكي من الويب القديم! رغم أنه قديم، لا يزال يعمل في معظم المتصفحات.<br><br>ملاحظة: للإنتاج، استخدم حركات CSS بدلاً منه. لكن للتعلم والمرح، marquee رائع!",
"task": "أنشئ marquee بسيط:<br>1. أضف عنصر <kbd>&lt;marquee&gt;</kbd><br>2. ضع نصاً بداخله مثل <code>مرحباً بك في موقعي!</code>",
"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; }",
"sandboxCSS": "",
@@ -21,15 +21,15 @@
{
"type": "element_exists",
"value": "marquee",
"message": "Add a <kbd>&lt;marquee&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;marquee&gt;</kbd>"
}
]
},
{
"id": "marquee-direction",
"title": "Direction & Behavior",
"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)",
"task": "Create a bouncing marquee:<br>1. Add a <kbd>&lt;marquee&gt;</kbd> element<br>2. Set <kbd>behavior=\"alternate\"</kbd> to make it bounce<br>3. Add some fun text",
"title": "الاتجاه والسلوك",
"description": "تحكم في marquee باستخدام السمات:<br>• <kbd>direction</kbd>: left، right، up، down<br>• <kbd>behavior</kbd>: scroll (افتراضي)، slide (يتوقف عند الحافة)، alternate (يرتد)<br>• <kbd>scrollamount</kbd>: السرعة (الافتراضي 6)",
"task": "أنشئ marquee مرتداً:<br>1. أضف عنصر <kbd>&lt;marquee&gt;</kbd><br>2. اضبط <kbd>behavior=\"alternate\"</kbd> ليرتد<br>3. أضف نصاً ممتعاً",
"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; }",
"sandboxCSS": "",
@@ -40,20 +40,20 @@
{
"type": "element_exists",
"value": "marquee",
"message": "Add a <kbd>&lt;marquee&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;marquee&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"title": "Retro News Ticker",
"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.",
"task": "Create a news ticker:<br>1. A <kbd>&lt;marquee&gt;</kbd> with <kbd>direction=\"left\"</kbd><br>2. Set <kbd>scrollamount=\"5\"</kbd> for smooth scrolling<br>3. Add a breaking news headline inside",
"title": "شريط أخبار كلاسيكي",
"description": "اجمع عدة سمات marquee لتأثير شريط أخبار كلاسيكي. يمكنك حتى وضع عناصر متعددة بداخله!<br><br>تذكر: هذا HTML قديم. المواقع الحديثة تستخدم حركات CSS، لكن marquee رائع لفهم تاريخ الويب.",
"task": "أنشئ شريط أخبار:<br>1. عنصر <kbd>&lt;marquee&gt;</kbd> مع <kbd>direction=\"left\"</kbd><br>2. اضبط <kbd>scrollamount=\"5\"</kbd> للتمرير السلس<br>3. أضف عنواناً إخبارياً عاجلاً بداخله",
"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; }",
"sandboxCSS": "",
@@ -64,17 +64,17 @@
{
"type": "element_exists",
"value": "marquee",
"message": "Add a <kbd>&lt;marquee&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;marquee&gt;</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "marquee", "attr": "direction", "value": "left" },
"message": "Add <kbd>direction=</kbd>\"left\" for horizontal scrolling"
"message": "أضف <kbd>direction=</kbd>\"left\" للتمرير الأفقي"
},
{
"type": "attribute_value",
"value": { "selector": "marquee", "attr": "scrollamount", "value": "5" },
"message": "Add <kbd>scrollamount=</kbd>\"5\" for smooth speed"
"message": "أضف <kbd>scrollamount=</kbd>\"5\" لسرعة سلسة"
}
]
}

View File

@@ -2,99 +2,169 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-svg",
"title": "HTML SVG",
"description": "Draw scalable vector graphics directly in HTML",
"description": "ارسم رسومات متجهة قابلة للتحجيم مباشرة في HTML",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "svg-circle",
"title": "Drawing Circles",
"description": "SVG (Scalable Vector Graphics) lets you draw shapes directly in HTML. The <kbd>&lt;svg&gt;</kbd> element is the container, with <kbd>width</kbd> and <kbd>height</kbd> attributes.<br><br>Use <kbd>&lt;circle&gt;</kbd> with <kbd>cx</kbd>, <kbd>cy</kbd> (center) and <kbd>r</kbd> (radius) to draw circles.",
"task": "Create an SVG with a circle:<br>1. An <kbd>&lt;svg&gt;</kbd> with width=\"200\" and height=\"200\"<br>2. A <kbd>&lt;circle&gt;</kbd> centered at (100,100) with radius 50<br>3. Add a <kbd>fill</kbd> color",
"title": "رسم الدوائر",
"description": "SVG (Scalable Vector Graphics) يتيح لك رسم الأشكال مباشرة في HTML. عنصر <kbd>&lt;svg&gt;</kbd> هو الحاوية بسمات <kbd>width</kbd> و <kbd>height</kbd>.<br><br>استخدم <kbd>&lt;circle&gt;</kbd> مع <kbd>cx</kbd> و <kbd>cy</kbd> (المركز) و <kbd>r</kbd> (نصف القطر) لرسم الدوائر.",
"task": "أنشئ SVG مع دائرة:<br>1. عنصر <kbd>&lt;svg&gt;</kbd> بـ width=\"200\" و height=\"200\"<br>2. عنصر <kbd>&lt;circle&gt;</kbd> متمركز عند (100,100) بنصف قطر 50<br>3. أضف لون <kbd>fill</kbd>",
"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); }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "svg",
"message": "Add an <kbd>&lt;svg&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;svg&gt;</kbd>"
},
{
"type": "element_exists",
"value": "circle",
"message": "Add a <kbd>&lt;circle&gt;</kbd> element inside the SVG"
"message": "أضف عنصر <kbd>&lt;circle&gt;</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",
"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",
"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",
"title": "Rectangles & Lines",
"description": "Draw rectangles with <kbd>&lt;rect&gt;</kbd> using <kbd>x</kbd>, <kbd>y</kbd>, <kbd>width</kbd>, <kbd>height</kbd>.<br><br>Draw lines with <kbd>&lt;line&gt;</kbd> using <kbd>x1</kbd>, <kbd>y1</kbd> (start) and <kbd>x2</kbd>, <kbd>y2</kbd> (end). Lines need a <kbd>stroke</kbd> color!",
"task": "Create an SVG with:<br>1. An <kbd>&lt;svg&gt;</kbd> (200x150)<br>2. A <kbd>&lt;rect&gt;</kbd> at position (20,20) with size 80x60<br>3. A <kbd>&lt;line&gt;</kbd> from (120,30) to (180,90) with a stroke color",
"title": "المستطيلات والخطوط",
"description": "ارسم المستطيلات باستخدام <kbd>&lt;rect&gt;</kbd> مع <kbd>x</kbd> و <kbd>y</kbd> و <kbd>width</kbd> و <kbd>height</kbd>.<br><br>ارسم الخطوط باستخدام <kbd>&lt;line&gt;</kbd> مع <kbd>x1</kbd> و <kbd>y1</kbd> (البداية) و <kbd>x2</kbd> و <kbd>y2</kbd> (النهاية). الخطوط تحتاج لون <kbd>stroke</kbd>!",
"task": "أنشئ SVG مع:<br>1. عنصر <kbd>&lt;svg&gt;</kbd> (200x150)<br>2. عنصر <kbd>&lt;rect&gt;</kbd> في الموضع (20,20) بحجم 80x60<br>3. عنصر <kbd>&lt;line&gt;</kbd> من (120,30) إلى (180,90) مع لون stroke",
"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); }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "svg",
"message": "Add an <kbd>&lt;svg&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;svg&gt;</kbd>"
},
{
"type": "element_exists",
"value": "rect",
"message": "Add a <kbd>&lt;rect&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;rect&gt;</kbd>"
},
{
"type": "element_exists",
"value": "line",
"message": "Add a <kbd>&lt;line&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;line&gt;</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",
"title": "Multiple Shapes",
"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.",
"task": "Create a simple face:<br>1. An <kbd>&lt;svg&gt;</kbd> (200x200)<br>2. A large <kbd>&lt;circle&gt;</kbd> for the face<br>3. Two small <kbd>&lt;circle&gt;</kbd> elements for eyes<br>4. A <kbd>&lt;line&gt;</kbd> for the smile",
"title": "أشكال متعددة",
"description": "ادمج الأشكال لإنشاء رسومات بسيطة! أضف <kbd>stroke</kbd> للحدود و <kbd>stroke-width</kbd> للسمك.<br><br>استخدم <kbd>fill=\"none\"</kbd> للأشكال المجوفة. الأشكال تتراكم بالترتيب - العناصر اللاحقة تظهر في الأعلى.",
"task": "أنشئ وجهاً بسيطاً:<br>1. عنصر <kbd>&lt;svg&gt;</kbd> (200x200)<br>2. <kbd>&lt;circle&gt;</kbd> كبير للوجه<br>3. اثنين <kbd>&lt;circle&gt;</kbd> صغيرين للعيون<br>4. عنصر <kbd>&lt;line&gt;</kbd> للابتسامة",
"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); }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "svg",
"message": "Add an <kbd>&lt;svg&gt;</kbd> element"
"message": "أضف عنصر <kbd>&lt;svg&gt;</kbd>"
},
{
"type": "element_count",
"value": { "selector": "circle", "min": 3 },
"message": "Add at least 3 circles (1 face + 2 eyes)"
"message": "أضف على الأقل 3 دوائر (1 للوجه + 2 للعيون)"
},
{
"type": "element_exists",
"value": "line",
"message": "Add a <kbd>&lt;line&gt;</kbd> for the smile"
"message": "أضف <kbd>&lt;line&gt;</kbd> للابتسامة"
}
]
}

View File

@@ -2,18 +2,18 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "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",
"lessons": [
{
"id": "box-model-1",
"title": "Box-Modell Komponenten",
"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.",
"task": "Setze <kbd>padding</kbd> auf <kbd>1rem</kbd>, um Abstand zwischen Inhalt und Rahmen zu schaffen.",
"previewHTML": "<div class=\"box\">Box-Modell Komponenten</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: lavender; border: 2px dashed slategray; }",
"title": "Padding",
"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": "Diese Profilkarte sieht beengt aus. Füge <kbd>padding: 1rem</kbd> hinzu, damit der Text Platz zum Atmen hat.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"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": "",
"codePrefix": ".box {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "padding: 1rem;",
@@ -28,56 +28,56 @@
},
{
"id": "box-model-2",
"title": "Rahmen hinzufügen",
"description": "Rahmen umranden ein Element und schaffen visuelle Trennung. Die border-Kurzschreibweise akzeptiert drei Werte: Breite, Stil und Farbe.",
"task": "Setze <kbd>border</kbd> auf <kbd>2px solid darkslategray</kbd>.",
"previewHTML": "<div class=\"box\">Diese Box braucht einen Rahmen</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: mintcream; padding: 1rem; }",
"title": "Borders",
"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": "Füge der Karte einen dezenten linken Akzent hinzu mit <kbd>border-left: 4px solid steelblue</kbd>.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"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": "",
"codePrefix": ".box {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "border: 2px solid darkslategray;",
"solution": "border-left: 4px solid steelblue;",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "border:\\s*2px\\s+solid\\s+darkslategray",
"message": "Setze <kbd>border: 2px solid darkslategray</kbd>",
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
"message": "Setze <kbd>border-left: 4px solid steelblue</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-3",
"title": "Außenabstände hinzufügen",
"description": "Margins schaffen Abstand zwischen Elementen. Anders als Padding (das den inneren Abstand beeinflusst) existiert Margin außerhalb des Rahmens.",
"task": "Setze <kbd>margin</kbd> auf <kbd>1rem</kbd>, um Abstand zum benachbarten Element zu schaffen.",
"previewHTML": "<div class=\"container\"><div class=\"outer\">Diese Box braucht Margins</div><div class=\"neighbor\">Benachbartes Element</div></div>",
"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; }",
"title": "Margins",
"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": "Füge Abstand zwischen diesen beiden Profilkarten hinzu mit <kbd>margin-bottom: 1rem</kbd> auf <kbd>.card</kbd>.",
"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; 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": "",
"codePrefix": ".outer {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "margin: 1rem;",
"solution": "margin-bottom: 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "margin", "expected": "1rem" },
"message": "Setze <kbd>margin: 1rem</kbd>"
"value": { "property": "margin-bottom", "expected": "1rem" },
"message": "Setze <kbd>margin-bottom: 1rem</kbd>"
}
]
},
{
"id": "box-model-4",
"title": "Box-Sizing: Border-Box",
"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.",
"task": "Setze <kbd>box-sizing</kbd> auf <kbd>border-box</kbd>, damit Padding und Border in die Breite einbezogen werden.",
"previewHTML": "<div class=\"sizing-demo\"><div class=\"box default\">Content-box (Standard)</div><div class=\"box sized\">Border-box</div></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; }",
"title": "Box Sizing",
"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": "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=\"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; background: #f5f5f5; } .demo { display: flex; gap: 1rem; } .card { width: 200px; padding: 1rem; border: 4px solid steelblue; background: white; border-radius: 8px; }",
"sandboxCSS": "",
"codePrefix": ".sized {\n ",
"codePrefix": ".fix {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "box-sizing: border-box;",
@@ -92,87 +92,98 @@
},
{
"id": "box-model-5",
"title": "Margin-Kollaps",
"description": "Wenn zwei vertikale Margins aufeinandertreffen, kollabieren sie zum größeren der beiden Werte statt zu addieren.",
"task": "Setze <kbd>margin-bottom</kbd> auf <kbd>2rem</kbd>. Der Abstand zwischen den Absätzen beträgt 2rem (nicht 3rem) durch Margin-Kollaps.",
"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>",
"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; }",
"title": "Padding Shorthand",
"description": "Padding akzeptiert 1-4 Werte:<br>• 1 Wert: alle Seiten<br>• 2 Werte: vertikal | horizontal<br>• 4 Werte: oben | rechts | unten | links",
"task": "Dieser Button braucht mehr horizontalen als vertikalen Platz. Setze <kbd>padding: 8px 1rem</kbd> (8px oben/unten, 1rem links/rechts).",
"previewHTML": "<button class=\"btn\">Follow</button>",
"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": "",
"codePrefix": ".first {\n ",
"codePrefix": ".btn {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "margin-bottom: 2rem;",
"solution": "padding: 8px 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "margin-bottom", "expected": "2rem" },
"message": "Setze <kbd>margin-bottom: 2rem</kbd>"
"type": "regex",
"value": "padding:\\s*8px\\s+1rem",
"message": "Setze <kbd>padding: 8px 1rem</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-6",
"title": "Margin-Kurzschreibweise",
"description": "Die Margin-Kurzschreibweise kann alle vier Seiten setzen. Zwei Werte setzen vertikale (oben/unten) und horizontale (links/rechts) Margins.",
"task": "Setze <kbd>margin</kbd> auf <kbd>1rem 2rem</kbd> für 1rem oben/unten und 2rem links/rechts.",
"previewHTML": "<div class=\"container\"><div class=\"spaced\">Diese Box braucht Margins: 1rem oben/unten, 2rem links/rechts</div></div>",
"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; }",
"title": "Margin Shorthand",
"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": "Zentriere diese Karte horizontal. Setze <kbd>margin: 0 auto</kbd>, um automatisch gleiche links/rechts-Margins zu berechnen.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"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": "",
"codePrefix": ".spaced {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "margin: 1rem 2rem;",
"solution": "margin: 0 auto;",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "margin:\\s*1rem\\s+2rem",
"message": "Setze <kbd>margin: 1rem 2rem</kbd>",
"value": "margin:\\s*0\\s+auto",
"message": "Setze <kbd>margin: 0 auto</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-7",
"title": "Padding-Kurzschreibweise",
"description": "Wie Margin erlaubt Padding-Kurzschreibweise das Setzen aller Seiten. Ein einzelner Wert gilt für alle vier Seiten.",
"task": "Setze <kbd>padding</kbd> auf <kbd>2rem</kbd> für gleichmäßiges Padding.",
"previewHTML": "<div class=\"padded\">Diese Box braucht gleichmäßiges Padding</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .padded { background-color: papayawhip; border: 2px solid orange; }",
"title": "Border Radius",
"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": "Mache das Avatar-Bild rund mit <kbd>border-radius: 50%</kbd>.",
"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; 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": "",
"codePrefix": ".padded {\n ",
"codePrefix": ".avatar {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "padding: 2rem;",
"solution": "border-radius: 50%;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "padding", "expected": "2rem" },
"message": "Setze <kbd>padding: 2rem</kbd>"
"value": { "property": "border-radius", "expected": "50%" },
"message": "Setze <kbd>border-radius: 50%</kbd>"
}
]
},
{
"id": "box-model-8",
"title": "Rahmen auf einzelnen Seiten",
"description": "Für feinere Kontrolle können einzelne Seiten mit border-top, border-right, border-bottom oder border-left angesprochen werden.",
"task": "Setze <kbd>border-bottom</kbd> auf <kbd>4px solid dodgerblue</kbd>.",
"previewHTML": "<div class=\"line\">Dieses Element braucht nur einen unteren Rahmen</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .line { padding: 1rem; background-color: aliceblue; }",
"title": "Complete Card",
"description": "Kombinieren wir alles. Diese Benachrichtigungskarte braucht Styling, um professionell auszusehen.",
"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=\"alert\"><strong>New message</strong><p>You have 3 unread notifications</p></div>",
"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": "",
"codePrefix": ".line {\n ",
"codePrefix": ".alert {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "border-bottom: 4px solid dodgerblue;",
"solution": "padding: 1rem;\n border-left: 4px solid coral;\n border-radius: 4px;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "padding", "expected": "1rem" },
"message": "Setze <kbd>padding: 1rem</kbd>"
},
{
"type": "regex",
"value": "border-bottom:\\s*4px\\s+solid\\s+dodgerblue",
"message": "Setze <kbd>border-bottom: 4px solid dodgerblue</kbd>",
"value": "border-left:\\s*4px\\s+solid\\s+coral",
"message": "Setze <kbd>border-left: 4px solid coral</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "border-radius", "expected": "4px" },
"message": "Setze <kbd>border-radius: 4px</kbd>"
}
]
}

View File

@@ -7,119 +7,94 @@
"lessons": [
{
"id": "units-1",
"title": "Absolute vs. Relative Einheiten",
"description": "Lerne den Unterschied zwischen px, rem, em, % und vw/vh für flexible, responsive Layouts.",
"task": "Setze die Breite von <kbd>.box</kbd> auf <kbd>80%</kbd> und max-width auf <kbd>37.5rem</kbd>.",
"previewHTML": "<div class=\"box\">Ändere meine Größe!</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { background: #f5f5f5; padding: 1rem; }",
"title": "Relative Units",
"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": "Dieser Artikeltext läuft auf großen Bildschirmen zu breit. Füge <kbd>max-width: 40rem</kbd> hinzu für optimale Lesebreite.",
"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: 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": "",
"codePrefix": "/* Setze flexible Größen */\n.box {",
"codePrefix": ".article {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: 80%;\n max-width: 37.5rem;",
"codeSuffix": "\n}",
"solution": "max-width: 40rem;",
"previewContainer": "preview-area",
"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",
"value": { "property": "max-width", "expected": "37.5rem" },
"message": "Setze max-width auf <kbd>37.5rem</kbd>"
"value": { "property": "max-width", "expected": "40rem" },
"message": "Setze <kbd>max-width: 40rem</kbd>"
}
]
},
{
"id": "units-2",
"title": "CSS Custom Properties",
"description": "Definiere und verwende Variablen (--custom properties) wieder, um deine Theme-Werte zu zentralisieren.",
"task": "Erstelle eine <code>--main-color</code> Variable in :root mit <kbd>#6200ee</kbd> und wende sie als Rahmenfarbe auf <kbd>.themed</kbd> an.",
"previewHTML": "<div class=\"themed\">Variablen-Box</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .themed { padding: 1rem; border: 0.125rem solid #ddd; }",
"title": "CSS Variables",
"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": "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=\"actions\"><button class=\"btn\">Subscribe</button><button class=\"btn\">Learn More</button></div>",
"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": "",
"codePrefix": "/* Definiere und verwende eine CSS-Variable */\n:root {",
"codePrefix": ":root {\n ",
"initialCode": "",
"codeSuffix": "}\n.themed { }",
"solution": " --main-color: #6200ee;\n}\n.themed {\n border-color: var(--main-color);",
"codeSuffix": "\n}\n\n.btn {\n background: var(--brand);\n}",
"solution": "--brand: steelblue;",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "--main-color",
"message": "Definiere <kbd>--main-color</kbd> in :root",
"value": "--brand",
"message": "Definiere die <kbd>--brand</kbd> Variable",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "var(--main-color)",
"message": "Verwende <kbd>var(--main-color)</kbd>",
"value": "steelblue",
"message": "Setze den Wert auf <kbd>steelblue</kbd>",
"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",
"title": "Einheiten-Berechnungen (calc)",
"description": "Verwende die <code>calc()</code> Funktion, um verschiedene Einheiten in einem Ausdruck zu kombinieren.",
"task": "Setze die Breite von <kbd>.sized</kbd> auf <kbd>calc(100% - 2rem)</kbd> und min-height auf <kbd>calc(10vh + 1rem)</kbd>.",
"previewHTML": "<div class=\"sized\">Calc Demo</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sized { background: #e8f5e9; padding: 1rem; }",
"title": "calc() Function",
"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": "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=\"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: 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": "",
"codePrefix": "/* Verwende calc für dynamische Größen */\n.sized {",
"codePrefix": ".main {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: calc(100% - 2rem);\n min-height: calc(10vh + 1rem);",
"codeSuffix": "\n}",
"solution": "width: calc(100% - 200px);",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "calc", "message": "Verwende die <kbd>calc()</kbd> Funktion", "options": { "caseSensitive": false } },
{
"type": "regex",
"value": "width:\\s*calc\\(100% - 2rem\\)",
"message": "Width sollte calc(100% - 2rem) sein",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "min-height:\\s*calc\\(10vh \\+ 1rem\\)",
"message": "Min-height sollte calc(10vh + 1rem) sein",
"value": "width:\\s*calc\\(\\s*100%\\s*-\\s*200px\\s*\\)",
"message": "Setze <kbd>width: calc(100% - 200px)</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "units-4",
"title": "Viewport & Responsive Einheiten",
"description": "Steuere Layouts relativ zur Viewport-Größe mit vw, vh und vmin/vmax Einheiten.",
"task": "Gib <kbd>.view</kbd> eine Breite von <kbd>50vw</kbd> und Höhe von <kbd>20vh</kbd>.",
"previewHTML": "<div class=\"view\">Viewport-Box</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .view { background: #ffe0b2; }",
"title": "Viewport Units",
"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": "Mache diese Hero-Sektion so hoch wie das Viewport mit <kbd>min-height: 100vh</kbd>.",
"previewHTML": "<section class=\"hero\"><h1>Welcome</h1><p>Scroll down to explore</p></section>",
"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": "",
"codePrefix": "/* Verwende Viewport-Einheiten */\n.view {",
"codePrefix": ".hero {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: 50vw;\n height: 20vh;",
"codeSuffix": "\n}",
"solution": "min-height: 100vh;",
"previewContainer": "preview-area",
"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", "value": { "property": "width", "expected": "50vw" }, "message": "Setze width auf <kbd>50vw</kbd>" },
{ "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Setze height auf <kbd>20vh</kbd>" }
{
"type": "property_value",
"value": { "property": "min-height", "expected": "100vh" },
"message": "Setze <kbd>min-height: 100vh</kbd>"
}
]
}
]

View File

@@ -7,13 +7,13 @@
"lessons": [
{
"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>",
"task": "Füge <kbd>transition: background-color 0.3s</kbd> zu <kbd>.btn</kbd> hinzu, damit die Farbe beim Hover sanft überblendet.",
"previewHTML": "<button class=\"btn\">Hover mich</button>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: #6200ee; color: white; padding: 0.5rem 1rem; border: none; } .btn:hover { background: #3700b3; }",
"task": "Füge <kbd>transition: background-color 0.3s</kbd> hinzu, damit die Farbe beim Hover sanft überblendet.",
"previewHTML": "<button class=\"btn\">Hover Me</button>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: black; color: white; padding: 0.5rem 1rem; border: none; cursor: pointer; } .btn:hover { background: white; color: black; }",
"sandboxCSS": "",
"codePrefix": "/* Füge Transition hinzu */\n.btn {",
"codePrefix": "/* Add transition */\n.btn {",
"initialCode": "",
"codeSuffix": "}",
"solution": " transition: background-color 0.3s;",
@@ -35,13 +35,13 @@
},
{
"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.",
"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>",
"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": "",
"codePrefix": "/* Setze Timing-Funktion */\n.btn {",
"codePrefix": "/* Set timing function */\n.btn {",
"initialCode": "",
"codeSuffix": "}",
"solution": " transition-timing-function: ease-in-out;",
@@ -56,19 +56,19 @@
{
"type": "property_value",
"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",
"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>",
"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>",
"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": "",
"codePrefix": "/* Definiere Keyframes und wende Animation an */\n@keyframes bounce {",
"codePrefix": "/* Define keyframes and apply animation */\n@keyframes bounce {",
"initialCode": "",
"codeSuffix": "}\n.ball { }",
"solution": " 50% { transform: translateY(-20px); }\n}\n.ball {\n animation: bounce 1s infinite;",
@@ -102,35 +102,22 @@
},
{
"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>.",
"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>.",
"previewHTML": "<div class=\"box\">Fade Demo</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { width: 100px; height: 100px; background: #4caf50; margin: 2rem auto; }",
"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\">Pulse</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { width: 100px; height: 100px; background: black; color: white; display: flex; align-items: center; justify-content: center; margin: 2rem auto; } @keyframes pulse { 0% { background: black; color: white; transform: scale(1); } 50% { background: white; color: black; transform: scale(1.2); } 100% { background: limegreen; color: black; transform: scale(1); } }",
"sandboxCSS": "",
"codePrefix": "/* Definiere fade und setze Eigenschaften */\n@keyframes fade { from { opacity: 0; } to { opacity: 1; } }\n.box {",
"codePrefix": "/* Apply animation properties */\n.box {",
"initialCode": "",
"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",
"validations": [
{
"type": "contains",
"value": "animation-delay",
"message": "Verwende <kbd>animation-delay</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",
"value": { "property": "animation-name", "expected": "pulse" },
"message": "Setze <kbd>animation-name: pulse</kbd>"
},
{
"type": "property_value",

View File

@@ -7,13 +7,13 @@
"lessons": [
{
"id": "responsive-1",
"title": "Einführung in Media Queries",
"description": "Verstehe die Syntax und Anwendungsfälle für CSS Media Queries, um Stile bedingt basierend auf Viewport-Eigenschaften anzuwenden.",
"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>.",
"previewHTML": "<div class=\"panel\">Ändere die Fenstergröße</div>",
"title": "Media Queries",
"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 mit <kbd>@media (max-width: 600px)</kbd>, die den Hintergrund von <kbd>.panel</kbd> auf <kbd>lightcoral</kbd> ändert.",
"previewHTML": "<div class=\"panel\">Resize the window</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .panel { padding: 1rem; background: lightblue; }",
"sandboxCSS": "",
"codePrefix": "/* Füge deine Media Query unten ein */\n",
"codePrefix": "/* Add your media query below */\n",
"initialCode": "",
"codeSuffix": "",
"solution": "@media (max-width: 600px) {\n .panel {\n background: lightcoral;\n }\n}",
@@ -22,7 +22,7 @@
{
"type": "regex",
"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 }
},
{
@@ -31,68 +31,49 @@
"message": "Adressiere <kbd>.panel</kbd> innerhalb der Media Query",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "background",
"message": "Ändere die <kbd>background</kbd> Eigenschaft",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "background", "expected": "lightcoral" },
"message": "Setze background auf <kbd>lightcoral</kbd>",
"message": "Setze <kbd>background: lightcoral</kbd>",
"options": { "exact": false }
}
]
},
{
"id": "responsive-2",
"title": "Flüssige Typografie",
"description": "Verwende relative Einheiten wie vw, 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.",
"previewHTML": "<p class=\"text\">Flüssige Typografie</p>",
"title": "Fluid Type",
"description": "Verwende relative Einheiten wie <kbd>vw</kbd>, damit Schriftgrößen mit der Viewport-Breite skalieren.",
"task": "Setze <kbd>font-size: 5vw</kbd>, damit sie sich mit dem Viewport ändert.",
"previewHTML": "<p class=\"text\">Fluid Typography</p>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; }",
"sandboxCSS": "",
"codePrefix": "/* Wende flüssige Schriftgröße an */\n.text {",
"codePrefix": "/* Apply fluid font sizing */\n.text {",
"initialCode": "",
"codeSuffix": "}",
"solution": " font-size: 5vw;",
"previewContainer": "preview-area",
"validations": [
{
"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>" }
{ "type": "property_value", "value": { "property": "font-size", "expected": "5vw" }, "message": "Setze <kbd>font-size: 5vw</kbd>" }
]
},
{
"id": "responsive-3",
"title": "Flexible Raster",
"description": "Kombiniere CSS Grid mit auto-fit oder auto-fill für responsive Spaltenlayouts.",
"task": "Definiere <kbd>.cards</kbd> mit <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd> und einem gap von <kbd>1rem</kbd>.",
"previewHTML": "<div class=\"cards\"><div>1</div><div>2</div><div>3</div><div>4</div></div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .cards > div { background: #d1c4e9; padding: 1rem; }",
"title": "Responsive Grid",
"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": "Füge <kbd>display: grid</kbd>, <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd> und <kbd>gap: 1rem</kbd> hinzu.",
"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: 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": "",
"codePrefix": "/* Erstelle ein responsives Raster */\n.cards {",
"codePrefix": ".features {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
"codeSuffix": "\n}",
"solution": "display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "grid-template-columns",
"message": "Definiere <kbd>grid-template-columns</kbd>",
"options": { "caseSensitive": false }
"type": "property_value",
"value": { "property": "display", "expected": "grid" },
"message": "Setze <kbd>display: grid</kbd>"
},
{
"type": "regex",
@@ -100,18 +81,22 @@
"message": "Verwende <kbd>repeat(auto-fit, minmax(200px, 1fr))</kbd>",
"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",
"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.",
"task": "Schreibe eine Media Query für min-width 768px, die die Breite von <kbd>.sidebar</kbd> auf <kbd>250px</kbd> setzt.",
"previewHTML": "<aside class=\"sidebar\">Seitenleiste</aside>",
"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\">Sidebar</aside>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sidebar { background: #c8e6c9; padding: 1rem; }",
"sandboxCSS": "",
"codePrefix": "/* Füge Mobile-First-Erweiterung hinzu */\n",
"codePrefix": "/* Add mobile-first enhancement */\n",
"initialCode": "",
"codeSuffix": "",
"solution": "@media (min-width: 768px) {\n .sidebar {\n width: 250px;\n }\n}",
@@ -120,7 +105,7 @@
{
"type": "regex",
"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 }
},
{
@@ -132,7 +117,7 @@
{
"type": "property_value",
"value": { "property": "width", "expected": "250px" },
"message": "Setze width auf <kbd>250px</kbd>",
"message": "Setze <kbd>width: 250px</kbd>",
"options": { "exact": false }
}
]

View File

@@ -1,21 +1,21 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-forms-validation",
"title": "HTML Validierung",
"description": "Lerne die eingebauten HTML5-Formular-Validierungsattribute kennen",
"title": "Formularvalidierung",
"description": "Verwende die eingebaute HTML5-Validierung für bessere Benutzererfahrung",
"mode": "html",
"difficulty": "intermediate",
"difficulty": "beginner",
"lessons": [
{
"id": "required-fields",
"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>&lt;input type=\"text\" required&gt;</kbd><br><br>Der Browser zeigt automatisch eine Validierungsmeldung an.",
"task": "Mache sowohl das Name- als auch das E-Mail-Feld zu Pflichtfeldern, indem du das <kbd>required</kbd>-Attribut hinzufügst.",
"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>&lt;input type=\"text\" required&gt;</kbd>",
"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": "",
"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": "",
"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>",
"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>",
"previewContainer": "preview-area",
"validations": [
{
@@ -29,84 +29,6 @@
"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"
}
]
}
]
}

View File

@@ -44,12 +44,12 @@
"id": "progress-indeterminate",
"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.",
"task": "Erstelle eine Ladeanzeige:<br>1. Füge ein <kbd>&lt;p&gt;</kbd> mit <code>Lädt...</code> hinzu<br>2. Füge ein <kbd>&lt;progress&gt;</kbd> ohne value-Attribut hinzu",
"task": "Erstelle eine Ladeanzeige:<br>1. Füge ein <kbd>&lt;p&gt;</kbd> mit <code>Loading...</code> hinzu<br>2. Füge ein <kbd>&lt;progress&gt;</kbd> ohne value-Attribut hinzu",
"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; }",
"sandboxCSS": "",
"initialCode": "",
"solution": "<p>Lädt...</p>\n<progress></progress>",
"solution": "<p>Loading...</p>\n<progress></progress>",
"previewContainer": "preview-area",
"validations": [
{
@@ -68,12 +68,12 @@
"id": "meter-gauge",
"title": "Meter-Anzeigen",
"description": "Das <kbd>&lt;meter&gt;</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>&lt;label&gt;</kbd> mit <code>Akku:</code> hinzu<br>2. Füge ein <kbd>&lt;meter&gt;</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>&lt;label&gt;</kbd> mit <code>Battery:</code> hinzu<br>2. Füge ein <kbd>&lt;meter&gt;</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": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
"sandboxCSS": "",
"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",
"validations": [
{
@@ -86,11 +86,31 @@
"value": { "selector": "meter", "attr": "value", "value": "0.8" },
"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",
"value": { "selector": "meter", "attr": "low", "value": "0.2" },
"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",
"value": "label",

View File

@@ -2,20 +2,20 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-tables",
"title": "HTML Tabellen",
"description": "Erstelle strukturierte Datentabellen mit Überschriften und Beschriftungen",
"description": "Erstelle strukturierte Datentabellen mit semantischem Markup",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "table-basic",
"title": "Grundlegende Tabellenstruktur",
"description": "Tabellen verwenden <kbd>&lt;table&gt;</kbd> mit <kbd>&lt;tr&gt;</kbd> für Zeilen. In Zeilen nutze <kbd>&lt;th&gt;</kbd> für Überschriften und <kbd>&lt;td&gt;</kbd> für Datenzellen.<br><br>Das <kbd>&lt;caption&gt;</kbd>-Element bietet einen zugänglichen Titelr die Tabelle.",
"task": "Erstelle eine einfache Tabelle mit:<br>1. Einer <kbd>&lt;caption&gt;</kbd> mit <code>Obstpreise</code><br>2. Einer Kopfzeile mit <code>Obst</code> und <code>Preis</code> Spalten<br>3. Mindestens 2 Datenzeilen",
"title": "Datentabellen",
"description": "Tabellen zeigen strukturierte Daten in Zeilen und Spalten. Verwende <kbd>&lt;table&gt;</kbd> als Container, <kbd>&lt;tr&gt;</kbd> für Zeilen, <kbd>&lt;th&gt;</kbd> für Kopfzellen und <kbd>&lt;td&gt;</kbd> für Datenzellen.<br><br>Füge <kbd>&lt;caption&gt;</kbd> hinzu für einen zugänglichen Titel, der den Tabelleninhalt beschreibt.",
"task": "Erstelle eine Preistabelle:<br>1. Eine <kbd>&lt;caption&gt;</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": "",
"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": "",
"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",
"validations": [
{
@@ -26,100 +26,17 @@
{
"type": "element_exists",
"value": "caption",
"message": "Füge eine <kbd>&lt;caption&gt;</kbd> als Tabellentitel hinzu"
"message": "Füge eine <kbd>&lt;caption&gt;</kbd> für den Tabellentitel hinzu"
},
{
"type": "element_count",
"value": { "selector": "th", "min": 2 },
"message": "Füge mindestens 2 Überschriftszellen (th) hinzu"
"message": "Füge Kopfzellen (<kbd>&lt;th&gt;</kbd>) für Plan und Price hinzu"
},
{
"type": "element_count",
"value": { "selector": "tr", "min": 3 },
"message": "Füge mindestens 3 Zeilen hinzu (1 Kopf + 2 Daten)"
}
]
},
{
"id": "table-thead-tbody",
"title": "Tabellenkopf & -körper",
"description": "Verwende <kbd>&lt;thead&gt;</kbd> zum Gruppieren von Kopfzeilen und <kbd>&lt;tbody&gt;</kbd> zum Gruppieren von Datenzeilen. Das hilft Browsern und Hilfstechnologien, die Tabellenstruktur zu verstehen.<br><br>Du kannst auch <kbd>&lt;tfoot&gt;</kbd> für Fußzeilen wie Summen verwenden.",
"task": "Erstelle eine strukturierte Tabelle:<br>1. Eine <kbd>&lt;caption&gt;</kbd> mit <code>Monatliche Verkäufe</code><br>2. Ein <kbd>&lt;thead&gt;</kbd> mit Monat und Umsatz Überschriften<br>3. Ein <kbd>&lt;tbody&gt;</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>&lt;table&gt;</kbd>-Element hinzu"
},
{
"type": "element_exists",
"value": "caption",
"message": "Füge ein <kbd>&lt;caption&gt;</kbd>-Element hinzu"
},
{
"type": "element_exists",
"value": "thead",
"message": "Füge ein <kbd>&lt;thead&gt;</kbd> für den Kopfbereich hinzu"
},
{
"type": "element_exists",
"value": "tbody",
"message": "Füge ein <kbd>&lt;tbody&gt;</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>&lt;tfoot&gt;</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>&lt;caption&gt;</kbd> mit <code>Bestellübersicht</code><br>2. Ein <kbd>&lt;thead&gt;</kbd> mit Artikel und Preis Überschriften<br>3. Ein <kbd>&lt;tbody&gt;</kbd> mit 2 Artikeln<br>4. Ein <kbd>&lt;tfoot&gt;</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>&lt;table&gt;</kbd>-Element hinzu"
},
{
"type": "element_exists",
"value": "caption",
"message": "Füge ein <kbd>&lt;caption&gt;</kbd>-Element hinzu"
},
{
"type": "element_exists",
"value": "thead",
"message": "Füge einen <kbd>&lt;thead&gt;</kbd>-Abschnitt hinzu"
},
{
"type": "element_exists",
"value": "tbody",
"message": "Füge einen <kbd>&lt;tbody&gt;</kbd>-Abschnitt hinzu"
},
{
"type": "element_exists",
"value": "tfoot",
"message": "Füge einen <kbd>&lt;tfoot&gt;</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"
"message": "Füge 3 Zeilen hinzu (1 Kopf + 2 Datenzeilen)"
}
]
}

View File

@@ -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); }",
"sandboxCSS": "",
"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",
"validations": [
{
@@ -28,6 +28,16 @@
"value": "circle",
"message": "Füge ein <kbd>&lt;circle&gt;</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",
"value": { "selector": "circle", "attr": "cx", "value": "100" },
@@ -37,6 +47,11 @@
"type": "attribute_value",
"value": { "selector": "circle", "attr": "cy", "value": "100" },
"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); }",
"sandboxCSS": "",
"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",
"validations": [
{
@@ -66,6 +81,61 @@
"type": "element_exists",
"value": "line",
"message": "Füge ein <kbd>&lt;line&gt;</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); }",
"sandboxCSS": "",
"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",
"validations": [
{

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "welcome",
"title": "Code Crispies",
"description": "Welcome to Code Crispies - your interactive web development learning platform",
"description": "Bienvenido a Code Crispies - tu plataforma interactiva de aprendizaje de desarrollo web",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "get-started",
"title": "Get 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",
"task": "Write <code>Hello World</code>",
"title": "Comenzar",
"description": "<strong>Code Crispies</strong> es una plataforma gratuita y de código abierto para aprender desarrollo web mediante ejercicios prácticos. ¡No se requiere cuenta!<br><br><strong>Lo que aprenderás:</strong><br>• <strong>HTML</strong> - Elementos semánticos, formularios, tablas, SVG (<em>HTML Bloque e Inline</em>, <em>HTML Formularios</em>, <em>HTML Tablas</em>)<br>• <strong>CSS</strong> - Selectores, modelo de caja, flexbox, animaciones (<em>CSS Selectores</em>, <em>CSS Modelo de Caja</em>, <em>CSS Flexbox</em>)<br>• <strong>Diseño Responsivo</strong> - Media queries y layouts mobile-first<br><br><strong>Cómo funciona:</strong><br>1. Lee la tarea en el panel izquierdo<br>2. Escribe código en el editor<br>3. Ve los resultados en vivo en la vista previa<br>4. Recibe retroalimentación instantánea con pistas<br><br><strong>Atajos de teclado:</strong> <kbd>Ctrl+Z</kbd> deshacer, <kbd>Ctrl+Shift+Z</kbd> rehacer<br><br><strong>Más recursos:</strong><br>• <a href=\"https://nextlevelshit.github.io/html-over-js/\" target=\"_blank\">HTML over JS</a> - HTML nativo vs soluciones JavaScript<br>• <a href=\"https://nextlevelshit.github.io/web-engineering-mandala/\" target=\"_blank\">Web Engineering Mandala</a> - Mapa de tecnologías JavaScript",
"task": "Escribe <code>Hello World</code>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 2rem; color: #6366f1; font-weight: bold; }",
"sandboxCSS": "",
@@ -21,15 +21,15 @@
{
"type": "contains",
"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",
"title": "Vista General",
"description": "<strong>¡Estás listo!</strong> Abre el menú (☰) para explorar todos los módulos.<br><br><strong>Ruta de aprendizaje recomendada:</strong><br>1. <em>HTML Bloque e Inline</em> - Entiende elementos contenedores vs inline<br>2. <em>HTML Formularios</em> - Crea formularios interactivos con validación<br>3. <em>CSS Selectores</em> - Selecciona elementos con precisión<br>4. <em>CSS Modelo de Caja</em> - Domina padding, margin, borders<br>5. <em>CSS Flexbox</em> - Crea layouts flexibles<br>6. <em>CSS Animaciones</em> - Añade movimiento y transiciones<br><br><strong>Consejos:</strong><br>• Usa <em>Mostrar Esperado</em> para ver el resultado objetivo<br>• Tu progreso se guarda automáticamente<br>• Prueba Emmet en modo HTML: <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>• Hecho por <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": "Haz clic en Siguiente para continuar",
"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": "",
@@ -39,8 +39,8 @@
"validations": [
{
"type": "contains",
"value": "Hello World",
"message": "Click Next to continue"
"value": "Hello",
"message": "Haz clic en Siguiente para continuar"
}
]
},

View File

@@ -2,18 +2,18 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "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",
"lessons": [
{
"id": "box-model-1",
"title": "Box Model Components",
"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.",
"task": "Set <kbd>padding</kbd> to <kbd>1rem</kbd> to create space between the content and border.",
"previewHTML": "<div class=\"box\">Box Model Components</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: lavender; border: 2px dashed slategray; }",
"title": "Padding",
"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": "Esta tarjeta de perfil se ve apretada. Añade <kbd>padding: 1rem</kbd> para que el texto tenga espacio para respirar.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"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": "",
"codePrefix": ".box {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "padding: 1rem;",
@@ -22,62 +22,62 @@
{
"type": "property_value",
"value": { "property": "padding", "expected": "1rem" },
"message": "Set <kbd>padding: 1rem</kbd>"
"message": "Establece <kbd>padding: 1rem</kbd>"
}
]
},
{
"id": "box-model-2",
"title": "Adding Borders",
"description": "Borders outline an element, creating visual separation from surrounding content. The border shorthand accepts three values: width, style, and color.",
"task": "Set <kbd>border</kbd> to <kbd>2px solid darkslategray</kbd>.",
"previewHTML": "<div class=\"box\">This box needs a border</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: mintcream; padding: 1rem; }",
"title": "Borders",
"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": "Añade un acento sutil a la izquierda de la tarjeta con <kbd>border-left: 4px solid steelblue</kbd>.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"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": "",
"codePrefix": ".box {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "border: 2px solid darkslategray;",
"solution": "border-left: 4px solid steelblue;",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "border:\\s*2px\\s+solid\\s+darkslategray",
"message": "Set <kbd>border: 2px solid darkslategray</kbd>",
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
"message": "Establece <kbd>border-left: 4px solid steelblue</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-3",
"title": "Adding 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.",
"task": "Set <kbd>margin</kbd> to <kbd>1rem</kbd> to create space between this element and its neighbors.",
"previewHTML": "<div class=\"container\"><div class=\"outer\">This box needs margins</div><div class=\"neighbor\">Adjacent element</div></div>",
"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; }",
"title": "Margins",
"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": "Añade espacio entre estas dos tarjetas de perfil con <kbd>margin-bottom: 1rem</kbd> en <kbd>.card</kbd>.",
"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; 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": "",
"codePrefix": ".outer {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "margin: 1rem;",
"solution": "margin-bottom: 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "margin", "expected": "1rem" },
"message": "Set <kbd>margin: 1rem</kbd>"
"value": { "property": "margin-bottom", "expected": "1rem" },
"message": "Establece <kbd>margin-bottom: 1rem</kbd>"
}
]
},
{
"id": "box-model-4",
"title": "Box Sizing: Border-Box",
"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.",
"task": "Set <kbd>box-sizing</kbd> to <kbd>border-box</kbd> so padding and border are included in the width.",
"previewHTML": "<div class=\"sizing-demo\"><div class=\"box default\">Content-box (default)</div><div class=\"box sized\">Border-box</div></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; }",
"title": "Box Sizing",
"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": "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=\"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; background: #f5f5f5; } .demo { display: flex; gap: 1rem; } .card { width: 200px; padding: 1rem; border: 4px solid steelblue; background: white; border-radius: 8px; }",
"sandboxCSS": "",
"codePrefix": ".sized {\n ",
"codePrefix": ".fix {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "box-sizing: border-box;",
@@ -86,93 +86,104 @@
{
"type": "property_value",
"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",
"title": "Margin Collapse",
"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.",
"task": "Set <kbd>margin-bottom</kbd> to <kbd>2rem</kbd>. Notice the space between paragraphs equals 2rem (not 3rem) due to margin collapse.",
"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>",
"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; }",
"title": "Padding Shorthand",
"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": "Este botón necesita más espacio horizontal que vertical. Establece <kbd>padding: 8px 1rem</kbd> (8px arriba/abajo, 1rem izquierda/derecha).",
"previewHTML": "<button class=\"btn\">Follow</button>",
"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": "",
"codePrefix": ".first {\n ",
"codePrefix": ".btn {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "margin-bottom: 2rem;",
"solution": "padding: 8px 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "margin-bottom", "expected": "2rem" },
"message": "Set <kbd>margin-bottom: 2rem</kbd>"
"type": "regex",
"value": "padding:\\s*8px\\s+1rem",
"message": "Establece <kbd>padding: 8px 1rem</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-6",
"title": "Margin Shorthand Notation",
"description": "The margin shorthand can set all four sides at once. Two values set vertical (top/bottom) and horizontal (left/right) margins respectively.",
"task": "Set <kbd>margin</kbd> to <kbd>1rem 2rem</kbd> for 1rem top/bottom and 2rem left/right.",
"previewHTML": "<div class=\"container\"><div class=\"spaced\">This box needs margins: 1rem top/bottom, 2rem left/right</div></div>",
"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; }",
"title": "Margin Shorthand",
"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": "Centra esta tarjeta horizontalmente. Establece <kbd>margin: 0 auto</kbd> para calcular automáticamente márgenes iguales izquierda/derecha.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"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": "",
"codePrefix": ".spaced {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "margin: 1rem 2rem;",
"solution": "margin: 0 auto;",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "margin:\\s*1rem\\s+2rem",
"message": "Set <kbd>margin: 1rem 2rem</kbd>",
"value": "margin:\\s*0\\s+auto",
"message": "Establece <kbd>margin: 0 auto</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-7",
"title": "Padding Shorthand Notation",
"description": "Like margin, padding shorthand allows setting all sides at once. A single value applies to all four sides equally.",
"task": "Set <kbd>padding</kbd> to <kbd>2rem</kbd> to add equal padding on all sides.",
"previewHTML": "<div class=\"padded\">This box needs equal padding on all sides</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .padded { background-color: papayawhip; border: 2px solid orange; }",
"title": "Border Radius",
"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": "Haz la imagen del avatar circular con <kbd>border-radius: 50%</kbd>.",
"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; 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": "",
"codePrefix": ".padded {\n ",
"codePrefix": ".avatar {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "padding: 2rem;",
"solution": "border-radius: 50%;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "padding", "expected": "2rem" },
"message": "Set <kbd>padding: 2rem</kbd>"
"value": { "property": "border-radius", "expected": "50%" },
"message": "Establece <kbd>border-radius: 50%</kbd>"
}
]
},
{
"id": "box-model-8",
"title": "Border on Specific Sides",
"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>.",
"task": "Set <kbd>border-bottom</kbd> to <kbd>4px solid dodgerblue</kbd>.",
"previewHTML": "<div class=\"line\">This element needs only a bottom border</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .line { padding: 1rem; background-color: aliceblue; }",
"title": "Complete Card",
"description": "Combinemos todo. Esta tarjeta de notificación necesita estilo para verse profesional.",
"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=\"alert\"><strong>New message</strong><p>You have 3 unread notifications</p></div>",
"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": "",
"codePrefix": ".line {\n ",
"codePrefix": ".alert {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "border-bottom: 4px solid dodgerblue;",
"solution": "padding: 1rem;\n border-left: 4px solid coral;\n border-radius: 4px;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "padding", "expected": "1rem" },
"message": "Establece <kbd>padding: 1rem</kbd>"
},
{
"type": "regex",
"value": "border-bottom:\\s*4px\\s+solid\\s+dodgerblue",
"message": "Set <kbd>border-bottom: 4px solid dodgerblue</kbd>",
"value": "border-left:\\s*4px\\s+solid\\s+coral",
"message": "Establece <kbd>border-left: 4px solid coral</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "border-radius", "expected": "4px" },
"message": "Establece <kbd>border-radius: 4px</kbd>"
}
]
}

View File

@@ -1,115 +1,100 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "units-variables",
"title": "CSS Units & Variables",
"description": "Understand the variety of CSS measurement units and how to define and use custom properties for maintainable styles.",
"title": "Unidades y Variables CSS",
"description": "Comprende la variedad de unidades de medida CSS y cómo definir y usar propiedades personalizadas para estilos mantenibles.",
"difficulty": "beginner",
"lessons": [
{
"id": "units-1",
"title": "Absolute vs. Relative Units",
"description": "Learn the difference between px, rem, em, %, and vw/vh for flexible, responsive layouts.",
"task": "Set the <kbd>width</kbd> of <kbd>.box</kbd> to <kbd>80%</kbd> and <kbd>max-width</kbd> to <kbd>37.5rem</kbd>.",
"previewHTML": "<div class=\"box\">Resize me!</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { background: #f5f5f5; padding: 1rem; }",
"title": "Relative Units",
"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": "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": "<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: 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": "",
"codePrefix": "/* Set flexible sizing */\n.box {",
"codePrefix": ".article {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: 80%;\n max-width: 37.5rem;",
"codeSuffix": "\n}",
"solution": "max-width: 40rem;",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "width", "message": "Use <kbd>width</kbd> property", "options": { "caseSensitive": false } },
{ "type": "property_value", "value": { "property": "width", "expected": "80%" }, "message": "Set width to <kbd>80%</kbd>" },
{ "type": "contains", "value": "max-width", "message": "Use <kbd>max-width</kbd> property", "options": { "caseSensitive": false } },
{
"type": "property_value",
"value": { "property": "max-width", "expected": "37.5rem" },
"message": "Set max-width to <kbd>37.5rem</kbd>"
"value": { "property": "max-width", "expected": "40rem" },
"message": "Establece <kbd>max-width: 40rem</kbd>"
}
]
},
{
"id": "units-2",
"title": "CSS Custom Properties",
"description": "Define and reuse variables (--custom properties) to centralize your theme values.",
"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>.",
"previewHTML": "<div class=\"themed\">Variable Box</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .themed { padding: 1rem; border: 0.125rem solid #ddd; }",
"title": "CSS Variables",
"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": "Define <kbd>--brand: steelblue</kbd> en <kbd>:root</kbd>, luego úsala como color de <kbd>background</kbd> para <kbd>.btn</kbd>.",
"previewHTML": "<div class=\"actions\"><button class=\"btn\">Subscribe</button><button class=\"btn\">Learn More</button></div>",
"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": "",
"codePrefix": "/* Define and use a CSS variable */\n:root {",
"codePrefix": ":root {\n ",
"initialCode": "",
"codeSuffix": "}\n.themed { }",
"solution": " --main-color: #6200ee;\n}\n.themed {\n border-color: var(--main-color);",
"codeSuffix": "\n}\n\n.btn {\n background: var(--brand);\n}",
"solution": "--brand: steelblue;",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "--main-color",
"message": "Define <kbd>--main-color</kbd> in :root",
"value": "--brand",
"message": "Define la variable <kbd>--brand</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "var(--main-color)",
"message": "Use <kbd>var(--main-color)</kbd>",
"value": "steelblue",
"message": "Establece el valor a <kbd>steelblue</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "border", "expected": "var(--main-color)" },
"message": "Apply variable to border color",
"options": { "exact": false }
}
]
},
{
"id": "units-3",
"title": "Unit Calculations (calc)",
"description": "Use the <kbd>calc()</kbd> function to combine different units in one expression.",
"task": "Set the <kbd>width</kbd> of <kbd>.sized</kbd> to <kbd>calc(100% - 2rem)</kbd> and <kbd>min-height</kbd> to <kbd>calc(10vh + 1rem)</kbd>.",
"previewHTML": "<div class=\"sized\">Calc Demo</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sized { background: #e8f5e9; padding: 1rem; }",
"title": "calc() Function",
"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": "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=\"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: 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": "",
"codePrefix": "/* Use calc for dynamic sizing */\n.sized {",
"codePrefix": ".main {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: calc(100% - 2rem);\n min-height: calc(10vh + 1rem);",
"codeSuffix": "\n}",
"solution": "width: calc(100% - 200px);",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "calc", "message": "Use <kbd>calc()</kbd> function", "options": { "caseSensitive": false } },
{
"type": "regex",
"value": "width:\\s*calc\\(100% - 2rem\\)",
"message": "Width should be <kbd>calc(100% - 2rem)</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "min-height:\\s*calc\\(10vh \\+ 1rem\\)",
"message": "Min-height should be <kbd>calc(10vh + 1rem)</kbd>",
"value": "width:\\s*calc\\(\\s*100%\\s*-\\s*200px\\s*\\)",
"message": "Establece <kbd>width: calc(100% - 200px)</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "units-4",
"title": "Viewport & Responsive Units",
"description": "Control layouts relative to viewport size with vw, vh, and vmin/vmax units.",
"task": "Give <kbd>.view</kbd> a <kbd>width</kbd> of <kbd>50vw</kbd> and <kbd>height</kbd> of <kbd>20vh</kbd>.",
"previewHTML": "<div class=\"view\">Viewport Box</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .view { background: #ffe0b2; }",
"title": "Viewport 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": "Haz que esta sección hero llene la altura del viewport estableciendo <kbd>min-height: 100vh</kbd>.",
"previewHTML": "<section class=\"hero\"><h1>Welcome</h1><p>Scroll down to explore</p></section>",
"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": "",
"codePrefix": "/* Use viewport units */\n.view {",
"codePrefix": ".hero {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: 50vw;\n height: 20vh;",
"codeSuffix": "\n}",
"solution": "min-height: 100vh;",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "vw", "message": "Use <kbd>vw</kbd> unit", "options": { "caseSensitive": false } },
{ "type": "contains", "value": "vh", "message": "Use <kbd>vh</kbd> unit", "options": { "caseSensitive": false } },
{ "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Set width to <kbd>50vw</kbd>" },
{ "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Set height to <kbd>20vh</kbd>" }
{
"type": "property_value",
"value": { "property": "min-height", "expected": "100vh" },
"message": "Establece <kbd>min-height: 100vh</kbd>"
}
]
}
]

View File

@@ -1,15 +1,15 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "transitions-animations",
"title": "CSS Animations",
"description": "Bring interactivity to your UI by smoothly transitioning properties and creating keyframe-driven animations.",
"title": "Animaciones CSS",
"description": "Añade interactividad a tu UI mediante transiciones suaves de propiedades y animaciones basadas en keyframes.",
"difficulty": "intermediate",
"lessons": [
{
"id": "transitions-1",
"title": "Transitions",
"description": "Learn how to apply <kbd>transition</kbd> to properties for smooth changes on state changes.<br><br><pre>transition: property duration;\n/* e.g. transition: background-color 0.3s; */</pre>",
"task": "Add <kbd>transition: background-color 0.3s</kbd> to <kbd>.btn</kbd> so the color fades smoothly on hover.",
"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": "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>",
"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": "",
@@ -22,13 +22,13 @@
{
"type": "contains",
"value": "transition",
"message": "Use the <kbd>transition</kbd> property",
"message": "Usa la propiedad <kbd>transition</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"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 }
}
]
@@ -36,8 +36,8 @@
{
"id": "transitions-2",
"title": "Timing Funcs",
"description": "Explore easing functions like <kbd>ease</kbd>, <kbd>linear</kbd>, <kbd>ease-in</kbd>, <kbd>ease-out</kbd> to control animation pacing.",
"task": "Set <kbd>transition-timing-function</kbd> to <kbd>ease-in-out</kbd> on <kbd>.btn</kbd>.",
"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": "Establece <kbd>transition-timing-function</kbd> a <kbd>ease-in-out</kbd>.",
"previewHTML": "<button class=\"btn\">Timing</button>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: navy; color: gold; padding: 0.5rem 1rem; border: none; cursor: pointer; transition: all 0.5s; } .btn:hover { background: gold; color: navy; transform: scale(1.1); }",
"sandboxCSS": "",
@@ -50,21 +50,21 @@
{
"type": "contains",
"value": "transition-timing-function",
"message": "Use <kbd>transition-timing-function</kbd>",
"message": "Usa <kbd>transition-timing-function</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "transition-timing-function", "expected": "ease-in-out" },
"message": "Set timing to <kbd>ease-in-out</kbd>"
"message": "Establece timing a <kbd>ease-in-out</kbd>"
}
]
},
{
"id": "transitions-3",
"title": "Keyframes",
"description": "Create named animations using <kbd>@keyframes</kbd> and apply them via the <kbd>animation</kbd> shorthand.<br><br><pre>@keyframes bounce {\n 50% { transform: translateY(-20px); }\n}\n.ball {\n animation: bounce 1s infinite;\n}</pre>",
"task": "Define a keyframe at <kbd>50%</kbd> with <kbd>transform: translateY(-20px)</kbd> and apply <kbd>animation: bounce 1s infinite</kbd> to <kbd>.ball</kbd>.",
"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 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>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: crimson; border-radius: 50%; margin: 2rem auto; }",
"sandboxCSS": "",
@@ -83,19 +83,19 @@
{
"type": "regex",
"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 }
},
{
"type": "contains",
"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 }
},
{
"type": "regex",
"value": "animation:.*bounce.*1s.*infinite",
"message": "Apply <kbd>animation: bounce 1s infinite</kbd>",
"message": "Aplica <kbd>animation: bounce 1s infinite</kbd>",
"options": { "caseSensitive": false }
}
]
@@ -103,8 +103,8 @@
{
"id": "transitions-4",
"title": "Animation Properties",
"description": "Fine-tune animations with <kbd>animation-delay</kbd>, <kbd>animation-iteration-count</kbd>, <kbd>animation-direction</kbd>, and <kbd>animation-fill-mode</kbd>.",
"task": "Apply the <kbd>pulse</kbd> animation to <kbd>.box</kbd> with <kbd>animation-name: pulse</kbd>, <kbd>animation-duration: 2s</kbd>, <kbd>animation-delay: 1s</kbd>, <kbd>animation-iteration-count: 2</kbd>, and <kbd>animation-fill-mode: forwards</kbd>.",
"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": "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>",
"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": "",
@@ -117,27 +117,27 @@
{
"type": "property_value",
"value": { "property": "animation-name", "expected": "pulse" },
"message": "Set <kbd>animation-name: pulse</kbd>"
"message": "Establece <kbd>animation-name: pulse</kbd>"
},
{
"type": "property_value",
"value": { "property": "animation-duration", "expected": "2s" },
"message": "Set <kbd>animation-duration: 2s</kbd>"
"message": "Establece <kbd>animation-duration: 2s</kbd>"
},
{
"type": "property_value",
"value": { "property": "animation-delay", "expected": "1s" },
"message": "Set <kbd>animation-delay: 1s</kbd>"
"message": "Establece <kbd>animation-delay: 1s</kbd>"
},
{
"type": "property_value",
"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",
"value": { "property": "animation-fill-mode", "expected": "forwards" },
"message": "Set <kbd>animation-fill-mode: forwards</kbd>"
"message": "Establece <kbd>animation-fill-mode: forwards</kbd>"
}
]
}

View File

@@ -2,14 +2,14 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "responsive-design",
"title": "CSS Responsive Design",
"description": "Make your layouts adapt to different screen sizes using media queries and fluid design techniques.",
"description": "Adapta tus layouts a diferentes tamaños de pantalla usando media queries y técnicas de diseño fluido.",
"difficulty": "intermediate",
"lessons": [
{
"id": "responsive-1",
"title": "Media Queries",
"description": "Understand the syntax and use cases for CSS media queries to apply styles conditionally based on viewport characteristics.",
"task": "Write a media query with <kbd>@media (max-width: 600px)</kbd> that changes <kbd>.panel</kbd> background to <kbd>lightcoral</kbd>.",
"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": "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>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .panel { padding: 1rem; background: lightblue; }",
"sandboxCSS": "",
@@ -22,19 +22,19 @@
{
"type": "regex",
"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 }
},
{
"type": "contains",
"value": ".panel",
"message": "Target <kbd>.panel</kbd> inside the media query",
"message": "Selecciona <kbd>.panel</kbd> dentro de la media query",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "background", "expected": "lightcoral" },
"message": "Set <kbd>background: lightcoral</kbd>",
"message": "Establece <kbd>background: lightcoral</kbd>",
"options": { "exact": false }
}
]
@@ -42,8 +42,8 @@
{
"id": "responsive-2",
"title": "Fluid Type",
"description": "Use relative units like <kbd>vw</kbd> to make font sizes scale with the viewport width.",
"task": "Set <kbd>font-size: 5vw</kbd> on <kbd>.text</kbd> so it scales as the viewport changes.",
"description": "Usa unidades relativas como <kbd>vw</kbd> para que los tamaños de fuente escalen con el ancho del viewport.",
"task": "Establece <kbd>font-size: 5vw</kbd> para que escale con el viewport.",
"previewHTML": "<p class=\"text\">Fluid Typography</p>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; }",
"sandboxCSS": "",
@@ -53,46 +53,50 @@
"solution": " font-size: 5vw;",
"previewContainer": "preview-area",
"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",
"title": "Flex Grids",
"description": "Combine CSS Grid with <kbd>auto-fit</kbd> or <kbd>auto-fill</kbd> for responsive column layouts.",
"task": "Add <kbd>display: grid</kbd>, <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd>, and <kbd>gap: 1rem</kbd> to <kbd>.cards</kbd>.",
"previewHTML": "<div class=\"cards\"><div>1</div><div>2</div><div>3</div><div>4</div></div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .cards > div { background: #d1c4e9; padding: 1rem; }",
"title": "Responsive Grid",
"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": "Añade <kbd>display: grid</kbd>, <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd> y <kbd>gap: 1rem</kbd>.",
"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: 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": "",
"codePrefix": "/* Create a responsive grid */\n.cards {",
"codePrefix": ".features {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
"codeSuffix": "\n}",
"solution": "display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "display", "expected": "grid" },
"message": "Set <kbd>display: grid</kbd>"
"message": "Establece <kbd>display: grid</kbd>"
},
{
"type": "regex",
"value": "repeat\\(auto-fit,\\s*minmax\\(200px,\\s*1fr\\)\\)",
"message": "Use <kbd>repeat(auto-fit, minmax(200px, 1fr))</kbd>",
"message": "Usa <kbd>repeat(auto-fit, minmax(200px, 1fr))</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "gap", "expected": "1rem" },
"message": "Set <kbd>gap: 1rem</kbd>"
"message": "Establece <kbd>gap: 1rem</kbd>"
}
]
},
{
"id": "responsive-4",
"title": "Mobile-First",
"description": "Adopt a mobile-first approach by writing base styles for small screens and enhancing for larger viewports.",
"task": "Write a media query with <kbd>@media (min-width: 768px)</kbd> that sets <kbd>.sidebar</kbd> width to <kbd>250px</kbd>.",
"description": "Adopta un enfoque mobile-first escribiendo estilos base para pantallas pequeñas y mejorándolos para viewports más grandes.",
"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>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sidebar { background: #c8e6c9; padding: 1rem; }",
"sandboxCSS": "",
@@ -105,19 +109,19 @@
{
"type": "regex",
"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 }
},
{
"type": "contains",
"value": ".sidebar",
"message": "Target <kbd>.sidebar</kbd> inside media query",
"message": "Selecciona <kbd>.sidebar</kbd> en la media query",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "width", "expected": "250px" },
"message": "Set <kbd>width: 250px</kbd>",
"message": "Establece <kbd>width: 250px</kbd>",
"options": { "exact": false }
}
]

View File

@@ -2,65 +2,65 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-elements",
"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",
"difficulty": "beginner",
"lessons": [
{
"id": "block-vs-inline-intro",
"title": "Block vs Inline Elements",
"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>&lt;div&gt;</kbd>, <kbd>&lt;p&gt;</kbd>, <kbd>&lt;h1&gt;</kbd>, <kbd>&lt;section&gt;</kbd><br><br><strong>Inline elements</strong> flow within text and only take needed width. Examples: <kbd>&lt;span&gt;</kbd>, <kbd>&lt;a&gt;</kbd>, <kbd>&lt;strong&gt;</kbd>, <kbd>&lt;em&gt;</kbd>",
"task": "Wrap the word <kbd>important</kbd> with <kbd>&lt;strong&gt;</kbd> tags to make it bold. Notice how the paragraph (block) takes full width while strong (inline) flows with text.",
"title": "Elementos de bloque vs en línea",
"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>&lt;div&gt;</kbd>, <kbd>&lt;p&gt;</kbd>, <kbd>&lt;h1&gt;</kbd>, <kbd>&lt;section&gt;</kbd><br><br><strong>Elementos en línea</strong> fluyen dentro del texto y solo ocupan el ancho necesario. Ejemplos: <kbd>&lt;span&gt;</kbd>, <kbd>&lt;a&gt;</kbd>, <kbd>&lt;strong&gt;</kbd>, <kbd>&lt;em&gt;</kbd>",
"task": "Envuelve la palabra <kbd>importante</kbd> con etiquetas <kbd>&lt;strong&gt;</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": "",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; } p { background: #e3f2fd; padding: 10px; } strong { background: #ffecb3; }",
"sandboxCSS": "",
"initialCode": "<p>This is a paragraph with an important word.</p>",
"solution": "<p>This is a paragraph with an <strong>important</strong> word.</p>",
"initialCode": "<p>Este es un párrafo con una palabra importante.</p>",
"solution": "<p>Este es un párrafo con una palabra <strong>importante</strong>.</p>",
"previewContainer": "preview-area",
"validations": [
{
"type": "element_exists",
"value": "p",
"message": "Add a <kbd>&lt;p&gt;</kbd> paragraph element"
"message": "Añade un elemento de párrafo <kbd>&lt;p&gt;</kbd>"
},
{
"type": "parent_child",
"value": { "parent": "p", "child": "strong" },
"message": "Wrap the word <kbd>important</kbd> with <kbd>&lt;strong&gt;</kbd> tags"
"message": "Envuelve la palabra <kbd>importante</kbd> con etiquetas <kbd>&lt;strong&gt;</kbd>"
}
]
},
{
"id": "semantic-containers",
"title": "Semantic Tags",
"description": "Modern HTML uses semantic containers that describe their content:<br><br><kbd>&lt;header&gt;</kbd> - Page or section header<br><kbd>&lt;nav&gt;</kbd> - Navigation links<br><kbd>&lt;main&gt;</kbd> - Main content area<br><kbd>&lt;section&gt;</kbd> - Thematic grouping<br><kbd>&lt;article&gt;</kbd> - Self-contained content<br><kbd>&lt;footer&gt;</kbd> - Page or section footer",
"task": "Create a basic page structure:<br>1. Add a <kbd>&lt;header&gt;</kbd> with an <kbd>&lt;h1&gt;</kbd> containing the text <code>My Website</code><br>2. Add a <kbd>&lt;main&gt;</kbd> element with a paragraph saying <code>Welcome to my site!</code><br>3. Add a <kbd>&lt;footer&gt;</kbd> with a paragraph saying <code>Copyright 2026</code>",
"title": "Etiquetas semánticas",
"description": "El HTML moderno usa contenedores semánticos que describen su contenido:<br><br><kbd>&lt;header&gt;</kbd> - Encabezado de página o sección<br><kbd>&lt;nav&gt;</kbd> - Enlaces de navegación<br><kbd>&lt;main&gt;</kbd> - Área de contenido principal<br><kbd>&lt;section&gt;</kbd> - Agrupación temática<br><kbd>&lt;article&gt;</kbd> - Contenido independiente<br><kbd>&lt;footer&gt;</kbd> - Pie de página o sección",
"task": "Crea una estructura básica de página:<br>1. Añade un <kbd>&lt;header&gt;</kbd> con un <kbd>&lt;h1&gt;</kbd> que contenga el texto <code>Mi Sitio Web</code><br>2. Añade un elemento <kbd>&lt;main&gt;</kbd> con un párrafo que diga <code>¡Bienvenido a mi sitio!</code><br>3. Añade un <kbd>&lt;footer&gt;</kbd> con un párrafo que diga <code>Copyright 2026</code>",
"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; }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "header",
"message": "Add a <kbd>&lt;header&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;header&gt;</kbd>"
},
{
"type": "element_exists",
"value": "main",
"message": "Add a <kbd>&lt;main&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;main&gt;</kbd>"
},
{
"type": "element_exists",
"value": "footer",
"message": "Add a <kbd>&lt;footer&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;footer&gt;</kbd>"
},
{
"type": "parent_child",
"value": { "parent": "header", "child": "h1" },
"message": "Add an <kbd>&lt;h1&gt;</kbd> heading inside your header"
"message": "Añade un encabezado <kbd>&lt;h1&gt;</kbd> dentro de tu header"
}
]
}

View File

@@ -1,100 +1,100 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-forms-basic",
"title": "HTML Forms",
"description": "Learn to create forms with various input types",
"title": "Formularios HTML",
"description": "Aprende a crear formularios con varios tipos de campos",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "form-structure",
"title": "Form Structure",
"description": "Every form needs a <kbd>&lt;form&gt;</kbd> wrapper. Inside, use <kbd>&lt;label&gt;</kbd> to describe inputs and <kbd>&lt;input&gt;</kbd> for user data entry.<br><br>The <kbd>for</kbd> attribute on labels should match the <kbd>id</kbd> on inputs for accessibility.",
"task": "Create a form with:<br>1. A <kbd>&lt;label&gt;</kbd> with the text <code>Name:</code> and <kbd>for=\"name\"</kbd> attribute<br>2. A text <kbd>&lt;input&gt;</kbd> with <kbd>id=\"name\"</kbd> and <kbd>name=\"name\"</kbd> attributes",
"title": "Estructura del formulario",
"description": "Todo formulario necesita un contenedor <kbd>&lt;form&gt;</kbd>. Dentro, usa <kbd>&lt;label&gt;</kbd> para describir campos y <kbd>&lt;input&gt;</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": "Crea un formulario con:<br>1. Un <kbd>&lt;label&gt;</kbd> con el texto <code>Nombre:</code> y el atributo <kbd>for=\"name\"</kbd><br>2. Un <kbd>&lt;input&gt;</kbd> de texto con los atributos <kbd>id=\"name\"</kbd> y <kbd>name=\"name\"</kbd>",
"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; }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "form",
"message": "Wrap everything in a <kbd>&lt;form&gt;</kbd> element"
"message": "Envuelve todo en un elemento <kbd>&lt;form&gt;</kbd>"
},
{
"type": "element_exists",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for your input"
"message": "Añade un <kbd>&lt;label&gt;</kbd> para tu campo"
},
{
"type": "element_exists",
"value": "input",
"message": "Add an <kbd>&lt;input&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;input&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"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",
"title": "Input Types",
"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",
"task": "Create a login form with two fields:<br>1. An email field: <kbd>&lt;label for=\"email\"&gt;Email:&lt;/label&gt;</kbd> and <kbd>&lt;input type=\"email\" id=\"email\"&gt;</kbd><br>2. A password field: <kbd>&lt;label for=\"password\"&gt;Password:&lt;/label&gt;</kbd> and <kbd>&lt;input type=\"password\" id=\"password\"&gt;</kbd>",
"title": "Tipos de campos",
"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": "Crea un formulario de inicio de sesión con dos campos:<br>1. Campo de email: <kbd>&lt;label for=\"email\"&gt;Email:&lt;/label&gt;</kbd> y <kbd>&lt;input type=\"email\" id=\"email\"&gt;</kbd><br>2. Campo de contraseña: <kbd>&lt;label for=\"password\"&gt;Contraseña:&lt;/label&gt;</kbd> y <kbd>&lt;input type=\"password\" id=\"password\"&gt;</kbd>",
"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; }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "input[type='email']",
"message": "Add an input with type=\"email\""
"message": "Añade un campo con type=\"email\""
},
{
"type": "element_exists",
"value": "input[type='password']",
"message": "Add an input with type=\"password\""
"message": "Añade un campo con type=\"password\""
},
{
"type": "element_count",
"value": { "selector": "label", "min": 2 },
"message": "Add labels for both inputs"
"message": "Añade labels para ambos campos"
}
]
},
{
"id": "submit-button",
"title": "Submit Button",
"description": "Forms need a way to submit data. Use:<br><br><kbd>&lt;button type=\"submit\"&gt;</kbd> - Preferred, flexible content<br><kbd>&lt;input type=\"submit\"&gt;</kbd> - Simple text-only button<br><br>The button text should be action-oriented (e.g., <code>Sign In</code>, 'Register', 'Send').",
"task": "Add a submit button to the form with the text <code>Sign In</code>.",
"title": "Botón de envío",
"description": "Los formularios necesitan una forma de enviar datos. Usa:<br><br><kbd>&lt;button type=\"submit\"&gt;</kbd> - Preferido, contenido flexible<br><kbd>&lt;input type=\"submit\"&gt;</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": "Añade un botón de envío al formulario con el texto <code>Iniciar Sesión</code>.",
"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; }",
"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>",
"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>",
"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\">Contraseña:</label>\n <input type=\"password\" id=\"password\">\n \n <button type=\"submit\">Iniciar Sesión</button>\n</form>",
"previewContainer": "preview-area",
"validations": [
{
"type": "element_exists",
"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",
"value": { "selector": "button", "text": "Sign In" },
"message": "The button should say <kbd>Sign In</kbd>"
"value": { "selector": "button", "text": "Iniciar Sesión" },
"message": "El botón debe decir <kbd>Iniciar Sesión</kbd>"
}
]
}

View File

@@ -1,110 +1,32 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-forms-validation",
"title": "HTML Validation",
"description": "Learn HTML5 built-in form validation attributes",
"title": "Validación de formularios",
"description": "Usa la validación integrada de HTML5 para mejor experiencia de usuario",
"mode": "html",
"difficulty": "intermediate",
"difficulty": "beginner",
"lessons": [
{
"id": "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>&lt;input type=\"text\" required&gt;</kbd><br><br>The browser shows a validation message automatically.",
"task": "Make both the name and email fields required by adding the <kbd>required</kbd> attribute.",
"title": "Campos requeridos",
"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>&lt;input type=\"text\" required&gt;</kbd>",
"task": "Haz que ambos campos (nombre y email) sean requeridos añadiendo el atributo <kbd>required</kbd> a cada campo.",
"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": "",
"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>",
"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\">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",
"validations": [
{
"type": "attribute_value",
"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",
"value": { "selector": "input[name='email']", "attr": "required", "value": true },
"message": "Add the <kbd>required</kbd> attribute 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>"
"message": "Añade <kbd>required</kbd> al campo de email"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-details-summary",
"title": "HTML Details & Summary",
"description": "Create expandable content sections without JavaScript",
"description": "Crea secciones expandibles sin JavaScript",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "details-summary-basic",
"title": "First Widget",
"description": "The <kbd>&lt;details&gt;</kbd> element creates a collapsible section. The <kbd>&lt;summary&gt;</kbd> provides the clickable label.<br><br>Click the summary to toggle the hidden content - no JavaScript needed!",
"task": "Create a <kbd>&lt;details&gt;</kbd> element with:<br>1. A <kbd>&lt;summary&gt;</kbd> saying <code>Click to reveal</code><br>2. A <kbd>&lt;p&gt;</kbd> with the text <code>This content was hidden!</code>",
"title": "Primer widget",
"description": "El elemento <kbd>&lt;details&gt;</kbd> crea una sección plegable. El <kbd>&lt;summary&gt;</kbd> proporciona la etiqueta clickeable.<br><br>¡Haz clic en el resumen para mostrar el contenido oculto - sin JavaScript!",
"task": "Crea un elemento <kbd>&lt;details&gt;</kbd> con:<br>1. Un <kbd>&lt;summary&gt;</kbd> que diga <code>Click to reveal</code><br>2. Un <kbd>&lt;p&gt;</kbd> con el texto <code>This content was hidden!</code>",
"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; }",
"sandboxCSS": "",
@@ -21,30 +21,30 @@
{
"type": "element_exists",
"value": "details",
"message": "Add a <kbd>&lt;details&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;details&gt;</kbd>"
},
{
"type": "element_exists",
"value": "summary",
"message": "Add a <kbd>&lt;summary&gt;</kbd> inside the details"
"message": "Añade un <kbd>&lt;summary&gt;</kbd> dentro del details"
},
{
"type": "parent_child",
"value": { "parent": "details", "child": "summary" },
"message": "The <kbd>&lt;summary&gt;</kbd> must be inside <kbd>&lt;details&gt;</kbd>"
"message": "El <kbd>&lt;summary&gt;</kbd> debe estar dentro de <kbd>&lt;details&gt;</kbd>"
},
{
"type": "parent_child",
"value": { "parent": "details", "child": "p" },
"message": "Add a <kbd>&lt;p&gt;</kbd> inside <kbd>&lt;details&gt;</kbd> for the hidden content"
"message": "Añade un <kbd>&lt;p&gt;</kbd> dentro de <kbd>&lt;details&gt;</kbd> para el contenido oculto"
}
]
},
{
"id": "details-open-attribute",
"title": "Pre-expanded Details",
"description": "By default, <kbd>&lt;details&gt;</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.",
"task": "Add the <kbd>open</kbd> attribute to the <kbd>&lt;details&gt;</kbd> element to show the content by default.",
"title": "Expandido por defecto",
"description": "Por defecto, <kbd>&lt;details&gt;</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": "Añade el atributo <kbd>open</kbd> al elemento <kbd>&lt;details&gt;</kbd> para mostrar el contenido por defecto.",
"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; }",
"sandboxCSS": "",
@@ -55,15 +55,15 @@
{
"type": "attribute_value",
"value": { "selector": "details", "attr": "open", "value": true },
"message": "Add the <kbd>open</kbd> attribute to <kbd>&lt;details&gt;</kbd>"
"message": "Añade el atributo <kbd>open</kbd> a <kbd>&lt;details&gt;</kbd>"
}
]
},
{
"id": "faq-accordion",
"title": "FAQ Accordion",
"description": "Multiple <kbd>&lt;details&gt;</kbd> elements create an accordion-style FAQ. Each question can be expanded independently.<br><br><b>Pro tip:</b> Type <kbd>details*3&gt;summary+p</kbd> and press Tab for Emmet expansion. <kbd>*3</kbd> creates 3 elements, <kbd>&gt;</kbd> nests inside, <kbd>+</kbd> adds siblings.",
"task": "Create an FAQ section with:<br>1. An <kbd>&lt;h1&gt;</kbd> saying <code>Frequently Asked Questions</code><br>2. Three <kbd>&lt;details&gt;</kbd> elements, each with a question in <kbd>&lt;summary&gt;</kbd> and an answer in <kbd>&lt;p&gt;</kbd>",
"title": "Acordeón FAQ",
"description": "Múltiples elementos <kbd>&lt;details&gt;</kbd> crean un FAQ estilo acordeón. Cada pregunta puede expandirse independientemente.<br><br><b>Pro tip:</b> Escribe <kbd>details*3&gt;summary+p</kbd> y presiona Tab para expansión Emmet. <kbd>*3</kbd> crea 3 elementos, <kbd>&gt;</kbd> anida dentro, <kbd>+</kbd> añade hermanos.",
"task": "Crea una sección FAQ con:<br>1. Un <kbd>&lt;h1&gt;</kbd> que diga <code>Frequently Asked Questions</code><br>2. Tres elementos <kbd>&lt;details&gt;</kbd>, cada uno con una pregunta en <kbd>&lt;summary&gt;</kbd> y una respuesta en <kbd>&lt;p&gt;</kbd>",
"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; }",
"sandboxCSS": "",
@@ -74,22 +74,22 @@
{
"type": "element_exists",
"value": "h1",
"message": "Add an <kbd>&lt;h1&gt;</kbd> heading for the FAQ title"
"message": "Añade un encabezado <kbd>&lt;h1&gt;</kbd> para el título del FAQ"
},
{
"type": "element_count",
"value": { "selector": "details", "min": 3 },
"message": "Create at least 3 <kbd>&lt;details&gt;</kbd> elements for the FAQ"
"message": "Crea al menos 3 elementos <kbd>&lt;details&gt;</kbd> para el FAQ"
},
{
"type": "element_count",
"value": { "selector": "summary", "min": 3 },
"message": "Each <kbd>&lt;details&gt;</kbd> needs a <kbd>&lt;summary&gt;</kbd> for the question"
"message": "Cada <kbd>&lt;details&gt;</kbd> necesita un <kbd>&lt;summary&gt;</kbd> para la pregunta"
},
{
"type": "element_count",
"value": { "selector": "details p", "min": 3 },
"message": "Each <kbd>&lt;details&gt;</kbd> needs a <kbd>&lt;p&gt;</kbd> for the answer"
"message": "Cada <kbd>&lt;details&gt;</kbd> necesita un <kbd>&lt;p&gt;</kbd> para la respuesta"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "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",
"difficulty": "beginner",
"lessons": [
{
"id": "progress-basic",
"title": "Progress Bars",
"description": "The <kbd>&lt;progress&gt;</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>&lt;progress&gt;...&lt;/progress&gt;</kbd> with fallback text inside for older browsers.",
"task": "Create a progress bar showing 70% completion:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Download:</code><br>2. Add a <kbd>&lt;progress&gt;</kbd> with <kbd>value=\"70\"</kbd> and <kbd>max=\"100\"</kbd>",
"title": "Barras de progreso",
"description": "El elemento <kbd>&lt;progress&gt;</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>&lt;progress&gt;...&lt;/progress&gt;</kbd> con texto alternativo dentro para navegadores antiguos.",
"task": "Crea una barra de progreso mostrando 70% de completado:<br>1. Añade un <kbd>&lt;label&gt;</kbd> que diga <code>Download:</code><br>2. Añade un <kbd>&lt;progress&gt;</kbd> con <kbd>value=\"70\"</kbd> y <kbd>max=\"100\"</kbd>",
"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; }",
"sandboxCSS": "",
@@ -21,30 +21,30 @@
{
"type": "element_exists",
"value": "progress",
"message": "Add a <kbd>&lt;progress&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;progress&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"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",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for the progress bar"
"message": "Añade un <kbd>&lt;label&gt;</kbd> para la barra de progreso"
}
]
},
{
"id": "progress-indeterminate",
"title": "Indeterminate Progress",
"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.",
"task": "Create a loading indicator:<br>1. Add a <kbd>&lt;p&gt;</kbd> saying <code>Loading...</code><br>2. Add a <kbd>&lt;progress&gt;</kbd> without a value attribute",
"title": "Progreso indeterminado",
"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": "Crea un indicador de carga:<br>1. Añade un <kbd>&lt;p&gt;</kbd> que diga <code>Loading...</code><br>2. Añade un <kbd>&lt;progress&gt;</kbd> sin atributo value",
"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; }",
"sandboxCSS": "",
@@ -55,20 +55,20 @@
{
"type": "element_exists",
"value": "progress",
"message": "Add a <kbd>&lt;progress&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;progress&gt;</kbd>"
},
{
"type": "element_exists",
"value": "p",
"message": "Add a <kbd>&lt;p&gt;</kbd> with loading text"
"message": "Añade un <kbd>&lt;p&gt;</kbd> con texto de carga"
}
]
},
{
"id": "meter-gauge",
"title": "Meter Gauges",
"description": "The <kbd>&lt;meter&gt;</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!",
"task": "Create a battery level meter:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Battery:</code><br>2. Add a <kbd>&lt;meter&gt;</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>",
"title": "Indicadores meter",
"description": "El elemento <kbd>&lt;meter&gt;</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": "Crea un indicador de nivel de batería:<br>1. Añade un <kbd>&lt;label&gt;</kbd> que diga <code>Battery:</code><br>2. Añade un <kbd>&lt;meter&gt;</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": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
"sandboxCSS": "",
@@ -79,22 +79,42 @@
{
"type": "element_exists",
"value": "meter",
"message": "Add a <kbd>&lt;meter&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;meter&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"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",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for the meter"
"message": "Añade un <kbd>&lt;label&gt;</kbd> para el meter"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-datalist",
"title": "Datalist",
"description": "Provide suggestions for text inputs without JavaScript",
"description": "Proporciona sugerencias para campos de texto sin JavaScript",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "datalist-basic",
"title": "Input with Suggestions",
"description": "The <kbd>&lt;datalist&gt;</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!",
"task": "Create a browser selector:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Browser:</code><br>2. Add an <kbd>&lt;input&gt;</kbd> with <kbd>list=\"browsers\"</kbd><br>3. Add a <kbd>&lt;datalist id=\"browsers\"&gt;</kbd> with options for Chrome, Firefox, and Safari",
"title": "Campo con sugerencias",
"description": "El elemento <kbd>&lt;datalist&gt;</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": "Crea un selector de navegador:<br>1. Añade un <kbd>&lt;label&gt;</kbd> que diga <code>Navegador:</code><br>2. Añade un <kbd>&lt;input&gt;</kbd> con <kbd>list=\"browsers\"</kbd><br>3. Añade un <kbd>&lt;datalist id=\"browsers\"&gt;</kbd> con opciones para Chrome, Firefox y Safari",
"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; }",
"sandboxCSS": "",
@@ -21,30 +21,30 @@
{
"type": "element_exists",
"value": "datalist",
"message": "Add a <kbd>&lt;datalist&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;datalist&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"value": { "selector": "option", "min": 3 },
"message": "Add at least 3 <kbd>&lt;option&gt;</kbd> elements inside <kbd>&lt;datalist&gt;</kbd>"
"message": "Añade al menos 3 elementos <kbd>&lt;option&gt;</kbd> dentro de <kbd>&lt;datalist&gt;</kbd>"
},
{
"type": "element_exists",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for the input"
"message": "Añade un <kbd>&lt;label&gt;</kbd> para el campo"
}
]
},
{
"id": "datalist-countries",
"title": "Country Selector",
"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.",
"task": "Create a country input:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Country:</code><br>2. Add an <kbd>&lt;input&gt;</kbd> with <kbd>list=\"countries\"</kbd><br>3. Add a <kbd>&lt;datalist id=\"countries\"&gt;</kbd> with at least 4 country options",
"title": "Selector de países",
"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": "Crea un campo de país:<br>1. Añade un <kbd>&lt;label&gt;</kbd> que diga <code>País:</code><br>2. Añade un <kbd>&lt;input&gt;</kbd> con <kbd>list=\"countries\"</kbd><br>3. Añade un <kbd>&lt;datalist id=\"countries\"&gt;</kbd> con al menos 4 opciones de países",
"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); }",
"sandboxCSS": "",
@@ -55,22 +55,22 @@
{
"type": "element_exists",
"value": "datalist",
"message": "Add a <kbd>&lt;datalist&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;datalist&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"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",
"value": { "selector": "option", "min": 4 },
"message": "Add at least 4 country options"
"message": "Añade al menos 4 opciones de países"
}
]
}

View File

@@ -1,16 +1,16 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-dialog",
"title": "Dialogs",
"description": "Create modal dialogs without JavaScript libraries",
"title": "Diálogos",
"description": "Crea diálogos modales sin bibliotecas JavaScript",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "dialog-basic",
"title": "Open Dialog",
"description": "The <kbd>&lt;dialog&gt;</kbd> element creates a native modal. Add the <kbd>open</kbd> attribute to show it.<br><br>Use <kbd>&lt;form method=\"dialog\"&gt;</kbd> inside to close it when the form submits - no JavaScript needed!",
"task": "Create a dialog with:<br>1. The <kbd>open</kbd> attribute to show it<br>2. An <kbd>&lt;h2&gt;</kbd> saying <code>Welcome!</code><br>3. A <kbd>&lt;p&gt;</kbd> with a greeting message<br>4. A <kbd>&lt;form method=\"dialog\"&gt;</kbd> with a close button",
"title": "Abrir diálogo",
"description": "El elemento <kbd>&lt;dialog&gt;</kbd> crea un modal nativo. Añade el atributo <kbd>open</kbd> para mostrarlo.<br><br>¡Usa <kbd>&lt;form method=\"dialog\"&gt;</kbd> dentro para cerrarlo al enviar el formulario - sin JavaScript!",
"task": "Crea un diálogo con:<br>1. El atributo <kbd>open</kbd> para mostrarlo<br>2. Un <kbd>&lt;h2&gt;</kbd> que diga <code>¡Bienvenido!</code><br>3. Un <kbd>&lt;p&gt;</kbd> con un mensaje de saludo<br>4. Un <kbd>&lt;form method=\"dialog\"&gt;</kbd> con un botón de cerrar",
"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; }",
"sandboxCSS": "",
@@ -21,35 +21,35 @@
{
"type": "element_exists",
"value": "dialog",
"message": "Add a <kbd>&lt;dialog&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;dialog&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"value": "dialog h2",
"message": "Add an <kbd>&lt;h2&gt;</kbd> heading inside the dialog"
"message": "Añade un encabezado <kbd>&lt;h2&gt;</kbd> dentro del diálogo"
},
{
"type": "element_exists",
"value": "form[method='dialog']",
"message": "Add a <kbd>&lt;form method=\"dialog\"&gt;</kbd> for closing"
"message": "Añade un <kbd>&lt;form method=\"dialog\"&gt;</kbd> para cerrar"
},
{
"type": "element_exists",
"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",
"title": "Dialog + Form",
"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.",
"task": "Create a confirmation dialog:<br>1. Add <kbd>open</kbd> to show it<br>2. An <kbd>&lt;h2&gt;</kbd> saying <code>Confirm Delete</code><br>3. A <kbd>&lt;p&gt;</kbd> asking <code>Are you sure?</code><br>4. A <kbd>&lt;form method=\"dialog\"&gt;</kbd> with Cancel and Delete buttons",
"title": "Diálogo + Formulario",
"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": "Crea un diálogo de confirmación:<br>1. Añade <kbd>open</kbd> para mostrarlo<br>2. Un <kbd>&lt;h2&gt;</kbd> que diga <code>Confirmar eliminación</code><br>3. Un <kbd>&lt;p&gt;</kbd> preguntando <code>¿Estás seguro?</code><br>4. Un <kbd>&lt;form method=\"dialog\"&gt;</kbd> con botones Cancelar y Eliminar",
"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; }",
"sandboxCSS": "",
@@ -60,22 +60,22 @@
{
"type": "element_exists",
"value": "dialog[open]",
"message": "Add a <kbd>&lt;dialog&gt;</kbd> with the open attribute"
"message": "Añade un <kbd>&lt;dialog&gt;</kbd> con el atributo open"
},
{
"type": "element_exists",
"value": "dialog h2",
"message": "Add a heading to the dialog"
"message": "Añade un encabezado al diálogo"
},
{
"type": "element_exists",
"value": "form[method='dialog']",
"message": "Add a <kbd>&lt;form method=\"dialog\"&gt;</kbd> for the buttons"
"message": "Añade un <kbd>&lt;form method=\"dialog\"&gt;</kbd> para los botones"
},
{
"type": "element_count",
"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)"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-forms-fieldset",
"title": "Fieldsets",
"description": "Group form controls with fieldset and legend elements",
"description": "Agrupa controles de formulario con elementos fieldset y legend",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "fieldset-basic",
"title": "Grouping with Fieldset",
"description": "The <kbd>&lt;fieldset&gt;</kbd> element groups related form controls together. Add a <kbd>&lt;legend&gt;</kbd> as the first child to give the group a title.<br><br>This helps with accessibility and visual organization of complex forms.",
"task": "Create a form with a fieldset:<br>1. A <kbd>&lt;form&gt;</kbd> element<br>2. A <kbd>&lt;fieldset&gt;</kbd> inside<br>3. A <kbd>&lt;legend&gt;</kbd> saying <code>Personal Info</code><br>4. Two labeled inputs for name and email",
"title": "Agrupar con Fieldset",
"description": "El elemento <kbd>&lt;fieldset&gt;</kbd> agrupa controles de formulario relacionados. Añade un <kbd>&lt;legend&gt;</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": "Crea un formulario con un fieldset:<br>1. Un elemento <kbd>&lt;form&gt;</kbd><br>2. Un <kbd>&lt;fieldset&gt;</kbd> dentro<br>3. Un <kbd>&lt;legend&gt;</kbd> que diga <code>Información Personal</code><br>4. Dos campos etiquetados para nombre y email",
"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; }",
"sandboxCSS": "",
@@ -21,35 +21,35 @@
{
"type": "element_exists",
"value": "form",
"message": "Add a <kbd>&lt;form&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;form&gt;</kbd>"
},
{
"type": "element_exists",
"value": "fieldset",
"message": "Add a <kbd>&lt;fieldset&gt;</kbd> inside the form"
"message": "Añade un <kbd>&lt;fieldset&gt;</kbd> dentro del formulario"
},
{
"type": "element_exists",
"value": "legend",
"message": "Add a <kbd>&lt;legend&gt;</kbd> to title your fieldset"
"message": "Añade un <kbd>&lt;legend&gt;</kbd> para titular tu fieldset"
},
{
"type": "element_count",
"value": { "selector": "label", "min": 2 },
"message": "Add at least 2 labels"
"message": "Añade al menos 2 etiquetas"
},
{
"type": "element_count",
"value": { "selector": "input", "min": 2 },
"message": "Add at least 2 input fields"
"message": "Añade al menos 2 campos de entrada"
}
]
},
{
"id": "fieldset-textarea",
"title": "Adding Textarea",
"description": "The <kbd>&lt;textarea&gt;</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.",
"task": "Create a contact form:<br>1. A <kbd>&lt;fieldset&gt;</kbd> with <kbd>&lt;legend&gt;</kbd> <code>Contact Us</code><br>2. A labeled <kbd>&lt;input&gt;</kbd> for email<br>3. A labeled <kbd>&lt;textarea&gt;</kbd> for the message<br>4. A submit <kbd>&lt;button&gt;</kbd>",
"title": "Añadiendo Textarea",
"description": "El elemento <kbd>&lt;textarea&gt;</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": "Crea un formulario de contacto:<br>1. Un <kbd>&lt;fieldset&gt;</kbd> con <kbd>&lt;legend&gt;</kbd> <code>Contáctanos</code><br>2. Un <kbd>&lt;input&gt;</kbd> etiquetado para email<br>3. Un <kbd>&lt;textarea&gt;</kbd> etiquetado para el mensaje<br>4. Un <kbd>&lt;button&gt;</kbd> de envío",
"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; }",
"sandboxCSS": "",
@@ -60,35 +60,35 @@
{
"type": "element_exists",
"value": "fieldset",
"message": "Add a <kbd>&lt;fieldset&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;fieldset&gt;</kbd>"
},
{
"type": "element_exists",
"value": "legend",
"message": "Add a <kbd>&lt;legend&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;legend&gt;</kbd>"
},
{
"type": "element_exists",
"value": "textarea",
"message": "Add a <kbd>&lt;textarea&gt;</kbd> for the message"
"message": "Añade un <kbd>&lt;textarea&gt;</kbd> para el mensaje"
},
{
"type": "element_exists",
"value": "button",
"message": "Add a submit button"
"message": "Añade un botón de envío"
},
{
"type": "element_exists",
"value": "input",
"message": "Add an input field for email"
"message": "Añade un campo de entrada para el email"
}
]
},
{
"id": "fieldset-multiple",
"title": "Multiple Fieldsets",
"description": "Complex forms can use multiple <kbd>&lt;fieldset&gt;</kbd> elements to organize different sections.<br><br>This improves usability for long forms like registration or checkout pages.",
"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",
"title": "Múltiples Fieldsets",
"description": "Los formularios complejos pueden usar múltiples elementos <kbd>&lt;fieldset&gt;</kbd> para organizar diferentes secciones.<br><br>Esto mejora la usabilidad en formularios largos como registro o checkout.",
"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": "",
"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": "",
@@ -99,27 +99,27 @@
{
"type": "element_count",
"value": { "selector": "fieldset", "min": 2 },
"message": "Create at least 2 fieldsets"
"message": "Crea al menos 2 fieldsets"
},
{
"type": "element_count",
"value": { "selector": "legend", "min": 2 },
"message": "Add a legend to each fieldset"
"message": "Añade un legend a cada fieldset"
},
{
"type": "element_exists",
"value": "textarea",
"message": "Add a textarea for the bio"
"message": "Añade un textarea para la bio"
},
{
"type": "element_exists",
"value": "button",
"message": "Add a submit button"
"message": "Añade un botón de envío"
},
{
"type": "element_count",
"value": { "selector": "input", "min": 2 },
"message": "Add at least 2 input fields"
"message": "Añade al menos 2 campos de entrada"
}
]
}

View File

@@ -1,125 +1,42 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-tables",
"title": "HTML Tables",
"description": "Create structured data tables with headers and captions",
"title": "Tablas HTML",
"description": "Crea tablas de datos estructuradas con marcado semántico",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "table-basic",
"title": "Basic Table Structure",
"description": "Tables use <kbd>&lt;table&gt;</kbd> with <kbd>&lt;tr&gt;</kbd> for rows. Inside rows, use <kbd>&lt;th&gt;</kbd> for headers and <kbd>&lt;td&gt;</kbd> for data cells.<br><br>The <kbd>&lt;caption&gt;</kbd> element provides an accessible title for the table.",
"task": "Create a simple table with:<br>1. A <kbd>&lt;caption&gt;</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",
"title": "Tablas de datos",
"description": "Las tablas muestran datos estructurados en filas y columnas. Usa <kbd>&lt;table&gt;</kbd> como contenedor, <kbd>&lt;tr&gt;</kbd> para filas, <kbd>&lt;th&gt;</kbd> para celdas de encabezado y <kbd>&lt;td&gt;</kbd> para celdas de datos.<br><br>Añade <kbd>&lt;caption&gt;</kbd> para un título accesible que describa el contenido de la tabla.",
"task": "Crea una tabla de precios:<br>1. Un <kbd>&lt;caption&gt;</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": "",
"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": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "table",
"message": "Add a <kbd>&lt;table&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;table&gt;</kbd>"
},
{
"type": "element_exists",
"value": "caption",
"message": "Add a <kbd>&lt;caption&gt;</kbd> for the table title"
"message": "Añade un <kbd>&lt;caption&gt;</kbd> para el título de la tabla"
},
{
"type": "element_count",
"value": { "selector": "th", "min": 2 },
"message": "Add at least 2 header cells (th)"
"message": "Añade celdas de encabezado (<kbd>&lt;th&gt;</kbd>) para Plan y Price"
},
{
"type": "element_count",
"value": { "selector": "tr", "min": 3 },
"message": "Add at least 3 rows (1 header + 2 data rows)"
}
]
},
{
"id": "table-thead-tbody",
"title": "Table Head & Body",
"description": "Use <kbd>&lt;thead&gt;</kbd> to group header rows and <kbd>&lt;tbody&gt;</kbd> to group data rows. This helps browsers and assistive technology understand the table structure.<br><br>You can also use <kbd>&lt;tfoot&gt;</kbd> for footer rows like totals.",
"task": "Create a structured table:<br>1. A <kbd>&lt;caption&gt;</kbd> with <code>Monthly Sales</code><br>2. A <kbd>&lt;thead&gt;</kbd> with Month and Revenue headers<br>3. A <kbd>&lt;tbody&gt;</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>&lt;table&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "caption",
"message": "Add a <kbd>&lt;caption&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "thead",
"message": "Add a <kbd>&lt;thead&gt;</kbd> for the header section"
},
{
"type": "element_exists",
"value": "tbody",
"message": "Add a <kbd>&lt;tbody&gt;</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>&lt;tfoot&gt;</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>&lt;caption&gt;</kbd> with <code>Order Summary</code><br>2. A <kbd>&lt;thead&gt;</kbd> with Item and Price headers<br>3. A <kbd>&lt;tbody&gt;</kbd> with 2 items<br>4. A <kbd>&lt;tfoot&gt;</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>&lt;table&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "caption",
"message": "Add a <kbd>&lt;caption&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "thead",
"message": "Add a <kbd>&lt;thead&gt;</kbd> section"
},
{
"type": "element_exists",
"value": "tbody",
"message": "Add a <kbd>&lt;tbody&gt;</kbd> section"
},
{
"type": "element_exists",
"value": "tfoot",
"message": "Add a <kbd>&lt;tfoot&gt;</kbd> section for the total"
},
{
"type": "element_count",
"value": { "selector": "tbody tr", "min": 2 },
"message": "Add at least 2 item rows in tbody"
"message": "Añade 3 filas (1 encabezado + 2 filas de datos)"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "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",
"difficulty": "beginner",
"lessons": [
{
"id": "marquee-basic",
"title": "Scrolling Text",
"description": "The <kbd>&lt;marquee&gt;</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!",
"task": "Create a simple marquee:<br>1. Add a <kbd>&lt;marquee&gt;</kbd> element<br>2. Put some text inside like <code>Welcome to my website!</code>",
"title": "Texto Desplazable",
"description": "El elemento <kbd>&lt;marquee&gt;</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": "Crea un marquee simple:<br>1. Añade un elemento <kbd>&lt;marquee&gt;</kbd><br>2. Pon texto dentro como <code>¡Bienvenido a mi sitio web!</code>",
"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; }",
"sandboxCSS": "",
@@ -21,15 +21,15 @@
{
"type": "element_exists",
"value": "marquee",
"message": "Add a <kbd>&lt;marquee&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;marquee&gt;</kbd>"
}
]
},
{
"id": "marquee-direction",
"title": "Direction & Behavior",
"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)",
"task": "Create a bouncing marquee:<br>1. Add a <kbd>&lt;marquee&gt;</kbd> element<br>2. Set <kbd>behavior=\"alternate\"</kbd> to make it bounce<br>3. Add some fun text",
"title": "Dirección y Comportamiento",
"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": "Crea un marquee que rebota:<br>1. Añade un elemento <kbd>&lt;marquee&gt;</kbd><br>2. Pon <kbd>behavior=\"alternate\"</kbd> para hacerlo rebotar<br>3. Añade texto divertido",
"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; }",
"sandboxCSS": "",
@@ -40,20 +40,20 @@
{
"type": "element_exists",
"value": "marquee",
"message": "Add a <kbd>&lt;marquee&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;marquee&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"title": "Retro News Ticker",
"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.",
"task": "Create a news ticker:<br>1. A <kbd>&lt;marquee&gt;</kbd> with <kbd>direction=\"left\"</kbd><br>2. Set <kbd>scrollamount=\"5\"</kbd> for smooth scrolling<br>3. Add a breaking news headline inside",
"title": "Ticker de Noticias Retro",
"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": "Crea un ticker de noticias:<br>1. Un <kbd>&lt;marquee&gt;</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": "",
"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": "",
@@ -64,17 +64,17 @@
{
"type": "element_exists",
"value": "marquee",
"message": "Add a <kbd>&lt;marquee&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;marquee&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"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"
}
]
}

View File

@@ -2,99 +2,169 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-svg",
"title": "HTML SVG",
"description": "Draw scalable vector graphics directly in HTML",
"description": "Dibuja gráficos vectoriales escalables directamente en HTML",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "svg-circle",
"title": "Drawing Circles",
"description": "SVG (Scalable Vector Graphics) lets you draw shapes directly in HTML. The <kbd>&lt;svg&gt;</kbd> element is the container, with <kbd>width</kbd> and <kbd>height</kbd> attributes.<br><br>Use <kbd>&lt;circle&gt;</kbd> with <kbd>cx</kbd>, <kbd>cy</kbd> (center) and <kbd>r</kbd> (radius) to draw circles.",
"task": "Create an SVG with a circle:<br>1. An <kbd>&lt;svg&gt;</kbd> with width=\"200\" and height=\"200\"<br>2. A <kbd>&lt;circle&gt;</kbd> centered at (100,100) with radius 50<br>3. Add a <kbd>fill</kbd> color",
"title": "Dibujando círculos",
"description": "SVG (Scalable Vector Graphics) permite dibujar formas directamente en HTML. El elemento <kbd>&lt;svg&gt;</kbd> es el contenedor con atributos <kbd>width</kbd> y <kbd>height</kbd>.<br><br>Usa <kbd>&lt;circle&gt;</kbd> con <kbd>cx</kbd>, <kbd>cy</kbd> (centro) y <kbd>r</kbd> (radio) para dibujar círculos.",
"task": "Crea un SVG con un círculo:<br>1. Un <kbd>&lt;svg&gt;</kbd> con width=\"200\" y height=\"200\"<br>2. Un <kbd>&lt;circle&gt;</kbd> centrado en (100,100) con radio 50<br>3. Añade un color <kbd>fill</kbd>",
"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); }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "svg",
"message": "Add an <kbd>&lt;svg&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;svg&gt;</kbd>"
},
{
"type": "element_exists",
"value": "circle",
"message": "Add a <kbd>&lt;circle&gt;</kbd> element inside the SVG"
"message": "Añade un elemento <kbd>&lt;circle&gt;</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",
"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",
"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",
"title": "Rectangles & Lines",
"description": "Draw rectangles with <kbd>&lt;rect&gt;</kbd> using <kbd>x</kbd>, <kbd>y</kbd>, <kbd>width</kbd>, <kbd>height</kbd>.<br><br>Draw lines with <kbd>&lt;line&gt;</kbd> using <kbd>x1</kbd>, <kbd>y1</kbd> (start) and <kbd>x2</kbd>, <kbd>y2</kbd> (end). Lines need a <kbd>stroke</kbd> color!",
"task": "Create an SVG with:<br>1. An <kbd>&lt;svg&gt;</kbd> (200x150)<br>2. A <kbd>&lt;rect&gt;</kbd> at position (20,20) with size 80x60<br>3. A <kbd>&lt;line&gt;</kbd> from (120,30) to (180,90) with a stroke color",
"title": "Rectángulos y líneas",
"description": "Dibuja rectángulos con <kbd>&lt;rect&gt;</kbd> usando <kbd>x</kbd>, <kbd>y</kbd>, <kbd>width</kbd>, <kbd>height</kbd>.<br><br>Dibuja líneas con <kbd>&lt;line&gt;</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": "Crea un SVG con:<br>1. Un <kbd>&lt;svg&gt;</kbd> (200x150)<br>2. Un <kbd>&lt;rect&gt;</kbd> en posición (20,20) con tamaño 80x60<br>3. Una <kbd>&lt;line&gt;</kbd> de (120,30) a (180,90) con color stroke",
"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); }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "svg",
"message": "Add an <kbd>&lt;svg&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;svg&gt;</kbd>"
},
{
"type": "element_exists",
"value": "rect",
"message": "Add a <kbd>&lt;rect&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;rect&gt;</kbd>"
},
{
"type": "element_exists",
"value": "line",
"message": "Add a <kbd>&lt;line&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;line&gt;</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",
"title": "Multiple Shapes",
"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.",
"task": "Create a simple face:<br>1. An <kbd>&lt;svg&gt;</kbd> (200x200)<br>2. A large <kbd>&lt;circle&gt;</kbd> for the face<br>3. Two small <kbd>&lt;circle&gt;</kbd> elements for eyes<br>4. A <kbd>&lt;line&gt;</kbd> for the smile",
"title": "Múltiples formas",
"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": "Crea una cara simple:<br>1. Un <kbd>&lt;svg&gt;</kbd> (200x200)<br>2. Un <kbd>&lt;circle&gt;</kbd> grande para la cara<br>3. Dos <kbd>&lt;circle&gt;</kbd> pequeños para los ojos<br>4. Una <kbd>&lt;line&gt;</kbd> para la sonrisa",
"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); }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "svg",
"message": "Add an <kbd>&lt;svg&gt;</kbd> element"
"message": "Añade un elemento <kbd>&lt;svg&gt;</kbd>"
},
{
"type": "element_count",
"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",
"value": "line",
"message": "Add a <kbd>&lt;line&gt;</kbd> for the smile"
"message": "Añade una <kbd>&lt;line&gt;</kbd> para la sonrisa"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "welcome",
"title": "Code Crispies",
"description": "Welcome to Code Crispies - your interactive web development learning platform",
"description": "Witaj w Code Crispies - twojej interaktywnej platformie do nauki tworzenia stron",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "get-started",
"title": "Get 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",
"task": "Write <code>Hello World</code>",
"title": "Rozpocznij",
"description": "<strong>Code Crispies</strong> to darmowa platforma open-source do nauki tworzenia stron przez praktyczne ćwiczenia. Konto nie jest wymagane!<br><br><strong>Czego się nauczysz:</strong><br>• <strong>HTML</strong> - Elementy semantyczne, formularze, tabele, SVG (<em>HTML Blokowe i Liniowe</em>, <em>HTML Formularze</em>, <em>HTML Tabele</em>)<br>• <strong>CSS</strong> - Selektory, model pudełkowy, flexbox, animacje (<em>CSS Selektory</em>, <em>CSS Model pudełkowy</em>, <em>CSS Flexbox</em>)<br>• <strong>Responsive Design</strong> - Media queries i układy mobile-first<br><br><strong>Jak to działa:</strong><br>1. Przeczytaj zadanie w lewym panelu<br>2. Napisz kod w edytorze<br>3. Zobacz wyniki na żywo w podglądzie<br>4. Otrzymaj natychmiastową informację zwrotną ze wskazówkami<br><br><strong>Skróty klawiszowe:</strong> <kbd>Ctrl+Z</kbd> cofnij, <kbd>Ctrl+Shift+Z</kbd> ponów<br><br><strong>Więcej zasobów:</strong><br>• <a href=\"https://nextlevelshit.github.io/html-over-js/\" target=\"_blank\">HTML over JS</a> - Natywny HTML vs rozwiązania JavaScript<br>• <a href=\"https://nextlevelshit.github.io/web-engineering-mandala/\" target=\"_blank\">Web Engineering Mandala</a> - Mapa technologii JavaScript",
"task": "Napisz <code>Hello World</code>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 2rem; color: #6366f1; font-weight: bold; }",
"sandboxCSS": "",
@@ -21,15 +21,15 @@
{
"type": "contains",
"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",
"title": "Przegląd",
"description": "<strong>Jesteś gotowy!</strong> Otwórz menu (☰) aby odkryć wszystkie moduły.<br><br><strong>Zalecana ścieżka nauki:</strong><br>1. <em>HTML Blokowe i Liniowe</em> - Zrozum elementy kontenerowe vs liniowe<br>2. <em>HTML Formularze</em> - Twórz interaktywne formularze z walidacją<br>3. <em>CSS Selektory</em> - Celuj w elementy precyzyjnie<br>4. <em>CSS Model pudełkowy</em> - Opanuj padding, margin, borders<br>5. <em>CSS Flexbox</em> - Twórz elastyczne układy<br>6. <em>CSS Animacje</em> - Dodaj ruch i przejścia<br><br><strong>Wskazówki:</strong><br>• Użyj <em>Pokaż oczekiwane</em> aby zobaczyć docelowy wynik<br>• Twój postęp jest zapisywany automatycznie<br>• Wypróbuj Emmet w trybie HTML: <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>• Stworzone przez <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": "Kliknij Dalej aby kontynuować",
"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": "",
@@ -39,8 +39,8 @@
"validations": [
{
"type": "contains",
"value": "Hello World",
"message": "Click Next to continue"
"value": "Hello",
"message": "Kliknij Dalej aby kontynuować"
}
]
},

View File

@@ -2,18 +2,18 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "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",
"lessons": [
{
"id": "box-model-1",
"title": "Box Model Components",
"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.",
"task": "Set <kbd>padding</kbd> to <kbd>1rem</kbd> to create space between the content and border.",
"previewHTML": "<div class=\"box\">Box Model Components</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: lavender; border: 2px dashed slategray; }",
"title": "Padding",
"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": "Ta karta profilu wygląda na ciasną. Dodaj <kbd>padding: 1rem</kbd>, aby tekst miał miejsce do oddychania.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"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": "",
"codePrefix": ".box {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "padding: 1rem;",
@@ -22,62 +22,62 @@
{
"type": "property_value",
"value": { "property": "padding", "expected": "1rem" },
"message": "Set <kbd>padding: 1rem</kbd>"
"message": "Ustaw <kbd>padding: 1rem</kbd>"
}
]
},
{
"id": "box-model-2",
"title": "Adding Borders",
"description": "Borders outline an element, creating visual separation from surrounding content. The border shorthand accepts three values: width, style, and color.",
"task": "Set <kbd>border</kbd> to <kbd>2px solid darkslategray</kbd>.",
"previewHTML": "<div class=\"box\">This box needs a border</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: mintcream; padding: 1rem; }",
"title": "Borders",
"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": "Dodaj subtelny lewy akcent do karty za pomocą <kbd>border-left: 4px solid steelblue</kbd>.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"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": "",
"codePrefix": ".box {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "border: 2px solid darkslategray;",
"solution": "border-left: 4px solid steelblue;",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "border:\\s*2px\\s+solid\\s+darkslategray",
"message": "Set <kbd>border: 2px solid darkslategray</kbd>",
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
"message": "Ustaw <kbd>border-left: 4px solid steelblue</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-3",
"title": "Adding 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.",
"task": "Set <kbd>margin</kbd> to <kbd>1rem</kbd> to create space between this element and its neighbors.",
"previewHTML": "<div class=\"container\"><div class=\"outer\">This box needs margins</div><div class=\"neighbor\">Adjacent element</div></div>",
"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; }",
"title": "Margins",
"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": "Dodaj przestrzeń między tymi dwiema kartami profilu za pomocą <kbd>margin-bottom: 1rem</kbd> na <kbd>.card</kbd>.",
"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; 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": "",
"codePrefix": ".outer {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "margin: 1rem;",
"solution": "margin-bottom: 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "margin", "expected": "1rem" },
"message": "Set <kbd>margin: 1rem</kbd>"
"value": { "property": "margin-bottom", "expected": "1rem" },
"message": "Ustaw <kbd>margin-bottom: 1rem</kbd>"
}
]
},
{
"id": "box-model-4",
"title": "Box Sizing: Border-Box",
"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.",
"task": "Set <kbd>box-sizing</kbd> to <kbd>border-box</kbd> so padding and border are included in the width.",
"previewHTML": "<div class=\"sizing-demo\"><div class=\"box default\">Content-box (default)</div><div class=\"box sized\">Border-box</div></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; }",
"title": "Box Sizing",
"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": "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=\"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; background: #f5f5f5; } .demo { display: flex; gap: 1rem; } .card { width: 200px; padding: 1rem; border: 4px solid steelblue; background: white; border-radius: 8px; }",
"sandboxCSS": "",
"codePrefix": ".sized {\n ",
"codePrefix": ".fix {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "box-sizing: border-box;",
@@ -86,93 +86,104 @@
{
"type": "property_value",
"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",
"title": "Margin Collapse",
"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.",
"task": "Set <kbd>margin-bottom</kbd> to <kbd>2rem</kbd>. Notice the space between paragraphs equals 2rem (not 3rem) due to margin collapse.",
"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>",
"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; }",
"title": "Padding Shorthand",
"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": "Ten przycisk potrzebuje więcej miejsca poziomego niż pionowego. Ustaw <kbd>padding: 8px 1rem</kbd> (8px góra/dół, 1rem lewo/prawo).",
"previewHTML": "<button class=\"btn\">Follow</button>",
"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": "",
"codePrefix": ".first {\n ",
"codePrefix": ".btn {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "margin-bottom: 2rem;",
"solution": "padding: 8px 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "margin-bottom", "expected": "2rem" },
"message": "Set <kbd>margin-bottom: 2rem</kbd>"
"type": "regex",
"value": "padding:\\s*8px\\s+1rem",
"message": "Ustaw <kbd>padding: 8px 1rem</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-6",
"title": "Margin Shorthand Notation",
"description": "The margin shorthand can set all four sides at once. Two values set vertical (top/bottom) and horizontal (left/right) margins respectively.",
"task": "Set <kbd>margin</kbd> to <kbd>1rem 2rem</kbd> for 1rem top/bottom and 2rem left/right.",
"previewHTML": "<div class=\"container\"><div class=\"spaced\">This box needs margins: 1rem top/bottom, 2rem left/right</div></div>",
"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; }",
"title": "Margin Shorthand",
"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": "Wycentruj tę kartę poziomo. Ustaw <kbd>margin: 0 auto</kbd>, aby automatycznie obliczyć równe marginesy lewo/prawo.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"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": "",
"codePrefix": ".spaced {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "margin: 1rem 2rem;",
"solution": "margin: 0 auto;",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "margin:\\s*1rem\\s+2rem",
"message": "Set <kbd>margin: 1rem 2rem</kbd>",
"value": "margin:\\s*0\\s+auto",
"message": "Ustaw <kbd>margin: 0 auto</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-7",
"title": "Padding Shorthand Notation",
"description": "Like margin, padding shorthand allows setting all sides at once. A single value applies to all four sides equally.",
"task": "Set <kbd>padding</kbd> to <kbd>2rem</kbd> to add equal padding on all sides.",
"previewHTML": "<div class=\"padded\">This box needs equal padding on all sides</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .padded { background-color: papayawhip; border: 2px solid orange; }",
"title": "Border Radius",
"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": "Zrób okrągły obrazek awatara za pomocą <kbd>border-radius: 50%</kbd>.",
"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; 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": "",
"codePrefix": ".padded {\n ",
"codePrefix": ".avatar {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "padding: 2rem;",
"solution": "border-radius: 50%;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "padding", "expected": "2rem" },
"message": "Set <kbd>padding: 2rem</kbd>"
"value": { "property": "border-radius", "expected": "50%" },
"message": "Ustaw <kbd>border-radius: 50%</kbd>"
}
]
},
{
"id": "box-model-8",
"title": "Border on Specific Sides",
"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>.",
"task": "Set <kbd>border-bottom</kbd> to <kbd>4px solid dodgerblue</kbd>.",
"previewHTML": "<div class=\"line\">This element needs only a bottom border</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .line { padding: 1rem; background-color: aliceblue; }",
"title": "Complete Card",
"description": "Połączmy wszystko razem. Ta karta powiadomienia potrzebuje stylowania, żeby wyglądać profesjonalnie.",
"task": "Ostyluj powiadomienie: dodaj <kbd>padding: 1rem</kbd>, <kbd>border-left: 4px solid coral</kbd> i <kbd>border-radius: 4px</kbd>.",
"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; } .alert { background: seashell; } .alert strong { color: coral; } .alert p { margin: 4px 0 0; color: #666; }",
"sandboxCSS": "",
"codePrefix": ".line {\n ",
"codePrefix": ".alert {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "border-bottom: 4px solid dodgerblue;",
"solution": "padding: 1rem;\n border-left: 4px solid coral;\n border-radius: 4px;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "padding", "expected": "1rem" },
"message": "Ustaw <kbd>padding: 1rem</kbd>"
},
{
"type": "regex",
"value": "border-bottom:\\s*4px\\s+solid\\s+dodgerblue",
"message": "Set <kbd>border-bottom: 4px solid dodgerblue</kbd>",
"value": "border-left:\\s*4px\\s+solid\\s+coral",
"message": "Ustaw <kbd>border-left: 4px solid coral</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "border-radius", "expected": "4px" },
"message": "Ustaw <kbd>border-radius: 4px</kbd>"
}
]
}

View File

@@ -1,115 +1,100 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "units-variables",
"title": "CSS Units & Variables",
"description": "Understand the variety of CSS measurement units and how to define and use custom properties for maintainable styles.",
"title": "Jednostki CSS i zmienne",
"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",
"lessons": [
{
"id": "units-1",
"title": "Absolute vs. Relative Units",
"description": "Learn the difference between px, rem, em, %, and vw/vh for flexible, responsive layouts.",
"task": "Set the <kbd>width</kbd> of <kbd>.box</kbd> to <kbd>80%</kbd> and <kbd>max-width</kbd> to <kbd>37.5rem</kbd>.",
"previewHTML": "<div class=\"box\">Resize me!</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { background: #f5f5f5; padding: 1rem; }",
"title": "Relative Units",
"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": "Ten tekst artykułu jest zbyt szeroki na dużych ekranach. Dodaj <kbd>max-width: 40rem</kbd> dla optymalnej szerokości czytania.",
"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: 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": "",
"codePrefix": "/* Set flexible sizing */\n.box {",
"codePrefix": ".article {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: 80%;\n max-width: 37.5rem;",
"codeSuffix": "\n}",
"solution": "max-width: 40rem;",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "width", "message": "Use <kbd>width</kbd> property", "options": { "caseSensitive": false } },
{ "type": "property_value", "value": { "property": "width", "expected": "80%" }, "message": "Set width to <kbd>80%</kbd>" },
{ "type": "contains", "value": "max-width", "message": "Use <kbd>max-width</kbd> property", "options": { "caseSensitive": false } },
{
"type": "property_value",
"value": { "property": "max-width", "expected": "37.5rem" },
"message": "Set max-width to <kbd>37.5rem</kbd>"
"value": { "property": "max-width", "expected": "40rem" },
"message": "Ustaw <kbd>max-width: 40rem</kbd>"
}
]
},
{
"id": "units-2",
"title": "CSS Custom Properties",
"description": "Define and reuse variables (--custom properties) to centralize your theme values.",
"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>.",
"previewHTML": "<div class=\"themed\">Variable Box</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .themed { padding: 1rem; border: 0.125rem solid #ddd; }",
"title": "CSS Variables",
"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": "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=\"actions\"><button class=\"btn\">Subscribe</button><button class=\"btn\">Learn More</button></div>",
"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": "",
"codePrefix": "/* Define and use a CSS variable */\n:root {",
"codePrefix": ":root {\n ",
"initialCode": "",
"codeSuffix": "}\n.themed { }",
"solution": " --main-color: #6200ee;\n}\n.themed {\n border-color: var(--main-color);",
"codeSuffix": "\n}\n\n.btn {\n background: var(--brand);\n}",
"solution": "--brand: steelblue;",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "--main-color",
"message": "Define <kbd>--main-color</kbd> in :root",
"value": "--brand",
"message": "Zdefiniuj zmienną <kbd>--brand</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "var(--main-color)",
"message": "Use <kbd>var(--main-color)</kbd>",
"value": "steelblue",
"message": "Ustaw wartość na <kbd>steelblue</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "border", "expected": "var(--main-color)" },
"message": "Apply variable to border color",
"options": { "exact": false }
}
]
},
{
"id": "units-3",
"title": "Unit Calculations (calc)",
"description": "Use the <kbd>calc()</kbd> function to combine different units in one expression.",
"task": "Set the <kbd>width</kbd> of <kbd>.sized</kbd> to <kbd>calc(100% - 2rem)</kbd> and <kbd>min-height</kbd> to <kbd>calc(10vh + 1rem)</kbd>.",
"previewHTML": "<div class=\"sized\">Calc Demo</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sized { background: #e8f5e9; padding: 1rem; }",
"title": "calc() Function",
"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": "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=\"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: 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": "",
"codePrefix": "/* Use calc for dynamic sizing */\n.sized {",
"codePrefix": ".main {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: calc(100% - 2rem);\n min-height: calc(10vh + 1rem);",
"codeSuffix": "\n}",
"solution": "width: calc(100% - 200px);",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "calc", "message": "Use <kbd>calc()</kbd> function", "options": { "caseSensitive": false } },
{
"type": "regex",
"value": "width:\\s*calc\\(100% - 2rem\\)",
"message": "Width should be <kbd>calc(100% - 2rem)</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "min-height:\\s*calc\\(10vh \\+ 1rem\\)",
"message": "Min-height should be <kbd>calc(10vh + 1rem)</kbd>",
"value": "width:\\s*calc\\(\\s*100%\\s*-\\s*200px\\s*\\)",
"message": "Ustaw <kbd>width: calc(100% - 200px)</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "units-4",
"title": "Viewport & Responsive Units",
"description": "Control layouts relative to viewport size with vw, vh, and vmin/vmax units.",
"task": "Give <kbd>.view</kbd> a <kbd>width</kbd> of <kbd>50vw</kbd> and <kbd>height</kbd> of <kbd>20vh</kbd>.",
"previewHTML": "<div class=\"view\">Viewport Box</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .view { background: #ffe0b2; }",
"title": "Viewport 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": "Spraw, aby ta sekcja hero wypełniła wysokość viewport ustawiając <kbd>min-height: 100vh</kbd>.",
"previewHTML": "<section class=\"hero\"><h1>Welcome</h1><p>Scroll down to explore</p></section>",
"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": "",
"codePrefix": "/* Use viewport units */\n.view {",
"codePrefix": ".hero {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: 50vw;\n height: 20vh;",
"codeSuffix": "\n}",
"solution": "min-height: 100vh;",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "vw", "message": "Use <kbd>vw</kbd> unit", "options": { "caseSensitive": false } },
{ "type": "contains", "value": "vh", "message": "Use <kbd>vh</kbd> unit", "options": { "caseSensitive": false } },
{ "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Set width to <kbd>50vw</kbd>" },
{ "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Set height to <kbd>20vh</kbd>" }
{
"type": "property_value",
"value": { "property": "min-height", "expected": "100vh" },
"message": "Ustaw <kbd>min-height: 100vh</kbd>"
}
]
}
]

View File

@@ -1,15 +1,15 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "transitions-animations",
"title": "CSS Animations",
"description": "Bring interactivity to your UI by smoothly transitioning properties and creating keyframe-driven animations.",
"title": "Animacje CSS",
"description": "Dodaj interaktywność do interfejsu poprzez płynne przejścia właściwości i animacje oparte na keyframes.",
"difficulty": "intermediate",
"lessons": [
{
"id": "transitions-1",
"title": "Transitions",
"description": "Learn how to apply <kbd>transition</kbd> to properties for smooth changes on state changes.<br><br><pre>transition: property duration;\n/* e.g. transition: background-color 0.3s; */</pre>",
"task": "Add <kbd>transition: background-color 0.3s</kbd> to <kbd>.btn</kbd> so the color fades smoothly on hover.",
"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": "Dodaj <kbd>transition: background-color 0.3s</kbd>, aby kolor płynnie zmieniał się przy hover.",
"previewHTML": "<button class=\"btn\">Hover Me</button>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: black; color: white; padding: 0.5rem 1rem; border: none; cursor: pointer; } .btn:hover { background: white; color: black; }",
"sandboxCSS": "",
@@ -22,13 +22,13 @@
{
"type": "contains",
"value": "transition",
"message": "Use the <kbd>transition</kbd> property",
"message": "Użyj właściwości <kbd>transition</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"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 }
}
]
@@ -36,8 +36,8 @@
{
"id": "transitions-2",
"title": "Timing Funcs",
"description": "Explore easing functions like <kbd>ease</kbd>, <kbd>linear</kbd>, <kbd>ease-in</kbd>, <kbd>ease-out</kbd> to control animation pacing.",
"task": "Set <kbd>transition-timing-function</kbd> to <kbd>ease-in-out</kbd> on <kbd>.btn</kbd>.",
"description": "Poznaj funkcje easing jak <kbd>ease</kbd>, <kbd>linear</kbd>, <kbd>ease-in</kbd>, <kbd>ease-out</kbd> do kontrolowania tempa animacji.",
"task": "Ustaw <kbd>transition-timing-function</kbd> na <kbd>ease-in-out</kbd>.",
"previewHTML": "<button class=\"btn\">Timing</button>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: navy; color: gold; padding: 0.5rem 1rem; border: none; cursor: pointer; transition: all 0.5s; } .btn:hover { background: gold; color: navy; transform: scale(1.1); }",
"sandboxCSS": "",
@@ -50,21 +50,21 @@
{
"type": "contains",
"value": "transition-timing-function",
"message": "Use <kbd>transition-timing-function</kbd>",
"message": "Użyj <kbd>transition-timing-function</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "transition-timing-function", "expected": "ease-in-out" },
"message": "Set timing to <kbd>ease-in-out</kbd>"
"message": "Ustaw timing na <kbd>ease-in-out</kbd>"
}
]
},
{
"id": "transitions-3",
"title": "Keyframes",
"description": "Create named animations using <kbd>@keyframes</kbd> and apply them via the <kbd>animation</kbd> shorthand.<br><br><pre>@keyframes bounce {\n 50% { transform: translateY(-20px); }\n}\n.ball {\n animation: bounce 1s infinite;\n}</pre>",
"task": "Define a keyframe at <kbd>50%</kbd> with <kbd>transform: translateY(-20px)</kbd> and apply <kbd>animation: bounce 1s infinite</kbd> to <kbd>.ball</kbd>.",
"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": "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>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: crimson; border-radius: 50%; margin: 2rem auto; }",
"sandboxCSS": "",
@@ -77,25 +77,25 @@
{
"type": "contains",
"value": "@keyframes bounce",
"message": "Define <kbd>@keyframes bounce</kbd>",
"message": "Zdefiniuj <kbd>@keyframes bounce</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "50%.*transform: translateY\\(-20px\\)",
"message": "At <kbd>50%</kbd>, use <kbd>transform: translateY(-20px)</kbd>",
"message": "Przy <kbd>50%</kbd>, użyj <kbd>transform: translateY(-20px)</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"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 }
},
{
"type": "regex",
"value": "animation:.*bounce.*1s.*infinite",
"message": "Apply <kbd>animation: bounce 1s infinite</kbd>",
"message": "Zastosuj <kbd>animation: bounce 1s infinite</kbd>",
"options": { "caseSensitive": false }
}
]
@@ -103,8 +103,8 @@
{
"id": "transitions-4",
"title": "Animation Properties",
"description": "Fine-tune animations with <kbd>animation-delay</kbd>, <kbd>animation-iteration-count</kbd>, <kbd>animation-direction</kbd>, and <kbd>animation-fill-mode</kbd>.",
"task": "Apply the <kbd>pulse</kbd> animation to <kbd>.box</kbd> with <kbd>animation-name: pulse</kbd>, <kbd>animation-duration: 2s</kbd>, <kbd>animation-delay: 1s</kbd>, <kbd>animation-iteration-count: 2</kbd>, and <kbd>animation-fill-mode: forwards</kbd>.",
"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": "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>",
"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": "",
@@ -117,27 +117,27 @@
{
"type": "property_value",
"value": { "property": "animation-name", "expected": "pulse" },
"message": "Set <kbd>animation-name: pulse</kbd>"
"message": "Ustaw <kbd>animation-name: pulse</kbd>"
},
{
"type": "property_value",
"value": { "property": "animation-duration", "expected": "2s" },
"message": "Set <kbd>animation-duration: 2s</kbd>"
"message": "Ustaw <kbd>animation-duration: 2s</kbd>"
},
{
"type": "property_value",
"value": { "property": "animation-delay", "expected": "1s" },
"message": "Set <kbd>animation-delay: 1s</kbd>"
"message": "Ustaw <kbd>animation-delay: 1s</kbd>"
},
{
"type": "property_value",
"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",
"value": { "property": "animation-fill-mode", "expected": "forwards" },
"message": "Set <kbd>animation-fill-mode: forwards</kbd>"
"message": "Ustaw <kbd>animation-fill-mode: forwards</kbd>"
}
]
}

View File

@@ -2,14 +2,14 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "responsive-design",
"title": "CSS Responsive Design",
"description": "Make your layouts adapt to different screen sizes using media queries and fluid design techniques.",
"description": "Dostosuj swoje layouty do różnych rozmiarów ekranów używając media queries i technik płynnego designu.",
"difficulty": "intermediate",
"lessons": [
{
"id": "responsive-1",
"title": "Media Queries",
"description": "Understand the syntax and use cases for CSS media queries to apply styles conditionally based on viewport characteristics.",
"task": "Write a media query with <kbd>@media (max-width: 600px)</kbd> that changes <kbd>.panel</kbd> background to <kbd>lightcoral</kbd>.",
"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": "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>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .panel { padding: 1rem; background: lightblue; }",
"sandboxCSS": "",
@@ -22,19 +22,19 @@
{
"type": "regex",
"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 }
},
{
"type": "contains",
"value": ".panel",
"message": "Target <kbd>.panel</kbd> inside the media query",
"message": "Zaadresuj <kbd>.panel</kbd> wewnątrz media query",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "background", "expected": "lightcoral" },
"message": "Set <kbd>background: lightcoral</kbd>",
"message": "Ustaw <kbd>background: lightcoral</kbd>",
"options": { "exact": false }
}
]
@@ -42,8 +42,8 @@
{
"id": "responsive-2",
"title": "Fluid Type",
"description": "Use relative units like <kbd>vw</kbd> to make font sizes scale with the viewport width.",
"task": "Set <kbd>font-size: 5vw</kbd> on <kbd>.text</kbd> so it scales as the viewport changes.",
"description": "Używaj jednostek względnych jak <kbd>vw</kbd>, aby rozmiary czcionek skalowały się z szerokością viewport.",
"task": "Ustaw <kbd>font-size: 5vw</kbd>, aby skalowała się ze zmianą viewport.",
"previewHTML": "<p class=\"text\">Fluid Typography</p>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; }",
"sandboxCSS": "",
@@ -53,46 +53,46 @@
"solution": " font-size: 5vw;",
"previewContainer": "preview-area",
"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",
"title": "Flex Grids",
"description": "Combine CSS Grid with <kbd>auto-fit</kbd> or <kbd>auto-fill</kbd> for responsive column layouts.",
"task": "Add <kbd>display: grid</kbd>, <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd>, and <kbd>gap: 1rem</kbd> to <kbd>.cards</kbd>.",
"previewHTML": "<div class=\"cards\"><div>1</div><div>2</div><div>3</div><div>4</div></div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .cards > div { background: #d1c4e9; padding: 1rem; }",
"title": "Responsive Grid",
"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": "Dodaj <kbd>display: grid</kbd>, <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd> i <kbd>gap: 1rem</kbd>.",
"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: 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": "",
"codePrefix": "/* Create a responsive grid */\n.cards {",
"codePrefix": ".features {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
"codeSuffix": "\n}",
"solution": "display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "display", "expected": "grid" },
"message": "Set <kbd>display: grid</kbd>"
"message": "Ustaw <kbd>display: grid</kbd>"
},
{
"type": "regex",
"value": "repeat\\(auto-fit,\\s*minmax\\(200px,\\s*1fr\\)\\)",
"message": "Use <kbd>repeat(auto-fit, minmax(200px, 1fr))</kbd>",
"message": "Użyj <kbd>repeat(auto-fit, minmax(200px, 1fr))</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "gap", "expected": "1rem" },
"message": "Set <kbd>gap: 1rem</kbd>"
"message": "Ustaw <kbd>gap: 1rem</kbd>"
}
]
},
{
"id": "responsive-4",
"title": "Mobile-First",
"description": "Adopt a mobile-first approach by writing base styles for small screens and enhancing for larger viewports.",
"task": "Write a media query with <kbd>@media (min-width: 768px)</kbd> that sets <kbd>.sidebar</kbd> width to <kbd>250px</kbd>.",
"description": "Zastosuj podejście mobile-first: pisz bazowe style dla małych ekranów i rozszerzaj dla większych viewport.",
"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>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sidebar { background: #c8e6c9; padding: 1rem; }",
"sandboxCSS": "",
@@ -105,19 +105,19 @@
{
"type": "regex",
"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 }
},
{
"type": "contains",
"value": ".sidebar",
"message": "Target <kbd>.sidebar</kbd> inside media query",
"message": "Zaadresuj <kbd>.sidebar</kbd> w media query",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "width", "expected": "250px" },
"message": "Set <kbd>width: 250px</kbd>",
"message": "Ustaw <kbd>width: 250px</kbd>",
"options": { "exact": false }
}
]

View File

@@ -2,65 +2,65 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-elements",
"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",
"difficulty": "beginner",
"lessons": [
{
"id": "block-vs-inline-intro",
"title": "Block vs Inline Elements",
"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>&lt;div&gt;</kbd>, <kbd>&lt;p&gt;</kbd>, <kbd>&lt;h1&gt;</kbd>, <kbd>&lt;section&gt;</kbd><br><br><strong>Inline elements</strong> flow within text and only take needed width. Examples: <kbd>&lt;span&gt;</kbd>, <kbd>&lt;a&gt;</kbd>, <kbd>&lt;strong&gt;</kbd>, <kbd>&lt;em&gt;</kbd>",
"task": "Wrap the word <kbd>important</kbd> with <kbd>&lt;strong&gt;</kbd> tags to make it bold. Notice how the paragraph (block) takes full width while strong (inline) flows with text.",
"title": "Elementy blokowe vs liniowe",
"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>&lt;div&gt;</kbd>, <kbd>&lt;p&gt;</kbd>, <kbd>&lt;h1&gt;</kbd>, <kbd>&lt;section&gt;</kbd><br><br><strong>Elementy liniowe</strong> płyną wewnątrz tekstu i zajmują tylko potrzebną szerokość. Przykłady: <kbd>&lt;span&gt;</kbd>, <kbd>&lt;a&gt;</kbd>, <kbd>&lt;strong&gt;</kbd>, <kbd>&lt;em&gt;</kbd>",
"task": "Otocz słowo <kbd>ważne</kbd> tagami <kbd>&lt;strong&gt;</kbd>, aby je pogrubić. Zauważ, jak akapit (blok) zajmuje pełną szerokość, podczas gdy strong (liniowy) płynie z tekstem.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; } p { background: #e3f2fd; padding: 10px; } strong { background: #ffecb3; }",
"sandboxCSS": "",
"initialCode": "<p>This is a paragraph with an important word.</p>",
"solution": "<p>This is a paragraph with an <strong>important</strong> word.</p>",
"initialCode": "<p>To jest akapit z ważne słowem.</p>",
"solution": "<p>To jest akapit z <strong>ważne</strong> słowem.</p>",
"previewContainer": "preview-area",
"validations": [
{
"type": "element_exists",
"value": "p",
"message": "Add a <kbd>&lt;p&gt;</kbd> paragraph element"
"message": "Dodaj element akapitu <kbd>&lt;p&gt;</kbd>"
},
{
"type": "parent_child",
"value": { "parent": "p", "child": "strong" },
"message": "Wrap the word <kbd>important</kbd> with <kbd>&lt;strong&gt;</kbd> tags"
"message": "Otocz słowo <kbd>ważne</kbd> tagami <kbd>&lt;strong&gt;</kbd>"
}
]
},
{
"id": "semantic-containers",
"title": "Semantic Tags",
"description": "Modern HTML uses semantic containers that describe their content:<br><br><kbd>&lt;header&gt;</kbd> - Page or section header<br><kbd>&lt;nav&gt;</kbd> - Navigation links<br><kbd>&lt;main&gt;</kbd> - Main content area<br><kbd>&lt;section&gt;</kbd> - Thematic grouping<br><kbd>&lt;article&gt;</kbd> - Self-contained content<br><kbd>&lt;footer&gt;</kbd> - Page or section footer",
"task": "Create a basic page structure:<br>1. Add a <kbd>&lt;header&gt;</kbd> with an <kbd>&lt;h1&gt;</kbd> containing the text <code>My Website</code><br>2. Add a <kbd>&lt;main&gt;</kbd> element with a paragraph saying <code>Welcome to my site!</code><br>3. Add a <kbd>&lt;footer&gt;</kbd> with a paragraph saying <code>Copyright 2026</code>",
"title": "Tagi semantyczne",
"description": "Nowoczesny HTML używa semantycznych kontenerów, które opisują swoją zawartość:<br><br><kbd>&lt;header&gt;</kbd> - Nagłówek strony lub sekcji<br><kbd>&lt;nav&gt;</kbd> - Linki nawigacyjne<br><kbd>&lt;main&gt;</kbd> - Główna treść<br><kbd>&lt;section&gt;</kbd> - Grupa tematyczna<br><kbd>&lt;article&gt;</kbd> - Samodzielna treść<br><kbd>&lt;footer&gt;</kbd> - Stopka strony lub sekcji",
"task": "Stwórz podstawową strukturę strony:<br>1. Dodaj <kbd>&lt;header&gt;</kbd> z <kbd>&lt;h1&gt;</kbd> zawierającym tekst <code>Moja Strona</code><br>2. Dodaj element <kbd>&lt;main&gt;</kbd> z akapitem mówiącym <code>Witaj na mojej stronie!</code><br>3. Dodaj <kbd>&lt;footer&gt;</kbd> z akapitem mówiącym <code>Copyright 2026</code>",
"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; }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "header",
"message": "Add a <kbd>&lt;header&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;header&gt;</kbd>"
},
{
"type": "element_exists",
"value": "main",
"message": "Add a <kbd>&lt;main&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;main&gt;</kbd>"
},
{
"type": "element_exists",
"value": "footer",
"message": "Add a <kbd>&lt;footer&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;footer&gt;</kbd>"
},
{
"type": "parent_child",
"value": { "parent": "header", "child": "h1" },
"message": "Add an <kbd>&lt;h1&gt;</kbd> heading inside your header"
"message": "Dodaj nagłówek <kbd>&lt;h1&gt;</kbd> wewnątrz header"
}
]
}

View File

@@ -1,100 +1,100 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-forms-basic",
"title": "HTML Forms",
"description": "Learn to create forms with various input types",
"title": "Formularze HTML",
"description": "Naucz się tworzyć formularze z różnymi typami pól",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "form-structure",
"title": "Form Structure",
"description": "Every form needs a <kbd>&lt;form&gt;</kbd> wrapper. Inside, use <kbd>&lt;label&gt;</kbd> to describe inputs and <kbd>&lt;input&gt;</kbd> for user data entry.<br><br>The <kbd>for</kbd> attribute on labels should match the <kbd>id</kbd> on inputs for accessibility.",
"task": "Create a form with:<br>1. A <kbd>&lt;label&gt;</kbd> with the text <code>Name:</code> and <kbd>for=\"name\"</kbd> attribute<br>2. A text <kbd>&lt;input&gt;</kbd> with <kbd>id=\"name\"</kbd> and <kbd>name=\"name\"</kbd> attributes",
"title": "Struktura formularza",
"description": "Każdy formularz potrzebuje wrappera <kbd>&lt;form&gt;</kbd>. Wewnątrz używaj <kbd>&lt;label&gt;</kbd> do opisywania pól i <kbd>&lt;input&gt;</kbd> do wprowadzania danych.<br><br>Atrybut <kbd>for</kbd> w labelach powinien odpowiadać <kbd>id</kbd> pól dla dostępności.",
"task": "Stwórz formularz z:<br>1. <kbd>&lt;label&gt;</kbd> z tekstem <code>Imię:</code> i atrybutem <kbd>for=\"name\"</kbd><br>2. Tekstowym <kbd>&lt;input&gt;</kbd> z atrybutami <kbd>id=\"name\"</kbd> i <kbd>name=\"name\"</kbd>",
"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; }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "form",
"message": "Wrap everything in a <kbd>&lt;form&gt;</kbd> element"
"message": "Otocz wszystko elementem <kbd>&lt;form&gt;</kbd>"
},
{
"type": "element_exists",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for your input"
"message": "Dodaj <kbd>&lt;label&gt;</kbd> dla swojego pola"
},
{
"type": "element_exists",
"value": "input",
"message": "Add an <kbd>&lt;input&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;input&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"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",
"title": "Input Types",
"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",
"task": "Create a login form with two fields:<br>1. An email field: <kbd>&lt;label for=\"email\"&gt;Email:&lt;/label&gt;</kbd> and <kbd>&lt;input type=\"email\" id=\"email\"&gt;</kbd><br>2. A password field: <kbd>&lt;label for=\"password\"&gt;Password:&lt;/label&gt;</kbd> and <kbd>&lt;input type=\"password\" id=\"password\"&gt;</kbd>",
"title": "Typy pól",
"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": "Stwórz formularz logowania z dwoma polami:<br>1. Pole email: <kbd>&lt;label for=\"email\"&gt;Email:&lt;/label&gt;</kbd> i <kbd>&lt;input type=\"email\" id=\"email\"&gt;</kbd><br>2. Pole hasła: <kbd>&lt;label for=\"password\"&gt;Hasło:&lt;/label&gt;</kbd> i <kbd>&lt;input type=\"password\" id=\"password\"&gt;</kbd>",
"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; }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "input[type='email']",
"message": "Add an input with type=\"email\""
"message": "Dodaj pole z type=\"email\""
},
{
"type": "element_exists",
"value": "input[type='password']",
"message": "Add an input with type=\"password\""
"message": "Dodaj pole z type=\"password\""
},
{
"type": "element_count",
"value": { "selector": "label", "min": 2 },
"message": "Add labels for both inputs"
"message": "Dodaj labele dla obu pól"
}
]
},
{
"id": "submit-button",
"title": "Submit Button",
"description": "Forms need a way to submit data. Use:<br><br><kbd>&lt;button type=\"submit\"&gt;</kbd> - Preferred, flexible content<br><kbd>&lt;input type=\"submit\"&gt;</kbd> - Simple text-only button<br><br>The button text should be action-oriented (e.g., <code>Sign In</code>, 'Register', 'Send').",
"task": "Add a submit button to the form with the text <code>Sign In</code>.",
"title": "Przycisk wysyłania",
"description": "Formularze potrzebują sposobu na wysłanie danych. Użyj:<br><br><kbd>&lt;button type=\"submit\"&gt;</kbd> - Preferowany, elastyczna zawartość<br><kbd>&lt;input type=\"submit\"&gt;</kbd> - Prosty przycisk tekstowy<br><br>Tekst przycisku powinien być zorientowany na akcję (np. <code>Zaloguj</code>, 'Zarejestruj', 'Wyślij').",
"task": "Dodaj przycisk wysyłania do formularza z tekstem <code>Zaloguj</code>.",
"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; }",
"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>",
"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>",
"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\">Hasło:</label>\n <input type=\"password\" id=\"password\">\n \n <button type=\"submit\">Zaloguj</button>\n</form>",
"previewContainer": "preview-area",
"validations": [
{
"type": "element_exists",
"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",
"value": { "selector": "button", "text": "Sign In" },
"message": "The button should say <kbd>Sign In</kbd>"
"value": { "selector": "button", "text": "Zaloguj" },
"message": "Przycisk powinien wyświetlać <kbd>Zaloguj</kbd>"
}
]
}

View File

@@ -1,110 +1,32 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-forms-validation",
"title": "HTML Validation",
"description": "Learn HTML5 built-in form validation attributes",
"title": "Walidacja formularzy",
"description": "Użyj wbudowanej walidacji HTML5 dla lepszego doświadczenia użytkownika",
"mode": "html",
"difficulty": "intermediate",
"difficulty": "beginner",
"lessons": [
{
"id": "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>&lt;input type=\"text\" required&gt;</kbd><br><br>The browser shows a validation message automatically.",
"task": "Make both the name and email fields required by adding the <kbd>required</kbd> attribute.",
"title": "Pola wymagane",
"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>&lt;input type=\"text\" required&gt;</kbd>",
"task": "Uczyń oba pola (imię i email) wymaganymi, dodając atrybut <kbd>required</kbd> do każdego pola.",
"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": "",
"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>",
"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\">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",
"validations": [
{
"type": "attribute_value",
"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",
"value": { "selector": "input[name='email']", "attr": "required", "value": true },
"message": "Add the <kbd>required</kbd> attribute 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>"
"message": "Dodaj <kbd>required</kbd> do pola email"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-details-summary",
"title": "HTML Details & Summary",
"description": "Create expandable content sections without JavaScript",
"description": "Twórz rozwijane sekcje bez JavaScript",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "details-summary-basic",
"title": "First Widget",
"description": "The <kbd>&lt;details&gt;</kbd> element creates a collapsible section. The <kbd>&lt;summary&gt;</kbd> provides the clickable label.<br><br>Click the summary to toggle the hidden content - no JavaScript needed!",
"task": "Create a <kbd>&lt;details&gt;</kbd> element with:<br>1. A <kbd>&lt;summary&gt;</kbd> saying <code>Click to reveal</code><br>2. A <kbd>&lt;p&gt;</kbd> with the text <code>This content was hidden!</code>",
"title": "Pierwszy widget",
"description": "Element <kbd>&lt;details&gt;</kbd> tworzy zwijany sekcję. Element <kbd>&lt;summary&gt;</kbd> zapewnia klikalną etykietę.<br><br>Kliknij podsumowanie, aby przełączyć ukrytą treść - bez JavaScript!",
"task": "Utwórz element <kbd>&lt;details&gt;</kbd> z:<br>1. Elementem <kbd>&lt;summary&gt;</kbd> z tekstem <code>Click to reveal</code><br>2. Elementem <kbd>&lt;p&gt;</kbd> z tekstem <code>This content was hidden!</code>",
"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; }",
"sandboxCSS": "",
@@ -21,30 +21,30 @@
{
"type": "element_exists",
"value": "details",
"message": "Add a <kbd>&lt;details&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;details&gt;</kbd>"
},
{
"type": "element_exists",
"value": "summary",
"message": "Add a <kbd>&lt;summary&gt;</kbd> inside the details"
"message": "Dodaj <kbd>&lt;summary&gt;</kbd> wewnątrz details"
},
{
"type": "parent_child",
"value": { "parent": "details", "child": "summary" },
"message": "The <kbd>&lt;summary&gt;</kbd> must be inside <kbd>&lt;details&gt;</kbd>"
"message": "Element <kbd>&lt;summary&gt;</kbd> musi być wewnątrz <kbd>&lt;details&gt;</kbd>"
},
{
"type": "parent_child",
"value": { "parent": "details", "child": "p" },
"message": "Add a <kbd>&lt;p&gt;</kbd> inside <kbd>&lt;details&gt;</kbd> for the hidden content"
"message": "Dodaj <kbd>&lt;p&gt;</kbd> wewnątrz <kbd>&lt;details&gt;</kbd> dla ukrytej treści"
}
]
},
{
"id": "details-open-attribute",
"title": "Pre-expanded Details",
"description": "By default, <kbd>&lt;details&gt;</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.",
"task": "Add the <kbd>open</kbd> attribute to the <kbd>&lt;details&gt;</kbd> element to show the content by default.",
"title": "Domyślnie rozwinięte",
"description": "Domyślnie <kbd>&lt;details&gt;</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": "Dodaj atrybut <kbd>open</kbd> do elementu <kbd>&lt;details&gt;</kbd>, aby domyślnie pokazać treść.",
"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; }",
"sandboxCSS": "",
@@ -55,15 +55,15 @@
{
"type": "attribute_value",
"value": { "selector": "details", "attr": "open", "value": true },
"message": "Add the <kbd>open</kbd> attribute to <kbd>&lt;details&gt;</kbd>"
"message": "Dodaj atrybut <kbd>open</kbd> do <kbd>&lt;details&gt;</kbd>"
}
]
},
{
"id": "faq-accordion",
"title": "FAQ Accordion",
"description": "Multiple <kbd>&lt;details&gt;</kbd> elements create an accordion-style FAQ. Each question can be expanded independently.<br><br><b>Pro tip:</b> Type <kbd>details*3&gt;summary+p</kbd> and press Tab for Emmet expansion. <kbd>*3</kbd> creates 3 elements, <kbd>&gt;</kbd> nests inside, <kbd>+</kbd> adds siblings.",
"task": "Create an FAQ section with:<br>1. An <kbd>&lt;h1&gt;</kbd> saying <code>Frequently Asked Questions</code><br>2. Three <kbd>&lt;details&gt;</kbd> elements, each with a question in <kbd>&lt;summary&gt;</kbd> and an answer in <kbd>&lt;p&gt;</kbd>",
"title": "Akordeon FAQ",
"description": "Wiele elementów <kbd>&lt;details&gt;</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&gt;summary+p</kbd> i naciśnij Tab dla rozwinięcia Emmet. <kbd>*3</kbd> tworzy 3 elementy, <kbd>&gt;</kbd> zagnieżdża wewnątrz, <kbd>+</kbd> dodaje rodzeństwo.",
"task": "Utwórz sekcję FAQ z:<br>1. Nagłówkiem <kbd>&lt;h1&gt;</kbd> z tekstem <code>Frequently Asked Questions</code><br>2. Trzema elementami <kbd>&lt;details&gt;</kbd>, każdy z pytaniem w <kbd>&lt;summary&gt;</kbd> i odpowiedzią w <kbd>&lt;p&gt;</kbd>",
"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; }",
"sandboxCSS": "",
@@ -74,22 +74,22 @@
{
"type": "element_exists",
"value": "h1",
"message": "Add an <kbd>&lt;h1&gt;</kbd> heading for the FAQ title"
"message": "Dodaj nagłówek <kbd>&lt;h1&gt;</kbd> dla tytułu FAQ"
},
{
"type": "element_count",
"value": { "selector": "details", "min": 3 },
"message": "Create at least 3 <kbd>&lt;details&gt;</kbd> elements for the FAQ"
"message": "Utwórz co najmniej 3 elementy <kbd>&lt;details&gt;</kbd> dla FAQ"
},
{
"type": "element_count",
"value": { "selector": "summary", "min": 3 },
"message": "Each <kbd>&lt;details&gt;</kbd> needs a <kbd>&lt;summary&gt;</kbd> for the question"
"message": "Każdy <kbd>&lt;details&gt;</kbd> potrzebuje <kbd>&lt;summary&gt;</kbd> dla pytania"
},
{
"type": "element_count",
"value": { "selector": "details p", "min": 3 },
"message": "Each <kbd>&lt;details&gt;</kbd> needs a <kbd>&lt;p&gt;</kbd> for the answer"
"message": "Każdy <kbd>&lt;details&gt;</kbd> potrzebuje <kbd>&lt;p&gt;</kbd> dla odpowiedzi"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "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",
"difficulty": "beginner",
"lessons": [
{
"id": "progress-basic",
"title": "Progress Bars",
"description": "The <kbd>&lt;progress&gt;</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>&lt;progress&gt;...&lt;/progress&gt;</kbd> with fallback text inside for older browsers.",
"task": "Create a progress bar showing 70% completion:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Download:</code><br>2. Add a <kbd>&lt;progress&gt;</kbd> with <kbd>value=\"70\"</kbd> and <kbd>max=\"100\"</kbd>",
"title": "Paski postępu",
"description": "Element <kbd>&lt;progress&gt;</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>&lt;progress&gt;...&lt;/progress&gt;</kbd> z tekstem zastępczym w środku dla starszych przeglądarek.",
"task": "Utwórz pasek postępu pokazujący 70% ukończenia:<br>1. Dodaj <kbd>&lt;label&gt;</kbd> z tekstem <code>Download:</code><br>2. Dodaj <kbd>&lt;progress&gt;</kbd> z <kbd>value=\"70\"</kbd> i <kbd>max=\"100\"</kbd>",
"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; }",
"sandboxCSS": "",
@@ -21,30 +21,30 @@
{
"type": "element_exists",
"value": "progress",
"message": "Add a <kbd>&lt;progress&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;progress&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"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",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for the progress bar"
"message": "Dodaj <kbd>&lt;label&gt;</kbd> dla paska postępu"
}
]
},
{
"id": "progress-indeterminate",
"title": "Indeterminate Progress",
"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.",
"task": "Create a loading indicator:<br>1. Add a <kbd>&lt;p&gt;</kbd> saying <code>Loading...</code><br>2. Add a <kbd>&lt;progress&gt;</kbd> without a value attribute",
"title": "Nieokreślony postęp",
"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": "Utwórz wskaźnik ładowania:<br>1. Dodaj <kbd>&lt;p&gt;</kbd> z tekstem <code>Loading...</code><br>2. Dodaj <kbd>&lt;progress&gt;</kbd> bez atrybutu value",
"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; }",
"sandboxCSS": "",
@@ -55,20 +55,20 @@
{
"type": "element_exists",
"value": "progress",
"message": "Add a <kbd>&lt;progress&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;progress&gt;</kbd>"
},
{
"type": "element_exists",
"value": "p",
"message": "Add a <kbd>&lt;p&gt;</kbd> with loading text"
"message": "Dodaj <kbd>&lt;p&gt;</kbd> z tekstem ładowania"
}
]
},
{
"id": "meter-gauge",
"title": "Meter Gauges",
"description": "The <kbd>&lt;meter&gt;</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!",
"task": "Create a battery level meter:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Battery:</code><br>2. Add a <kbd>&lt;meter&gt;</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>",
"title": "Wskaźniki meter",
"description": "Element <kbd>&lt;meter&gt;</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": "Utwórz wskaźnik poziomu baterii:<br>1. Dodaj <kbd>&lt;label&gt;</kbd> z tekstem <code>Battery:</code><br>2. Dodaj <kbd>&lt;meter&gt;</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": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
"sandboxCSS": "",
@@ -79,22 +79,42 @@
{
"type": "element_exists",
"value": "meter",
"message": "Add a <kbd>&lt;meter&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;meter&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"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",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for the meter"
"message": "Dodaj <kbd>&lt;label&gt;</kbd> dla meter"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-datalist",
"title": "Datalist",
"description": "Provide suggestions for text inputs without JavaScript",
"description": "Dodaj sugestie do pól tekstowych bez JavaScript",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "datalist-basic",
"title": "Input with Suggestions",
"description": "The <kbd>&lt;datalist&gt;</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!",
"task": "Create a browser selector:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Browser:</code><br>2. Add an <kbd>&lt;input&gt;</kbd> with <kbd>list=\"browsers\"</kbd><br>3. Add a <kbd>&lt;datalist id=\"browsers\"&gt;</kbd> with options for Chrome, Firefox, and Safari",
"title": "Pole z sugestiami",
"description": "Element <kbd>&lt;datalist&gt;</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": "Utwórz selektor przeglądarki:<br>1. Dodaj <kbd>&lt;label&gt;</kbd> z tekstem <code>Przeglądarka:</code><br>2. Dodaj <kbd>&lt;input&gt;</kbd> z <kbd>list=\"browsers\"</kbd><br>3. Dodaj <kbd>&lt;datalist id=\"browsers\"&gt;</kbd> z opcjami Chrome, Firefox i Safari",
"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; }",
"sandboxCSS": "",
@@ -21,30 +21,30 @@
{
"type": "element_exists",
"value": "datalist",
"message": "Add a <kbd>&lt;datalist&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;datalist&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"value": { "selector": "option", "min": 3 },
"message": "Add at least 3 <kbd>&lt;option&gt;</kbd> elements inside <kbd>&lt;datalist&gt;</kbd>"
"message": "Dodaj co najmniej 3 elementy <kbd>&lt;option&gt;</kbd> wewnątrz <kbd>&lt;datalist&gt;</kbd>"
},
{
"type": "element_exists",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for the input"
"message": "Dodaj <kbd>&lt;label&gt;</kbd> dla pola"
}
]
},
{
"id": "datalist-countries",
"title": "Country Selector",
"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.",
"task": "Create a country input:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Country:</code><br>2. Add an <kbd>&lt;input&gt;</kbd> with <kbd>list=\"countries\"</kbd><br>3. Add a <kbd>&lt;datalist id=\"countries\"&gt;</kbd> with at least 4 country options",
"title": "Selektor krajów",
"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": "Utwórz pole kraju:<br>1. Dodaj <kbd>&lt;label&gt;</kbd> z tekstem <code>Kraj:</code><br>2. Dodaj <kbd>&lt;input&gt;</kbd> z <kbd>list=\"countries\"</kbd><br>3. Dodaj <kbd>&lt;datalist id=\"countries\"&gt;</kbd> z co najmniej 4 opcjami krajów",
"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); }",
"sandboxCSS": "",
@@ -55,22 +55,22 @@
{
"type": "element_exists",
"value": "datalist",
"message": "Add a <kbd>&lt;datalist&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;datalist&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"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",
"value": { "selector": "option", "min": 4 },
"message": "Add at least 4 country options"
"message": "Dodaj co najmniej 4 opcje krajów"
}
]
}

View File

@@ -1,16 +1,16 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-dialog",
"title": "Dialogs",
"description": "Create modal dialogs without JavaScript libraries",
"title": "Dialogi",
"description": "Twórz okna dialogowe bez bibliotek JavaScript",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "dialog-basic",
"title": "Open Dialog",
"description": "The <kbd>&lt;dialog&gt;</kbd> element creates a native modal. Add the <kbd>open</kbd> attribute to show it.<br><br>Use <kbd>&lt;form method=\"dialog\"&gt;</kbd> inside to close it when the form submits - no JavaScript needed!",
"task": "Create a dialog with:<br>1. The <kbd>open</kbd> attribute to show it<br>2. An <kbd>&lt;h2&gt;</kbd> saying <code>Welcome!</code><br>3. A <kbd>&lt;p&gt;</kbd> with a greeting message<br>4. A <kbd>&lt;form method=\"dialog\"&gt;</kbd> with a close button",
"title": "Otwórz dialog",
"description": "Element <kbd>&lt;dialog&gt;</kbd> tworzy natywne okno modalne. Dodaj atrybut <kbd>open</kbd>, aby je wyświetlić.<br><br>Użyj <kbd>&lt;form method=\"dialog\"&gt;</kbd> wewnątrz, aby zamknąć je przy wysłaniu formularza - bez JavaScript!",
"task": "Utwórz dialog z:<br>1. Atrybutem <kbd>open</kbd> aby go wyświetlić<br>2. Elementem <kbd>&lt;h2&gt;</kbd> z tekstem <code>Witaj!</code><br>3. Elementem <kbd>&lt;p&gt;</kbd> z wiadomością powitalną<br>4. Elementem <kbd>&lt;form method=\"dialog\"&gt;</kbd> z przyciskiem zamknięcia",
"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; }",
"sandboxCSS": "",
@@ -21,35 +21,35 @@
{
"type": "element_exists",
"value": "dialog",
"message": "Add a <kbd>&lt;dialog&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;dialog&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"value": "dialog h2",
"message": "Add an <kbd>&lt;h2&gt;</kbd> heading inside the dialog"
"message": "Dodaj nagłówek <kbd>&lt;h2&gt;</kbd> wewnątrz dialogu"
},
{
"type": "element_exists",
"value": "form[method='dialog']",
"message": "Add a <kbd>&lt;form method=\"dialog\"&gt;</kbd> for closing"
"message": "Dodaj <kbd>&lt;form method=\"dialog\"&gt;</kbd> do zamknięcia"
},
{
"type": "element_exists",
"value": "dialog button",
"message": "Add a close button inside the form"
"message": "Dodaj przycisk zamknięcia wewnątrz formularza"
}
]
},
{
"id": "dialog-form",
"title": "Dialog + Form",
"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.",
"task": "Create a confirmation dialog:<br>1. Add <kbd>open</kbd> to show it<br>2. An <kbd>&lt;h2&gt;</kbd> saying <code>Confirm Delete</code><br>3. A <kbd>&lt;p&gt;</kbd> asking <code>Are you sure?</code><br>4. A <kbd>&lt;form method=\"dialog\"&gt;</kbd> with Cancel and Delete buttons",
"title": "Dialog z formularzem",
"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": "Utwórz dialog potwierdzenia:<br>1. Dodaj <kbd>open</kbd> aby go wyświetlić<br>2. Element <kbd>&lt;h2&gt;</kbd> z tekstem <code>Potwierdź usunięcie</code><br>3. Element <kbd>&lt;p&gt;</kbd> z pytaniem <code>Czy jesteś pewien?</code><br>4. Element <kbd>&lt;form method=\"dialog\"&gt;</kbd> z przyciskami Anuluj i Usuń",
"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; }",
"sandboxCSS": "",
@@ -60,22 +60,22 @@
{
"type": "element_exists",
"value": "dialog[open]",
"message": "Add a <kbd>&lt;dialog&gt;</kbd> with the open attribute"
"message": "Dodaj <kbd>&lt;dialog&gt;</kbd> z atrybutem open"
},
{
"type": "element_exists",
"value": "dialog h2",
"message": "Add a heading to the dialog"
"message": "Dodaj nagłówek do dialogu"
},
{
"type": "element_exists",
"value": "form[method='dialog']",
"message": "Add a <kbd>&lt;form method=\"dialog\"&gt;</kbd> for the buttons"
"message": "Dodaj <kbd>&lt;form method=\"dialog\"&gt;</kbd> dla przycisków"
},
{
"type": "element_count",
"value": { "selector": "dialog button", "min": 2 },
"message": "Add at least 2 buttons (Cancel and Confirm)"
"message": "Dodaj co najmniej 2 przyciski (Anuluj i Potwierdź)"
}
]
}

View File

@@ -1,16 +1,16 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-forms-fieldset",
"title": "Fieldsets",
"description": "Group form controls with fieldset and legend elements",
"title": "Fieldset",
"description": "Grupuj elementy formularza za pomocą fieldset i legend",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "fieldset-basic",
"title": "Grouping with Fieldset",
"description": "The <kbd>&lt;fieldset&gt;</kbd> element groups related form controls together. Add a <kbd>&lt;legend&gt;</kbd> as the first child to give the group a title.<br><br>This helps with accessibility and visual organization of complex forms.",
"task": "Create a form with a fieldset:<br>1. A <kbd>&lt;form&gt;</kbd> element<br>2. A <kbd>&lt;fieldset&gt;</kbd> inside<br>3. A <kbd>&lt;legend&gt;</kbd> saying <code>Personal Info</code><br>4. Two labeled inputs for name and email",
"title": "Grupowanie z Fieldset",
"description": "Element <kbd>&lt;fieldset&gt;</kbd> grupuje powiązane elementy formularza. Dodaj <kbd>&lt;legend&gt;</kbd> jako pierwsze dziecko, aby nadać grupie tytuł.<br><br>To pomaga w dostępności i wizualnej organizacji złożonych formularzy.",
"task": "Utwórz formularz z fieldset:<br>1. Element <kbd>&lt;form&gt;</kbd><br>2. Element <kbd>&lt;fieldset&gt;</kbd> wewnątrz<br>3. Element <kbd>&lt;legend&gt;</kbd> z tekstem <code>Dane osobowe</code><br>4. Dwa opisane pola dla imienia i e-maila",
"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; }",
"sandboxCSS": "",
@@ -21,35 +21,35 @@
{
"type": "element_exists",
"value": "form",
"message": "Add a <kbd>&lt;form&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;form&gt;</kbd>"
},
{
"type": "element_exists",
"value": "fieldset",
"message": "Add a <kbd>&lt;fieldset&gt;</kbd> inside the form"
"message": "Dodaj <kbd>&lt;fieldset&gt;</kbd> wewnątrz formularza"
},
{
"type": "element_exists",
"value": "legend",
"message": "Add a <kbd>&lt;legend&gt;</kbd> to title your fieldset"
"message": "Dodaj <kbd>&lt;legend&gt;</kbd> jako tytuł fieldset"
},
{
"type": "element_count",
"value": { "selector": "label", "min": 2 },
"message": "Add at least 2 labels"
"message": "Dodaj co najmniej 2 etykiety"
},
{
"type": "element_count",
"value": { "selector": "input", "min": 2 },
"message": "Add at least 2 input fields"
"message": "Dodaj co najmniej 2 pola wprowadzania"
}
]
},
{
"id": "fieldset-textarea",
"title": "Adding Textarea",
"description": "The <kbd>&lt;textarea&gt;</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.",
"task": "Create a contact form:<br>1. A <kbd>&lt;fieldset&gt;</kbd> with <kbd>&lt;legend&gt;</kbd> <code>Contact Us</code><br>2. A labeled <kbd>&lt;input&gt;</kbd> for email<br>3. A labeled <kbd>&lt;textarea&gt;</kbd> for the message<br>4. A submit <kbd>&lt;button&gt;</kbd>",
"title": "Dodawanie Textarea",
"description": "Element <kbd>&lt;textarea&gt;</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": "Utwórz formularz kontaktowy:<br>1. Element <kbd>&lt;fieldset&gt;</kbd> z <kbd>&lt;legend&gt;</kbd> <code>Kontakt</code><br>2. Opisane <kbd>&lt;input&gt;</kbd> dla e-maila<br>3. Opisane <kbd>&lt;textarea&gt;</kbd> dla wiadomości<br>4. Przycisk <kbd>&lt;button&gt;</kbd> wysyłania",
"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; }",
"sandboxCSS": "",
@@ -60,35 +60,35 @@
{
"type": "element_exists",
"value": "fieldset",
"message": "Add a <kbd>&lt;fieldset&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;fieldset&gt;</kbd>"
},
{
"type": "element_exists",
"value": "legend",
"message": "Add a <kbd>&lt;legend&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;legend&gt;</kbd>"
},
{
"type": "element_exists",
"value": "textarea",
"message": "Add a <kbd>&lt;textarea&gt;</kbd> for the message"
"message": "Dodaj <kbd>&lt;textarea&gt;</kbd> dla wiadomości"
},
{
"type": "element_exists",
"value": "button",
"message": "Add a submit button"
"message": "Dodaj przycisk wysyłania"
},
{
"type": "element_exists",
"value": "input",
"message": "Add an input field for email"
"message": "Dodaj pole dla e-maila"
}
]
},
{
"id": "fieldset-multiple",
"title": "Multiple Fieldsets",
"description": "Complex forms can use multiple <kbd>&lt;fieldset&gt;</kbd> elements to organize different sections.<br><br>This improves usability for long forms like registration or checkout pages.",
"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",
"title": "Wiele Fieldsetów",
"description": "Złożone formularze mogą używać wielu elementów <kbd>&lt;fieldset&gt;</kbd> do organizacji różnych sekcji.<br><br>To poprawia użyteczność długich formularzy jak rejestracja czy koszyk.",
"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": "",
"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": "",
@@ -99,27 +99,27 @@
{
"type": "element_count",
"value": { "selector": "fieldset", "min": 2 },
"message": "Create at least 2 fieldsets"
"message": "Utwórz co najmniej 2 fieldsety"
},
{
"type": "element_count",
"value": { "selector": "legend", "min": 2 },
"message": "Add a legend to each fieldset"
"message": "Dodaj legend do każdego fieldseta"
},
{
"type": "element_exists",
"value": "textarea",
"message": "Add a textarea for the bio"
"message": "Dodaj textarea dla bio"
},
{
"type": "element_exists",
"value": "button",
"message": "Add a submit button"
"message": "Dodaj przycisk wysyłania"
},
{
"type": "element_count",
"value": { "selector": "input", "min": 2 },
"message": "Add at least 2 input fields"
"message": "Dodaj co najmniej 2 pola wprowadzania"
}
]
}

View File

@@ -1,125 +1,42 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-tables",
"title": "HTML Tables",
"description": "Create structured data tables with headers and captions",
"title": "Tabele HTML",
"description": "Twórz strukturalne tabele danych z semantycznym znacznikiem",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "table-basic",
"title": "Basic Table Structure",
"description": "Tables use <kbd>&lt;table&gt;</kbd> with <kbd>&lt;tr&gt;</kbd> for rows. Inside rows, use <kbd>&lt;th&gt;</kbd> for headers and <kbd>&lt;td&gt;</kbd> for data cells.<br><br>The <kbd>&lt;caption&gt;</kbd> element provides an accessible title for the table.",
"task": "Create a simple table with:<br>1. A <kbd>&lt;caption&gt;</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",
"title": "Tabele danych",
"description": "Tabele wyświetlają strukturalne dane w wierszach i kolumnach. Użyj <kbd>&lt;table&gt;</kbd> jako kontenera, <kbd>&lt;tr&gt;</kbd> dla wierszy, <kbd>&lt;th&gt;</kbd> dla komórek nagłówka i <kbd>&lt;td&gt;</kbd> dla komórek danych.<br><br>Dodaj <kbd>&lt;caption&gt;</kbd> dla dostępnego tytułu opisującego zawartość tabeli.",
"task": "Utwórz tabelę cenową:<br>1. Element <kbd>&lt;caption&gt;</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": "",
"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": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "table",
"message": "Add a <kbd>&lt;table&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;table&gt;</kbd>"
},
{
"type": "element_exists",
"value": "caption",
"message": "Add a <kbd>&lt;caption&gt;</kbd> for the table title"
"message": "Dodaj <kbd>&lt;caption&gt;</kbd> dla tytułu tabeli"
},
{
"type": "element_count",
"value": { "selector": "th", "min": 2 },
"message": "Add at least 2 header cells (th)"
"message": "Dodaj komórki nagłówka (<kbd>&lt;th&gt;</kbd>) dla Plan i Price"
},
{
"type": "element_count",
"value": { "selector": "tr", "min": 3 },
"message": "Add at least 3 rows (1 header + 2 data rows)"
}
]
},
{
"id": "table-thead-tbody",
"title": "Table Head & Body",
"description": "Use <kbd>&lt;thead&gt;</kbd> to group header rows and <kbd>&lt;tbody&gt;</kbd> to group data rows. This helps browsers and assistive technology understand the table structure.<br><br>You can also use <kbd>&lt;tfoot&gt;</kbd> for footer rows like totals.",
"task": "Create a structured table:<br>1. A <kbd>&lt;caption&gt;</kbd> with <code>Monthly Sales</code><br>2. A <kbd>&lt;thead&gt;</kbd> with Month and Revenue headers<br>3. A <kbd>&lt;tbody&gt;</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>&lt;table&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "caption",
"message": "Add a <kbd>&lt;caption&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "thead",
"message": "Add a <kbd>&lt;thead&gt;</kbd> for the header section"
},
{
"type": "element_exists",
"value": "tbody",
"message": "Add a <kbd>&lt;tbody&gt;</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>&lt;tfoot&gt;</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>&lt;caption&gt;</kbd> with <code>Order Summary</code><br>2. A <kbd>&lt;thead&gt;</kbd> with Item and Price headers<br>3. A <kbd>&lt;tbody&gt;</kbd> with 2 items<br>4. A <kbd>&lt;tfoot&gt;</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>&lt;table&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "caption",
"message": "Add a <kbd>&lt;caption&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "thead",
"message": "Add a <kbd>&lt;thead&gt;</kbd> section"
},
{
"type": "element_exists",
"value": "tbody",
"message": "Add a <kbd>&lt;tbody&gt;</kbd> section"
},
{
"type": "element_exists",
"value": "tfoot",
"message": "Add a <kbd>&lt;tfoot&gt;</kbd> section for the total"
},
{
"type": "element_count",
"value": { "selector": "tbody tr", "min": 2 },
"message": "Add at least 2 item rows in tbody"
"message": "Dodaj 3 wiersze (1 nagłówek + 2 wiersze danych)"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "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",
"difficulty": "beginner",
"lessons": [
{
"id": "marquee-basic",
"title": "Scrolling Text",
"description": "The <kbd>&lt;marquee&gt;</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!",
"task": "Create a simple marquee:<br>1. Add a <kbd>&lt;marquee&gt;</kbd> element<br>2. Put some text inside like <code>Welcome to my website!</code>",
"title": "Przewijający się tekst",
"description": "Element <kbd>&lt;marquee&gt;</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": "Utwórz prosty marquee:<br>1. Dodaj element <kbd>&lt;marquee&gt;</kbd><br>2. Umieść w nim tekst jak <code>Witaj na mojej stronie!</code>",
"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; }",
"sandboxCSS": "",
@@ -21,15 +21,15 @@
{
"type": "element_exists",
"value": "marquee",
"message": "Add a <kbd>&lt;marquee&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;marquee&gt;</kbd>"
}
]
},
{
"id": "marquee-direction",
"title": "Direction & Behavior",
"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)",
"task": "Create a bouncing marquee:<br>1. Add a <kbd>&lt;marquee&gt;</kbd> element<br>2. Set <kbd>behavior=\"alternate\"</kbd> to make it bounce<br>3. Add some fun text",
"title": "Kierunek i zachowanie",
"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": "Utwórz odbijający się marquee:<br>1. Dodaj element <kbd>&lt;marquee&gt;</kbd><br>2. Ustaw <kbd>behavior=\"alternate\"</kbd> aby się odbijał<br>3. Dodaj jakiś fajny tekst",
"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; }",
"sandboxCSS": "",
@@ -40,20 +40,20 @@
{
"type": "element_exists",
"value": "marquee",
"message": "Add a <kbd>&lt;marquee&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;marquee&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"title": "Retro News Ticker",
"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.",
"task": "Create a news ticker:<br>1. A <kbd>&lt;marquee&gt;</kbd> with <kbd>direction=\"left\"</kbd><br>2. Set <kbd>scrollamount=\"5\"</kbd> for smooth scrolling<br>3. Add a breaking news headline inside",
"title": "Retro pasek wiadomości",
"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": "Utwórz pasek wiadomości:<br>1. Element <kbd>&lt;marquee&gt;</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": "",
"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": "",
@@ -64,17 +64,17 @@
{
"type": "element_exists",
"value": "marquee",
"message": "Add a <kbd>&lt;marquee&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;marquee&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"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"
}
]
}

View File

@@ -2,99 +2,169 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-svg",
"title": "HTML SVG",
"description": "Draw scalable vector graphics directly in HTML",
"description": "Rysuj skalowalne grafiki wektorowe bezpośrednio w HTML",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "svg-circle",
"title": "Drawing Circles",
"description": "SVG (Scalable Vector Graphics) lets you draw shapes directly in HTML. The <kbd>&lt;svg&gt;</kbd> element is the container, with <kbd>width</kbd> and <kbd>height</kbd> attributes.<br><br>Use <kbd>&lt;circle&gt;</kbd> with <kbd>cx</kbd>, <kbd>cy</kbd> (center) and <kbd>r</kbd> (radius) to draw circles.",
"task": "Create an SVG with a circle:<br>1. An <kbd>&lt;svg&gt;</kbd> with width=\"200\" and height=\"200\"<br>2. A <kbd>&lt;circle&gt;</kbd> centered at (100,100) with radius 50<br>3. Add a <kbd>fill</kbd> color",
"title": "Rysowanie kół",
"description": "SVG (Scalable Vector Graphics) pozwala rysować kształty bezpośrednio w HTML. Element <kbd>&lt;svg&gt;</kbd> jest kontenerem z atrybutami <kbd>width</kbd> i <kbd>height</kbd>.<br><br>Użyj <kbd>&lt;circle&gt;</kbd> z <kbd>cx</kbd>, <kbd>cy</kbd> (środek) i <kbd>r</kbd> (promień) do rysowania kół.",
"task": "Utwórz SVG z kołem:<br>1. Element <kbd>&lt;svg&gt;</kbd> z width=\"200\" i height=\"200\"<br>2. Element <kbd>&lt;circle&gt;</kbd> wyśrodkowany w (100,100) z promieniem 50<br>3. Dodaj kolor <kbd>fill</kbd>",
"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); }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "svg",
"message": "Add an <kbd>&lt;svg&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;svg&gt;</kbd>"
},
{
"type": "element_exists",
"value": "circle",
"message": "Add a <kbd>&lt;circle&gt;</kbd> element inside the SVG"
"message": "Dodaj element <kbd>&lt;circle&gt;</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",
"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",
"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",
"title": "Rectangles & Lines",
"description": "Draw rectangles with <kbd>&lt;rect&gt;</kbd> using <kbd>x</kbd>, <kbd>y</kbd>, <kbd>width</kbd>, <kbd>height</kbd>.<br><br>Draw lines with <kbd>&lt;line&gt;</kbd> using <kbd>x1</kbd>, <kbd>y1</kbd> (start) and <kbd>x2</kbd>, <kbd>y2</kbd> (end). Lines need a <kbd>stroke</kbd> color!",
"task": "Create an SVG with:<br>1. An <kbd>&lt;svg&gt;</kbd> (200x150)<br>2. A <kbd>&lt;rect&gt;</kbd> at position (20,20) with size 80x60<br>3. A <kbd>&lt;line&gt;</kbd> from (120,30) to (180,90) with a stroke color",
"title": "Prostokąty i linie",
"description": "Rysuj prostokąty za pomocą <kbd>&lt;rect&gt;</kbd> używając <kbd>x</kbd>, <kbd>y</kbd>, <kbd>width</kbd>, <kbd>height</kbd>.<br><br>Rysuj linie za pomocą <kbd>&lt;line&gt;</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": "Utwórz SVG z:<br>1. Elementem <kbd>&lt;svg&gt;</kbd> (200x150)<br>2. Elementem <kbd>&lt;rect&gt;</kbd> na pozycji (20,20) o rozmiarze 80x60<br>3. Elementem <kbd>&lt;line&gt;</kbd> od (120,30) do (180,90) z kolorem stroke",
"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); }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "svg",
"message": "Add an <kbd>&lt;svg&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;svg&gt;</kbd>"
},
{
"type": "element_exists",
"value": "rect",
"message": "Add a <kbd>&lt;rect&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;rect&gt;</kbd>"
},
{
"type": "element_exists",
"value": "line",
"message": "Add a <kbd>&lt;line&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;line&gt;</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",
"title": "Multiple Shapes",
"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.",
"task": "Create a simple face:<br>1. An <kbd>&lt;svg&gt;</kbd> (200x200)<br>2. A large <kbd>&lt;circle&gt;</kbd> for the face<br>3. Two small <kbd>&lt;circle&gt;</kbd> elements for eyes<br>4. A <kbd>&lt;line&gt;</kbd> for the smile",
"title": "Wiele kształtów",
"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": "Utwórz prostą twarz:<br>1. Element <kbd>&lt;svg&gt;</kbd> (200x200)<br>2. Duże <kbd>&lt;circle&gt;</kbd> dla twarzy<br>3. Dwa małe <kbd>&lt;circle&gt;</kbd> dla oczu<br>4. Element <kbd>&lt;line&gt;</kbd> dla uśmiechu",
"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); }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "svg",
"message": "Add an <kbd>&lt;svg&gt;</kbd> element"
"message": "Dodaj element <kbd>&lt;svg&gt;</kbd>"
},
{
"type": "element_count",
"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",
"value": "line",
"message": "Add a <kbd>&lt;line&gt;</kbd> for the smile"
"message": "Dodaj <kbd>&lt;line&gt;</kbd> dla uśmiechu"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "welcome",
"title": "Code Crispies",
"description": "Welcome to Code Crispies - your interactive web development learning platform",
"description": "Ласкаво просимо до Code Crispies - вашої інтерактивної платформи для вивчення веб-розробки",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "get-started",
"title": "Get 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",
"task": "Write <code>Hello World</code>",
"title": "Почати",
"description": "<strong>Code Crispies</strong> - це безкоштовна платформа з відкритим кодом для вивчення веб-розробки через практичні вправи. Обліковий запис не потрібен!<br><br><strong>Що ви вивчите:</strong><br>• <strong>HTML</strong> - Семантичні елементи, форми, таблиці, SVG (<em>HTML Блокові та рядкові</em>, <em>HTML Форми</em>, <em>HTML Таблиці</em>)<br>• <strong>CSS</strong> - Селектори, блокова модель, flexbox, анімації (<em>CSS Селектори</em>, <em>CSS Блокова модель</em>, <em>CSS Flexbox</em>)<br>• <strong>Адаптивний дизайн</strong> - Media queries та mobile-first макети<br><br><strong>Як це працює:</strong><br>1. Прочитайте завдання в лівій панелі<br>2. Напишіть код в редакторі<br>3. Побачте результати в попередньому перегляді<br>4. Отримайте миттєвий зворотний зв'язок з підказками<br><br><strong>Гарячі клавіші:</strong> <kbd>Ctrl+Z</kbd> скасувати, <kbd>Ctrl+Shift+Z</kbd> повторити<br><br><strong>Більше ресурсів:</strong><br>• <a href=\"https://nextlevelshit.github.io/html-over-js/\" target=\"_blank\">HTML over JS</a> - Нативний HTML проти JavaScript рішень<br>• <a href=\"https://nextlevelshit.github.io/web-engineering-mandala/\" target=\"_blank\">Web Engineering Mandala</a> - Карта JavaScript технологій",
"task": "Напишіть <code>Hello World</code>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 2rem; color: #6366f1; font-weight: bold; }",
"sandboxCSS": "",
@@ -21,15 +21,15 @@
{
"type": "contains",
"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",
"title": "Огляд",
"description": "<strong>Ви готові!</strong> Відкрийте меню (☰) щоб дослідити всі модулі.<br><br><strong>Рекомендований шлях навчання:</strong><br>1. <em>HTML Блокові та рядкові</em> - Зрозумійте контейнерні vs рядкові елементи<br>2. <em>HTML Форми</em> - Створюйте інтерактивні форми з валідацією<br>3. <em>CSS Селектори</em> - Точно вибирайте елементи<br>4. <em>CSS Блокова модель</em> - Опануйте padding, margin, borders<br>5. <em>CSS Flexbox</em> - Створюйте гнучкі макети<br>6. <em>CSS Анімації</em> - Додавайте рух та переходи<br><br><strong>Поради:</strong><br>• Використовуйте <em>Показати очікуване</em> щоб побачити цільовий результат<br>• Ваш прогрес зберігається автоматично<br>• Спробуйте Emmet в режимі HTML: <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>• Створено <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": "Натисніть Далі щоб продовжити",
"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": "",
@@ -39,8 +39,8 @@
"validations": [
{
"type": "contains",
"value": "Hello World",
"message": "Click Next to continue"
"value": "Hello",
"message": "Натисніть Далі щоб продовжити"
}
]
},

View File

@@ -2,18 +2,18 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "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",
"lessons": [
{
"id": "box-model-1",
"title": "Box Model Components",
"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.",
"task": "Set <kbd>padding</kbd> to <kbd>1rem</kbd> to create space between the content and border.",
"previewHTML": "<div class=\"box\">Box Model Components</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: lavender; border: 2px dashed slategray; }",
"title": "Padding",
"description": "Кожен елемент у CSS - це блок з чотирма шарами: контент, відступ (padding), межа та поле. <strong>Padding</strong> створює простір для дихання між вашим контентом і краєм блоку.<br><br>Без padding текст незручно притискається до меж. Padding робить контент читабельним і візуально збалансованим.<br><br><pre>.card {\n padding: 1rem;\n}</pre>",
"task": "Ця картка профілю виглядає тісною. Додайте <kbd>padding: 1rem</kbd>, щоб текст мав простір для дихання.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"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": "",
"codePrefix": ".box {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "padding: 1rem;",
@@ -22,62 +22,62 @@
{
"type": "property_value",
"value": { "property": "padding", "expected": "1rem" },
"message": "Set <kbd>padding: 1rem</kbd>"
"message": "Встановіть <kbd>padding: 1rem</kbd>"
}
]
},
{
"id": "box-model-2",
"title": "Adding Borders",
"description": "Borders outline an element, creating visual separation from surrounding content. The border shorthand accepts three values: width, style, and color.",
"task": "Set <kbd>border</kbd> to <kbd>2px solid darkslategray</kbd>.",
"previewHTML": "<div class=\"box\">This box needs a border</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: mintcream; padding: 1rem; }",
"title": "Borders",
"description": "Межі створюють візуальні границі навколо елементів. Скорочення <kbd>border</kbd> приймає три значення: ширину, стиль і колір.<br><br>Поширені стилі: <kbd>solid</kbd>, <kbd>dashed</kbd>, <kbd>dotted</kbd>, <kbd>none</kbd>",
"task": "Додайте тонкий лівий акцент до картки за допомогою <kbd>border-left: 4px solid steelblue</kbd>.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"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": "",
"codePrefix": ".box {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "border: 2px solid darkslategray;",
"solution": "border-left: 4px solid steelblue;",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "border:\\s*2px\\s+solid\\s+darkslategray",
"message": "Set <kbd>border: 2px solid darkslategray</kbd>",
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
"message": "Встановіть <kbd>border-left: 4px solid steelblue</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-3",
"title": "Adding 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.",
"task": "Set <kbd>margin</kbd> to <kbd>1rem</kbd> to create space between this element and its neighbors.",
"previewHTML": "<div class=\"container\"><div class=\"outer\">This box needs margins</div><div class=\"neighbor\">Adjacent element</div></div>",
"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; }",
"title": "Margins",
"description": "Поля створюють простір <em>зовні</em> елемента, відділяючи його від сусідів. Тоді як padding штовхає контент всередину, поля відштовхують інші елементи.",
"task": "Додайте простір між цими двома картками профілю за допомогою <kbd>margin-bottom: 1rem</kbd> на <kbd>.card</kbd>.",
"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; 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": "",
"codePrefix": ".outer {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "margin: 1rem;",
"solution": "margin-bottom: 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "margin", "expected": "1rem" },
"message": "Set <kbd>margin: 1rem</kbd>"
"value": { "property": "margin-bottom", "expected": "1rem" },
"message": "Встановіть <kbd>margin-bottom: 1rem</kbd>"
}
]
},
{
"id": "box-model-4",
"title": "Box Sizing: Border-Box",
"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.",
"task": "Set <kbd>box-sizing</kbd> to <kbd>border-box</kbd> so padding and border are included in the width.",
"previewHTML": "<div class=\"sizing-demo\"><div class=\"box default\">Content-box (default)</div><div class=\"box sized\">Border-box</div></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; }",
"title": "Box Sizing",
"description": "За замовчуванням <kbd>width</kbd> встановлює лише ширину контенту. Padding і межі додаються до загальної суми. Це спричиняє проблеми з макетом.<br><br><kbd>box-sizing: border-box</kbd> включає padding і межу у ширину, роблячи розмір передбачуваним. Більшість розробників застосовують це до всіх елементів.",
"task": "Обидві картки мають <kbd>width: 200px</kbd>. Ліва використовує стандартний розмір (content-box), стаючи ширшою за очікуване. Виправте праву картку за допомогою <kbd>box-sizing: border-box</kbd>.",
"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; background: #f5f5f5; } .demo { display: flex; gap: 1rem; } .card { width: 200px; padding: 1rem; border: 4px solid steelblue; background: white; border-radius: 8px; }",
"sandboxCSS": "",
"codePrefix": ".sized {\n ",
"codePrefix": ".fix {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "box-sizing: border-box;",
@@ -86,93 +86,104 @@
{
"type": "property_value",
"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",
"title": "Margin Collapse",
"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.",
"task": "Set <kbd>margin-bottom</kbd> to <kbd>2rem</kbd>. Notice the space between paragraphs equals 2rem (not 3rem) due to margin collapse.",
"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>",
"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; }",
"title": "Padding Shorthand",
"description": "Padding приймає 1-4 значення:<br>• 1 значення: всі сторони<br>• 2 значення: вертикально | горизонтально<br>• 4 значення: верх | право | низ | ліво",
"task": "Ця кнопка потребує більше горизонтального простору, ніж вертикального. Встановіть <kbd>padding: 8px 1rem</kbd> (8px верх/низ, 1rem ліво/право).",
"previewHTML": "<button class=\"btn\">Follow</button>",
"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": "",
"codePrefix": ".first {\n ",
"codePrefix": ".btn {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "margin-bottom: 2rem;",
"solution": "padding: 8px 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "margin-bottom", "expected": "2rem" },
"message": "Set <kbd>margin-bottom: 2rem</kbd>"
"type": "regex",
"value": "padding:\\s*8px\\s+1rem",
"message": "Встановіть <kbd>padding: 8px 1rem</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-6",
"title": "Margin Shorthand Notation",
"description": "The margin shorthand can set all four sides at once. Two values set vertical (top/bottom) and horizontal (left/right) margins respectively.",
"task": "Set <kbd>margin</kbd> to <kbd>1rem 2rem</kbd> for 1rem top/bottom and 2rem left/right.",
"previewHTML": "<div class=\"container\"><div class=\"spaced\">This box needs margins: 1rem top/bottom, 2rem left/right</div></div>",
"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; }",
"title": "Margin Shorthand",
"description": "Margin використовує той самий шаблон скорочення, що й padding. Поширений шаблон - горизонтальне центрування блокових елементів за допомогою <kbd>margin: 0 auto</kbd>.",
"task": "Відцентруйте цю картку горизонтально. Встановіть <kbd>margin: 0 auto</kbd>, щоб автоматично обчислити рівні ліві/праві поля.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"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": "",
"codePrefix": ".spaced {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "margin: 1rem 2rem;",
"solution": "margin: 0 auto;",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "margin:\\s*1rem\\s+2rem",
"message": "Set <kbd>margin: 1rem 2rem</kbd>",
"value": "margin:\\s*0\\s+auto",
"message": "Встановіть <kbd>margin: 0 auto</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-7",
"title": "Padding Shorthand Notation",
"description": "Like margin, padding shorthand allows setting all sides at once. A single value applies to all four sides equally.",
"task": "Set <kbd>padding</kbd> to <kbd>2rem</kbd> to add equal padding on all sides.",
"previewHTML": "<div class=\"padded\">This box needs equal padding on all sides</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .padded { background-color: papayawhip; border: 2px solid orange; }",
"title": "Border Radius",
"description": "Хоча не є частиною класичної блокової моделі, <kbd>border-radius</kbd> заокруглює кути межі елемента. Використовуйте <kbd>50%</kbd> на квадратному елементі, щоб створити коло.",
"task": "Зробіть зображення аватара круглим за допомогою <kbd>border-radius: 50%</kbd>.",
"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; 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": "",
"codePrefix": ".padded {\n ",
"codePrefix": ".avatar {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "padding: 2rem;",
"solution": "border-radius: 50%;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "padding", "expected": "2rem" },
"message": "Set <kbd>padding: 2rem</kbd>"
"value": { "property": "border-radius", "expected": "50%" },
"message": "Встановіть <kbd>border-radius: 50%</kbd>"
}
]
},
{
"id": "box-model-8",
"title": "Border on Specific Sides",
"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>.",
"task": "Set <kbd>border-bottom</kbd> to <kbd>4px solid dodgerblue</kbd>.",
"previewHTML": "<div class=\"line\">This element needs only a bottom border</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .line { padding: 1rem; background-color: aliceblue; }",
"title": "Complete Card",
"description": "Об'єднаймо все разом. Ця картка сповіщення потребує стилізації, щоб виглядати професійно.",
"task": "Стилізуйте сповіщення: додайте <kbd>padding: 1rem</kbd>, <kbd>border-left: 4px solid coral</kbd> та <kbd>border-radius: 4px</kbd>.",
"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; } .alert { background: seashell; } .alert strong { color: coral; } .alert p { margin: 4px 0 0; color: #666; }",
"sandboxCSS": "",
"codePrefix": ".line {\n ",
"codePrefix": ".alert {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "border-bottom: 4px solid dodgerblue;",
"solution": "padding: 1rem;\n border-left: 4px solid coral;\n border-radius: 4px;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "padding", "expected": "1rem" },
"message": "Встановіть <kbd>padding: 1rem</kbd>"
},
{
"type": "regex",
"value": "border-bottom:\\s*4px\\s+solid\\s+dodgerblue",
"message": "Set <kbd>border-bottom: 4px solid dodgerblue</kbd>",
"value": "border-left:\\s*4px\\s+solid\\s+coral",
"message": "Встановіть <kbd>border-left: 4px solid coral</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "border-radius", "expected": "4px" },
"message": "Встановіть <kbd>border-radius: 4px</kbd>"
}
]
}

View File

@@ -1,115 +1,100 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "units-variables",
"title": "CSS Units & Variables",
"description": "Understand the variety of CSS measurement units and how to define and use custom properties for maintainable styles.",
"title": "Одиниці CSS та змінні",
"description": "Зрозумійте різноманітність одиниць вимірювання CSS та як визначати й використовувати кастомні властивості для стилів, які легко підтримувати.",
"difficulty": "beginner",
"lessons": [
{
"id": "units-1",
"title": "Absolute vs. Relative Units",
"description": "Learn the difference between px, rem, em, %, and vw/vh for flexible, responsive layouts.",
"task": "Set the <kbd>width</kbd> of <kbd>.box</kbd> to <kbd>80%</kbd> and <kbd>max-width</kbd> to <kbd>37.5rem</kbd>.",
"previewHTML": "<div class=\"box\">Resize me!</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { background: #f5f5f5; padding: 1rem; }",
"title": "Relative Units",
"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": "Цей текст статті занадто широкий на великих екранах. Додайте <kbd>max-width: 40rem</kbd> для оптимальної ширини читання.",
"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: 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": "",
"codePrefix": "/* Set flexible sizing */\n.box {",
"codePrefix": ".article {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: 80%;\n max-width: 37.5rem;",
"codeSuffix": "\n}",
"solution": "max-width: 40rem;",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "width", "message": "Use <kbd>width</kbd> property", "options": { "caseSensitive": false } },
{ "type": "property_value", "value": { "property": "width", "expected": "80%" }, "message": "Set width to <kbd>80%</kbd>" },
{ "type": "contains", "value": "max-width", "message": "Use <kbd>max-width</kbd> property", "options": { "caseSensitive": false } },
{
"type": "property_value",
"value": { "property": "max-width", "expected": "37.5rem" },
"message": "Set max-width to <kbd>37.5rem</kbd>"
"value": { "property": "max-width", "expected": "40rem" },
"message": "Встановіть <kbd>max-width: 40rem</kbd>"
}
]
},
{
"id": "units-2",
"title": "CSS Custom Properties",
"description": "Define and reuse variables (--custom properties) to centralize your theme values.",
"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>.",
"previewHTML": "<div class=\"themed\">Variable Box</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .themed { padding: 1rem; border: 0.125rem solid #ddd; }",
"title": "CSS Variables",
"description": "Кастомні властивості CSS (змінні) дозволяють визначати значення для повторного використання. Визначайте їх за допомогою <kbd>--назва</kbd> та використовуйте з <kbd>var(--назва)</kbd>. Змінні, визначені на <kbd>:root</kbd>, доступні всюди.",
"task": "Визначте <kbd>--brand: steelblue</kbd> в <kbd>:root</kbd>, потім використайте як колір <kbd>background</kbd> для <kbd>.btn</kbd>.",
"previewHTML": "<div class=\"actions\"><button class=\"btn\">Subscribe</button><button class=\"btn\">Learn More</button></div>",
"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": "",
"codePrefix": "/* Define and use a CSS variable */\n:root {",
"codePrefix": ":root {\n ",
"initialCode": "",
"codeSuffix": "}\n.themed { }",
"solution": " --main-color: #6200ee;\n}\n.themed {\n border-color: var(--main-color);",
"codeSuffix": "\n}\n\n.btn {\n background: var(--brand);\n}",
"solution": "--brand: steelblue;",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "--main-color",
"message": "Define <kbd>--main-color</kbd> in :root",
"value": "--brand",
"message": "Визначте змінну <kbd>--brand</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "var(--main-color)",
"message": "Use <kbd>var(--main-color)</kbd>",
"value": "steelblue",
"message": "Встановіть значення <kbd>steelblue</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "border", "expected": "var(--main-color)" },
"message": "Apply variable to border color",
"options": { "exact": false }
}
]
},
{
"id": "units-3",
"title": "Unit Calculations (calc)",
"description": "Use the <kbd>calc()</kbd> function to combine different units in one expression.",
"task": "Set the <kbd>width</kbd> of <kbd>.sized</kbd> to <kbd>calc(100% - 2rem)</kbd> and <kbd>min-height</kbd> to <kbd>calc(10vh + 1rem)</kbd>.",
"previewHTML": "<div class=\"sized\">Calc Demo</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sized { background: #e8f5e9; padding: 1rem; }",
"title": "calc() Function",
"description": "Функція <kbd>calc()</kbd> дозволяє змішувати різні одиниці в обчисленнях. Це важливо для макетів, що поєднують фіксовані та гнучкі розміри, як макет із сайдбаром.",
"task": "Основний контент повинен заповнити залишок місця після сайдбару 200px. Встановіть <kbd>width: calc(100% - 200px)</kbd> на <kbd>.main</kbd>.",
"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: 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": "",
"codePrefix": "/* Use calc for dynamic sizing */\n.sized {",
"codePrefix": ".main {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: calc(100% - 2rem);\n min-height: calc(10vh + 1rem);",
"codeSuffix": "\n}",
"solution": "width: calc(100% - 200px);",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "calc", "message": "Use <kbd>calc()</kbd> function", "options": { "caseSensitive": false } },
{
"type": "regex",
"value": "width:\\s*calc\\(100% - 2rem\\)",
"message": "Width should be <kbd>calc(100% - 2rem)</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "min-height:\\s*calc\\(10vh \\+ 1rem\\)",
"message": "Min-height should be <kbd>calc(10vh + 1rem)</kbd>",
"value": "width:\\s*calc\\(\\s*100%\\s*-\\s*200px\\s*\\)",
"message": "Встановіть <kbd>width: calc(100% - 200px)</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "units-4",
"title": "Viewport & Responsive Units",
"description": "Control layouts relative to viewport size with vw, vh, and vmin/vmax units.",
"task": "Give <kbd>.view</kbd> a <kbd>width</kbd> of <kbd>50vw</kbd> and <kbd>height</kbd> of <kbd>20vh</kbd>.",
"previewHTML": "<div class=\"view\">Viewport Box</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .view { background: #ffe0b2; }",
"title": "Viewport Units",
"description": "Одиниці viewport розміряють елементи відносно вікна браузера:<br>• <kbd>vw</kbd> 1% ширини viewport<br>• <kbd>vh</kbd> 1% висоти viewport<br><br>Вони ідеальні для повноекранних секцій як hero-банери.",
"task": "Зробіть цю hero-секцію висотою з viewport, встановивши <kbd>min-height: 100vh</kbd>.",
"previewHTML": "<section class=\"hero\"><h1>Welcome</h1><p>Scroll down to explore</p></section>",
"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": "",
"codePrefix": "/* Use viewport units */\n.view {",
"codePrefix": ".hero {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: 50vw;\n height: 20vh;",
"codeSuffix": "\n}",
"solution": "min-height: 100vh;",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "vw", "message": "Use <kbd>vw</kbd> unit", "options": { "caseSensitive": false } },
{ "type": "contains", "value": "vh", "message": "Use <kbd>vh</kbd> unit", "options": { "caseSensitive": false } },
{ "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Set width to <kbd>50vw</kbd>" },
{ "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Set height to <kbd>20vh</kbd>" }
{
"type": "property_value",
"value": { "property": "min-height", "expected": "100vh" },
"message": "Встановіть <kbd>min-height: 100vh</kbd>"
}
]
}
]

View File

@@ -1,15 +1,15 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "transitions-animations",
"title": "CSS Animations",
"description": "Bring interactivity to your UI by smoothly transitioning properties and creating keyframe-driven animations.",
"title": "CSS Анімації",
"description": "Додайте інтерактивність до інтерфейсу через плавні переходи властивостей та анімації на основі keyframes.",
"difficulty": "intermediate",
"lessons": [
{
"id": "transitions-1",
"title": "Transitions",
"description": "Learn how to apply <kbd>transition</kbd> to properties for smooth changes on state changes.<br><br><pre>transition: property duration;\n/* e.g. transition: background-color 0.3s; */</pre>",
"task": "Add <kbd>transition: background-color 0.3s</kbd> to <kbd>.btn</kbd> so the color fades smoothly on hover.",
"description": "Навчіться застосовувати <kbd>transition</kbd> до властивостей для плавних змін при зміні стану.<br><br><pre>transition: property duration;\n/* напр. transition: background-color 0.3s; */</pre>",
"task": "Додайте <kbd>transition: background-color 0.3s</kbd>, щоб колір плавно змінювався при наведенні.",
"previewHTML": "<button class=\"btn\">Hover Me</button>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: black; color: white; padding: 0.5rem 1rem; border: none; cursor: pointer; } .btn:hover { background: white; color: black; }",
"sandboxCSS": "",
@@ -22,13 +22,13 @@
{
"type": "contains",
"value": "transition",
"message": "Use the <kbd>transition</kbd> property",
"message": "Використайте властивість <kbd>transition</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"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 }
}
]
@@ -36,8 +36,8 @@
{
"id": "transitions-2",
"title": "Timing Funcs",
"description": "Explore easing functions like <kbd>ease</kbd>, <kbd>linear</kbd>, <kbd>ease-in</kbd>, <kbd>ease-out</kbd> to control animation pacing.",
"task": "Set <kbd>transition-timing-function</kbd> to <kbd>ease-in-out</kbd> on <kbd>.btn</kbd>.",
"description": "Дослідіть функції пом'якшення як <kbd>ease</kbd>, <kbd>linear</kbd>, <kbd>ease-in</kbd>, <kbd>ease-out</kbd> для контролю темпу анімації.",
"task": "Встановіть <kbd>transition-timing-function</kbd> на <kbd>ease-in-out</kbd>.",
"previewHTML": "<button class=\"btn\">Timing</button>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: navy; color: gold; padding: 0.5rem 1rem; border: none; cursor: pointer; transition: all 0.5s; } .btn:hover { background: gold; color: navy; transform: scale(1.1); }",
"sandboxCSS": "",
@@ -50,21 +50,21 @@
{
"type": "contains",
"value": "transition-timing-function",
"message": "Use <kbd>transition-timing-function</kbd>",
"message": "Використайте <kbd>transition-timing-function</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "transition-timing-function", "expected": "ease-in-out" },
"message": "Set timing to <kbd>ease-in-out</kbd>"
"message": "Встановіть timing на <kbd>ease-in-out</kbd>"
}
]
},
{
"id": "transitions-3",
"title": "Keyframes",
"description": "Create named animations using <kbd>@keyframes</kbd> and apply them via the <kbd>animation</kbd> shorthand.<br><br><pre>@keyframes bounce {\n 50% { transform: translateY(-20px); }\n}\n.ball {\n animation: bounce 1s infinite;\n}</pre>",
"task": "Define a keyframe at <kbd>50%</kbd> with <kbd>transform: translateY(-20px)</kbd> and apply <kbd>animation: bounce 1s infinite</kbd> to <kbd>.ball</kbd>.",
"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": "Визначте keyframe при <kbd>50%</kbd> з <kbd>transform: translateY(-20px)</kbd> та застосуйте <kbd>animation: bounce 1s infinite</kbd> на <kbd>.ball</kbd>.",
"previewHTML": "<div class=\"ball\"></div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: crimson; border-radius: 50%; margin: 2rem auto; }",
"sandboxCSS": "",
@@ -77,25 +77,25 @@
{
"type": "contains",
"value": "@keyframes bounce",
"message": "Define <kbd>@keyframes bounce</kbd>",
"message": "Визначте <kbd>@keyframes bounce</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "50%.*transform: translateY\\(-20px\\)",
"message": "At <kbd>50%</kbd>, use <kbd>transform: translateY(-20px)</kbd>",
"message": "При <kbd>50%</kbd>, використайте <kbd>transform: translateY(-20px)</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "animation",
"message": "Use <kbd>animation</kbd> property on <kbd>.ball</kbd>",
"message": "Використайте властивість <kbd>animation</kbd> на <kbd>.ball</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "animation:.*bounce.*1s.*infinite",
"message": "Apply <kbd>animation: bounce 1s infinite</kbd>",
"message": "Застосуйте <kbd>animation: bounce 1s infinite</kbd>",
"options": { "caseSensitive": false }
}
]
@@ -103,8 +103,8 @@
{
"id": "transitions-4",
"title": "Animation Properties",
"description": "Fine-tune animations with <kbd>animation-delay</kbd>, <kbd>animation-iteration-count</kbd>, <kbd>animation-direction</kbd>, and <kbd>animation-fill-mode</kbd>.",
"task": "Apply the <kbd>pulse</kbd> animation to <kbd>.box</kbd> with <kbd>animation-name: pulse</kbd>, <kbd>animation-duration: 2s</kbd>, <kbd>animation-delay: 1s</kbd>, <kbd>animation-iteration-count: 2</kbd>, and <kbd>animation-fill-mode: forwards</kbd>.",
"description": "Налаштуйте анімації за допомогою <kbd>animation-delay</kbd>, <kbd>animation-iteration-count</kbd>, <kbd>animation-direction</kbd> та <kbd>animation-fill-mode</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>",
"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": "",
@@ -117,27 +117,27 @@
{
"type": "property_value",
"value": { "property": "animation-name", "expected": "pulse" },
"message": "Set <kbd>animation-name: pulse</kbd>"
"message": "Встановіть <kbd>animation-name: pulse</kbd>"
},
{
"type": "property_value",
"value": { "property": "animation-duration", "expected": "2s" },
"message": "Set <kbd>animation-duration: 2s</kbd>"
"message": "Встановіть <kbd>animation-duration: 2s</kbd>"
},
{
"type": "property_value",
"value": { "property": "animation-delay", "expected": "1s" },
"message": "Set <kbd>animation-delay: 1s</kbd>"
"message": "Встановіть <kbd>animation-delay: 1s</kbd>"
},
{
"type": "property_value",
"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",
"value": { "property": "animation-fill-mode", "expected": "forwards" },
"message": "Set <kbd>animation-fill-mode: forwards</kbd>"
"message": "Встановіть <kbd>animation-fill-mode: forwards</kbd>"
}
]
}

View File

@@ -2,14 +2,14 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "responsive-design",
"title": "CSS Responsive Design",
"description": "Make your layouts adapt to different screen sizes using media queries and fluid design techniques.",
"description": "Адаптуйте ваші макети до різних розмірів екранів використовуючи media queries та техніки плавного дизайну.",
"difficulty": "intermediate",
"lessons": [
{
"id": "responsive-1",
"title": "Media Queries",
"description": "Understand the syntax and use cases for CSS media queries to apply styles conditionally based on viewport characteristics.",
"task": "Write a media query with <kbd>@media (max-width: 600px)</kbd> that changes <kbd>.panel</kbd> background to <kbd>lightcoral</kbd>.",
"description": "Зрозумійте синтаксис та випадки використання CSS media queries для умовного застосування стилів на основі характеристик viewport.<br><br><pre>@media (max-width: 600px) {\n .panel {\n background: lightcoral;\n }\n}</pre>",
"task": "Напишіть media query з <kbd>@media (max-width: 600px)</kbd>, яка змінює фон <kbd>.panel</kbd> на <kbd>lightcoral</kbd>.",
"previewHTML": "<div class=\"panel\">Resize the window</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .panel { padding: 1rem; background: lightblue; }",
"sandboxCSS": "",
@@ -22,19 +22,19 @@
{
"type": "regex",
"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 }
},
{
"type": "contains",
"value": ".panel",
"message": "Target <kbd>.panel</kbd> inside the media query",
"message": "Вкажіть <kbd>.panel</kbd> всередині media query",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "background", "expected": "lightcoral" },
"message": "Set <kbd>background: lightcoral</kbd>",
"message": "Встановіть <kbd>background: lightcoral</kbd>",
"options": { "exact": false }
}
]
@@ -42,8 +42,8 @@
{
"id": "responsive-2",
"title": "Fluid Type",
"description": "Use relative units like <kbd>vw</kbd> to make font sizes scale with the viewport width.",
"task": "Set <kbd>font-size: 5vw</kbd> on <kbd>.text</kbd> so it scales as the viewport changes.",
"description": "Використовуйте відносні одиниці як <kbd>vw</kbd>, щоб розміри шрифтів масштабувались з шириною viewport.",
"task": "Встановіть <kbd>font-size: 5vw</kbd>, щоб масштабувалась з viewport.",
"previewHTML": "<p class=\"text\">Fluid Typography</p>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; }",
"sandboxCSS": "",
@@ -53,46 +53,50 @@
"solution": " font-size: 5vw;",
"previewContainer": "preview-area",
"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",
"title": "Flex Grids",
"description": "Combine CSS Grid with <kbd>auto-fit</kbd> or <kbd>auto-fill</kbd> for responsive column layouts.",
"task": "Add <kbd>display: grid</kbd>, <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd>, and <kbd>gap: 1rem</kbd> to <kbd>.cards</kbd>.",
"previewHTML": "<div class=\"cards\"><div>1</div><div>2</div><div>3</div><div>4</div></div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .cards > div { background: #d1c4e9; padding: 1rem; }",
"title": "Responsive Grid",
"description": "Поєднайте CSS Grid з <kbd>auto-fit</kbd> або <kbd>auto-fill</kbd> для адаптивних колонкових макетів, які автоматично налаштовують кількість колонок на основі доступного простору.",
"task": "Додайте <kbd>display: grid</kbd>, <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd> та <kbd>gap: 1rem</kbd>.",
"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: 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": "",
"codePrefix": "/* Create a responsive grid */\n.cards {",
"codePrefix": ".features {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
"codeSuffix": "\n}",
"solution": "display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "display", "expected": "grid" },
"message": "Set <kbd>display: grid</kbd>"
"message": "Встановіть <kbd>display: grid</kbd>"
},
{
"type": "regex",
"value": "repeat\\(auto-fit,\\s*minmax\\(200px,\\s*1fr\\)\\)",
"message": "Use <kbd>repeat(auto-fit, minmax(200px, 1fr))</kbd>",
"message": "Використайте <kbd>repeat(auto-fit, minmax(200px, 1fr))</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "gap", "expected": "1rem" },
"message": "Set <kbd>gap: 1rem</kbd>"
"message": "Встановіть <kbd>gap: 1rem</kbd>"
}
]
},
{
"id": "responsive-4",
"title": "Mobile-First",
"description": "Adopt a mobile-first approach by writing base styles for small screens and enhancing for larger viewports.",
"task": "Write a media query with <kbd>@media (min-width: 768px)</kbd> that sets <kbd>.sidebar</kbd> width to <kbd>250px</kbd>.",
"description": "Застосуйте підхід mobile-first: пишіть базові стилі для малих екранів і розширюйте для більших viewport.",
"task": "Напишіть media query з <kbd>@media (min-width: 768px)</kbd>, яка встановлює ширину <kbd>.sidebar</kbd> на <kbd>250px</kbd>.",
"previewHTML": "<aside class=\"sidebar\">Sidebar</aside>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sidebar { background: #c8e6c9; padding: 1rem; }",
"sandboxCSS": "",
@@ -105,19 +109,19 @@
{
"type": "regex",
"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 }
},
{
"type": "contains",
"value": ".sidebar",
"message": "Target <kbd>.sidebar</kbd> inside media query",
"message": "Вкажіть <kbd>.sidebar</kbd> в media query",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "width", "expected": "250px" },
"message": "Set <kbd>width: 250px</kbd>",
"message": "Встановіть <kbd>width: 250px</kbd>",
"options": { "exact": false }
}
]

View File

@@ -2,65 +2,65 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-elements",
"title": "HTML Block & Inline",
"description": "Understanding the fundamental difference between container (block) and inline elements",
"description": "Зрозумійте основну різницю між контейнерними (блоковими) та рядковими елементами",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "block-vs-inline-intro",
"title": "Block vs Inline Elements",
"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>&lt;div&gt;</kbd>, <kbd>&lt;p&gt;</kbd>, <kbd>&lt;h1&gt;</kbd>, <kbd>&lt;section&gt;</kbd><br><br><strong>Inline elements</strong> flow within text and only take needed width. Examples: <kbd>&lt;span&gt;</kbd>, <kbd>&lt;a&gt;</kbd>, <kbd>&lt;strong&gt;</kbd>, <kbd>&lt;em&gt;</kbd>",
"task": "Wrap the word <kbd>important</kbd> with <kbd>&lt;strong&gt;</kbd> tags to make it bold. Notice how the paragraph (block) takes full width while strong (inline) flows with text.",
"title": "Блокові vs рядкові елементи",
"description": "HTML елементи поділяються на дві основні категорії:<br><br><strong>Блокові елементи</strong> (контейнери) починаються з нового рядка і займають повну ширину. Приклади: <kbd>&lt;div&gt;</kbd>, <kbd>&lt;p&gt;</kbd>, <kbd>&lt;h1&gt;</kbd>, <kbd>&lt;section&gt;</kbd><br><br><strong>Рядкові елементи</strong> розміщуються всередині тексту і займають лише необхідну ширину. Приклади: <kbd>&lt;span&gt;</kbd>, <kbd>&lt;a&gt;</kbd>, <kbd>&lt;strong&gt;</kbd>, <kbd>&lt;em&gt;</kbd>",
"task": "Оберніть слово <kbd>важливе</kbd> тегами <kbd>&lt;strong&gt;</kbd>, щоб зробити його жирним. Зверніть увагу, як абзац (блок) займає повну ширину, тоді як strong (рядковий) тече з текстом.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; } p { background: #e3f2fd; padding: 10px; } strong { background: #ffecb3; }",
"sandboxCSS": "",
"initialCode": "<p>This is a paragraph with an important word.</p>",
"solution": "<p>This is a paragraph with an <strong>important</strong> word.</p>",
"initialCode": "<p>Це абзац з важливе словом.</p>",
"solution": "<p>Це абзац з <strong>важливе</strong> словом.</p>",
"previewContainer": "preview-area",
"validations": [
{
"type": "element_exists",
"value": "p",
"message": "Add a <kbd>&lt;p&gt;</kbd> paragraph element"
"message": "Додайте елемент абзацу <kbd>&lt;p&gt;</kbd>"
},
{
"type": "parent_child",
"value": { "parent": "p", "child": "strong" },
"message": "Wrap the word <kbd>important</kbd> with <kbd>&lt;strong&gt;</kbd> tags"
"message": "Оберніть слово <kbd>важливе</kbd> тегами <kbd>&lt;strong&gt;</kbd>"
}
]
},
{
"id": "semantic-containers",
"title": "Semantic Tags",
"description": "Modern HTML uses semantic containers that describe their content:<br><br><kbd>&lt;header&gt;</kbd> - Page or section header<br><kbd>&lt;nav&gt;</kbd> - Navigation links<br><kbd>&lt;main&gt;</kbd> - Main content area<br><kbd>&lt;section&gt;</kbd> - Thematic grouping<br><kbd>&lt;article&gt;</kbd> - Self-contained content<br><kbd>&lt;footer&gt;</kbd> - Page or section footer",
"task": "Create a basic page structure:<br>1. Add a <kbd>&lt;header&gt;</kbd> with an <kbd>&lt;h1&gt;</kbd> containing the text <code>My Website</code><br>2. Add a <kbd>&lt;main&gt;</kbd> element with a paragraph saying <code>Welcome to my site!</code><br>3. Add a <kbd>&lt;footer&gt;</kbd> with a paragraph saying <code>Copyright 2026</code>",
"title": "Семантичні теги",
"description": "Сучасний HTML використовує семантичні контейнери, які описують свій вміст:<br><br><kbd>&lt;header&gt;</kbd> - Заголовок сторінки або секції<br><kbd>&lt;nav&gt;</kbd> - Навігаційні посилання<br><kbd>&lt;main&gt;</kbd> - Основний вміст<br><kbd>&lt;section&gt;</kbd> - Тематичне групування<br><kbd>&lt;article&gt;</kbd> - Самостійний вміст<br><kbd>&lt;footer&gt;</kbd> - Підвал сторінки або секції",
"task": "Створіть базову структуру сторінки:<br>1. Додайте <kbd>&lt;header&gt;</kbd> з <kbd>&lt;h1&gt;</kbd>, що містить текст <code>Мій Сайт</code><br>2. Додайте елемент <kbd>&lt;main&gt;</kbd> з абзацом, що каже <code>Ласкаво просимо на мій сайт!</code><br>3. Додайте <kbd>&lt;footer&gt;</kbd> з абзацом, що каже <code>Copyright 2026</code>",
"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; }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "header",
"message": "Add a <kbd>&lt;header&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;header&gt;</kbd>"
},
{
"type": "element_exists",
"value": "main",
"message": "Add a <kbd>&lt;main&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;main&gt;</kbd>"
},
{
"type": "element_exists",
"value": "footer",
"message": "Add a <kbd>&lt;footer&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;footer&gt;</kbd>"
},
{
"type": "parent_child",
"value": { "parent": "header", "child": "h1" },
"message": "Add an <kbd>&lt;h1&gt;</kbd> heading inside your header"
"message": "Додайте заголовок <kbd>&lt;h1&gt;</kbd> всередині header"
}
]
}

View File

@@ -1,100 +1,100 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-forms-basic",
"title": "HTML Forms",
"description": "Learn to create forms with various input types",
"title": "HTML Форми",
"description": "Навчіться створювати форми з різними типами полів",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "form-structure",
"title": "Form Structure",
"description": "Every form needs a <kbd>&lt;form&gt;</kbd> wrapper. Inside, use <kbd>&lt;label&gt;</kbd> to describe inputs and <kbd>&lt;input&gt;</kbd> for user data entry.<br><br>The <kbd>for</kbd> attribute on labels should match the <kbd>id</kbd> on inputs for accessibility.",
"task": "Create a form with:<br>1. A <kbd>&lt;label&gt;</kbd> with the text <code>Name:</code> and <kbd>for=\"name\"</kbd> attribute<br>2. A text <kbd>&lt;input&gt;</kbd> with <kbd>id=\"name\"</kbd> and <kbd>name=\"name\"</kbd> attributes",
"title": "Структура форми",
"description": "Кожна форма потребує обгортки <kbd>&lt;form&gt;</kbd>. Всередині використовуйте <kbd>&lt;label&gt;</kbd> для опису полів та <kbd>&lt;input&gt;</kbd> для введення даних.<br><br>Атрибут <kbd>for</kbd> у label має відповідати <kbd>id</kbd> полів для доступності.",
"task": "Створіть форму з:<br>1. <kbd>&lt;label&gt;</kbd> з текстом <code>Ім'я:</code> та атрибутом <kbd>for=\"name\"</kbd><br>2. Текстовим <kbd>&lt;input&gt;</kbd> з атрибутами <kbd>id=\"name\"</kbd> та <kbd>name=\"name\"</kbd>",
"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; }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "form",
"message": "Wrap everything in a <kbd>&lt;form&gt;</kbd> element"
"message": "Оберніть все елементом <kbd>&lt;form&gt;</kbd>"
},
{
"type": "element_exists",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for your input"
"message": "Додайте <kbd>&lt;label&gt;</kbd> для вашого поля"
},
{
"type": "element_exists",
"value": "input",
"message": "Add an <kbd>&lt;input&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;input&gt;</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "label", "attr": "for", "value": null },
"message": "Add a <kbd>for</kbd> attribute to your label"
"message": "Додайте атрибут <kbd>for</kbd> до вашого label"
},
{
"type": "attribute_value",
"value": { "selector": "input", "attr": "id", "value": null },
"message": "Add an <kbd>id</kbd> attribute to your input"
"message": "Додайте атрибут <kbd>id</kbd> до вашого поля"
}
]
},
{
"id": "input-types",
"title": "Input Types",
"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",
"task": "Create a login form with two fields:<br>1. An email field: <kbd>&lt;label for=\"email\"&gt;Email:&lt;/label&gt;</kbd> and <kbd>&lt;input type=\"email\" id=\"email\"&gt;</kbd><br>2. A password field: <kbd>&lt;label for=\"password\"&gt;Password:&lt;/label&gt;</kbd> and <kbd>&lt;input type=\"password\" id=\"password\"&gt;</kbd>",
"title": "Типи полів",
"description": "Різні типи полів надають відповідні клавіатури та валідацію:<br><br><kbd>type=\"text\"</kbd> - Загальний текст<br><kbd>type=\"email\"</kbd> - Email з валідацією @<br><kbd>type=\"password\"</kbd> - Приховані символи<br><kbd>type=\"number\"</kbd> - Цифрова клавіатура<br><kbd>type=\"tel\"</kbd> - Телефонна клавіатура",
"task": "Створіть форму входу з двома полями:<br>1. Поле email: <kbd>&lt;label for=\"email\"&gt;Email:&lt;/label&gt;</kbd> та <kbd>&lt;input type=\"email\" id=\"email\"&gt;</kbd><br>2. Поле пароля: <kbd>&lt;label for=\"password\"&gt;Пароль:&lt;/label&gt;</kbd> та <kbd>&lt;input type=\"password\" id=\"password\"&gt;</kbd>",
"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; }",
"sandboxCSS": "",
"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\">Пароль:</label>\n <input type=\"password\" id=\"password\" name=\"password\">\n</form>",
"previewContainer": "preview-area",
"validations": [
{
"type": "element_exists",
"value": "input[type='email']",
"message": "Add an input with type=\"email\""
"message": "Додайте поле з type=\"email\""
},
{
"type": "element_exists",
"value": "input[type='password']",
"message": "Add an input with type=\"password\""
"message": "Додайте поле з type=\"password\""
},
{
"type": "element_count",
"value": { "selector": "label", "min": 2 },
"message": "Add labels for both inputs"
"message": "Додайте label для обох полів"
}
]
},
{
"id": "submit-button",
"title": "Submit Button",
"description": "Forms need a way to submit data. Use:<br><br><kbd>&lt;button type=\"submit\"&gt;</kbd> - Preferred, flexible content<br><kbd>&lt;input type=\"submit\"&gt;</kbd> - Simple text-only button<br><br>The button text should be action-oriented (e.g., <code>Sign In</code>, 'Register', 'Send').",
"task": "Add a submit button to the form with the text <code>Sign In</code>.",
"title": "Кнопка відправки",
"description": "Форми потребують способу відправки даних. Використовуйте:<br><br><kbd>&lt;button type=\"submit\"&gt;</kbd> - Переважно, гнучкий вміст<br><kbd>&lt;input type=\"submit\"&gt;</kbd> - Простий текстовий кнопка<br><br>Текст кнопки має бути орієнтованим на дію (напр. <code>Увійти</code>, 'Зареєструватись', 'Надіслати').",
"task": "Додайте кнопку відправки до форми з текстом <code>Увійти</code>.",
"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; }",
"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>",
"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>",
"initialCode": "<form>\n <label for=\"email\">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\">Пароль:</label>\n <input type=\"password\" id=\"password\">\n \n <button type=\"submit\">Увійти</button>\n</form>",
"previewContainer": "preview-area",
"validations": [
{
"type": "element_exists",
"value": "button[type='submit'], input[type='submit']",
"message": "Add a submit button to your form"
"message": "Додайте кнопку відправки до форми"
},
{
"type": "element_text",
"value": { "selector": "button", "text": "Sign In" },
"message": "The button should say <kbd>Sign In</kbd>"
"value": { "selector": "button", "text": "Увійти" },
"message": "Кнопка має відображати <kbd>Увійти</kbd>"
}
]
}

View File

@@ -1,110 +1,32 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-forms-validation",
"title": "HTML Validation",
"description": "Learn HTML5 built-in form validation attributes",
"title": "Валідація форм",
"description": "Використовуйте вбудовану валідацію HTML5 для кращого досвіду користувача",
"mode": "html",
"difficulty": "intermediate",
"difficulty": "beginner",
"lessons": [
{
"id": "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>&lt;input type=\"text\" required&gt;</kbd><br><br>The browser shows a validation message automatically.",
"task": "Make both the name and email fields required by adding the <kbd>required</kbd> attribute.",
"title": "Обов'язкові поля",
"description": "Атрибут <kbd>required</kbd> запобігає відправці форми, якщо поле порожнє. Браузер автоматично показує повідомлення валідації - без JavaScript!<br><br>Додайте його до будь-якого поля, яке має бути заповнене:<br><kbd>&lt;input type=\"text\" required&gt;</kbd>",
"task": "Зробіть обидва поля (ім'я та email) обов'язковими, додавши атрибут <kbd>required</kbd> до кожного поля.",
"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": "",
"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>",
"initialCode": "<form>\n <label for=\"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\">Надіслати</button>\n</form>",
"solution": "<form>\n <label for=\"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\">Надіслати</button>\n</form>",
"previewContainer": "preview-area",
"validations": [
{
"type": "attribute_value",
"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",
"value": { "selector": "input[name='email']", "attr": "required", "value": true },
"message": "Add the <kbd>required</kbd> attribute 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>"
"message": "Додайте <kbd>required</kbd> до поля email"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-details-summary",
"title": "HTML Details & Summary",
"description": "Create expandable content sections without JavaScript",
"description": "Створюйте розгортувані секції без JavaScript",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "details-summary-basic",
"title": "First Widget",
"description": "The <kbd>&lt;details&gt;</kbd> element creates a collapsible section. The <kbd>&lt;summary&gt;</kbd> provides the clickable label.<br><br>Click the summary to toggle the hidden content - no JavaScript needed!",
"task": "Create a <kbd>&lt;details&gt;</kbd> element with:<br>1. A <kbd>&lt;summary&gt;</kbd> saying <code>Click to reveal</code><br>2. A <kbd>&lt;p&gt;</kbd> with the text <code>This content was hidden!</code>",
"title": "Перший віджет",
"description": "Елемент <kbd>&lt;details&gt;</kbd> створює згортувану секцію. Елемент <kbd>&lt;summary&gt;</kbd> надає клікабельний заголовок.<br><br>Натисніть на summary, щоб показати прихований вміст - без JavaScript!",
"task": "Створіть елемент <kbd>&lt;details&gt;</kbd> з:<br>1. Елементом <kbd>&lt;summary&gt;</kbd> з текстом <code>Click to reveal</code><br>2. Елементом <kbd>&lt;p&gt;</kbd> з текстом <code>This content was hidden!</code>",
"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; }",
"sandboxCSS": "",
@@ -21,30 +21,30 @@
{
"type": "element_exists",
"value": "details",
"message": "Add a <kbd>&lt;details&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;details&gt;</kbd>"
},
{
"type": "element_exists",
"value": "summary",
"message": "Add a <kbd>&lt;summary&gt;</kbd> inside the details"
"message": "Додайте <kbd>&lt;summary&gt;</kbd> всередину details"
},
{
"type": "parent_child",
"value": { "parent": "details", "child": "summary" },
"message": "The <kbd>&lt;summary&gt;</kbd> must be inside <kbd>&lt;details&gt;</kbd>"
"message": "Елемент <kbd>&lt;summary&gt;</kbd> має бути всередині <kbd>&lt;details&gt;</kbd>"
},
{
"type": "parent_child",
"value": { "parent": "details", "child": "p" },
"message": "Add a <kbd>&lt;p&gt;</kbd> inside <kbd>&lt;details&gt;</kbd> for the hidden content"
"message": "Додайте <kbd>&lt;p&gt;</kbd> всередину <kbd>&lt;details&gt;</kbd> для прихованого вмісту"
}
]
},
{
"id": "details-open-attribute",
"title": "Pre-expanded Details",
"description": "By default, <kbd>&lt;details&gt;</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.",
"task": "Add the <kbd>open</kbd> attribute to the <kbd>&lt;details&gt;</kbd> element to show the content by default.",
"title": "Розгорнуто за замовчуванням",
"description": "За замовчуванням <kbd>&lt;details&gt;</kbd> закритий. Додайте атрибут <kbd>open</kbd>, щоб показати вміст спочатку.<br><br>Це булевий атрибут - просто додайте <kbd>open</kbd> без значення.",
"task": "Додайте атрибут <kbd>open</kbd> до елемента <kbd>&lt;details&gt;</kbd>, щоб показати вміст за замовчуванням.",
"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; }",
"sandboxCSS": "",
@@ -55,15 +55,15 @@
{
"type": "attribute_value",
"value": { "selector": "details", "attr": "open", "value": true },
"message": "Add the <kbd>open</kbd> attribute to <kbd>&lt;details&gt;</kbd>"
"message": "Додайте атрибут <kbd>open</kbd> до <kbd>&lt;details&gt;</kbd>"
}
]
},
{
"id": "faq-accordion",
"title": "FAQ Accordion",
"description": "Multiple <kbd>&lt;details&gt;</kbd> elements create an accordion-style FAQ. Each question can be expanded independently.<br><br><b>Pro tip:</b> Type <kbd>details*3&gt;summary+p</kbd> and press Tab for Emmet expansion. <kbd>*3</kbd> creates 3 elements, <kbd>&gt;</kbd> nests inside, <kbd>+</kbd> adds siblings.",
"task": "Create an FAQ section with:<br>1. An <kbd>&lt;h1&gt;</kbd> saying <code>Frequently Asked Questions</code><br>2. Three <kbd>&lt;details&gt;</kbd> elements, each with a question in <kbd>&lt;summary&gt;</kbd> and an answer in <kbd>&lt;p&gt;</kbd>",
"title": "FAQ акордеон",
"description": "Кілька елементів <kbd>&lt;details&gt;</kbd> створюють FAQ у стилі акордеону. Кожне питання можна розгортати незалежно.<br><br><b>Порада:</b> Введіть <kbd>details*3&gt;summary+p</kbd> і натисніть Tab для розгортання Emmet. <kbd>*3</kbd> створює 3 елементи, <kbd>&gt;</kbd> вкладає всередину, <kbd>+</kbd> додає сусідні.",
"task": "Створіть секцію FAQ з:<br>1. Заголовком <kbd>&lt;h1&gt;</kbd> з текстом <code>Frequently Asked Questions</code><br>2. Трьома елементами <kbd>&lt;details&gt;</kbd>, кожен з питанням у <kbd>&lt;summary&gt;</kbd> та відповіддю у <kbd>&lt;p&gt;</kbd>",
"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; }",
"sandboxCSS": "",
@@ -74,22 +74,22 @@
{
"type": "element_exists",
"value": "h1",
"message": "Add an <kbd>&lt;h1&gt;</kbd> heading for the FAQ title"
"message": "Додайте заголовок <kbd>&lt;h1&gt;</kbd> для назви FAQ"
},
{
"type": "element_count",
"value": { "selector": "details", "min": 3 },
"message": "Create at least 3 <kbd>&lt;details&gt;</kbd> elements for the FAQ"
"message": "Створіть принаймні 3 елементи <kbd>&lt;details&gt;</kbd> для FAQ"
},
{
"type": "element_count",
"value": { "selector": "summary", "min": 3 },
"message": "Each <kbd>&lt;details&gt;</kbd> needs a <kbd>&lt;summary&gt;</kbd> for the question"
"message": "Кожен <kbd>&lt;details&gt;</kbd> потребує <kbd>&lt;summary&gt;</kbd> для питання"
},
{
"type": "element_count",
"value": { "selector": "details p", "min": 3 },
"message": "Each <kbd>&lt;details&gt;</kbd> needs a <kbd>&lt;p&gt;</kbd> for the answer"
"message": "Кожен <kbd>&lt;details&gt;</kbd> потребує <kbd>&lt;p&gt;</kbd> для відповіді"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-progress-meter",
"title": "HTML Progress & Meter",
"description": "Display completion status and scalar measurements natively",
"description": "Відображайте статус виконання та вимірювання нативно",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "progress-basic",
"title": "Progress Bars",
"description": "The <kbd>&lt;progress&gt;</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>&lt;progress&gt;...&lt;/progress&gt;</kbd> with fallback text inside for older browsers.",
"task": "Create a progress bar showing 70% completion:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Download:</code><br>2. Add a <kbd>&lt;progress&gt;</kbd> with <kbd>value=\"70\"</kbd> and <kbd>max=\"100\"</kbd>",
"title": "Смуги прогресу",
"description": "Елемент <kbd>&lt;progress&gt;</kbd> показує виконання завдання. Використовуйте <kbd>value</kbd> для поточного прогресу та <kbd>max</kbd> для загального.<br><br><b>Примітка:</b> Це не самозакриваючий тег! Пишіть <kbd>&lt;progress&gt;...&lt;/progress&gt;</kbd> з резервним текстом всередині для старих браузерів.",
"task": "Створіть смугу прогресу, що показує 70% виконання:<br>1. Додайте <kbd>&lt;label&gt;</kbd> з текстом <code>Download:</code><br>2. Додайте <kbd>&lt;progress&gt;</kbd> з <kbd>value=\"70\"</kbd> та <kbd>max=\"100\"</kbd>",
"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; }",
"sandboxCSS": "",
@@ -21,30 +21,30 @@
{
"type": "element_exists",
"value": "progress",
"message": "Add a <kbd>&lt;progress&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;progress&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"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",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for the progress bar"
"message": "Додайте <kbd>&lt;label&gt;</kbd> для смуги прогресу"
}
]
},
{
"id": "progress-indeterminate",
"title": "Indeterminate Progress",
"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.",
"task": "Create a loading indicator:<br>1. Add a <kbd>&lt;p&gt;</kbd> saying <code>Loading...</code><br>2. Add a <kbd>&lt;progress&gt;</kbd> without a value attribute",
"title": "Невизначений прогрес",
"description": "Коли прогрес невідомий (як при завантаженні), опустіть атрибут <kbd>value</kbd>. Це створює анімований невизначений стан.<br><br>Корисно для мережевих запитів або процесів з невідомою тривалістю.",
"task": "Створіть індикатор завантаження:<br>1. Додайте <kbd>&lt;p&gt;</kbd> з текстом <code>Loading...</code><br>2. Додайте <kbd>&lt;progress&gt;</kbd> без атрибута value",
"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; }",
"sandboxCSS": "",
@@ -55,20 +55,20 @@
{
"type": "element_exists",
"value": "progress",
"message": "Add a <kbd>&lt;progress&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;progress&gt;</kbd>"
},
{
"type": "element_exists",
"value": "p",
"message": "Add a <kbd>&lt;p&gt;</kbd> with loading text"
"message": "Додайте <kbd>&lt;p&gt;</kbd> з текстом завантаження"
}
]
},
{
"id": "meter-gauge",
"title": "Meter Gauges",
"description": "The <kbd>&lt;meter&gt;</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!",
"task": "Create a battery level meter:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Battery:</code><br>2. Add a <kbd>&lt;meter&gt;</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>",
"title": "Шкали meter",
"description": "Елемент <kbd>&lt;meter&gt;</kbd> відображає скалярне значення в діапазоні. Використовуйте його для вимірювань, як-от місце на диску, батарея або рейтинги.<br><br>Встановіть <kbd>low</kbd>, <kbd>high</kbd> та <kbd>optimum</kbd>, щоб визначити хороші/погані діапазони - браузер забарвлює відповідно!",
"task": "Створіть шкалу рівня батареї:<br>1. Додайте <kbd>&lt;label&gt;</kbd> з текстом <code>Battery:</code><br>2. Додайте <kbd>&lt;meter&gt;</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": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
"sandboxCSS": "",
@@ -79,22 +79,42 @@
{
"type": "element_exists",
"value": "meter",
"message": "Add a <kbd>&lt;meter&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;meter&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"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",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for the meter"
"message": "Додайте <kbd>&lt;label&gt;</kbd> для meter"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-datalist",
"title": "Datalist",
"description": "Provide suggestions for text inputs without JavaScript",
"description": "Додайте підказки для текстових полів без JavaScript",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "datalist-basic",
"title": "Input with Suggestions",
"description": "The <kbd>&lt;datalist&gt;</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!",
"task": "Create a browser selector:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Browser:</code><br>2. Add an <kbd>&lt;input&gt;</kbd> with <kbd>list=\"browsers\"</kbd><br>3. Add a <kbd>&lt;datalist id=\"browsers\"&gt;</kbd> with options for Chrome, Firefox, and Safari",
"title": "Поле з підказками",
"description": "Елемент <kbd>&lt;datalist&gt;</kbd> надає підказки автозаповнення для полів. З'єднайте його за допомогою атрибута <kbd>list</kbd> на полі, що відповідає <kbd>id</kbd> datalist.<br><br>Користувачі все одно можуть вводити вільно - підказки лише помічники!",
"task": "Створіть селектор браузера:<br>1. Додайте <kbd>&lt;label&gt;</kbd> з текстом <code>Браузер:</code><br>2. Додайте <kbd>&lt;input&gt;</kbd> з <kbd>list=\"browsers\"</kbd><br>3. Додайте <kbd>&lt;datalist id=\"browsers\"&gt;</kbd> з варіантами Chrome, Firefox та Safari",
"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; }",
"sandboxCSS": "",
@@ -21,30 +21,30 @@
{
"type": "element_exists",
"value": "datalist",
"message": "Add a <kbd>&lt;datalist&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;datalist&gt;</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "input", "attr": "list", "value": "browsers" },
"message": "Connect the input to datalist using <kbd>list=</kbd>\"browsers\""
"message": "З'єднайте поле з datalist за допомогою <kbd>list=</kbd>\"browsers\""
},
{
"type": "element_count",
"value": { "selector": "option", "min": 3 },
"message": "Add at least 3 <kbd>&lt;option&gt;</kbd> elements inside <kbd>&lt;datalist&gt;</kbd>"
"message": "Додайте принаймні 3 елементи <kbd>&lt;option&gt;</kbd> всередину <kbd>&lt;datalist&gt;</kbd>"
},
{
"type": "element_exists",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for the input"
"message": "Додайте <kbd>&lt;label&gt;</kbd> для поля"
}
]
},
{
"id": "datalist-countries",
"title": "Country Selector",
"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.",
"task": "Create a country input:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Country:</code><br>2. Add an <kbd>&lt;input&gt;</kbd> with <kbd>list=\"countries\"</kbd><br>3. Add a <kbd>&lt;datalist id=\"countries\"&gt;</kbd> with at least 4 country options",
"title": "Селектор країн",
"description": "Datalist чудово працює для довгих списків, як країни. Користувачі можуть вводити, щоб миттєво фільтрувати підказки.<br><br>Атрибут <kbd>value</kbd> - це те, що вводиться, і ви можете додати текст для відображення після нього.",
"task": "Створіть поле країни:<br>1. Додайте <kbd>&lt;label&gt;</kbd> з текстом <code>Країна:</code><br>2. Додайте <kbd>&lt;input&gt;</kbd> з <kbd>list=\"countries\"</kbd><br>3. Додайте <kbd>&lt;datalist id=\"countries\"&gt;</kbd> з принаймні 4 варіантами країн",
"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); }",
"sandboxCSS": "",
@@ -55,22 +55,22 @@
{
"type": "element_exists",
"value": "datalist",
"message": "Add a <kbd>&lt;datalist&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;datalist&gt;</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "datalist", "attr": "id", "value": "countries" },
"message": "Set <kbd>id=</kbd>\"countries\" on the datalist"
"message": "Встановіть <kbd>id=</kbd>\"countries\" в datalist"
},
{
"type": "attribute_value",
"value": { "selector": "input", "attr": "list", "value": "countries" },
"message": "Connect the input using <kbd>list=</kbd>\"countries\""
"message": "З'єднайте поле за допомогою <kbd>list=</kbd>\"countries\""
},
{
"type": "element_count",
"value": { "selector": "option", "min": 4 },
"message": "Add at least 4 country options"
"message": "Додайте принаймні 4 варіанти країн"
}
]
}

View File

@@ -1,16 +1,16 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-dialog",
"title": "Dialogs",
"description": "Create modal dialogs without JavaScript libraries",
"title": "Діалоги",
"description": "Створюйте модальні діалоги без JavaScript бібліотек",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "dialog-basic",
"title": "Open Dialog",
"description": "The <kbd>&lt;dialog&gt;</kbd> element creates a native modal. Add the <kbd>open</kbd> attribute to show it.<br><br>Use <kbd>&lt;form method=\"dialog\"&gt;</kbd> inside to close it when the form submits - no JavaScript needed!",
"task": "Create a dialog with:<br>1. The <kbd>open</kbd> attribute to show it<br>2. An <kbd>&lt;h2&gt;</kbd> saying <code>Welcome!</code><br>3. A <kbd>&lt;p&gt;</kbd> with a greeting message<br>4. A <kbd>&lt;form method=\"dialog\"&gt;</kbd> with a close button",
"title": "Відкрити діалог",
"description": "Елемент <kbd>&lt;dialog&gt;</kbd> створює нативне модальне вікно. Додайте атрибут <kbd>open</kbd>, щоб показати його.<br><br>Використовуйте <kbd>&lt;form method=\"dialog\"&gt;</kbd> всередині, щоб закрити його при відправці форми - без JavaScript!",
"task": "Створіть діалог з:<br>1. Атрибутом <kbd>open</kbd> для відображення<br>2. Елементом <kbd>&lt;h2&gt;</kbd> з текстом <code>Вітаємо!</code><br>3. Елементом <kbd>&lt;p&gt;</kbd> з привітальним повідомленням<br>4. Елементом <kbd>&lt;form method=\"dialog\"&gt;</kbd> з кнопкою закриття",
"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; }",
"sandboxCSS": "",
@@ -21,35 +21,35 @@
{
"type": "element_exists",
"value": "dialog",
"message": "Add a <kbd>&lt;dialog&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;dialog&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"value": "dialog h2",
"message": "Add an <kbd>&lt;h2&gt;</kbd> heading inside the dialog"
"message": "Додайте заголовок <kbd>&lt;h2&gt;</kbd> всередині діалогу"
},
{
"type": "element_exists",
"value": "form[method='dialog']",
"message": "Add a <kbd>&lt;form method=\"dialog\"&gt;</kbd> for closing"
"message": "Додайте <kbd>&lt;form method=\"dialog\"&gt;</kbd> для закриття"
},
{
"type": "element_exists",
"value": "dialog button",
"message": "Add a close button inside the form"
"message": "Додайте кнопку закриття всередині форми"
}
]
},
{
"id": "dialog-form",
"title": "Dialog + Form",
"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.",
"task": "Create a confirmation dialog:<br>1. Add <kbd>open</kbd> to show it<br>2. An <kbd>&lt;h2&gt;</kbd> saying <code>Confirm Delete</code><br>3. A <kbd>&lt;p&gt;</kbd> asking <code>Are you sure?</code><br>4. A <kbd>&lt;form method=\"dialog\"&gt;</kbd> with Cancel and Delete buttons",
"title": "Діалог + Форма",
"description": "Діалоги можуть містити повні форми. <kbd>method=\"dialog\"</kbd> змушує форму закривати діалог при відправці замість відправки даних.<br><br>Цей шаблон ідеальний для діалогів підтвердження, швидкого введення або панелей налаштувань.",
"task": "Створіть діалог підтвердження:<br>1. Додайте <kbd>open</kbd> для відображення<br>2. Елемент <kbd>&lt;h2&gt;</kbd> з текстом <code>Підтвердити видалення</code><br>3. Елемент <kbd>&lt;p&gt;</kbd> з питанням <code>Ви впевнені?</code><br>4. Елемент <kbd>&lt;form method=\"dialog\"&gt;</kbd> з кнопками Скасувати та Видалити",
"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; }",
"sandboxCSS": "",
@@ -60,22 +60,22 @@
{
"type": "element_exists",
"value": "dialog[open]",
"message": "Add a <kbd>&lt;dialog&gt;</kbd> with the open attribute"
"message": "Додайте <kbd>&lt;dialog&gt;</kbd> з атрибутом open"
},
{
"type": "element_exists",
"value": "dialog h2",
"message": "Add a heading to the dialog"
"message": "Додайте заголовок до діалогу"
},
{
"type": "element_exists",
"value": "form[method='dialog']",
"message": "Add a <kbd>&lt;form method=\"dialog\"&gt;</kbd> for the buttons"
"message": "Додайте <kbd>&lt;form method=\"dialog\"&gt;</kbd> для кнопок"
},
{
"type": "element_count",
"value": { "selector": "dialog button", "min": 2 },
"message": "Add at least 2 buttons (Cancel and Confirm)"
"message": "Додайте принаймні 2 кнопки (Скасувати та Підтвердити)"
}
]
}

View File

@@ -1,16 +1,16 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-forms-fieldset",
"title": "Fieldsets",
"description": "Group form controls with fieldset and legend elements",
"title": "Fieldset",
"description": "Групуйте елементи форми за допомогою fieldset та legend",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "fieldset-basic",
"title": "Grouping with Fieldset",
"description": "The <kbd>&lt;fieldset&gt;</kbd> element groups related form controls together. Add a <kbd>&lt;legend&gt;</kbd> as the first child to give the group a title.<br><br>This helps with accessibility and visual organization of complex forms.",
"task": "Create a form with a fieldset:<br>1. A <kbd>&lt;form&gt;</kbd> element<br>2. A <kbd>&lt;fieldset&gt;</kbd> inside<br>3. A <kbd>&lt;legend&gt;</kbd> saying <code>Personal Info</code><br>4. Two labeled inputs for name and email",
"title": "Групування з Fieldset",
"description": "Елемент <kbd>&lt;fieldset&gt;</kbd> групує пов'язані елементи форми разом. Додайте <kbd>&lt;legend&gt;</kbd> як перший дочірній елемент, щоб дати групі заголовок.<br><br>Це допомагає з доступністю та візуальною організацією складних форм.",
"task": "Створіть форму з fieldset:<br>1. Елемент <kbd>&lt;form&gt;</kbd><br>2. Елемент <kbd>&lt;fieldset&gt;</kbd> всередині<br>3. Елемент <kbd>&lt;legend&gt;</kbd> з текстом <code>Особисті дані</code><br>4. Два підписаних поля для імені та email",
"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; }",
"sandboxCSS": "",
@@ -21,35 +21,35 @@
{
"type": "element_exists",
"value": "form",
"message": "Add a <kbd>&lt;form&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;form&gt;</kbd>"
},
{
"type": "element_exists",
"value": "fieldset",
"message": "Add a <kbd>&lt;fieldset&gt;</kbd> inside the form"
"message": "Додайте <kbd>&lt;fieldset&gt;</kbd> всередину форми"
},
{
"type": "element_exists",
"value": "legend",
"message": "Add a <kbd>&lt;legend&gt;</kbd> to title your fieldset"
"message": "Додайте <kbd>&lt;legend&gt;</kbd> як заголовок fieldset"
},
{
"type": "element_count",
"value": { "selector": "label", "min": 2 },
"message": "Add at least 2 labels"
"message": "Додайте принаймні 2 мітки"
},
{
"type": "element_count",
"value": { "selector": "input", "min": 2 },
"message": "Add at least 2 input fields"
"message": "Додайте принаймні 2 поля введення"
}
]
},
{
"id": "fieldset-textarea",
"title": "Adding Textarea",
"description": "The <kbd>&lt;textarea&gt;</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.",
"task": "Create a contact form:<br>1. A <kbd>&lt;fieldset&gt;</kbd> with <kbd>&lt;legend&gt;</kbd> <code>Contact Us</code><br>2. A labeled <kbd>&lt;input&gt;</kbd> for email<br>3. A labeled <kbd>&lt;textarea&gt;</kbd> for the message<br>4. A submit <kbd>&lt;button&gt;</kbd>",
"title": "Додавання Textarea",
"description": "Елемент <kbd>&lt;textarea&gt;</kbd> створює багаторядкове текстове поле, ідеальне для довшого вмісту як повідомлення або описи.<br><br>Використовуйте атрибути <kbd>rows</kbd> та <kbd>cols</kbd> для встановлення розміру за замовчуванням.",
"task": "Створіть контактну форму:<br>1. Елемент <kbd>&lt;fieldset&gt;</kbd> з <kbd>&lt;legend&gt;</kbd> <code>Зв'яжіться з нами</code><br>2. Підписане <kbd>&lt;input&gt;</kbd> для email<br>3. Підписане <kbd>&lt;textarea&gt;</kbd> для повідомлення<br>4. Кнопка <kbd>&lt;button&gt;</kbd> відправки",
"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; }",
"sandboxCSS": "",
@@ -60,35 +60,35 @@
{
"type": "element_exists",
"value": "fieldset",
"message": "Add a <kbd>&lt;fieldset&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;fieldset&gt;</kbd>"
},
{
"type": "element_exists",
"value": "legend",
"message": "Add a <kbd>&lt;legend&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;legend&gt;</kbd>"
},
{
"type": "element_exists",
"value": "textarea",
"message": "Add a <kbd>&lt;textarea&gt;</kbd> for the message"
"message": "Додайте <kbd>&lt;textarea&gt;</kbd> для повідомлення"
},
{
"type": "element_exists",
"value": "button",
"message": "Add a submit button"
"message": "Додайте кнопку відправки"
},
{
"type": "element_exists",
"value": "input",
"message": "Add an input field for email"
"message": "Додайте поле для email"
}
]
},
{
"id": "fieldset-multiple",
"title": "Multiple Fieldsets",
"description": "Complex forms can use multiple <kbd>&lt;fieldset&gt;</kbd> elements to organize different sections.<br><br>This improves usability for long forms like registration or checkout pages.",
"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",
"title": "Кілька Fieldsetів",
"description": "Складні форми можуть використовувати кілька елементів <kbd>&lt;fieldset&gt;</kbd> для організації різних секцій.<br><br>Це покращує зручність використання довгих форм як реєстрація або оформлення замовлення.",
"task": "Створіть форму реєстрації з 2 fieldsetами:<br>1. <code>Дані облікового запису</code> з полями ім'я користувача та пароль<br>2. <code>Налаштування</code> з textarea для біо<br>3. Кнопка відправки поза fieldsetами",
"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; }",
"sandboxCSS": "",
@@ -99,27 +99,27 @@
{
"type": "element_count",
"value": { "selector": "fieldset", "min": 2 },
"message": "Create at least 2 fieldsets"
"message": "Створіть принаймні 2 fieldseti"
},
{
"type": "element_count",
"value": { "selector": "legend", "min": 2 },
"message": "Add a legend to each fieldset"
"message": "Додайте legend до кожного fieldseta"
},
{
"type": "element_exists",
"value": "textarea",
"message": "Add a textarea for the bio"
"message": "Додайте textarea для біо"
},
{
"type": "element_exists",
"value": "button",
"message": "Add a submit button"
"message": "Додайте кнопку відправки"
},
{
"type": "element_count",
"value": { "selector": "input", "min": 2 },
"message": "Add at least 2 input fields"
"message": "Додайте принаймні 2 поля введення"
}
]
}

View File

@@ -1,125 +1,42 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-tables",
"title": "HTML Tables",
"description": "Create structured data tables with headers and captions",
"title": "Таблиці HTML",
"description": "Створюйте структуровані таблиці даних із семантичною розміткою",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "table-basic",
"title": "Basic Table Structure",
"description": "Tables use <kbd>&lt;table&gt;</kbd> with <kbd>&lt;tr&gt;</kbd> for rows. Inside rows, use <kbd>&lt;th&gt;</kbd> for headers and <kbd>&lt;td&gt;</kbd> for data cells.<br><br>The <kbd>&lt;caption&gt;</kbd> element provides an accessible title for the table.",
"task": "Create a simple table with:<br>1. A <kbd>&lt;caption&gt;</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",
"title": "Таблиці даних",
"description": "Таблиці відображають структуровані дані в рядках і стовпцях. Використовуйте <kbd>&lt;table&gt;</kbd> як контейнер, <kbd>&lt;tr&gt;</kbd> для рядків, <kbd>&lt;th&gt;</kbd> для комірок заголовка та <kbd>&lt;td&gt;</kbd> для комірок даних.<br><br>Додайте <kbd>&lt;caption&gt;</kbd> для доступного заголовка, що описує вміст таблиці.",
"task": "Створіть таблицю цін:<br>1. Елемент <kbd>&lt;caption&gt;</kbd> з текстом <code>Pricing</code><br>2. Рядок заголовка з <code>Plan</code> та <code>Price</code><br>3. Два рядки даних для Basic ($9) та Pro ($29)",
"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": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "table",
"message": "Add a <kbd>&lt;table&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;table&gt;</kbd>"
},
{
"type": "element_exists",
"value": "caption",
"message": "Add a <kbd>&lt;caption&gt;</kbd> for the table title"
"message": "Додайте <kbd>&lt;caption&gt;</kbd> для заголовка таблиці"
},
{
"type": "element_count",
"value": { "selector": "th", "min": 2 },
"message": "Add at least 2 header cells (th)"
"message": "Додайте комірки заголовка (<kbd>&lt;th&gt;</kbd>) для Plan та Price"
},
{
"type": "element_count",
"value": { "selector": "tr", "min": 3 },
"message": "Add at least 3 rows (1 header + 2 data rows)"
}
]
},
{
"id": "table-thead-tbody",
"title": "Table Head & Body",
"description": "Use <kbd>&lt;thead&gt;</kbd> to group header rows and <kbd>&lt;tbody&gt;</kbd> to group data rows. This helps browsers and assistive technology understand the table structure.<br><br>You can also use <kbd>&lt;tfoot&gt;</kbd> for footer rows like totals.",
"task": "Create a structured table:<br>1. A <kbd>&lt;caption&gt;</kbd> with <code>Monthly Sales</code><br>2. A <kbd>&lt;thead&gt;</kbd> with Month and Revenue headers<br>3. A <kbd>&lt;tbody&gt;</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>&lt;table&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "caption",
"message": "Add a <kbd>&lt;caption&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "thead",
"message": "Add a <kbd>&lt;thead&gt;</kbd> for the header section"
},
{
"type": "element_exists",
"value": "tbody",
"message": "Add a <kbd>&lt;tbody&gt;</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>&lt;tfoot&gt;</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>&lt;caption&gt;</kbd> with <code>Order Summary</code><br>2. A <kbd>&lt;thead&gt;</kbd> with Item and Price headers<br>3. A <kbd>&lt;tbody&gt;</kbd> with 2 items<br>4. A <kbd>&lt;tfoot&gt;</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>&lt;table&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "caption",
"message": "Add a <kbd>&lt;caption&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "thead",
"message": "Add a <kbd>&lt;thead&gt;</kbd> section"
},
{
"type": "element_exists",
"value": "tbody",
"message": "Add a <kbd>&lt;tbody&gt;</kbd> section"
},
{
"type": "element_exists",
"value": "tfoot",
"message": "Add a <kbd>&lt;tfoot&gt;</kbd> section for the total"
},
{
"type": "element_count",
"value": { "selector": "tbody tr", "min": 2 },
"message": "Add at least 2 item rows in tbody"
"message": "Додайте 3 рядки (1 заголовок + 2 рядки даних)"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-marquee",
"title": "HTML Marquee",
"description": "Create scrolling text with the classic (deprecated but fun!) marquee element",
"description": "Створюйте біжучий текст за допомогою класичного (застарілого але веселого!) елемента marquee",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "marquee-basic",
"title": "Scrolling Text",
"description": "The <kbd>&lt;marquee&gt;</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!",
"task": "Create a simple marquee:<br>1. Add a <kbd>&lt;marquee&gt;</kbd> element<br>2. Put some text inside like <code>Welcome to my website!</code>",
"title": "Біжучий текст",
"description": "Елемент <kbd>&lt;marquee&gt;</kbd> створює біжучий текст - класика раннього вебу! Хоча він застарілий, все ще працює в більшості браузерів.<br><br>Примітка: Для продакшену використовуйте CSS-анімації. Але для навчання та розваги marquee чудовий!",
"task": "Створіть простий marquee:<br>1. Додайте елемент <kbd>&lt;marquee&gt;</kbd><br>2. Помістіть текст всередину, наприклад <code>Ласкаво просимо на мій сайт!</code>",
"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; }",
"sandboxCSS": "",
@@ -21,15 +21,15 @@
{
"type": "element_exists",
"value": "marquee",
"message": "Add a <kbd>&lt;marquee&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;marquee&gt;</kbd>"
}
]
},
{
"id": "marquee-direction",
"title": "Direction & Behavior",
"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)",
"task": "Create a bouncing marquee:<br>1. Add a <kbd>&lt;marquee&gt;</kbd> element<br>2. Set <kbd>behavior=\"alternate\"</kbd> to make it bounce<br>3. Add some fun text",
"title": "Напрямок та поведінка",
"description": "Керуйте marquee за допомогою атрибутів:<br>• <kbd>direction</kbd>: left, right, up, down<br>• <kbd>behavior</kbd>: scroll (за замовчуванням), slide (зупиняється на краю), alternate (відбивається)<br>• <kbd>scrollamount</kbd>: швидкість (за замовчуванням 6)",
"task": "Створіть marquee що відбивається:<br>1. Додайте елемент <kbd>&lt;marquee&gt;</kbd><br>2. Встановіть <kbd>behavior=\"alternate\"</kbd> щоб він відбивався<br>3. Додайте веселий текст",
"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; }",
"sandboxCSS": "",
@@ -40,20 +40,20 @@
{
"type": "element_exists",
"value": "marquee",
"message": "Add a <kbd>&lt;marquee&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;marquee&gt;</kbd>"
},
{
"type": "attribute_value",
"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",
"title": "Retro News Ticker",
"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.",
"task": "Create a news ticker:<br>1. A <kbd>&lt;marquee&gt;</kbd> with <kbd>direction=\"left\"</kbd><br>2. Set <kbd>scrollamount=\"5\"</kbd> for smooth scrolling<br>3. Add a breaking news headline inside",
"title": "Ретро-стрічка новин",
"description": "Поєднайте кілька атрибутів marquee для класичного ефекту стрічки новин. Ви навіть можете помістити кілька елементів всередину!<br><br>Пам'ятайте: Це застарілий HTML. Сучасні сайти використовують CSS-анімації, але marquee чудовий для розуміння історії вебу.",
"task": "Створіть стрічку новин:<br>1. Елемент <kbd>&lt;marquee&gt;</kbd> з <kbd>direction=\"left\"</kbd><br>2. Встановіть <kbd>scrollamount=\"5\"</kbd> для плавного прокручування<br>3. Додайте всередину заголовок термінових новин",
"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; }",
"sandboxCSS": "",
@@ -64,17 +64,17 @@
{
"type": "element_exists",
"value": "marquee",
"message": "Add a <kbd>&lt;marquee&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;marquee&gt;</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "marquee", "attr": "direction", "value": "left" },
"message": "Add <kbd>direction=</kbd>\"left\" for horizontal scrolling"
"message": "Додайте <kbd>direction=</kbd>\"left\" для горизонтального прокручування"
},
{
"type": "attribute_value",
"value": { "selector": "marquee", "attr": "scrollamount", "value": "5" },
"message": "Add <kbd>scrollamount=</kbd>\"5\" for smooth speed"
"message": "Додайте <kbd>scrollamount=</kbd>\"5\" для плавної швидкості"
}
]
}

View File

@@ -2,99 +2,169 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-svg",
"title": "HTML SVG",
"description": "Draw scalable vector graphics directly in HTML",
"description": "Малюйте масштабовану векторну графіку безпосередньо в HTML",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "svg-circle",
"title": "Drawing Circles",
"description": "SVG (Scalable Vector Graphics) lets you draw shapes directly in HTML. The <kbd>&lt;svg&gt;</kbd> element is the container, with <kbd>width</kbd> and <kbd>height</kbd> attributes.<br><br>Use <kbd>&lt;circle&gt;</kbd> with <kbd>cx</kbd>, <kbd>cy</kbd> (center) and <kbd>r</kbd> (radius) to draw circles.",
"task": "Create an SVG with a circle:<br>1. An <kbd>&lt;svg&gt;</kbd> with width=\"200\" and height=\"200\"<br>2. A <kbd>&lt;circle&gt;</kbd> centered at (100,100) with radius 50<br>3. Add a <kbd>fill</kbd> color",
"title": "Малювання кіл",
"description": "SVG (Scalable Vector Graphics) дозволяє малювати фігури безпосередньо в HTML. Елемент <kbd>&lt;svg&gt;</kbd> є контейнером з атрибутами <kbd>width</kbd> та <kbd>height</kbd>.<br><br>Використовуйте <kbd>&lt;circle&gt;</kbd> з <kbd>cx</kbd>, <kbd>cy</kbd> (центр) та <kbd>r</kbd> (радіус) для малювання кіл.",
"task": "Створіть SVG з колом:<br>1. Елемент <kbd>&lt;svg&gt;</kbd> з width=\"200\" та height=\"200\"<br>2. Елемент <kbd>&lt;circle&gt;</kbd> з центром у (100,100) та радіусом 50<br>3. Додайте колір <kbd>fill</kbd>",
"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); }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "svg",
"message": "Add an <kbd>&lt;svg&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;svg&gt;</kbd>"
},
{
"type": "element_exists",
"value": "circle",
"message": "Add a <kbd>&lt;circle&gt;</kbd> element inside the SVG"
"message": "Додайте елемент <kbd>&lt;circle&gt;</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",
"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",
"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",
"title": "Rectangles & Lines",
"description": "Draw rectangles with <kbd>&lt;rect&gt;</kbd> using <kbd>x</kbd>, <kbd>y</kbd>, <kbd>width</kbd>, <kbd>height</kbd>.<br><br>Draw lines with <kbd>&lt;line&gt;</kbd> using <kbd>x1</kbd>, <kbd>y1</kbd> (start) and <kbd>x2</kbd>, <kbd>y2</kbd> (end). Lines need a <kbd>stroke</kbd> color!",
"task": "Create an SVG with:<br>1. An <kbd>&lt;svg&gt;</kbd> (200x150)<br>2. A <kbd>&lt;rect&gt;</kbd> at position (20,20) with size 80x60<br>3. A <kbd>&lt;line&gt;</kbd> from (120,30) to (180,90) with a stroke color",
"title": "Прямокутники та лінії",
"description": "Малюйте прямокутники за допомогою <kbd>&lt;rect&gt;</kbd> використовуючи <kbd>x</kbd>, <kbd>y</kbd>, <kbd>width</kbd>, <kbd>height</kbd>.<br><br>Малюйте лінії за допомогою <kbd>&lt;line&gt;</kbd> використовуючи <kbd>x1</kbd>, <kbd>y1</kbd> (початок) та <kbd>x2</kbd>, <kbd>y2</kbd> (кінець). Лінії потребують колір <kbd>stroke</kbd>!",
"task": "Створіть SVG з:<br>1. Елементом <kbd>&lt;svg&gt;</kbd> (200x150)<br>2. Елементом <kbd>&lt;rect&gt;</kbd> на позиції (20,20) розміром 80x60<br>3. Елементом <kbd>&lt;line&gt;</kbd> від (120,30) до (180,90) з кольором stroke",
"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); }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "svg",
"message": "Add an <kbd>&lt;svg&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;svg&gt;</kbd>"
},
{
"type": "element_exists",
"value": "rect",
"message": "Add a <kbd>&lt;rect&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;rect&gt;</kbd>"
},
{
"type": "element_exists",
"value": "line",
"message": "Add a <kbd>&lt;line&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;line&gt;</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",
"title": "Multiple Shapes",
"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.",
"task": "Create a simple face:<br>1. An <kbd>&lt;svg&gt;</kbd> (200x200)<br>2. A large <kbd>&lt;circle&gt;</kbd> for the face<br>3. Two small <kbd>&lt;circle&gt;</kbd> elements for eyes<br>4. A <kbd>&lt;line&gt;</kbd> for the smile",
"title": "Кілька фігур",
"description": "Комбінуйте фігури для створення простої графіки! Додайте <kbd>stroke</kbd> для контурів та <kbd>stroke-width</kbd> для товщини.<br><br>Використовуйте <kbd>fill=\"none\"</kbd> для порожніх фігур. Фігури накладаються в порядку - пізніші елементи з'являються зверху.",
"task": "Створіть просте обличчя:<br>1. Елемент <kbd>&lt;svg&gt;</kbd> (200x200)<br>2. Велике <kbd>&lt;circle&gt;</kbd> для обличчя<br>3. Два маленьких <kbd>&lt;circle&gt;</kbd> для очей<br>4. Елемент <kbd>&lt;line&gt;</kbd> для посмішки",
"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); }",
"sandboxCSS": "",
"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",
"validations": [
{
"type": "element_exists",
"value": "svg",
"message": "Add an <kbd>&lt;svg&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;svg&gt;</kbd>"
},
{
"type": "element_count",
"value": { "selector": "circle", "min": 3 },
"message": "Add at least 3 circles (1 face + 2 eyes)"
"message": "Додайте принаймні 3 кола (1 обличчя + 2 ока)"
},
{
"type": "element_exists",
"value": "line",
"message": "Add a <kbd>&lt;line&gt;</kbd> for the smile"
"message": "Додайте <kbd>&lt;line&gt;</kbd> для посмішки"
}
]
}