diff --git a/lessons/ar/00-welcome.json b/lessons/ar/00-welcome.json index 459920c..64beb21 100644 --- a/lessons/ar/00-welcome.json +++ b/lessons/ar/00-welcome.json @@ -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": "Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required!

What you'll learn:
HTML - Semantic elements, forms, tables, SVG (HTML Block & Inline, HTML Forms, HTML Tables)
CSS - Selectors, box model, flexbox, animations (CSS Selectors, CSS Box Model, CSS Flexbox)
Responsive Design - Media queries and mobile-first layouts

How it works:
1. Read the task in the left panel
2. Write code in the editor
3. See live results in the preview
4. Get instant feedback with hints

Keyboard shortcuts: Ctrl+Z to undo, Ctrl+Shift+Z to redo

More resources:
HTML over JS - Native HTML vs JavaScript solutions
Web Engineering Mandala - JavaScript technology roadmap", - "task": "Write Hello World", + "title": "ابدأ", + "description": "Code Crispies منصة مجانية ومفتوحة المصدر لتعلم تطوير الويب من خلال التمارين العملية. لا حاجة لحساب!

ما ستتعلمه:
HTML - العناصر الدلالية، النماذج، الجداول، SVG (HTML كتلي وسطري، HTML النماذج، HTML الجداول)
CSS - المحددات، نموذج الصندوق، flexbox، الحركات (CSS المحددات، CSS نموذج الصندوق، CSS Flexbox)
التصميم المتجاوب - استعلامات الوسائط وتخطيطات mobile-first

كيف يعمل:
1. اقرأ المهمة في اللوحة اليسرى
2. اكتب الكود في المحرر
3. شاهد النتائج مباشرة في المعاينة
4. احصل على ملاحظات فورية مع تلميحات

اختصارات لوحة المفاتيح: Ctrl+Z تراجع، Ctrl+Shift+Z إعادة

المزيد من الموارد:
HTML over JS - HTML الأصلي مقابل حلول JavaScript
Web Engineering Mandala - خارطة تقنيات JavaScript", + "task": "اكتب Hello World", "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 Hello World" + "message": "اكتب Hello World" } ] }, { "id": "overview", - "title": "Overview", - "description": "You're ready! Open the menu (☰) to explore all modules.

Recommended learning path:
1. HTML Block & Inline - Understand container vs inline elements
2. HTML Forms - Build interactive forms with validation
3. CSS Selectors - Target elements precisely
4. CSS Box Model - Master padding, margin, borders
5. CSS Flexbox - Create flexible layouts
6. CSS Animations - Add motion and transitions

Tips:
• Use Show Expected to see the target result
• Your progress is saved automatically
• Try Emmet in HTML mode: ul>li*3 + Tab

Open Source:
Gitea (Source) · GitHub (Mirror)
• Made by LibreTECH · Michael Czechowski", - "task": "Click Next to continue", + "title": "نظرة عامة", + "description": "أنت جاهز! افتح القائمة (☰) لاستكشاف جميع الوحدات.

مسار التعلم الموصى به:
1. HTML كتلي وسطري - افهم عناصر الحاوية مقابل السطرية
2. HTML النماذج - أنشئ نماذج تفاعلية مع التحقق
3. CSS المحددات - استهدف العناصر بدقة
4. CSS نموذج الصندوق - أتقن padding، margin، borders
5. CSS Flexbox - أنشئ تخطيطات مرنة
6. CSS الحركات - أضف الحركة والانتقالات

نصائح:
• استخدم إظهار المتوقع لرؤية النتيجة المستهدفة
• يُحفظ تقدمك تلقائياً
• جرب Emmet في وضع HTML: ul>li*3 + Tab

مفتوح المصدر:
Gitea (Source) · GitHub (Mirror)
• صُنع بواسطة LibreTECH · Michael Czechowski", + "task": "انقر التالي للمتابعة", "previewHTML": "

Hello World! 🌍

Hallo Welt!

Bonjour le monde!

¡Hola Mundo!

Ciao Mondo!

Olá Mundo!

こんにちは世界!

你好世界!

안녕 세상!

Привет мир!

שלום עולם!

مرحبا بالعالم!

", "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": "انقر التالي للمتابعة" } ] }, diff --git a/lessons/ar/01-box-model.json b/lessons/ar/01-box-model.json index d525bcf..0b9ca38 100644 --- a/lessons/ar/01-box-model.json +++ b/lessons/ar/01-box-model.json @@ -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 padding to 1rem to create space between the content and border.", - "previewHTML": "
Box Model Components
", - "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: lavender; border: 2px dashed slategray; }", + "title": "Padding", + "description": "كل عنصر في CSS هو صندوق بأربع طبقات: المحتوى، الحشو (padding)، الحدود، والهامش. Padding يخلق مساحة تنفس بين محتواك وحافة الصندوق.

بدون padding، يضغط النص بشكل محرج على الحدود. Padding يجعل المحتوى قابلاً للقراءة ومتوازناً بصرياً.

.card {\n  padding: 1rem;\n}
", + "task": "بطاقة الملف الشخصي هذه تبدو ضيقة. أضف padding: 1rem ليكون للنص مجال للتنفس.", + "previewHTML": "

Sarah Chen

Frontend Developer

", + "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 padding: 1rem" + "message": "اضبط padding: 1rem" } ] }, { "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 border to 2px solid darkslategray.", - "previewHTML": "
This box needs a border
", - "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: mintcream; padding: 1rem; }", + "title": "Borders", + "description": "الحدود تنشئ حدوداً مرئية حول العناصر. اختصار border يقبل ثلاث قيم: العرض، النمط، واللون.

الأنماط الشائعة: solid، dashed، dotted، none", + "task": "أضف لمسة يسارية خفيفة للبطاقة باستخدام border-left: 4px solid steelblue.", + "previewHTML": "

Sarah Chen

Frontend Developer

", + "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 border: 2px solid darkslategray", + "value": "border-left:\\s*4px\\s+solid\\s+steelblue", + "message": "اضبط border-left: 4px solid steelblue", "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 margin to 1rem to create space between this element and its neighbors.", - "previewHTML": "
This box needs margins
Adjacent element
", - "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": "الهوامش تنشئ مساحة خارج العنصر، تفصله عن جيرانه. بينما يدفع padding المحتوى للداخل، الهوامش تدفع العناصر الأخرى بعيداً.", + "task": "أضف مساحة بين بطاقتي الملف الشخصي هاتين باستخدام margin-bottom: 1rem على .card.", + "previewHTML": "

Sarah Chen

Frontend Developer

Alex Rivera

UX Designer

", + "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 margin: 1rem" + "value": { "property": "margin-bottom", "expected": "1rem" }, + "message": "اضبط margin-bottom: 1rem" } ] }, { "id": "box-model-4", - "title": "Box Sizing: Border-Box", - "description": "The box-sizing property determines how element dimensions are calculated. The default content-box excludes padding and border from width/height, while border-box includes them, making layout calculations more intuitive.", - "task": "Set box-sizing to border-box so padding and border are included in the width.", - "previewHTML": "
Content-box (default)
Border-box
", - "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": "افتراضياً، width يحدد فقط عرض المحتوى. Padding والحدود تُضاف للمجموع. هذا يسبب مشاكل في التخطيط.

box-sizing: border-box يشمل padding والحدود في العرض، مما يجعل التحجيم متوقعاً. معظم المطورين يطبقون هذا على جميع العناصر.", + "task": "كلا البطاقتين لهما width: 200px. اليسرى تستخدم التحجيم الافتراضي (content-box)، مما يجعلها أعرض من المتوقع. أصلح البطاقة اليمنى باستخدام box-sizing: border-box.", + "previewHTML": "
Content-box
Border-box
", + "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .demo { display: flex; gap: 1rem; } .card { width: 200px; padding: 1rem; border: 4px solid steelblue; background: white; border-radius: 8px; }", "sandboxCSS": "", - "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 box-sizing: border-box" + "message": "اضبط box-sizing: border-box" } ] }, { "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 margin-bottom to 2rem. Notice the space between paragraphs equals 2rem (not 3rem) due to margin collapse.", - "previewHTML": "

This paragraph has a bottom margin.

This paragraph has a top margin of 1rem.

", - "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 قيم:
• قيمة واحدة: جميع الجوانب
• قيمتان: عمودي | أفقي
• 4 قيم: أعلى | يمين | أسفل | يسار", + "task": "هذا الزر يحتاج مساحة أفقية أكثر من العمودية. اضبط padding: 8px 1rem (8px أعلى/أسفل، 1rem يسار/يمين).", + "previewHTML": "", + "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 margin-bottom: 2rem" + "type": "regex", + "value": "padding:\\s*8px\\s+1rem", + "message": "اضبط padding: 8px 1rem", + "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 margin to 1rem 2rem for 1rem top/bottom and 2rem left/right.", - "previewHTML": "
This box needs margins: 1rem top/bottom, 2rem left/right
", - "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. نمط شائع هو توسيط عناصر الكتلة أفقياً باستخدام margin: 0 auto.", + "task": "وسّط هذه البطاقة أفقياً. اضبط margin: 0 auto لحساب هوامش يسار/يمين متساوية تلقائياً.", + "previewHTML": "

Sarah Chen

Frontend Developer

", + "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 margin: 1rem 2rem", + "value": "margin:\\s*0\\s+auto", + "message": "اضبط margin: 0 auto", "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 padding to 2rem to add equal padding on all sides.", - "previewHTML": "
This box needs equal padding on all sides
", - "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .padded { background-color: papayawhip; border: 2px solid orange; }", + "title": "Border Radius", + "description": "على الرغم من أنه ليس جزءاً من نموذج الصندوق الكلاسيكي، border-radius يُدوّر زوايا صندوق حدود العنصر. استخدم 50% على عنصر مربع لإنشاء دائرة.", + "task": "اجعل صورة الأفاتار دائرية باستخدام border-radius: 50%.", + "previewHTML": "
\"Avatar\"

Sarah Chen

Frontend Developer

", + "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 padding: 2rem" + "value": { "property": "border-radius", "expected": "50%" }, + "message": "اضبط border-radius: 50%" } ] }, { "id": "box-model-8", - "title": "Border on Specific Sides", - "description": "For granular control, you can target specific sides with border-top, border-right, border-bottom, or border-left.", - "task": "Set border-bottom to 4px solid dodgerblue.", - "previewHTML": "
This element needs only a bottom border
", - "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .line { padding: 1rem; background-color: aliceblue; }", + "title": "Complete Card", + "description": "لنجمع كل شيء معاً. بطاقة الإشعار هذه تحتاج تنسيقاً لتبدو احترافية.", + "task": "نسّق الإشعار: أضف padding: 1rem، border-left: 4px solid coral، وborder-radius: 4px.", + "previewHTML": "
New message

You have 3 unread notifications

", + "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": "اضبط padding: 1rem" + }, { "type": "regex", - "value": "border-bottom:\\s*4px\\s+solid\\s+dodgerblue", - "message": "Set border-bottom: 4px solid dodgerblue", + "value": "border-left:\\s*4px\\s+solid\\s+coral", + "message": "اضبط border-left: 4px solid coral", "options": { "caseSensitive": false } + }, + { + "type": "property_value", + "value": { "property": "border-radius", "expected": "4px" }, + "message": "اضبط border-radius: 4px" } ] } diff --git a/lessons/ar/05-units-variables.json b/lessons/ar/05-units-variables.json index 67a9272..1217140 100644 --- a/lessons/ar/05-units-variables.json +++ b/lessons/ar/05-units-variables.json @@ -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 width of .box to 80% and max-width to 37.5rem.", - "previewHTML": "
Resize me!
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { background: #f5f5f5; padding: 1rem; }", + "title": "Relative Units", + "description": "يقدم CSS نوعين من الوحدات: مطلقة (مثل px) ونسبية (مثل % و rem). الوحدات النسبية تتكيف مع سياقها، مما يجعل التخطيطات مرنة وسهلة الوصول.

الوحدات النسبية الشائعة:
% – نسبة للعنصر الأب
rem – نسبة لحجم خط الجذر (عادة 16px)
em – نسبة لحجم خط العنصر

نمط شائع للمحتوى القابل للقراءة: اضبط width: 100% لملء المساحة المتاحة، ثم max-width: 40rem لتحديد طول السطر للقراءة.", + "task": "نص هذه المقالة عريض جداً على الشاشات الكبيرة. أضف max-width: 40rem للحصول على عرض قراءة مثالي.", + "previewHTML": "

The Art of Typography

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.

The ideal line length is 45-75 characters per line. At typical font sizes, this works out to roughly 40rem maximum width.

", + "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 width property", "options": { "caseSensitive": false } }, - { "type": "property_value", "value": { "property": "width", "expected": "80%" }, "message": "Set width to 80%" }, - { "type": "contains", "value": "max-width", "message": "Use max-width property", "options": { "caseSensitive": false } }, { "type": "property_value", - "value": { "property": "max-width", "expected": "37.5rem" }, - "message": "Set max-width to 37.5rem" + "value": { "property": "max-width", "expected": "40rem" }, + "message": "اضبط max-width: 40rem" } ] }, { "id": "units-2", - "title": "CSS Custom Properties", - "description": "Define and reuse variables (--custom properties) to centralize your theme values.", - "task": "Create a --main-color variable in :root with #6200ee and apply it as the border color on .themed.", - "previewHTML": "
Variable Box
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .themed { padding: 1rem; border: 0.125rem solid #ddd; }", + "title": "CSS Variables", + "description": "الخصائص المخصصة في CSS (المتغيرات) تتيح لك تعريف قيم قابلة لإعادة الاستخدام. عرّفها بـ --اسم واستخدمها بـ var(--اسم). المتغيرات المعرّفة على :root متاحة في كل مكان.", + "task": "عرّف --brand: steelblue في :root، ثم استخدمها كلون background لـ .btn.", + "previewHTML": "
", + "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 --main-color in :root", + "value": "--brand", + "message": "عرّف المتغير --brand", "options": { "caseSensitive": false } }, { "type": "contains", - "value": "var(--main-color)", - "message": "Use var(--main-color)", + "value": "steelblue", + "message": "اضبط القيمة على steelblue", "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 calc() function to combine different units in one expression.", - "task": "Set the width of .sized to calc(100% - 2rem) and min-height to calc(10vh + 1rem).", - "previewHTML": "
Calc Demo
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sized { background: #e8f5e9; padding: 1rem; }", + "title": "calc() Function", + "description": "دالة calc() تتيح لك خلط وحدات مختلفة في الحسابات. هذا ضروري للتخطيطات التي تجمع بين الأحجام الثابتة والمرنة، مثل تخطيط الشريط الجانبي.", + "task": "المحتوى الرئيسي يجب أن يملأ المساحة المتبقية بعد الشريط الجانبي 200px. اضبط width: calc(100% - 200px) على .main.", + "previewHTML": "

Main Content

This area should fill the remaining width after accounting for the fixed-width sidebar.

", + "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 calc() function", "options": { "caseSensitive": false } }, { "type": "regex", - "value": "width:\\s*calc\\(100% - 2rem\\)", - "message": "Width should be calc(100% - 2rem)", - "options": { "caseSensitive": false } - }, - { - "type": "regex", - "value": "min-height:\\s*calc\\(10vh \\+ 1rem\\)", - "message": "Min-height should be calc(10vh + 1rem)", + "value": "width:\\s*calc\\(\\s*100%\\s*-\\s*200px\\s*\\)", + "message": "اضبط width: calc(100% - 200px)", "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 .view a width of 50vw and height of 20vh.", - "previewHTML": "
Viewport Box
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .view { background: #ffe0b2; }", + "title": "Viewport Units", + "description": "وحدات العرض تحدد حجم العناصر نسبة لنافذة المتصفح:
vw – 1% من عرض العرض
vh – 1% من ارتفاع العرض

هذه مثالية للأقسام بملء الشاشة مثل لافتات hero.", + "task": "اجعل قسم hero هذا يملأ ارتفاع العرض بضبط min-height: 100vh.", + "previewHTML": "

Welcome

Scroll down to explore

", + "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 vw unit", "options": { "caseSensitive": false } }, - { "type": "contains", "value": "vh", "message": "Use vh unit", "options": { "caseSensitive": false } }, - { "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Set width to 50vw" }, - { "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Set height to 20vh" } + { + "type": "property_value", + "value": { "property": "min-height", "expected": "100vh" }, + "message": "اضبط min-height: 100vh" + } ] } ] diff --git a/lessons/ar/06-transitions-animations.json b/lessons/ar/06-transitions-animations.json index 5f8280d..9c52658 100644 --- a/lessons/ar/06-transitions-animations.json +++ b/lessons/ar/06-transitions-animations.json @@ -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 transition to properties for smooth changes on state changes.

transition: property duration;\n/* e.g. transition: background-color 0.3s; */
", - "task": "Add transition: background-color 0.3s to .btn so the color fades smoothly on hover.", + "description": "تعلم كيفية تطبيق transition على الخصائص للتغييرات السلسة عند تغيير الحالة.

transition: property duration;\n/* مثال: transition: background-color 0.3s; */
", + "task": "أضف transition: background-color 0.3s ليتغير اللون بسلاسة عند التمرير.", "previewHTML": "", "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 transition property", + "message": "استخدم خاصية transition", "options": { "caseSensitive": false } }, { "type": "regex", "value": "transition:\\s*background-color\\s*0\\.3s", - "message": "Set transition: background-color 0.3s", + "message": "اضبط transition: background-color 0.3s", "options": { "caseSensitive": false } } ] @@ -36,8 +36,8 @@ { "id": "transitions-2", "title": "Timing Funcs", - "description": "Explore easing functions like ease, linear, ease-in, ease-out to control animation pacing.", - "task": "Set transition-timing-function to ease-in-out on .btn.", + "description": "استكشف دوال التسهيل مثل ease، linear، ease-in، ease-out للتحكم في إيقاع الحركة.", + "task": "اضبط transition-timing-function على ease-in-out.", "previewHTML": "", "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 transition-timing-function", + "message": "استخدم transition-timing-function", "options": { "caseSensitive": false } }, { "type": "property_value", "value": { "property": "transition-timing-function", "expected": "ease-in-out" }, - "message": "Set timing to ease-in-out" + "message": "اضبط التوقيت على ease-in-out" } ] }, { "id": "transitions-3", "title": "Keyframes", - "description": "Create named animations using @keyframes and apply them via the animation shorthand.

@keyframes bounce {\n  50% { transform: translateY(-20px); }\n}\n.ball {\n  animation: bounce 1s infinite;\n}
", - "task": "Define a keyframe at 50% with transform: translateY(-20px) and apply animation: bounce 1s infinite to .ball.", + "description": "أنشئ حركات مسماة باستخدام @keyframes وطبّقها عبر اختصار animation.

@keyframes bounce {\n  50% { transform: translateY(-20px); }\n}\n.ball {\n  animation: bounce 1s infinite;\n}
", + "task": "عرّف keyframe عند 50% مع transform: translateY(-20px) وطبّق animation: bounce 1s infinite على .ball.", "previewHTML": "
", "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 @keyframes bounce", + "message": "عرّف @keyframes bounce", "options": { "caseSensitive": false } }, { "type": "regex", "value": "50%.*transform: translateY\\(-20px\\)", - "message": "At 50%, use transform: translateY(-20px)", + "message": "عند 50%، استخدم transform: translateY(-20px)", "options": { "caseSensitive": false } }, { "type": "contains", "value": "animation", - "message": "Use animation property on .ball", + "message": "استخدم خاصية animation على .ball", "options": { "caseSensitive": false } }, { "type": "regex", "value": "animation:.*bounce.*1s.*infinite", - "message": "Apply animation: bounce 1s infinite", + "message": "طبّق animation: bounce 1s infinite", "options": { "caseSensitive": false } } ] @@ -103,8 +103,8 @@ { "id": "transitions-4", "title": "Animation Properties", - "description": "Fine-tune animations with animation-delay, animation-iteration-count, animation-direction, and animation-fill-mode.", - "task": "Apply the pulse animation to .box with animation-name: pulse, animation-duration: 2s, animation-delay: 1s, animation-iteration-count: 2, and animation-fill-mode: forwards.", + "description": "اضبط الحركات بـ animation-delay، animation-iteration-count، animation-direction، و animation-fill-mode.", + "task": "طبّق حركة pulse على .box مع animation-name: pulse، animation-duration: 2s، animation-delay: 1s، animation-iteration-count: 2، و animation-fill-mode: forwards.", "previewHTML": "
Pulse
", "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 animation-name: pulse" + "message": "اضبط animation-name: pulse" }, { "type": "property_value", "value": { "property": "animation-duration", "expected": "2s" }, - "message": "Set animation-duration: 2s" + "message": "اضبط animation-duration: 2s" }, { "type": "property_value", "value": { "property": "animation-delay", "expected": "1s" }, - "message": "Set animation-delay: 1s" + "message": "اضبط animation-delay: 1s" }, { "type": "property_value", "value": { "property": "animation-iteration-count", "expected": "2" }, - "message": "Set animation-iteration-count: 2" + "message": "اضبط animation-iteration-count: 2" }, { "type": "property_value", "value": { "property": "animation-fill-mode", "expected": "forwards" }, - "message": "Set animation-fill-mode: forwards" + "message": "اضبط animation-fill-mode: forwards" } ] } diff --git a/lessons/ar/08-responsive.json b/lessons/ar/08-responsive.json index 0db9b98..5602b28 100644 --- a/lessons/ar/08-responsive.json +++ b/lessons/ar/08-responsive.json @@ -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 @media (max-width: 600px) that changes .panel background to lightcoral.", + "description": "افهم صياغة واستخدامات CSS media queries لتطبيق الأنماط شرطياً بناءً على خصائص viewport.

@media (max-width: 600px) {\n  .panel {\n    background: lightcoral;\n  }\n}
", + "task": "اكتب media query باستخدام @media (max-width: 600px) لتغيير خلفية .panel إلى lightcoral.", "previewHTML": "
Resize the window
", "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 @media (max-width: 600px)", + "message": "استخدم @media (max-width: 600px)", "options": { "caseSensitive": false } }, { "type": "contains", "value": ".panel", - "message": "Target .panel inside the media query", + "message": "استهدف .panel داخل media query", "options": { "caseSensitive": false } }, { "type": "property_value", "value": { "property": "background", "expected": "lightcoral" }, - "message": "Set background: lightcoral", + "message": "اضبط background: lightcoral", "options": { "exact": false } } ] @@ -42,8 +42,8 @@ { "id": "responsive-2", "title": "Fluid Type", - "description": "Use relative units like vw to make font sizes scale with the viewport width.", - "task": "Set font-size: 5vw on .text so it scales as the viewport changes.", + "description": "استخدم وحدات نسبية مثل vw لجعل أحجام الخطوط تتناسب مع عرض viewport.", + "task": "اضبط font-size: 5vw لتتغير مع viewport.", "previewHTML": "

Fluid Typography

", "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 font-size: 5vw" } + { "type": "property_value", "value": { "property": "font-size", "expected": "5vw" }, "message": "اضبط font-size: 5vw" } ] }, { "id": "responsive-3", - "title": "Flex Grids", - "description": "Combine CSS Grid with auto-fit or auto-fill for responsive column layouts.", - "task": "Add display: grid, grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)), and gap: 1rem to .cards.", - "previewHTML": "
1
2
3
4
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .cards > div { background: #d1c4e9; padding: 1rem; }", + "title": "Responsive Grid", + "description": "ادمج CSS Grid مع auto-fit أو auto-fill لتخطيطات أعمدة متجاوبة تضبط عدد الأعمدة تلقائياً بناءً على المساحة المتاحة.", + "task": "أضف display: grid و grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) و gap: 1rem.", + "previewHTML": "

Fast

Lightning quick load times

Secure

Enterprise-grade security

Reliable

99.9% uptime guaranteed

Support

24/7 customer service

", + "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 display: grid" + "message": "اضبط display: grid" }, { "type": "regex", "value": "repeat\\(auto-fit,\\s*minmax\\(200px,\\s*1fr\\)\\)", - "message": "Use repeat(auto-fit, minmax(200px, 1fr))", + "message": "استخدم repeat(auto-fit, minmax(200px, 1fr))", "options": { "caseSensitive": false } }, { "type": "property_value", "value": { "property": "gap", "expected": "1rem" }, - "message": "Set gap: 1rem" + "message": "اضبط gap: 1rem" } ] }, { "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 @media (min-width: 768px) that sets .sidebar width to 250px.", + "description": "اتبع نهج mobile-first بكتابة أنماط أساسية للشاشات الصغيرة وتحسينها لـ viewports أكبر.", + "task": "اكتب media query باستخدام @media (min-width: 768px) لضبط عرض .sidebar إلى 250px.", "previewHTML": "", "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 @media (min-width: 768px)", + "message": "استخدم @media (min-width: 768px)", "options": { "caseSensitive": false } }, { "type": "contains", "value": ".sidebar", - "message": "Target .sidebar inside media query", + "message": "استهدف .sidebar في media query", "options": { "caseSensitive": false } }, { "type": "property_value", "value": { "property": "width", "expected": "250px" }, - "message": "Set width: 250px", + "message": "اضبط width: 250px", "options": { "exact": false } } ] diff --git a/lessons/ar/20-html-elements.json b/lessons/ar/20-html-elements.json index 4d8553a..c1c6c16 100644 --- a/lessons/ar/20-html-elements.json +++ b/lessons/ar/20-html-elements.json @@ -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:

Block elements (containers) start on a new line and take full width. Examples: <div>, <p>, <h1>, <section>

Inline elements flow within text and only take needed width. Examples: <span>, <a>, <strong>, <em>", - "task": "Wrap the word important with <strong> tags to make it bold. Notice how the paragraph (block) takes full width while strong (inline) flows with text.", + "title": "العناصر الكتلية vs السطرية", + "description": "تنقسم عناصر HTML إلى فئتين رئيسيتين:

العناصر الكتلية (الحاويات) تبدأ في سطر جديد وتأخذ العرض الكامل. أمثلة: <div>, <p>, <h1>, <section>

العناصر السطرية تتدفق داخل النص وتأخذ العرض المطلوب فقط. أمثلة: <span>, <a>, <strong>, <em>", + "task": "أحط الكلمة مهمة بوسوم <strong> لجعلها عريضة. لاحظ كيف يأخذ الفقرة (كتلي) العرض الكامل بينما strong (سطري) يتدفق مع النص.", "previewHTML": "", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; } p { background: #e3f2fd; padding: 10px; } strong { background: #ffecb3; }", "sandboxCSS": "", - "initialCode": "

This is a paragraph with an important word.

", - "solution": "

This is a paragraph with an important word.

", + "initialCode": "

هذه فقرة تحتوي على كلمة مهمة.

", + "solution": "

هذه فقرة تحتوي على كلمة مهمة.

", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "p", - "message": "Add a <p> paragraph element" + "message": "أضف عنصر فقرة <p>" }, { "type": "parent_child", "value": { "parent": "p", "child": "strong" }, - "message": "Wrap the word important with <strong> tags" + "message": "أحط الكلمة مهمة بوسوم <strong>" } ] }, { "id": "semantic-containers", - "title": "Semantic Tags", - "description": "Modern HTML uses semantic containers that describe their content:

<header> - Page or section header
<nav> - Navigation links
<main> - Main content area
<section> - Thematic grouping
<article> - Self-contained content
<footer> - Page or section footer", - "task": "Create a basic page structure:
1. Add a <header> with an <h1> containing the text My Website
2. Add a <main> element with a paragraph saying Welcome to my site!
3. Add a <footer> with a paragraph saying Copyright 2026", + "title": "الوسوم الدلالية", + "description": "يستخدم HTML الحديث حاويات دلالية تصف محتواها:

<header> - رأس الصفحة أو القسم
<nav> - روابط التنقل
<main> - منطقة المحتوى الرئيسي
<section> - تجميع موضوعي
<article> - محتوى مستقل
<footer> - تذييل الصفحة أو القسم", + "task": "أنشئ هيكل صفحة أساسي:
1. أضف <header> مع <h1> يحتوي على النص موقعي
2. أضف عنصر <main> مع فقرة تقول مرحباً بك في موقعي!
3. أضف <footer> مع فقرة تقول Copyright 2026", "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": "
\n

My Website

\n
\n
\n

Welcome to my site!

\n
\n", + "solution": "
\n

موقعي

\n
\n
\n

مرحباً بك في موقعي!

\n
\n", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "header", - "message": "Add a <header> element" + "message": "أضف عنصر <header>" }, { "type": "element_exists", "value": "main", - "message": "Add a <main> element" + "message": "أضف عنصر <main>" }, { "type": "element_exists", "value": "footer", - "message": "Add a <footer> element" + "message": "أضف عنصر <footer>" }, { "type": "parent_child", "value": { "parent": "header", "child": "h1" }, - "message": "Add an <h1> heading inside your header" + "message": "أضف عنوان <h1> داخل header" } ] } diff --git a/lessons/ar/21-html-forms-basic.json b/lessons/ar/21-html-forms-basic.json index e92962f..80bda24 100644 --- a/lessons/ar/21-html-forms-basic.json +++ b/lessons/ar/21-html-forms-basic.json @@ -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 <form> wrapper. Inside, use <label> to describe inputs and <input> for user data entry.

The for attribute on labels should match the id on inputs for accessibility.", - "task": "Create a form with:
1. A <label> with the text Name: and for=\"name\" attribute
2. A text <input> with id=\"name\" and name=\"name\" attributes", + "title": "هيكل النموذج", + "description": "كل نموذج يحتاج غلاف <form>. بداخله، استخدم <label> لوصف الحقول و <input> لإدخال البيانات.

سمة for في التسميات يجب أن تطابق id في الحقول للوصولية.", + "task": "أنشئ نموذجاً مع:
1. <label> بالنص الاسم: وسمة for=\"name\"
2. <input> نصي بسمات id=\"name\" و name=\"name\"", "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": "
\n \n \n
", + "solution": "
\n \n \n
", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "form", - "message": "Wrap everything in a <form> element" + "message": "أحط كل شيء بعنصر <form>" }, { "type": "element_exists", "value": "label", - "message": "Add a <label> for your input" + "message": "أضف <label> لحقلك" }, { "type": "element_exists", "value": "input", - "message": "Add an <input> element" + "message": "أضف عنصر <input>" }, { "type": "attribute_value", "value": { "selector": "label", "attr": "for", "value": null }, - "message": "Add a for attribute to your label" + "message": "أضف سمة for للتسمية" }, { "type": "attribute_value", "value": { "selector": "input", "attr": "id", "value": null }, - "message": "Add an id attribute to your input" + "message": "أضف سمة id لحقلك" } ] }, { "id": "input-types", - "title": "Input Types", - "description": "Different input types provide appropriate keyboards and validation:

type=\"text\" - General text
type=\"email\" - Email with @ validation
type=\"password\" - Hidden characters
type=\"number\" - Numeric keyboard
type=\"tel\" - Phone keyboard", - "task": "Create a login form with two fields:
1. An email field: <label for=\"email\">Email:</label> and <input type=\"email\" id=\"email\">
2. A password field: <label for=\"password\">Password:</label> and <input type=\"password\" id=\"password\">", + "title": "أنواع الحقول", + "description": "أنواع الحقول المختلفة توفر لوحات مفاتيح وتحقق مناسب:

type=\"text\" - نص عام
type=\"email\" - بريد إلكتروني مع تحقق @
type=\"password\" - أحرف مخفية
type=\"number\" - لوحة مفاتيح رقمية
type=\"tel\" - لوحة مفاتيح هاتف", + "task": "أنشئ نموذج تسجيل دخول بحقلين:
1. حقل بريد: <label for=\"email\">البريد:</label> و <input type=\"email\" id=\"email\">
2. حقل كلمة مرور: <label for=\"password\">كلمة المرور:</label> و <input type=\"password\" id=\"password\">", "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": "
\n \n
", - "solution": "
\n \n \n \n \n \n
", + "solution": "
\n \n \n \n \n \n
", "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:

<button type=\"submit\"> - Preferred, flexible content
<input type=\"submit\"> - Simple text-only button

The button text should be action-oriented (e.g., Sign In, 'Register', 'Send').", - "task": "Add a submit button to the form with the text Sign In.", + "title": "زر الإرسال", + "description": "النماذج تحتاج طريقة لإرسال البيانات. استخدم:

<button type=\"submit\"> - مفضل، محتوى مرن
<input type=\"submit\"> - زر نص بسيط

نص الزر يجب أن يكون موجه للعمل (مثل تسجيل الدخول، 'التسجيل'، 'إرسال').", + "task": "أضف زر إرسال للنموذج بالنص تسجيل الدخول.", "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": "
\n \n \n \n \n \n \n
", - "solution": "
\n \n \n \n \n \n \n \n
", + "initialCode": "
\n \n \n \n \n \n \n
", + "solution": "
\n \n \n \n \n \n \n \n
", "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 Sign In" + "value": { "selector": "button", "text": "تسجيل الدخول" }, + "message": "يجب أن يعرض الزر تسجيل الدخول" } ] } diff --git a/lessons/ar/22-html-forms-validation.json b/lessons/ar/22-html-forms-validation.json index 72f4cd1..811fcca 100644 --- a/lessons/ar/22-html-forms-validation.json +++ b/lessons/ar/22-html-forms-validation.json @@ -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 required attribute prevents form submission if the field is empty.

Add it to any input that must be filled:
<input type=\"text\" required>

The browser shows a validation message automatically.", - "task": "Make both the name and email fields required by adding the required attribute.", + "title": "الحقول المطلوبة", + "description": "سمة required تمنع إرسال النموذج إذا كان الحقل فارغاً. يعرض المتصفح رسالة تحقق تلقائياً - بدون JavaScript!

أضفها لأي حقل يجب ملؤه:
<input type=\"text\" required>", + "task": "اجعل كلا الحقلين (الاسم والبريد) مطلوبين بإضافة سمة required لكل حقل.", "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": "
\n \n \n \n \n \n \n \n
", - "solution": "
\n \n \n \n \n \n \n \n
", + "initialCode": "
\n \n \n \n \n \n \n \n
", + "solution": "
\n \n \n \n \n \n \n \n
", "previewContainer": "preview-area", "validations": [ { "type": "attribute_value", "value": { "selector": "input[name='name']", "attr": "required", "value": true }, - "message": "Add the required attribute to the name input" + "message": "أضف required لحقل الاسم" }, { "type": "attribute_value", "value": { "selector": "input[name='email']", "attr": "required", "value": true }, - "message": "Add the required attribute to the email input" - } - ] - }, - { - "id": "input-constraints", - "title": "Constraints", - "description": "Control what users can enter:

minlength / maxlength - Text length limits
min / max - Number range
pattern - Regex pattern matching
placeholder - Hint text (not a label!)", - "task": "Add validation to the password input:
1. Add minlength=\"8\" for minimum length
2. Add maxlength=\"20\" for maximum length
3. Add placeholder=\"Enter password\" 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": "
\n \n \n Must be 8-20 characters\n \n \n
", - "solution": "
\n \n \n Must be 8-20 characters\n \n \n
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "attribute_value", - "value": { "selector": "input[type='password']", "attr": "minlength", "value": "8" }, - "message": "Add minlength=\"8\" to the password input" - }, - { - "type": "attribute_value", - "value": { "selector": "input[type='password']", "attr": "maxlength", "value": "20" }, - "message": "Add maxlength=\"20\" to the password input" - }, - { - "type": "attribute_value", - "value": { "selector": "input[type='password']", "attr": "placeholder", "value": null }, - "message": "Add a placeholder to hint what to enter" - } - ] - }, - { - "id": "complete-registration", - "title": "Full Form", - "description": "Build a complete registration form with all validation concepts:

- Required fields marked with *
- Email validation (use type=\"email\")
- Password with length constraints
- Terms checkbox (required)
- 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": "
\n

Create Account

\n \n \n \n \n \n \n \n \n \n \n \n \n \n
", - "solution": "
\n

Create Account

\n \n \n \n \n \n \n \n \n \n \n \n \n \n
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "attribute_value", - "value": { "selector": "#fullname", "attr": "required", "value": true }, - "message": "Make the full name field required" - }, - { - "type": "attribute_value", - "value": { "selector": "#email", "attr": "type", "value": "email" }, - "message": "Set the email input type=\"email\"" - }, - { - "type": "attribute_value", - "value": { "selector": "#email", "attr": "required", "value": true }, - "message": "Make the email field required" - }, - { - "type": "attribute_value", - "value": { "selector": "#password", "attr": "type", "value": "password" }, - "message": "Set the password input type=\"password\"" - }, - { - "type": "attribute_value", - "value": { "selector": "#password", "attr": "required", "value": true }, - "message": "Make the password field required" - }, - { - "type": "attribute_value", - "value": { "selector": "#password", "attr": "minlength", "value": "8" }, - "message": "Add minlength=\"8\" to password" - }, - { - "type": "attribute_value", - "value": { "selector": "#terms", "attr": "required", "value": true }, - "message": "Make the terms checkbox required" + "message": "أضف required لحقل البريد" } ] } diff --git a/lessons/ar/23-html-details-summary.json b/lessons/ar/23-html-details-summary.json index ff83fc3..85cf859 100644 --- a/lessons/ar/23-html-details-summary.json +++ b/lessons/ar/23-html-details-summary.json @@ -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 <details> element creates a collapsible section. The <summary> provides the clickable label.

Click the summary to toggle the hidden content - no JavaScript needed!", - "task": "Create a <details> element with:
1. A <summary> saying Click to reveal
2. A <p> with the text This content was hidden!", + "title": "أول عنصر تفاعلي", + "description": "عنصر <details> ينشئ قسماً قابلاً للطي. عنصر <summary> يوفر التسمية القابلة للنقر.

انقر على الملخص لإظهار المحتوى المخفي - بدون JavaScript!", + "task": "أنشئ عنصر <details> مع:
1. عنصر <summary> يقول Click to reveal
2. عنصر <p> بالنص This content was hidden!", "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 <details> element" + "message": "أضف عنصر <details>" }, { "type": "element_exists", "value": "summary", - "message": "Add a <summary> inside the details" + "message": "أضف <summary> داخل details" }, { "type": "parent_child", "value": { "parent": "details", "child": "summary" }, - "message": "The <summary> must be inside <details>" + "message": "يجب أن يكون <summary> داخل <details>" }, { "type": "parent_child", "value": { "parent": "details", "child": "p" }, - "message": "Add a <p> inside <details> for the hidden content" + "message": "أضف <p> داخل <details> للمحتوى المخفي" } ] }, { "id": "details-open-attribute", - "title": "Pre-expanded Details", - "description": "By default, <details> is closed. Add the open attribute to show the content initially.

This is a boolean attribute - just add open without a value.", - "task": "Add the open attribute to the <details> element to show the content by default.", + "title": "موسع افتراضياً", + "description": "افتراضياً، <details> مغلق. أضف سمة open لإظهار المحتوى في البداية.

هذه سمة منطقية - فقط أضف open بدون قيمة.", + "task": "أضف سمة open لعنصر <details> لإظهار المحتوى افتراضياً.", "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 open attribute to <details>" + "message": "أضف سمة open إلى <details>" } ] }, { "id": "faq-accordion", - "title": "FAQ Accordion", - "description": "Multiple <details> elements create an accordion-style FAQ. Each question can be expanded independently.

Pro tip: Type details*3>summary+p and press Tab for Emmet expansion. *3 creates 3 elements, > nests inside, + adds siblings.", - "task": "Create an FAQ section with:
1. An <h1> saying Frequently Asked Questions
2. Three <details> elements, each with a question in <summary> and an answer in <p>", + "title": "أكورديون FAQ", + "description": "عناصر <details> المتعددة تنشئ FAQ بأسلوب الأكورديون. كل سؤال يمكن توسيعه بشكل مستقل.

نصيحة: اكتب details*3>summary+p واضغط Tab لتوسيع Emmet. *3 ينشئ 3 عناصر، > يضع بالداخل، + يضيف أشقاء.", + "task": "أنشئ قسم FAQ مع:
1. عنصر <h1> يقول Frequently Asked Questions
2. ثلاثة عناصر <details>، كل واحد بسؤال في <summary> وإجابة في <p>", "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 <h1> heading for the FAQ title" + "message": "أضف عنوان <h1> لعنوان FAQ" }, { "type": "element_count", "value": { "selector": "details", "min": 3 }, - "message": "Create at least 3 <details> elements for the FAQ" + "message": "أنشئ على الأقل 3 عناصر <details> للـ FAQ" }, { "type": "element_count", "value": { "selector": "summary", "min": 3 }, - "message": "Each <details> needs a <summary> for the question" + "message": "كل <details> يحتاج <summary> للسؤال" }, { "type": "element_count", "value": { "selector": "details p", "min": 3 }, - "message": "Each <details> needs a <p> for the answer" + "message": "كل <details> يحتاج <p> للإجابة" } ] } diff --git a/lessons/ar/24-html-progress-meter.json b/lessons/ar/24-html-progress-meter.json index 3a89bc3..5f0a23b 100644 --- a/lessons/ar/24-html-progress-meter.json +++ b/lessons/ar/24-html-progress-meter.json @@ -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 <progress> element shows task completion. Use value for current progress and max for the total.

Note: This is not a self-closing tag! Write <progress>...</progress> with fallback text inside for older browsers.", - "task": "Create a progress bar showing 70% completion:
1. Add a <label> saying Download:
2. Add a <progress> with value=\"70\" and max=\"100\"", + "title": "أشرطة التقدم", + "description": "عنصر <progress> يُظهر إكمال المهمة. استخدم value للتقدم الحالي و max للإجمالي.

ملاحظة: هذا ليس وسماً ذاتي الإغلاق! اكتب <progress>...</progress> مع نص بديل بالداخل للمتصفحات القديمة.", + "task": "أنشئ شريط تقدم يُظهر 70% إكمال:
1. أضف <label> يقول Download:
2. أضف <progress> مع value=\"70\" و max=\"100\"", "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 <progress> element" + "message": "أضف عنصر <progress>" }, { "type": "attribute_value", "value": { "selector": "progress", "attr": "value", "value": "70" }, - "message": "Set value=\"70\" on the progress element" + "message": "عيّن value=\"70\" في عنصر progress" }, { "type": "attribute_value", "value": { "selector": "progress", "attr": "max", "value": "100" }, - "message": "Set max=\"100\" on the progress element" + "message": "عيّن max=\"100\" في عنصر progress" }, { "type": "element_exists", "value": "label", - "message": "Add a <label> for the progress bar" + "message": "أضف <label> لشريط التقدم" } ] }, { "id": "progress-indeterminate", - "title": "Indeterminate Progress", - "description": "When progress is unknown (like loading), omit the value attribute. This creates an animated indeterminate state.

Useful for network requests or processes with unknown duration.", - "task": "Create a loading indicator:
1. Add a <p> saying Loading...
2. Add a <progress> without a value attribute", + "title": "تقدم غير محدد", + "description": "عندما يكون التقدم غير معروف (مثل التحميل)، احذف سمة value. هذا ينشئ حالة متحركة غير محددة.

مفيد لطلبات الشبكة أو العمليات ذات المدة غير المعروفة.", + "task": "أنشئ مؤشر تحميل:
1. أضف <p> يقول Loading...
2. أضف <progress> بدون سمة 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 <progress> element" + "message": "أضف عنصر <progress>" }, { "type": "element_exists", "value": "p", - "message": "Add a <p> with loading text" + "message": "أضف <p> مع نص التحميل" } ] }, { "id": "meter-gauge", - "title": "Meter Gauges", - "description": "The <meter> element displays a scalar value within a range. Use it for measurements like disk space, battery, or ratings.

Set low, high, and optimum to define good/bad ranges - the browser colors it accordingly!", - "task": "Create a battery level meter:
1. Add a <label> saying Battery:
2. Add a <meter> with:
- value=\"0.8\"
- min=\"0\" and max=\"1\"
- low=\"0.2\" and high=\"0.8\"
- optimum=\"1\"", + "title": "مقاييس meter", + "description": "عنصر <meter> يعرض قيمة قياسية ضمن نطاق. استخدمه للقياسات مثل مساحة القرص، البطارية، أو التقييمات.

عيّن low و high و optimum لتحديد النطاقات الجيدة/السيئة - المتصفح يلونها وفقاً لذلك!", + "task": "أنشئ مقياس مستوى البطارية:
1. أضف <label> يقول Battery:
2. أضف <meter> مع:
- value=\"0.8\"
- min=\"0\" و max=\"1\"
- low=\"0.2\" و high=\"0.8\"
- optimum=\"1\"", "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 <meter> element" + "message": "أضف عنصر <meter>" }, { "type": "attribute_value", "value": { "selector": "meter", "attr": "value", "value": "0.8" }, - "message": "Set value=\"0.8\" on the meter" + "message": "عيّن value=\"0.8\" في meter" + }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "min", "value": "0" }, + "message": "عيّن min=\"0\" في meter" + }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "max", "value": "1" }, + "message": "عيّن max=\"1\" في meter" }, { "type": "attribute_value", "value": { "selector": "meter", "attr": "low", "value": "0.2" }, - "message": "Set low=\"0.2\" to define the low threshold" + "message": "عيّن low=\"0.2\" لتحديد العتبة المنخفضة" + }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "high", "value": "0.8" }, + "message": "عيّن high=\"0.8\" لتحديد العتبة العالية" + }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "optimum", "value": "1" }, + "message": "عيّن optimum=\"1\" للإشارة إلى القيمة المثلى" }, { "type": "element_exists", "value": "label", - "message": "Add a <label> for the meter" + "message": "أضف <label> للـ meter" } ] } diff --git a/lessons/ar/25-html-datalist.json b/lessons/ar/25-html-datalist.json index 8d33f10..f110ca2 100644 --- a/lessons/ar/25-html-datalist.json +++ b/lessons/ar/25-html-datalist.json @@ -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 <datalist> element provides autocomplete suggestions for inputs. Connect it using the list attribute on the input matching the datalist's id.

Users can still type freely - suggestions are just helpers!", - "task": "Create a browser selector:
1. Add a <label> saying Browser:
2. Add an <input> with list=\"browsers\"
3. Add a <datalist id=\"browsers\"> with options for Chrome, Firefox, and Safari", + "title": "حقل مع اقتراحات", + "description": "عنصر <datalist> يوفر اقتراحات الإكمال التلقائي للحقول. اربطه باستخدام سمة list في الحقل بما يتطابق مع id قائمة البيانات.

يمكن للمستخدمين الكتابة بحرية - الاقتراحات مجرد مساعدات!", + "task": "أنشئ محدد متصفح:
1. أضف <label> يقول المتصفح:
2. أضف <input> مع list=\"browsers\"
3. أضف <datalist id=\"browsers\"> مع خيارات 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 <datalist> element" + "message": "أضف عنصر <datalist>" }, { "type": "attribute_value", "value": { "selector": "input", "attr": "list", "value": "browsers" }, - "message": "Connect the input to datalist using list=\"browsers\"" + "message": "اربط الحقل بقائمة البيانات باستخدام list=\"browsers\"" }, { "type": "element_count", "value": { "selector": "option", "min": 3 }, - "message": "Add at least 3 <option> elements inside <datalist>" + "message": "أضف على الأقل 3 عناصر <option> داخل <datalist>" }, { "type": "element_exists", "value": "label", - "message": "Add a <label> for the input" + "message": "أضف <label> للحقل" } ] }, { "id": "datalist-countries", - "title": "Country Selector", - "description": "Datalists work great for long lists like countries. Users can type to filter suggestions instantly.

The value attribute is what gets entered, and you can add display text after it.", - "task": "Create a country input:
1. Add a <label> saying Country:
2. Add an <input> with list=\"countries\"
3. Add a <datalist id=\"countries\"> with at least 4 country options", + "title": "محدد الدول", + "description": "قوائم البيانات تعمل بشكل رائع للقوائم الطويلة مثل الدول. يمكن للمستخدمين الكتابة لتصفية الاقتراحات فوراً.

سمة value هي ما يتم إدخاله، ويمكنك إضافة نص عرض بعدها.", + "task": "أنشئ حقل دولة:
1. أضف <label> يقول الدولة:
2. أضف <input> مع list=\"countries\"
3. أضف <datalist id=\"countries\"> مع 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 <datalist> element" + "message": "أضف عنصر <datalist>" }, { "type": "attribute_value", "value": { "selector": "datalist", "attr": "id", "value": "countries" }, - "message": "Set id=\"countries\" on the datalist" + "message": "عيّن id=\"countries\" في قائمة البيانات" }, { "type": "attribute_value", "value": { "selector": "input", "attr": "list", "value": "countries" }, - "message": "Connect the input using list=\"countries\"" + "message": "اربط الحقل باستخدام list=\"countries\"" }, { "type": "element_count", "value": { "selector": "option", "min": 4 }, - "message": "Add at least 4 country options" + "message": "أضف على الأقل 4 خيارات دول" } ] } diff --git a/lessons/ar/27-html-dialog.json b/lessons/ar/27-html-dialog.json index 4b721c7..28e07ec 100644 --- a/lessons/ar/27-html-dialog.json +++ b/lessons/ar/27-html-dialog.json @@ -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 <dialog> element creates a native modal. Add the open attribute to show it.

Use <form method=\"dialog\"> inside to close it when the form submits - no JavaScript needed!", - "task": "Create a dialog with:
1. The open attribute to show it
2. An <h2> saying Welcome!
3. A <p> with a greeting message
4. A <form method=\"dialog\"> with a close button", + "title": "فتح مربع الحوار", + "description": "عنصر <dialog> ينشئ نافذة نموذجية أصلية. أضف سمة open لعرضها.

استخدم <form method=\"dialog\"> بداخلها لإغلاقها عند إرسال النموذج - بدون JavaScript!", + "task": "أنشئ مربع حوار مع:
1. سمة open لعرضه
2. عنصر <h2> يقول أهلاً!
3. عنصر <p> مع رسالة ترحيب
4. عنصر <form method=\"dialog\"> مع زر إغلاق", "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 <dialog> element" + "message": "أضف عنصر <dialog>" }, { "type": "attribute_value", "value": { "selector": "dialog", "attr": "open", "value": true }, - "message": "Add the open attribute to show the dialog" + "message": "أضف سمة open لعرض مربع الحوار" }, { "type": "element_exists", "value": "dialog h2", - "message": "Add an <h2> heading inside the dialog" + "message": "أضف عنوان <h2> داخل مربع الحوار" }, { "type": "element_exists", "value": "form[method='dialog']", - "message": "Add a <form method=\"dialog\"> for closing" + "message": "أضف <form method=\"dialog\"> للإغلاق" }, { "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 method=\"dialog\" makes the form close the dialog on submit instead of sending data.

This pattern is perfect for confirmation dialogs, quick inputs, or settings panels.", - "task": "Create a confirmation dialog:
1. Add open to show it
2. An <h2> saying Confirm Delete
3. A <p> asking Are you sure?
4. A <form method=\"dialog\"> with Cancel and Delete buttons", + "title": "حوار + نموذج", + "description": "مربعات الحوار يمكن أن تحتوي على نماذج كاملة. method=\"dialog\" يجعل النموذج يغلق مربع الحوار عند الإرسال بدلاً من إرسال البيانات.

هذا النمط مثالي لحوارات التأكيد، المدخلات السريعة، أو لوحات الإعدادات.", + "task": "أنشئ مربع حوار تأكيد:
1. أضف open لعرضه
2. عنصر <h2> يقول تأكيد الحذف
3. عنصر <p> يسأل هل أنت متأكد؟
4. عنصر <form method=\"dialog\"> مع أزرار إلغاء وحذف", "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 <dialog> with the open attribute" + "message": "أضف <dialog> مع سمة 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 <form method=\"dialog\"> for the buttons" + "message": "أضف <form method=\"dialog\"> للأزرار" }, { "type": "element_count", "value": { "selector": "dialog button", "min": 2 }, - "message": "Add at least 2 buttons (Cancel and Confirm)" + "message": "أضف على الأقل زرين (إلغاء وتأكيد)" } ] } diff --git a/lessons/ar/28-html-forms-fieldset.json b/lessons/ar/28-html-forms-fieldset.json index add7ef9..40176b7 100644 --- a/lessons/ar/28-html-forms-fieldset.json +++ b/lessons/ar/28-html-forms-fieldset.json @@ -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 <fieldset> element groups related form controls together. Add a <legend> as the first child to give the group a title.

This helps with accessibility and visual organization of complex forms.", - "task": "Create a form with a fieldset:
1. A <form> element
2. A <fieldset> inside
3. A <legend> saying Personal Info
4. Two labeled inputs for name and email", + "title": "التجميع مع Fieldset", + "description": "عنصر <fieldset> يجمع عناصر التحكم المرتبطة في النموذج معاً. أضف <legend> كأول عنصر فرعي لإعطاء المجموعة عنواناً.

هذا يساعد في إمكانية الوصول والتنظيم البصري للنماذج المعقدة.", + "task": "أنشئ نموذجاً مع fieldset:
1. عنصر <form>
2. عنصر <fieldset> بداخله
3. عنصر <legend> يقول المعلومات الشخصية
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 <form> element" + "message": "أضف عنصر <form>" }, { "type": "element_exists", "value": "fieldset", - "message": "Add a <fieldset> inside the form" + "message": "أضف <fieldset> داخل النموذج" }, { "type": "element_exists", "value": "legend", - "message": "Add a <legend> to title your fieldset" + "message": "أضف <legend> لعنونة مجموعة الحقول" }, { "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 <textarea> element creates a multi-line text input, perfect for longer content like messages or descriptions.

Use rows and cols attributes to set default size.", - "task": "Create a contact form:
1. A <fieldset> with <legend> Contact Us
2. A labeled <input> for email
3. A labeled <textarea> for the message
4. A submit <button>", + "title": "إضافة Textarea", + "description": "عنصر <textarea> ينشئ حقل إدخال نص متعدد الأسطر، مثالي للمحتوى الطويل مثل الرسائل أو الأوصاف.

استخدم سمات rows و cols لتعيين الحجم الافتراضي.", + "task": "أنشئ نموذج اتصال:
1. عنصر <fieldset> مع <legend> تواصل معنا
2. حقل <input> مُعنون للبريد الإلكتروني
3. حقل <textarea> مُعنون للرسالة
4. زر <button> للإرسال", "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 <fieldset> element" + "message": "أضف عنصر <fieldset>" }, { "type": "element_exists", "value": "legend", - "message": "Add a <legend> element" + "message": "أضف عنصر <legend>" }, { "type": "element_exists", "value": "textarea", - "message": "Add a <textarea> for the message" + "message": "أضف <textarea> للرسالة" }, { "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 <fieldset> elements to organize different sections.

This improves usability for long forms like registration or checkout pages.", - "task": "Create a registration form with 2 fieldsets:
1. Account Info with username and password inputs
2. Preferences with a textarea for bio
3. A submit button outside the fieldsets", + "title": "مجموعات حقول متعددة", + "description": "النماذج المعقدة يمكنها استخدام عناصر <fieldset> متعددة لتنظيم أقسام مختلفة.

هذا يحسن قابلية الاستخدام للنماذج الطويلة مثل التسجيل أو الدفع.", + "task": "أنشئ نموذج تسجيل مع 2 مجموعات حقول:
1. معلومات الحساب مع حقول اسم المستخدم وكلمة المرور
2. التفضيلات مع textarea للسيرة الذاتية
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": "أضف على الأقل حقلي إدخال" } ] } diff --git a/lessons/ar/30-html-tables.json b/lessons/ar/30-html-tables.json index f4d29cf..88234d7 100644 --- a/lessons/ar/30-html-tables.json +++ b/lessons/ar/30-html-tables.json @@ -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 <table> with <tr> for rows. Inside rows, use <th> for headers and <td> for data cells.

The <caption> element provides an accessible title for the table.", - "task": "Create a simple table with:
1. A <caption> saying Fruit Prices
2. A header row with Fruit and Price columns
3. At least 2 data rows", + "title": "جداول البيانات", + "description": "الجداول تعرض البيانات المنظمة في صفوف وأعمدة. استخدم <table> كحاوية، <tr> للصفوف، <th> لخلايا الرأس و <td> لخلايا البيانات.

أضف <caption> لعنوان قابل للوصول يصف محتوى الجدول.", + "task": "أنشئ جدول أسعار:
1. عنصر <caption> يقول Pricing
2. صف رأس مع Plan و Price
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": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n
Fruit Prices
FruitPrice
Apple$1.50
Banana$0.75
", + "solution": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n
Pricing
PlanPrice
Basic$9
Pro$29
", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "table", - "message": "Add a <table> element" + "message": "أضف عنصر <table>" }, { "type": "element_exists", "value": "caption", - "message": "Add a <caption> for the table title" + "message": "أضف <caption> لعنوان الجدول" }, { "type": "element_count", "value": { "selector": "th", "min": 2 }, - "message": "Add at least 2 header cells (th)" + "message": "أضف خلايا رأس (<th>) لـ 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 <thead> to group header rows and <tbody> to group data rows. This helps browsers and assistive technology understand the table structure.

You can also use <tfoot> for footer rows like totals.", - "task": "Create a structured table:
1. A <caption> with Monthly Sales
2. A <thead> with Month and Revenue headers
3. A <tbody> 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": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Monthly Sales
MonthRevenue
January$12,500
February$14,200
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "element_exists", - "value": "table", - "message": "Add a <table> element" - }, - { - "type": "element_exists", - "value": "caption", - "message": "Add a <caption> element" - }, - { - "type": "element_exists", - "value": "thead", - "message": "Add a <thead> for the header section" - }, - { - "type": "element_exists", - "value": "tbody", - "message": "Add a <tbody> 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 <tfoot> to create a footer section for totals or summary data. The footer stays at the bottom even if tbody has many rows.

Combine all sections for a fully structured, accessible table.", - "task": "Create a complete table:
1. A <caption> with Order Summary
2. A <thead> with Item and Price headers
3. A <tbody> with 2 items
4. A <tfoot> 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": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Order Summary
ItemPrice
Widget$25.00
Gadget$35.00
Total$60.00
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "element_exists", - "value": "table", - "message": "Add a <table> element" - }, - { - "type": "element_exists", - "value": "caption", - "message": "Add a <caption> element" - }, - { - "type": "element_exists", - "value": "thead", - "message": "Add a <thead> section" - }, - { - "type": "element_exists", - "value": "tbody", - "message": "Add a <tbody> section" - }, - { - "type": "element_exists", - "value": "tfoot", - "message": "Add a <tfoot> 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 صفوف بيانات)" } ] } diff --git a/lessons/ar/31-html-marquee.json b/lessons/ar/31-html-marquee.json index ec3a486..b6a1da5 100644 --- a/lessons/ar/31-html-marquee.json +++ b/lessons/ar/31-html-marquee.json @@ -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 <marquee> element creates scrolling text - a classic from the early web! While deprecated, it still works in most browsers.

Note: For production, use CSS animations instead. But for learning and fun, marquee is great!", - "task": "Create a simple marquee:
1. Add a <marquee> element
2. Put some text inside like Welcome to my website!", + "title": "النص المتحرك", + "description": "عنصر <marquee> ينشئ نصاً متحركاً - كلاسيكي من الويب القديم! رغم أنه قديم، لا يزال يعمل في معظم المتصفحات.

ملاحظة: للإنتاج، استخدم حركات CSS بدلاً منه. لكن للتعلم والمرح، marquee رائع!", + "task": "أنشئ marquee بسيط:
1. أضف عنصر <marquee>
2. ضع نصاً بداخله مثل مرحباً بك في موقعي!", "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 <marquee> element" + "message": "أضف عنصر <marquee>" } ] }, { "id": "marquee-direction", - "title": "Direction & Behavior", - "description": "Control the marquee with attributes:
direction: left, right, up, down
behavior: scroll (default), slide (stops at edge), alternate (bounces)
scrollamount: speed (default is 6)", - "task": "Create a bouncing marquee:
1. Add a <marquee> element
2. Set behavior=\"alternate\" to make it bounce
3. Add some fun text", + "title": "الاتجاه والسلوك", + "description": "تحكم في marquee باستخدام السمات:
direction: left، right، up، down
behavior: scroll (افتراضي)، slide (يتوقف عند الحافة)، alternate (يرتد)
scrollamount: السرعة (الافتراضي 6)", + "task": "أنشئ marquee مرتداً:
1. أضف عنصر <marquee>
2. اضبط behavior=\"alternate\" ليرتد
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 <marquee> element" + "message": "أضف عنصر <marquee>" }, { "type": "attribute_value", "value": { "selector": "marquee", "attr": "behavior", "value": "alternate" }, - "message": "Add behavior=\"alternate\" to make it bounce" + "message": "أضف behavior=\"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!

Remember: This is deprecated HTML. Modern sites use CSS animations, but marquee is great for understanding web history.", - "task": "Create a news ticker:
1. A <marquee> with direction=\"left\"
2. Set scrollamount=\"5\" for smooth scrolling
3. Add a breaking news headline inside", + "title": "شريط أخبار كلاسيكي", + "description": "اجمع عدة سمات marquee لتأثير شريط أخبار كلاسيكي. يمكنك حتى وضع عناصر متعددة بداخله!

تذكر: هذا HTML قديم. المواقع الحديثة تستخدم حركات CSS، لكن marquee رائع لفهم تاريخ الويب.", + "task": "أنشئ شريط أخبار:
1. عنصر <marquee> مع direction=\"left\"
2. اضبط scrollamount=\"5\" للتمرير السلس
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 <marquee> element" + "message": "أضف عنصر <marquee>" }, { "type": "attribute_value", "value": { "selector": "marquee", "attr": "direction", "value": "left" }, - "message": "Add direction=\"left\" for horizontal scrolling" + "message": "أضف direction=\"left\" للتمرير الأفقي" }, { "type": "attribute_value", "value": { "selector": "marquee", "attr": "scrollamount", "value": "5" }, - "message": "Add scrollamount=\"5\" for smooth speed" + "message": "أضف scrollamount=\"5\" لسرعة سلسة" } ] } diff --git a/lessons/ar/32-html-svg.json b/lessons/ar/32-html-svg.json index 59b9dbb..6621e0b 100644 --- a/lessons/ar/32-html-svg.json +++ b/lessons/ar/32-html-svg.json @@ -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 <svg> element is the container, with width and height attributes.

Use <circle> with cx, cy (center) and r (radius) to draw circles.", - "task": "Create an SVG with a circle:
1. An <svg> with width=\"200\" and height=\"200\"
2. A <circle> centered at (100,100) with radius 50
3. Add a fill color", + "title": "رسم الدوائر", + "description": "SVG (Scalable Vector Graphics) يتيح لك رسم الأشكال مباشرة في HTML. عنصر <svg> هو الحاوية بسمات width و height.

استخدم <circle> مع cx و cy (المركز) و r (نصف القطر) لرسم الدوائر.", + "task": "أنشئ SVG مع دائرة:
1. عنصر <svg> بـ width=\"200\" و height=\"200\"
2. عنصر <circle> متمركز عند (100,100) بنصف قطر 50
3. أضف لون fill", "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": "\n \n", + "solution": "\n \n", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "svg", - "message": "Add an <svg> element" + "message": "أضف عنصر <svg>" }, { "type": "element_exists", "value": "circle", - "message": "Add a <circle> element inside the SVG" + "message": "أضف عنصر <circle> داخل SVG" + }, + { + "type": "attribute_value", + "value": { "selector": "svg", "attr": "width", "value": "200" }, + "message": "عيّن width=\"200\" في SVG" + }, + { + "type": "attribute_value", + "value": { "selector": "svg", "attr": "height", "value": "200" }, + "message": "عيّن height=\"200\" في SVG" }, { "type": "attribute_value", "value": { "selector": "circle", "attr": "cx", "value": "100" }, - "message": "Set cx=\"100\" for the circle's horizontal center" + "message": "عيّن cx=\"100\" للمركز الأفقي للدائرة" }, { "type": "attribute_value", "value": { "selector": "circle", "attr": "cy", "value": "100" }, - "message": "Set cy=\"100\" for the circle's vertical center" + "message": "عيّن cy=\"100\" للمركز العمودي للدائرة" + }, + { + "type": "attribute_value", + "value": { "selector": "circle", "attr": "r", "value": "50" }, + "message": "عيّن r=\"50\" لنصف قطر الدائرة" } ] }, { "id": "svg-rect-line", - "title": "Rectangles & Lines", - "description": "Draw rectangles with <rect> using x, y, width, height.

Draw lines with <line> using x1, y1 (start) and x2, y2 (end). Lines need a stroke color!", - "task": "Create an SVG with:
1. An <svg> (200x150)
2. A <rect> at position (20,20) with size 80x60
3. A <line> from (120,30) to (180,90) with a stroke color", + "title": "المستطيلات والخطوط", + "description": "ارسم المستطيلات باستخدام <rect> مع x و y و width و height.

ارسم الخطوط باستخدام <line> مع x1 و y1 (البداية) و x2 و y2 (النهاية). الخطوط تحتاج لون stroke!", + "task": "أنشئ SVG مع:
1. عنصر <svg> (200x150)
2. عنصر <rect> في الموضع (20,20) بحجم 80x60
3. عنصر <line> من (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": "\n \n \n", + "solution": "\n \n \n", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "svg", - "message": "Add an <svg> element" + "message": "أضف عنصر <svg>" }, { "type": "element_exists", "value": "rect", - "message": "Add a <rect> element" + "message": "أضف عنصر <rect>" }, { "type": "element_exists", "value": "line", - "message": "Add a <line> element" + "message": "أضف عنصر <line>" + }, + { + "type": "attribute_value", + "value": { "selector": "svg", "attr": "width", "value": "200" }, + "message": "عيّن width=\"200\" في SVG" + }, + { + "type": "attribute_value", + "value": { "selector": "svg", "attr": "height", "value": "150" }, + "message": "عيّن height=\"150\" في SVG" + }, + { + "type": "attribute_value", + "value": { "selector": "rect", "attr": "x", "value": "20" }, + "message": "عيّن x=\"20\" في rect" + }, + { + "type": "attribute_value", + "value": { "selector": "rect", "attr": "y", "value": "20" }, + "message": "عيّن y=\"20\" في rect" + }, + { + "type": "attribute_value", + "value": { "selector": "rect", "attr": "width", "value": "80" }, + "message": "عيّن width=\"80\" في rect" + }, + { + "type": "attribute_value", + "value": { "selector": "rect", "attr": "height", "value": "60" }, + "message": "عيّن height=\"60\" في rect" + }, + { + "type": "attribute_value", + "value": { "selector": "line", "attr": "x1", "value": "120" }, + "message": "عيّن x1=\"120\" في line" + }, + { + "type": "attribute_value", + "value": { "selector": "line", "attr": "y1", "value": "30" }, + "message": "عيّن y1=\"30\" في line" + }, + { + "type": "attribute_value", + "value": { "selector": "line", "attr": "x2", "value": "180" }, + "message": "عيّن x2=\"180\" في line" + }, + { + "type": "attribute_value", + "value": { "selector": "line", "attr": "y2", "value": "90" }, + "message": "عيّن y2=\"90\" في line" + }, + { + "type": "contains", + "value": "stroke", + "message": "أضف لون stroke إلى line" } ] }, { "id": "svg-shapes", - "title": "Multiple Shapes", - "description": "Combine shapes to create simple graphics! Add stroke for outlines and stroke-width for thickness.

Use fill=\"none\" for hollow shapes. Shapes stack in order - later elements appear on top.", - "task": "Create a simple face:
1. An <svg> (200x200)
2. A large <circle> for the face
3. Two small <circle> elements for eyes
4. A <line> for the smile", + "title": "أشكال متعددة", + "description": "ادمج الأشكال لإنشاء رسومات بسيطة! أضف stroke للحدود و stroke-width للسمك.

استخدم fill=\"none\" للأشكال المجوفة. الأشكال تتراكم بالترتيب - العناصر اللاحقة تظهر في الأعلى.", + "task": "أنشئ وجهاً بسيطاً:
1. عنصر <svg> (200x200)
2. <circle> كبير للوجه
3. اثنين <circle> صغيرين للعيون
4. عنصر <line> للابتسامة", "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": "\n \n \n \n \n", + "solution": "\n \n \n \n \n", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "svg", - "message": "Add an <svg> element" + "message": "أضف عنصر <svg>" }, { "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 <line> for the smile" + "message": "أضف <line> للابتسامة" } ] } diff --git a/lessons/de/01-box-model.json b/lessons/de/01-box-model.json index 1e4810c..641529c 100644 --- a/lessons/de/01-box-model.json +++ b/lessons/de/01-box-model.json @@ -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 padding auf 1rem, um Abstand zwischen Inhalt und Rahmen zu schaffen.", - "previewHTML": "
Box-Modell Komponenten
", - "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. Padding schafft Freiraum zwischen deinem Inhalt und dem Rand der Box.

Ohne Padding drückt sich Text unangenehm gegen Rahmen. Padding macht Inhalte lesbar und visuell ausgewogen.

.card {\n  padding: 1rem;\n}
", + "task": "Diese Profilkarte sieht beengt aus. Füge padding: 1rem hinzu, damit der Text Platz zum Atmen hat.", + "previewHTML": "

Sarah Chen

Frontend Developer

", + "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 border auf 2px solid darkslategray.", - "previewHTML": "
Diese Box braucht einen Rahmen
", - "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 border-Kurzschreibweise akzeptiert drei Werte: Breite, Stil und Farbe.

Häufige Stile: solid, dashed, dotted, none", + "task": "Füge der Karte einen dezenten linken Akzent hinzu mit border-left: 4px solid steelblue.", + "previewHTML": "

Sarah Chen

Frontend Developer

", + "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 border: 2px solid darkslategray", + "value": "border-left:\\s*4px\\s+solid\\s+steelblue", + "message": "Setze border-left: 4px solid steelblue", "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 margin auf 1rem, um Abstand zum benachbarten Element zu schaffen.", - "previewHTML": "
Diese Box braucht Margins
Benachbartes Element
", - "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 außerhalb 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 margin-bottom: 1rem auf .card.", + "previewHTML": "

Sarah Chen

Frontend Developer

Alex Rivera

UX Designer

", + "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 margin: 1rem" + "value": { "property": "margin-bottom", "expected": "1rem" }, + "message": "Setze margin-bottom: 1rem" } ] }, { "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 box-sizing auf border-box, damit Padding und Border in die Breite einbezogen werden.", - "previewHTML": "
Content-box (Standard)
Border-box
", - "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 width nur die Inhaltsbreite. Padding und Rahmen werden addiert. Das verursacht Layout-Probleme.

box-sizing: border-box 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 width: 200px. Die linke nutzt Standard-Sizing (content-box) und wird breiter als erwartet. Korrigiere die rechte Karte mit box-sizing: border-box.", + "previewHTML": "
Content-box
Border-box
", + "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .demo { display: flex; gap: 1rem; } .card { width: 200px; padding: 1rem; border: 4px solid steelblue; background: white; border-radius: 8px; }", "sandboxCSS": "", - "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 margin-bottom auf 2rem. Der Abstand zwischen den Absätzen beträgt 2rem (nicht 3rem) durch Margin-Kollaps.", - "previewHTML": "

Dieser Absatz hat einen Bottom-Margin.

Dieser Absatz hat einen Top-Margin von 1rem.

", - "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:
• 1 Wert: alle Seiten
• 2 Werte: vertikal | horizontal
• 4 Werte: oben | rechts | unten | links", + "task": "Dieser Button braucht mehr horizontalen als vertikalen Platz. Setze padding: 8px 1rem (8px oben/unten, 1rem links/rechts).", + "previewHTML": "", + "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 margin-bottom: 2rem" + "type": "regex", + "value": "padding:\\s*8px\\s+1rem", + "message": "Setze padding: 8px 1rem", + "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 margin auf 1rem 2rem für 1rem oben/unten und 2rem links/rechts.", - "previewHTML": "
Diese Box braucht Margins: 1rem oben/unten, 2rem links/rechts
", - "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 margin: 0 auto.", + "task": "Zentriere diese Karte horizontal. Setze margin: 0 auto, um automatisch gleiche links/rechts-Margins zu berechnen.", + "previewHTML": "

Sarah Chen

Frontend Developer

", + "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 margin: 1rem 2rem", + "value": "margin:\\s*0\\s+auto", + "message": "Setze margin: 0 auto", "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 padding auf 2rem für gleichmäßiges Padding.", - "previewHTML": "
Diese Box braucht gleichmäßiges Padding
", - "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 border-radius die Ecken der Rahmen-Box eines Elements. Verwende 50% bei einem quadratischen Element, um einen Kreis zu erstellen.", + "task": "Mache das Avatar-Bild rund mit border-radius: 50%.", + "previewHTML": "
\"Avatar\"

Sarah Chen

Frontend Developer

", + "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 padding: 2rem" + "value": { "property": "border-radius", "expected": "50%" }, + "message": "Setze border-radius: 50%" } ] }, { "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 border-bottom auf 4px solid dodgerblue.", - "previewHTML": "
Dieses Element braucht nur einen unteren Rahmen
", - "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 padding: 1rem, border-left: 4px solid coral und border-radius: 4px hinzu.", + "previewHTML": "
New message

You have 3 unread notifications

", + "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 padding: 1rem" + }, { "type": "regex", - "value": "border-bottom:\\s*4px\\s+solid\\s+dodgerblue", - "message": "Setze border-bottom: 4px solid dodgerblue", + "value": "border-left:\\s*4px\\s+solid\\s+coral", + "message": "Setze border-left: 4px solid coral", "options": { "caseSensitive": false } + }, + { + "type": "property_value", + "value": { "property": "border-radius", "expected": "4px" }, + "message": "Setze border-radius: 4px" } ] } diff --git a/lessons/de/05-units-variables.json b/lessons/de/05-units-variables.json index 66d64df..70e7556 100644 --- a/lessons/de/05-units-variables.json +++ b/lessons/de/05-units-variables.json @@ -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 .box auf 80% und max-width auf 37.5rem.", - "previewHTML": "
Ändere meine Größe!
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { background: #f5f5f5; padding: 1rem; }", + "title": "Relative Units", + "description": "CSS bietet zwei Arten von Einheiten: absolute (wie px) und relative (wie % und rem). Relative Einheiten passen sich ihrem Kontext an und machen Layouts flexibel und zugänglich.

Häufige relative Einheiten:
% – Relativ zum Elternelement
rem – Relativ zur Root-Schriftgröße (typisch 16px)
em – Relativ zur Schriftgröße des Elements

Ein häufiges Muster für lesbaren Inhalt: setze width: 100%, damit es den verfügbaren Platz füllt, dann max-width: 40rem um die Zeilenlänge für Lesbarkeit zu begrenzen.", + "task": "Dieser Artikeltext läuft auf großen Bildschirmen zu breit. Füge max-width: 40rem hinzu für optimale Lesebreite.", + "previewHTML": "

The Art of Typography

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.

The ideal line length is 45-75 characters per line. At typical font sizes, this works out to roughly 40rem maximum width.

", + "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 width Eigenschaft", - "options": { "caseSensitive": false } - }, - { "type": "property_value", "value": { "property": "width", "expected": "80%" }, "message": "Setze width auf 80%" }, - { - "type": "contains", - "value": "max-width", - "message": "Verwende die max-width Eigenschaft", - "options": { "caseSensitive": false } - }, { "type": "property_value", - "value": { "property": "max-width", "expected": "37.5rem" }, - "message": "Setze max-width auf 37.5rem" + "value": { "property": "max-width", "expected": "40rem" }, + "message": "Setze max-width: 40rem" } ] }, { "id": "units-2", - "title": "CSS Custom Properties", - "description": "Definiere und verwende Variablen (--custom properties) wieder, um deine Theme-Werte zu zentralisieren.", - "task": "Erstelle eine --main-color Variable in :root mit #6200ee und wende sie als Rahmenfarbe auf .themed an.", - "previewHTML": "
Variablen-Box
", - "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 --name und verwende sie mit var(--name). Variablen auf :root sind überall verfügbar.", + "task": "Definiere --brand: steelblue in :root, dann verwende es als background-Farbe für .btn.", + "previewHTML": "
", + "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 --main-color in :root", + "value": "--brand", + "message": "Definiere die --brand Variable", "options": { "caseSensitive": false } }, { "type": "contains", - "value": "var(--main-color)", - "message": "Verwende var(--main-color)", + "value": "steelblue", + "message": "Setze den Wert auf steelblue", "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 calc() Funktion, um verschiedene Einheiten in einem Ausdruck zu kombinieren.", - "task": "Setze die Breite von .sized auf calc(100% - 2rem) und min-height auf calc(10vh + 1rem).", - "previewHTML": "
Calc Demo
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sized { background: #e8f5e9; padding: 1rem; }", + "title": "calc() Function", + "description": "Die calc() 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 width: calc(100% - 200px) auf .main.", + "previewHTML": "

Main Content

This area should fill the remaining width after accounting for the fixed-width sidebar.

", + "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 calc() 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 width: calc(100% - 200px)", "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 .view eine Breite von 50vw und Höhe von 20vh.", - "previewHTML": "
Viewport-Box
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .view { background: #ffe0b2; }", + "title": "Viewport Units", + "description": "Viewport-Einheiten dimensionieren Elemente relativ zum Browserfenster:
vw – 1% der Viewport-Breite
vh – 1% der Viewport-Höhe

Diese sind perfekt für Vollbild-Sektionen wie Hero-Banner.", + "task": "Mache diese Hero-Sektion so hoch wie das Viewport mit min-height: 100vh.", + "previewHTML": "

Welcome

Scroll down to explore

", + "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 vw Einheit", "options": { "caseSensitive": false } }, - { "type": "contains", "value": "vh", "message": "Verwende vh Einheit", "options": { "caseSensitive": false } }, - { "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Setze width auf 50vw" }, - { "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Setze height auf 20vh" } + { + "type": "property_value", + "value": { "property": "min-height", "expected": "100vh" }, + "message": "Setze min-height: 100vh" + } ] } ] diff --git a/lessons/de/06-transitions-animations.json b/lessons/de/06-transitions-animations.json index 759d1f1..f9ae449 100644 --- a/lessons/de/06-transitions-animations.json +++ b/lessons/de/06-transitions-animations.json @@ -7,13 +7,13 @@ "lessons": [ { "id": "transitions-1", - "title": "Einfache Transitions", + "title": "Transitions", "description": "Lerne, wie du transition auf Eigenschaften anwendest für sanfte Änderungen bei Zustandswechseln.

transition: property duration;\n/* z.B. transition: background-color 0.3s; */
", - "task": "Füge transition: background-color 0.3s zu .btn hinzu, damit die Farbe beim Hover sanft überblendet.", - "previewHTML": "", - "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 transition: background-color 0.3s hinzu, damit die Farbe beim Hover sanft überblendet.", + "previewHTML": "", + "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 ease, linear, ease-in, ease-out, um das Animationstempo zu steuern.", - "task": "Setze transition-timing-function auf ease-in-out bei .btn.", + "task": "Setze transition-timing-function auf ease-in-out.", "previewHTML": "", - "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 transition-timing-function: ease-in-out" + "message": "Setze timing auf ease-in-out" } ] }, { "id": "transitions-3", - "title": "Keyframe-Animationen Grundlagen", + "title": "Keyframes", "description": "Erstelle benannte Animationen mit @keyframes und wende sie mit der animation Kurzschreibweise an.

@keyframes bounce {\n  50% { transform: translateY(-20px); }\n}\n.ball {\n  animation: bounce 1s infinite;\n}
", "task": "Definiere bei 50% ein transform: translateY(-20px) und wende animation: bounce 1s infinite auf .ball an.", "previewHTML": "
", - "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 animation-delay, animation-iteration-count, animation-direction und animation-fill-mode.", - "task": "Wende die fade Animation auf .box an mit animation-name: fade, animation-duration: 2s, animation-delay: 1s, animation-iteration-count: 2 und animation-fill-mode: forwards.", - "previewHTML": "
Fade Demo
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { width: 100px; height: 100px; background: #4caf50; margin: 2rem auto; }", + "task": "Wende die pulse Animation auf .box an mit animation-name: pulse, animation-duration: 2s, animation-delay: 1s, animation-iteration-count: 2 und animation-fill-mode: forwards.", + "previewHTML": "
Pulse
", + "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 animation-delay", - "options": { "caseSensitive": false } - }, - { - "type": "contains", - "value": "animation-iteration-count", - "message": "Verwende animation-iteration-count", - "options": { "caseSensitive": false } - }, - { - "type": "contains", - "value": "animation-fill-mode", - "message": "Verwende animation-fill-mode", - "options": { "caseSensitive": false } + "type": "property_value", + "value": { "property": "animation-name", "expected": "pulse" }, + "message": "Setze animation-name: pulse" }, { "type": "property_value", diff --git a/lessons/de/08-responsive.json b/lessons/de/08-responsive.json index dbbc060..9bd9c55 100644 --- a/lessons/de/08-responsive.json +++ b/lessons/de/08-responsive.json @@ -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 .panel auf lightcoral.", - "previewHTML": "
Ändere die Fenstergröße
", + "title": "Media Queries", + "description": "Verstehe die Syntax und Anwendungsfälle für CSS Media Queries, um Stile bedingt basierend auf Viewport-Eigenschaften anzuwenden.

@media (max-width: 600px) {\n  .panel {\n    background: lightcoral;\n  }\n}
", + "task": "Schreibe eine Media Query mit @media (max-width: 600px), die den Hintergrund von .panel auf lightcoral ändert.", + "previewHTML": "
Resize the window
", "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 @media (max-width: 600px)", "options": { "caseSensitive": false } }, { @@ -31,68 +31,49 @@ "message": "Adressiere .panel innerhalb der Media Query", "options": { "caseSensitive": false } }, - { - "type": "contains", - "value": "background", - "message": "Ändere die background Eigenschaft", - "options": { "caseSensitive": false } - }, { "type": "property_value", "value": { "property": "background", "expected": "lightcoral" }, - "message": "Setze background auf lightcoral", + "message": "Setze background: lightcoral", "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 .text auf 5vw, damit sie sich mit dem Viewport ändert.", - "previewHTML": "

Flüssige Typografie

", + "title": "Fluid Type", + "description": "Verwende relative Einheiten wie vw, damit Schriftgrößen mit der Viewport-Breite skalieren.", + "task": "Setze font-size: 5vw, damit sie sich mit dem Viewport ändert.", + "previewHTML": "

Fluid Typography

", "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 font-size Eigenschaft", - "options": { "caseSensitive": false } - }, - { - "type": "contains", - "value": "vw", - "message": "Verwende vw Einheit für flüssige Größe", - "options": { "caseSensitive": false } - }, - { "type": "property_value", "value": { "property": "font-size", "expected": "5vw" }, "message": "Setze font-size auf 5vw" } + { "type": "property_value", "value": { "property": "font-size", "expected": "5vw" }, "message": "Setze font-size: 5vw" } ] }, { "id": "responsive-3", - "title": "Flexible Raster", - "description": "Kombiniere CSS Grid mit auto-fit oder auto-fill für responsive Spaltenlayouts.", - "task": "Definiere .cards mit grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) und einem gap von 1rem.", - "previewHTML": "
1
2
3
4
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .cards > div { background: #d1c4e9; padding: 1rem; }", + "title": "Responsive Grid", + "description": "Kombiniere CSS Grid mit auto-fit oder auto-fill für responsive Spaltenlayouts, die automatisch die Anzahl der Spalten basierend auf verfügbarem Platz anpassen.", + "task": "Füge display: grid, grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) und gap: 1rem hinzu.", + "previewHTML": "

Fast

Lightning quick load times

Secure

Enterprise-grade security

Reliable

99.9% uptime guaranteed

Support

24/7 customer service

", + "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 grid-template-columns", - "options": { "caseSensitive": false } + "type": "property_value", + "value": { "property": "display", "expected": "grid" }, + "message": "Setze display: grid" }, { "type": "regex", @@ -100,18 +81,22 @@ "message": "Verwende repeat(auto-fit, minmax(200px, 1fr))", "options": { "caseSensitive": false } }, - { "type": "contains", "value": "gap", "message": "Verwende die gap Eigenschaft", "options": { "caseSensitive": false } } + { + "type": "property_value", + "value": { "property": "gap", "expected": "1rem" }, + "message": "Setze gap: 1rem" + } ] }, { "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 .sidebar auf 250px setzt.", - "previewHTML": "", + "task": "Schreibe eine Media Query mit @media (min-width: 768px), die die Breite von .sidebar auf 250px setzt.", + "previewHTML": "", "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 @media (min-width: 768px)", "options": { "caseSensitive": false } }, { @@ -132,7 +117,7 @@ { "type": "property_value", "value": { "property": "width", "expected": "250px" }, - "message": "Setze width auf 250px", + "message": "Setze width: 250px", "options": { "exact": false } } ] diff --git a/lessons/de/22-html-forms-validation.json b/lessons/de/22-html-forms-validation.json index a9a4031..a760144 100644 --- a/lessons/de/22-html-forms-validation.json +++ b/lessons/de/22-html-forms-validation.json @@ -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 required-Attribut verhindert das Absenden des Formulars, wenn das Feld leer ist.

Füge es zu jeder Eingabe hinzu, die ausgefüllt werden muss:
<input type=\"text\" required>

Der Browser zeigt automatisch eine Validierungsmeldung an.", - "task": "Mache sowohl das Name- als auch das E-Mail-Feld zu Pflichtfeldern, indem du das required-Attribut hinzufügst.", + "description": "Das required-Attribut verhindert das Absenden des Formulars, wenn das Feld leer ist. Der Browser zeigt automatisch eine Validierungsmeldung an - kein JavaScript nötig!

Füge es zu jeder Eingabe hinzu, die ausgefüllt werden muss:
<input type=\"text\" required>", + "task": "Mache sowohl das Name- als auch das E-Mail-Feld zu Pflichtfeldern, indem du das required-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": "
\n \n \n \n \n \n \n \n
", - "solution": "
\n \n \n \n \n \n \n \n
", + "initialCode": "
\n \n \n \n \n \n \n \n
", + "solution": "
\n \n \n \n \n \n \n \n
", "previewContainer": "preview-area", "validations": [ { @@ -29,84 +29,6 @@ "message": "Füge required zum E-Mail-Feld hinzu" } ] - }, - { - "id": "input-constraints", - "title": "Eingabebeschränkungen", - "description": "Kontrolliere, was Benutzer eingeben können:

minlength / maxlength - Textlängenbegrenzung
min / max - Zahlenbereich
pattern - Regex-Musterabgleich
placeholder - Hinweistext (kein Label!)", - "task": "Füge Validierung zur Passwort-Eingabe hinzu:
1. Füge minlength=\"8\" für die Mindestlänge hinzu
2. Füge maxlength=\"20\" für die Maximallänge hinzu
3. Füge placeholder=\"Passwort eingeben\" 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": "
\n \n \n Muss 8-20 Zeichen lang sein\n \n \n
", - "solution": "
\n \n \n Muss 8-20 Zeichen lang sein\n \n \n
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "attribute_value", - "value": { "selector": "input[type='password']", "attr": "minlength", "value": "8" }, - "message": "Füge minlength=\"8\" zum Passwort hinzu" - }, - { - "type": "attribute_value", - "value": { "selector": "input[type='password']", "attr": "maxlength", "value": "20" }, - "message": "Füge maxlength=\"20\" zum Passwort hinzu" - }, - { - "type": "attribute_value", - "value": { "selector": "input[type='password']", "attr": "placeholder", "value": null }, - "message": "Füge einen placeholder hinzu, der andeutet, was einzugeben ist" - } - ] - }, - { - "id": "complete-registration", - "title": "Vollständiges Registrierungsformular", - "description": "Erstelle ein vollständiges Registrierungsformular mit allen Validierungskonzepten:

- Pflichtfelder mit * markiert
- E-Mail-Validierung (type=\"email\" verwenden)
- Passwort mit Längenbeschränkungen
- AGB-Checkbox (Pflichtfeld)
- Absende-Button", - "task": "Vervollständige das Registrierungsformular. Füge required-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": "
\n

Konto erstellen

\n \n \n \n \n \n \n \n \n \n \n \n \n \n
", - "solution": "
\n

Konto erstellen

\n \n \n \n \n \n \n \n \n \n \n \n \n \n
", - "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 email" - }, - { - "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 password" - }, - { - "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 minlength=\"8\" zum Passwort hinzu" - }, - { - "type": "attribute_value", - "value": { "selector": "#terms", "attr": "required", "value": true }, - "message": "Mache die AGB-Checkbox zum Pflichtfeld" - } - ] } ] } diff --git a/lessons/de/24-html-progress-meter.json b/lessons/de/24-html-progress-meter.json index bb0fe69..4330657 100644 --- a/lessons/de/24-html-progress-meter.json +++ b/lessons/de/24-html-progress-meter.json @@ -44,12 +44,12 @@ "id": "progress-indeterminate", "title": "Unbestimmter Fortschritt", "description": "Wenn der Fortschritt unbekannt ist (wie beim Laden), lasse das value-Attribut weg. Dies erstellt einen animierten unbestimmten Zustand.

Nützlich für Netzwerkanfragen oder Prozesse mit unbekannter Dauer.", - "task": "Erstelle eine Ladeanzeige:
1. Füge ein <p> mit Lädt... hinzu
2. Füge ein <progress> ohne value-Attribut hinzu", + "task": "Erstelle eine Ladeanzeige:
1. Füge ein <p> mit Loading... hinzu
2. Füge ein <progress> 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": "

Lädt...

\n", + "solution": "

Loading...

\n", "previewContainer": "preview-area", "validations": [ { @@ -68,12 +68,12 @@ "id": "meter-gauge", "title": "Meter-Anzeigen", "description": "Das <meter>-Element zeigt einen Skalarwert innerhalb eines Bereichs. Verwende es für Messungen wie Speicherplatz, Akku oder Bewertungen.

Setze low, high und optimum, um gute/schlechte Bereiche zu definieren - der Browser färbt es entsprechend ein!", - "task": "Erstelle eine Akku-Anzeige:
1. Füge ein <label> mit Akku: hinzu
2. Füge ein <meter> hinzu mit:
- value=\"0.8\"
- min=\"0\" und max=\"1\"
- low=\"0.2\" und high=\"0.8\"
- optimum=\"1\"", + "task": "Erstelle eine Akku-Anzeige:
1. Füge ein <label> mit Battery: hinzu
2. Füge ein <meter> hinzu mit:
- value=\"0.8\"
- min=\"0\" und max=\"1\"
- low=\"0.2\" und high=\"0.8\"
- optimum=\"1\"", "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": "\n80%", + "solution": "\n80%", "previewContainer": "preview-area", "validations": [ { @@ -86,11 +86,31 @@ "value": { "selector": "meter", "attr": "value", "value": "0.8" }, "message": "Setze value=\"0.8\" beim Meter" }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "min", "value": "0" }, + "message": "Setze min=\"0\" beim Meter" + }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "max", "value": "1" }, + "message": "Setze max=\"1\" beim Meter" + }, { "type": "attribute_value", "value": { "selector": "meter", "attr": "low", "value": "0.2" }, "message": "Setze low=\"0.2\", um den niedrigen Schwellenwert zu definieren" }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "high", "value": "0.8" }, + "message": "Setze high=\"0.8\", um den hohen Schwellenwert zu definieren" + }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "optimum", "value": "1" }, + "message": "Setze optimum=\"1\", um den optimalen Wert anzugeben" + }, { "type": "element_exists", "value": "label", diff --git a/lessons/de/30-html-tables.json b/lessons/de/30-html-tables.json index d10c687..6b995cc 100644 --- a/lessons/de/30-html-tables.json +++ b/lessons/de/30-html-tables.json @@ -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 <table> mit <tr> für Zeilen. In Zeilen nutze <th> für Überschriften und <td> für Datenzellen.

Das <caption>-Element bietet einen zugänglichen Titel für die Tabelle.", - "task": "Erstelle eine einfache Tabelle mit:
1. Einer <caption> mit Obstpreise
2. Einer Kopfzeile mit Obst und Preis Spalten
3. Mindestens 2 Datenzeilen", + "title": "Datentabellen", + "description": "Tabellen zeigen strukturierte Daten in Zeilen und Spalten. Verwende <table> als Container, <tr> für Zeilen, <th> für Kopfzellen und <td> für Datenzellen.

Füge <caption> hinzu für einen zugänglichen Titel, der den Tabelleninhalt beschreibt.", + "task": "Erstelle eine Preistabelle:
1. Eine <caption> mit Pricing
2. Eine Kopfzeile mit Plan und Price
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": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n
Obstpreise
ObstPreis
Apfel1,50 €
Banane0,75 €
", + "solution": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n
Pricing
PlanPrice
Basic$9
Pro$29
", "previewContainer": "preview-area", "validations": [ { @@ -26,100 +26,17 @@ { "type": "element_exists", "value": "caption", - "message": "Füge eine <caption> als Tabellentitel hinzu" + "message": "Füge eine <caption> 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 (<th>) 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 <thead> zum Gruppieren von Kopfzeilen und <tbody> zum Gruppieren von Datenzeilen. Das hilft Browsern und Hilfstechnologien, die Tabellenstruktur zu verstehen.

Du kannst auch <tfoot> für Fußzeilen wie Summen verwenden.", - "task": "Erstelle eine strukturierte Tabelle:
1. Eine <caption> mit Monatliche Verkäufe
2. Ein <thead> mit Monat und Umsatz Überschriften
3. Ein <tbody> 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": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Monatliche Verkäufe
MonatUmsatz
Januar12.500 €
Februar14.200 €
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "element_exists", - "value": "table", - "message": "Füge ein <table>-Element hinzu" - }, - { - "type": "element_exists", - "value": "caption", - "message": "Füge ein <caption>-Element hinzu" - }, - { - "type": "element_exists", - "value": "thead", - "message": "Füge ein <thead> für den Kopfbereich hinzu" - }, - { - "type": "element_exists", - "value": "tbody", - "message": "Füge ein <tbody> 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 <tfoot> hinzu, um einen Fußbereich für Summen oder Zusammenfassungen zu erstellen. Der Fuß bleibt unten, auch wenn tbody viele Zeilen hat.

Kombiniere alle Abschnitte für eine vollständig strukturierte, zugängliche Tabelle.", - "task": "Erstelle eine vollständige Tabelle:
1. Eine <caption> mit Bestellübersicht
2. Ein <thead> mit Artikel und Preis Überschriften
3. Ein <tbody> mit 2 Artikeln
4. Ein <tfoot> 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": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Bestellübersicht
ArtikelPreis
Widget25,00 €
Gadget35,00 €
Gesamt60,00 €
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "element_exists", - "value": "table", - "message": "Füge ein <table>-Element hinzu" - }, - { - "type": "element_exists", - "value": "caption", - "message": "Füge ein <caption>-Element hinzu" - }, - { - "type": "element_exists", - "value": "thead", - "message": "Füge einen <thead>-Abschnitt hinzu" - }, - { - "type": "element_exists", - "value": "tbody", - "message": "Füge einen <tbody>-Abschnitt hinzu" - }, - { - "type": "element_exists", - "value": "tfoot", - "message": "Füge einen <tfoot>-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)" } ] } diff --git a/lessons/de/32-html-svg.json b/lessons/de/32-html-svg.json index 0beecf1..8dc1314 100644 --- a/lessons/de/32-html-svg.json +++ b/lessons/de/32-html-svg.json @@ -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": "\n \n", + "solution": "\n \n", "previewContainer": "preview-area", "validations": [ { @@ -28,6 +28,16 @@ "value": "circle", "message": "Füge ein <circle>-Element in das SVG ein" }, + { + "type": "attribute_value", + "value": { "selector": "svg", "attr": "width", "value": "200" }, + "message": "Setze width=\"200\" beim SVG" + }, + { + "type": "attribute_value", + "value": { "selector": "svg", "attr": "height", "value": "200" }, + "message": "Setze height=\"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 cy=\"100\" für das vertikale Zentrum" + }, + { + "type": "attribute_value", + "value": { "selector": "circle", "attr": "r", "value": "50" }, + "message": "Setze r=\"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": "\n \n \n", + "solution": "\n \n \n", "previewContainer": "preview-area", "validations": [ { @@ -66,6 +81,61 @@ "type": "element_exists", "value": "line", "message": "Füge ein <line>-Element hinzu" + }, + { + "type": "attribute_value", + "value": { "selector": "svg", "attr": "width", "value": "200" }, + "message": "Setze width=\"200\" beim SVG" + }, + { + "type": "attribute_value", + "value": { "selector": "svg", "attr": "height", "value": "150" }, + "message": "Setze height=\"150\" beim SVG" + }, + { + "type": "attribute_value", + "value": { "selector": "rect", "attr": "x", "value": "20" }, + "message": "Setze x=\"20\" beim rect" + }, + { + "type": "attribute_value", + "value": { "selector": "rect", "attr": "y", "value": "20" }, + "message": "Setze y=\"20\" beim rect" + }, + { + "type": "attribute_value", + "value": { "selector": "rect", "attr": "width", "value": "80" }, + "message": "Setze width=\"80\" beim rect" + }, + { + "type": "attribute_value", + "value": { "selector": "rect", "attr": "height", "value": "60" }, + "message": "Setze height=\"60\" beim rect" + }, + { + "type": "attribute_value", + "value": { "selector": "line", "attr": "x1", "value": "120" }, + "message": "Setze x1=\"120\" bei der line" + }, + { + "type": "attribute_value", + "value": { "selector": "line", "attr": "y1", "value": "30" }, + "message": "Setze y1=\"30\" bei der line" + }, + { + "type": "attribute_value", + "value": { "selector": "line", "attr": "x2", "value": "180" }, + "message": "Setze x2=\"180\" bei der line" + }, + { + "type": "attribute_value", + "value": { "selector": "line", "attr": "y2", "value": "90" }, + "message": "Setze y2=\"90\" bei der line" + }, + { + "type": "contains", + "value": "stroke", + "message": "Füge eine stroke-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": "\n \n \n \n \n", + "solution": "\n \n \n \n \n", "previewContainer": "preview-area", "validations": [ { diff --git a/lessons/es/00-welcome.json b/lessons/es/00-welcome.json index 459920c..740cb0f 100644 --- a/lessons/es/00-welcome.json +++ b/lessons/es/00-welcome.json @@ -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": "Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required!

What you'll learn:
HTML - Semantic elements, forms, tables, SVG (HTML Block & Inline, HTML Forms, HTML Tables)
CSS - Selectors, box model, flexbox, animations (CSS Selectors, CSS Box Model, CSS Flexbox)
Responsive Design - Media queries and mobile-first layouts

How it works:
1. Read the task in the left panel
2. Write code in the editor
3. See live results in the preview
4. Get instant feedback with hints

Keyboard shortcuts: Ctrl+Z to undo, Ctrl+Shift+Z to redo

More resources:
HTML over JS - Native HTML vs JavaScript solutions
Web Engineering Mandala - JavaScript technology roadmap", - "task": "Write Hello World", + "title": "Comenzar", + "description": "Code Crispies es una plataforma gratuita y de código abierto para aprender desarrollo web mediante ejercicios prácticos. ¡No se requiere cuenta!

Lo que aprenderás:
HTML - Elementos semánticos, formularios, tablas, SVG (HTML Bloque e Inline, HTML Formularios, HTML Tablas)
CSS - Selectores, modelo de caja, flexbox, animaciones (CSS Selectores, CSS Modelo de Caja, CSS Flexbox)
Diseño Responsivo - Media queries y layouts mobile-first

Cómo funciona:
1. Lee la tarea en el panel izquierdo
2. Escribe código en el editor
3. Ve los resultados en vivo en la vista previa
4. Recibe retroalimentación instantánea con pistas

Atajos de teclado: Ctrl+Z deshacer, Ctrl+Shift+Z rehacer

Más recursos:
HTML over JS - HTML nativo vs soluciones JavaScript
Web Engineering Mandala - Mapa de tecnologías JavaScript", + "task": "Escribe Hello World", "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 Hello World" + "message": "Escribe Hello World" } ] }, { "id": "overview", - "title": "Overview", - "description": "You're ready! Open the menu (☰) to explore all modules.

Recommended learning path:
1. HTML Block & Inline - Understand container vs inline elements
2. HTML Forms - Build interactive forms with validation
3. CSS Selectors - Target elements precisely
4. CSS Box Model - Master padding, margin, borders
5. CSS Flexbox - Create flexible layouts
6. CSS Animations - Add motion and transitions

Tips:
• Use Show Expected to see the target result
• Your progress is saved automatically
• Try Emmet in HTML mode: ul>li*3 + Tab

Open Source:
Gitea (Source) · GitHub (Mirror)
• Made by LibreTECH · Michael Czechowski", - "task": "Click Next to continue", + "title": "Vista General", + "description": "¡Estás listo! Abre el menú (☰) para explorar todos los módulos.

Ruta de aprendizaje recomendada:
1. HTML Bloque e Inline - Entiende elementos contenedores vs inline
2. HTML Formularios - Crea formularios interactivos con validación
3. CSS Selectores - Selecciona elementos con precisión
4. CSS Modelo de Caja - Domina padding, margin, borders
5. CSS Flexbox - Crea layouts flexibles
6. CSS Animaciones - Añade movimiento y transiciones

Consejos:
• Usa Mostrar Esperado para ver el resultado objetivo
• Tu progreso se guarda automáticamente
• Prueba Emmet en modo HTML: ul>li*3 + Tab

Open Source:
Gitea (Source) · GitHub (Mirror)
• Hecho por LibreTECH · Michael Czechowski", + "task": "Haz clic en Siguiente para continuar", "previewHTML": "

Hello World! 🌍

Hallo Welt!

Bonjour le monde!

¡Hola Mundo!

Ciao Mondo!

Olá Mundo!

こんにちは世界!

你好世界!

안녕 세상!

Привет мир!

שלום עולם!

مرحبا بالعالم!

", "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" } ] }, diff --git a/lessons/es/01-box-model.json b/lessons/es/01-box-model.json index d525bcf..6a6c3c8 100644 --- a/lessons/es/01-box-model.json +++ b/lessons/es/01-box-model.json @@ -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 padding to 1rem to create space between the content and border.", - "previewHTML": "
Box Model Components
", - "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. Padding crea espacio entre tu contenido y el borde de la caja.

Sin padding, el texto se aprieta incómodamente contra los bordes. El padding hace que el contenido sea legible y visualmente equilibrado.

.card {\n  padding: 1rem;\n}
", + "task": "Esta tarjeta de perfil se ve apretada. Añade padding: 1rem para que el texto tenga espacio para respirar.", + "previewHTML": "

Sarah Chen

Frontend Developer

", + "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 padding: 1rem" + "message": "Establece padding: 1rem" } ] }, { "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 border to 2px solid darkslategray.", - "previewHTML": "
This box needs a border
", - "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 border acepta tres valores: ancho, estilo y color.

Estilos comunes: solid, dashed, dotted, none", + "task": "Añade un acento sutil a la izquierda de la tarjeta con border-left: 4px solid steelblue.", + "previewHTML": "

Sarah Chen

Frontend Developer

", + "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 border: 2px solid darkslategray", + "value": "border-left:\\s*4px\\s+solid\\s+steelblue", + "message": "Establece border-left: 4px solid steelblue", "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 margin to 1rem to create space between this element and its neighbors.", - "previewHTML": "
This box needs margins
Adjacent element
", - "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 fuera 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 margin-bottom: 1rem en .card.", + "previewHTML": "

Sarah Chen

Frontend Developer

Alex Rivera

UX Designer

", + "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 margin: 1rem" + "value": { "property": "margin-bottom", "expected": "1rem" }, + "message": "Establece margin-bottom: 1rem" } ] }, { "id": "box-model-4", - "title": "Box Sizing: Border-Box", - "description": "The box-sizing property determines how element dimensions are calculated. The default content-box excludes padding and border from width/height, while border-box includes them, making layout calculations more intuitive.", - "task": "Set box-sizing to border-box so padding and border are included in the width.", - "previewHTML": "
Content-box (default)
Border-box
", - "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, width solo establece el ancho del contenido. Padding y bordes se suman al total. Esto causa problemas de diseño.

box-sizing: border-box 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 width: 200px. La izquierda usa el tamaño predeterminado (content-box), haciéndola más ancha de lo esperado. Corrige la tarjeta derecha con box-sizing: border-box.", + "previewHTML": "
Content-box
Border-box
", + "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .demo { display: flex; gap: 1rem; } .card { width: 200px; padding: 1rem; border: 4px solid steelblue; background: white; border-radius: 8px; }", "sandboxCSS": "", - "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 box-sizing: border-box" + "message": "Establece box-sizing: border-box" } ] }, { "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 margin-bottom to 2rem. Notice the space between paragraphs equals 2rem (not 3rem) due to margin collapse.", - "previewHTML": "

This paragraph has a bottom margin.

This paragraph has a top margin of 1rem.

", - "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:
• 1 valor: todos los lados
• 2 valores: vertical | horizontal
• 4 valores: arriba | derecha | abajo | izquierda", + "task": "Este botón necesita más espacio horizontal que vertical. Establece padding: 8px 1rem (8px arriba/abajo, 1rem izquierda/derecha).", + "previewHTML": "", + "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 margin-bottom: 2rem" + "type": "regex", + "value": "padding:\\s*8px\\s+1rem", + "message": "Establece padding: 8px 1rem", + "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 margin to 1rem 2rem for 1rem top/bottom and 2rem left/right.", - "previewHTML": "
This box needs margins: 1rem top/bottom, 2rem left/right
", - "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 margin: 0 auto.", + "task": "Centra esta tarjeta horizontalmente. Establece margin: 0 auto para calcular automáticamente márgenes iguales izquierda/derecha.", + "previewHTML": "

Sarah Chen

Frontend Developer

", + "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 margin: 1rem 2rem", + "value": "margin:\\s*0\\s+auto", + "message": "Establece margin: 0 auto", "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 padding to 2rem to add equal padding on all sides.", - "previewHTML": "
This box needs equal padding on all sides
", - "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, border-radius redondea las esquinas de la caja de borde de un elemento. Usa 50% en un elemento cuadrado para crear un círculo.", + "task": "Haz la imagen del avatar circular con border-radius: 50%.", + "previewHTML": "
\"Avatar\"

Sarah Chen

Frontend Developer

", + "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 padding: 2rem" + "value": { "property": "border-radius", "expected": "50%" }, + "message": "Establece border-radius: 50%" } ] }, { "id": "box-model-8", - "title": "Border on Specific Sides", - "description": "For granular control, you can target specific sides with border-top, border-right, border-bottom, or border-left.", - "task": "Set border-bottom to 4px solid dodgerblue.", - "previewHTML": "
This element needs only a bottom border
", - "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 padding: 1rem, border-left: 4px solid coral y border-radius: 4px.", + "previewHTML": "
New message

You have 3 unread notifications

", + "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 padding: 1rem" + }, { "type": "regex", - "value": "border-bottom:\\s*4px\\s+solid\\s+dodgerblue", - "message": "Set border-bottom: 4px solid dodgerblue", + "value": "border-left:\\s*4px\\s+solid\\s+coral", + "message": "Establece border-left: 4px solid coral", "options": { "caseSensitive": false } + }, + { + "type": "property_value", + "value": { "property": "border-radius", "expected": "4px" }, + "message": "Establece border-radius: 4px" } ] } diff --git a/lessons/es/05-units-variables.json b/lessons/es/05-units-variables.json index 67a9272..110d2f2 100644 --- a/lessons/es/05-units-variables.json +++ b/lessons/es/05-units-variables.json @@ -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 width of .box to 80% and max-width to 37.5rem.", - "previewHTML": "
Resize me!
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { background: #f5f5f5; padding: 1rem; }", + "title": "Relative Units", + "description": "CSS ofrece dos tipos de unidades: absolutas (como px) y relativas (como % y rem). Las unidades relativas se adaptan a su contexto, haciendo los layouts flexibles y accesibles.

Unidades relativas comunes:
% – Relativo al elemento padre
rem – Relativo al tamaño de fuente raíz (típicamente 16px)
em – Relativo al tamaño de fuente del elemento

Un patrón común para contenido legible: establece width: 100% para llenar el espacio disponible, luego max-width: 40rem para limitar la longitud de línea para legibilidad.", + "task": "Este texto de artículo es demasiado ancho en pantallas grandes. Añade max-width: 40rem para un ancho de lectura óptimo.", + "previewHTML": "

The Art of Typography

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.

The ideal line length is 45-75 characters per line. At typical font sizes, this works out to roughly 40rem maximum width.

", + "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 width property", "options": { "caseSensitive": false } }, - { "type": "property_value", "value": { "property": "width", "expected": "80%" }, "message": "Set width to 80%" }, - { "type": "contains", "value": "max-width", "message": "Use max-width property", "options": { "caseSensitive": false } }, { "type": "property_value", - "value": { "property": "max-width", "expected": "37.5rem" }, - "message": "Set max-width to 37.5rem" + "value": { "property": "max-width", "expected": "40rem" }, + "message": "Establece max-width: 40rem" } ] }, { "id": "units-2", - "title": "CSS Custom Properties", - "description": "Define and reuse variables (--custom properties) to centralize your theme values.", - "task": "Create a --main-color variable in :root with #6200ee and apply it as the border color on .themed.", - "previewHTML": "
Variable Box
", - "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 --nombre y úsalas con var(--nombre). Las variables definidas en :root están disponibles en todas partes.", + "task": "Define --brand: steelblue en :root, luego úsala como color de background para .btn.", + "previewHTML": "
", + "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 --main-color in :root", + "value": "--brand", + "message": "Define la variable --brand", "options": { "caseSensitive": false } }, { "type": "contains", - "value": "var(--main-color)", - "message": "Use var(--main-color)", + "value": "steelblue", + "message": "Establece el valor a steelblue", "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 calc() function to combine different units in one expression.", - "task": "Set the width of .sized to calc(100% - 2rem) and min-height to calc(10vh + 1rem).", - "previewHTML": "
Calc Demo
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sized { background: #e8f5e9; padding: 1rem; }", + "title": "calc() Function", + "description": "La función calc() 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 width: calc(100% - 200px) en .main.", + "previewHTML": "

Main Content

This area should fill the remaining width after accounting for the fixed-width sidebar.

", + "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 calc() function", "options": { "caseSensitive": false } }, { "type": "regex", - "value": "width:\\s*calc\\(100% - 2rem\\)", - "message": "Width should be calc(100% - 2rem)", - "options": { "caseSensitive": false } - }, - { - "type": "regex", - "value": "min-height:\\s*calc\\(10vh \\+ 1rem\\)", - "message": "Min-height should be calc(10vh + 1rem)", + "value": "width:\\s*calc\\(\\s*100%\\s*-\\s*200px\\s*\\)", + "message": "Establece width: calc(100% - 200px)", "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 .view a width of 50vw and height of 20vh.", - "previewHTML": "
Viewport Box
", - "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:
vw – 1% del ancho del viewport
vh – 1% de la altura del viewport

Son perfectas para secciones de pantalla completa como banners hero.", + "task": "Haz que esta sección hero llene la altura del viewport estableciendo min-height: 100vh.", + "previewHTML": "

Welcome

Scroll down to explore

", + "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 vw unit", "options": { "caseSensitive": false } }, - { "type": "contains", "value": "vh", "message": "Use vh unit", "options": { "caseSensitive": false } }, - { "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Set width to 50vw" }, - { "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Set height to 20vh" } + { + "type": "property_value", + "value": { "property": "min-height", "expected": "100vh" }, + "message": "Establece min-height: 100vh" + } ] } ] diff --git a/lessons/es/06-transitions-animations.json b/lessons/es/06-transitions-animations.json index 5f8280d..89de698 100644 --- a/lessons/es/06-transitions-animations.json +++ b/lessons/es/06-transitions-animations.json @@ -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 transition to properties for smooth changes on state changes.

transition: property duration;\n/* e.g. transition: background-color 0.3s; */
", - "task": "Add transition: background-color 0.3s to .btn so the color fades smoothly on hover.", + "description": "Aprende a aplicar transition a propiedades para cambios suaves en estados.

transition: property duration;\n/* ej. transition: background-color 0.3s; */
", + "task": "Añade transition: background-color 0.3s para que el color cambie suavemente al hacer hover.", "previewHTML": "", "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 transition property", + "message": "Usa la propiedad transition", "options": { "caseSensitive": false } }, { "type": "regex", "value": "transition:\\s*background-color\\s*0\\.3s", - "message": "Set transition: background-color 0.3s", + "message": "Establece transition: background-color 0.3s", "options": { "caseSensitive": false } } ] @@ -36,8 +36,8 @@ { "id": "transitions-2", "title": "Timing Funcs", - "description": "Explore easing functions like ease, linear, ease-in, ease-out to control animation pacing.", - "task": "Set transition-timing-function to ease-in-out on .btn.", + "description": "Explora funciones de easing como ease, linear, ease-in, ease-out para controlar el ritmo de la animación.", + "task": "Establece transition-timing-function a ease-in-out.", "previewHTML": "", "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 transition-timing-function", + "message": "Usa transition-timing-function", "options": { "caseSensitive": false } }, { "type": "property_value", "value": { "property": "transition-timing-function", "expected": "ease-in-out" }, - "message": "Set timing to ease-in-out" + "message": "Establece timing a ease-in-out" } ] }, { "id": "transitions-3", "title": "Keyframes", - "description": "Create named animations using @keyframes and apply them via the animation shorthand.

@keyframes bounce {\n  50% { transform: translateY(-20px); }\n}\n.ball {\n  animation: bounce 1s infinite;\n}
", - "task": "Define a keyframe at 50% with transform: translateY(-20px) and apply animation: bounce 1s infinite to .ball.", + "description": "Crea animaciones nombradas usando @keyframes y aplícalas con el atajo animation.

@keyframes bounce {\n  50% { transform: translateY(-20px); }\n}\n.ball {\n  animation: bounce 1s infinite;\n}
", + "task": "Define un keyframe en 50% con transform: translateY(-20px) y aplica animation: bounce 1s infinite a .ball.", "previewHTML": "
", "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 50%, use transform: translateY(-20px)", + "message": "En 50%, usa transform: translateY(-20px)", "options": { "caseSensitive": false } }, { "type": "contains", "value": "animation", - "message": "Use animation property on .ball", + "message": "Usa la propiedad animation en .ball", "options": { "caseSensitive": false } }, { "type": "regex", "value": "animation:.*bounce.*1s.*infinite", - "message": "Apply animation: bounce 1s infinite", + "message": "Aplica animation: bounce 1s infinite", "options": { "caseSensitive": false } } ] @@ -103,8 +103,8 @@ { "id": "transitions-4", "title": "Animation Properties", - "description": "Fine-tune animations with animation-delay, animation-iteration-count, animation-direction, and animation-fill-mode.", - "task": "Apply the pulse animation to .box with animation-name: pulse, animation-duration: 2s, animation-delay: 1s, animation-iteration-count: 2, and animation-fill-mode: forwards.", + "description": "Ajusta las animaciones con animation-delay, animation-iteration-count, animation-direction y animation-fill-mode.", + "task": "Aplica la animación pulse a .box con animation-name: pulse, animation-duration: 2s, animation-delay: 1s, animation-iteration-count: 2 y animation-fill-mode: forwards.", "previewHTML": "
Pulse
", "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 animation-name: pulse" + "message": "Establece animation-name: pulse" }, { "type": "property_value", "value": { "property": "animation-duration", "expected": "2s" }, - "message": "Set animation-duration: 2s" + "message": "Establece animation-duration: 2s" }, { "type": "property_value", "value": { "property": "animation-delay", "expected": "1s" }, - "message": "Set animation-delay: 1s" + "message": "Establece animation-delay: 1s" }, { "type": "property_value", "value": { "property": "animation-iteration-count", "expected": "2" }, - "message": "Set animation-iteration-count: 2" + "message": "Establece animation-iteration-count: 2" }, { "type": "property_value", "value": { "property": "animation-fill-mode", "expected": "forwards" }, - "message": "Set animation-fill-mode: forwards" + "message": "Establece animation-fill-mode: forwards" } ] } diff --git a/lessons/es/08-responsive.json b/lessons/es/08-responsive.json index 0db9b98..3612a6c 100644 --- a/lessons/es/08-responsive.json +++ b/lessons/es/08-responsive.json @@ -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 @media (max-width: 600px) that changes .panel background to lightcoral.", + "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.

@media (max-width: 600px) {\n  .panel {\n    background: lightcoral;\n  }\n}
", + "task": "Escribe una media query con @media (max-width: 600px) que cambie el fondo de .panel a lightcoral.", "previewHTML": "
Resize the window
", "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 @media (max-width: 600px)", + "message": "Usa @media (max-width: 600px)", "options": { "caseSensitive": false } }, { "type": "contains", "value": ".panel", - "message": "Target .panel inside the media query", + "message": "Selecciona .panel dentro de la media query", "options": { "caseSensitive": false } }, { "type": "property_value", "value": { "property": "background", "expected": "lightcoral" }, - "message": "Set background: lightcoral", + "message": "Establece background: lightcoral", "options": { "exact": false } } ] @@ -42,8 +42,8 @@ { "id": "responsive-2", "title": "Fluid Type", - "description": "Use relative units like vw to make font sizes scale with the viewport width.", - "task": "Set font-size: 5vw on .text so it scales as the viewport changes.", + "description": "Usa unidades relativas como vw para que los tamaños de fuente escalen con el ancho del viewport.", + "task": "Establece font-size: 5vw para que escale con el viewport.", "previewHTML": "

Fluid Typography

", "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 font-size: 5vw" } + { + "type": "property_value", + "value": { "property": "font-size", "expected": "5vw" }, + "message": "Establece font-size: 5vw" + } ] }, { "id": "responsive-3", - "title": "Flex Grids", - "description": "Combine CSS Grid with auto-fit or auto-fill for responsive column layouts.", - "task": "Add display: grid, grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)), and gap: 1rem to .cards.", - "previewHTML": "
1
2
3
4
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .cards > div { background: #d1c4e9; padding: 1rem; }", + "title": "Responsive Grid", + "description": "Combina CSS Grid con auto-fit o auto-fill para layouts de columnas responsivos que ajustan automáticamente el número de columnas según el espacio disponible.", + "task": "Añade display: grid, grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) y gap: 1rem.", + "previewHTML": "

Fast

Lightning quick load times

Secure

Enterprise-grade security

Reliable

99.9% uptime guaranteed

Support

24/7 customer service

", + "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 display: grid" + "message": "Establece display: grid" }, { "type": "regex", "value": "repeat\\(auto-fit,\\s*minmax\\(200px,\\s*1fr\\)\\)", - "message": "Use repeat(auto-fit, minmax(200px, 1fr))", + "message": "Usa repeat(auto-fit, minmax(200px, 1fr))", "options": { "caseSensitive": false } }, { "type": "property_value", "value": { "property": "gap", "expected": "1rem" }, - "message": "Set gap: 1rem" + "message": "Establece gap: 1rem" } ] }, { "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 @media (min-width: 768px) that sets .sidebar width to 250px.", + "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 @media (min-width: 768px) que establezca el ancho de .sidebar a 250px.", "previewHTML": "", "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 @media (min-width: 768px)", + "message": "Usa @media (min-width: 768px)", "options": { "caseSensitive": false } }, { "type": "contains", "value": ".sidebar", - "message": "Target .sidebar inside media query", + "message": "Selecciona .sidebar en la media query", "options": { "caseSensitive": false } }, { "type": "property_value", "value": { "property": "width", "expected": "250px" }, - "message": "Set width: 250px", + "message": "Establece width: 250px", "options": { "exact": false } } ] diff --git a/lessons/es/20-html-elements.json b/lessons/es/20-html-elements.json index 4d8553a..4670637 100644 --- a/lessons/es/20-html-elements.json +++ b/lessons/es/20-html-elements.json @@ -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:

Block elements (containers) start on a new line and take full width. Examples: <div>, <p>, <h1>, <section>

Inline elements flow within text and only take needed width. Examples: <span>, <a>, <strong>, <em>", - "task": "Wrap the word important with <strong> 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:

Elementos de bloque (contenedores) comienzan en una nueva línea y ocupan todo el ancho. Ejemplos: <div>, <p>, <h1>, <section>

Elementos en línea fluyen dentro del texto y solo ocupan el ancho necesario. Ejemplos: <span>, <a>, <strong>, <em>", + "task": "Envuelve la palabra importante con etiquetas <strong> 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": "

This is a paragraph with an important word.

", - "solution": "

This is a paragraph with an important word.

", + "initialCode": "

Este es un párrafo con una palabra importante.

", + "solution": "

Este es un párrafo con una palabra importante.

", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "p", - "message": "Add a <p> paragraph element" + "message": "Añade un elemento de párrafo <p>" }, { "type": "parent_child", "value": { "parent": "p", "child": "strong" }, - "message": "Wrap the word important with <strong> tags" + "message": "Envuelve la palabra importante con etiquetas <strong>" } ] }, { "id": "semantic-containers", - "title": "Semantic Tags", - "description": "Modern HTML uses semantic containers that describe their content:

<header> - Page or section header
<nav> - Navigation links
<main> - Main content area
<section> - Thematic grouping
<article> - Self-contained content
<footer> - Page or section footer", - "task": "Create a basic page structure:
1. Add a <header> with an <h1> containing the text My Website
2. Add a <main> element with a paragraph saying Welcome to my site!
3. Add a <footer> with a paragraph saying Copyright 2026", + "title": "Etiquetas semánticas", + "description": "El HTML moderno usa contenedores semánticos que describen su contenido:

<header> - Encabezado de página o sección
<nav> - Enlaces de navegación
<main> - Área de contenido principal
<section> - Agrupación temática
<article> - Contenido independiente
<footer> - Pie de página o sección", + "task": "Crea una estructura básica de página:
1. Añade un <header> con un <h1> que contenga el texto Mi Sitio Web
2. Añade un elemento <main> con un párrafo que diga ¡Bienvenido a mi sitio!
3. Añade un <footer> con un párrafo que diga Copyright 2026", "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": "
\n

My Website

\n
\n
\n

Welcome to my site!

\n
\n", + "solution": "
\n

Mi Sitio Web

\n
\n
\n

¡Bienvenido a mi sitio!

\n
\n", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "header", - "message": "Add a <header> element" + "message": "Añade un elemento <header>" }, { "type": "element_exists", "value": "main", - "message": "Add a <main> element" + "message": "Añade un elemento <main>" }, { "type": "element_exists", "value": "footer", - "message": "Add a <footer> element" + "message": "Añade un elemento <footer>" }, { "type": "parent_child", "value": { "parent": "header", "child": "h1" }, - "message": "Add an <h1> heading inside your header" + "message": "Añade un encabezado <h1> dentro de tu header" } ] } diff --git a/lessons/es/21-html-forms-basic.json b/lessons/es/21-html-forms-basic.json index e92962f..698724b 100644 --- a/lessons/es/21-html-forms-basic.json +++ b/lessons/es/21-html-forms-basic.json @@ -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 <form> wrapper. Inside, use <label> to describe inputs and <input> for user data entry.

The for attribute on labels should match the id on inputs for accessibility.", - "task": "Create a form with:
1. A <label> with the text Name: and for=\"name\" attribute
2. A text <input> with id=\"name\" and name=\"name\" attributes", + "title": "Estructura del formulario", + "description": "Todo formulario necesita un contenedor <form>. Dentro, usa <label> para describir campos y <input> para la entrada de datos.

El atributo for en los labels debe coincidir con el id de los inputs para accesibilidad.", + "task": "Crea un formulario con:
1. Un <label> con el texto Nombre: y el atributo for=\"name\"
2. Un <input> de texto con los atributos id=\"name\" y name=\"name\"", "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": "
\n \n \n
", + "solution": "
\n \n \n
", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "form", - "message": "Wrap everything in a <form> element" + "message": "Envuelve todo en un elemento <form>" }, { "type": "element_exists", "value": "label", - "message": "Add a <label> for your input" + "message": "Añade un <label> para tu campo" }, { "type": "element_exists", "value": "input", - "message": "Add an <input> element" + "message": "Añade un elemento <input>" }, { "type": "attribute_value", "value": { "selector": "label", "attr": "for", "value": null }, - "message": "Add a for attribute to your label" + "message": "Añade un atributo for a tu label" }, { "type": "attribute_value", "value": { "selector": "input", "attr": "id", "value": null }, - "message": "Add an id attribute to your input" + "message": "Añade un atributo id a tu campo" } ] }, { "id": "input-types", - "title": "Input Types", - "description": "Different input types provide appropriate keyboards and validation:

type=\"text\" - General text
type=\"email\" - Email with @ validation
type=\"password\" - Hidden characters
type=\"number\" - Numeric keyboard
type=\"tel\" - Phone keyboard", - "task": "Create a login form with two fields:
1. An email field: <label for=\"email\">Email:</label> and <input type=\"email\" id=\"email\">
2. A password field: <label for=\"password\">Password:</label> and <input type=\"password\" id=\"password\">", + "title": "Tipos de campos", + "description": "Diferentes tipos de campos proporcionan teclados apropiados y validación:

type=\"text\" - Texto general
type=\"email\" - Email con validación @
type=\"password\" - Caracteres ocultos
type=\"number\" - Teclado numérico
type=\"tel\" - Teclado telefónico", + "task": "Crea un formulario de inicio de sesión con dos campos:
1. Campo de email: <label for=\"email\">Email:</label> y <input type=\"email\" id=\"email\">
2. Campo de contraseña: <label for=\"password\">Contraseña:</label> y <input type=\"password\" id=\"password\">", "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": "
\n \n
", - "solution": "
\n \n \n \n \n \n
", + "solution": "
\n \n \n \n \n \n
", "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:

<button type=\"submit\"> - Preferred, flexible content
<input type=\"submit\"> - Simple text-only button

The button text should be action-oriented (e.g., Sign In, 'Register', 'Send').", - "task": "Add a submit button to the form with the text Sign In.", + "title": "Botón de envío", + "description": "Los formularios necesitan una forma de enviar datos. Usa:

<button type=\"submit\"> - Preferido, contenido flexible
<input type=\"submit\"> - Botón de solo texto

El texto del botón debe estar orientado a la acción (ej. Iniciar Sesión, 'Registrar', 'Enviar').", + "task": "Añade un botón de envío al formulario con el texto Iniciar Sesión.", "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": "
\n \n \n \n \n \n \n
", - "solution": "
\n \n \n \n \n \n \n \n
", + "initialCode": "
\n \n \n \n \n \n \n
", + "solution": "
\n \n \n \n \n \n \n \n
", "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 Sign In" + "value": { "selector": "button", "text": "Iniciar Sesión" }, + "message": "El botón debe decir Iniciar Sesión" } ] } diff --git a/lessons/es/22-html-forms-validation.json b/lessons/es/22-html-forms-validation.json index 72f4cd1..c89af07 100644 --- a/lessons/es/22-html-forms-validation.json +++ b/lessons/es/22-html-forms-validation.json @@ -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 required attribute prevents form submission if the field is empty.

Add it to any input that must be filled:
<input type=\"text\" required>

The browser shows a validation message automatically.", - "task": "Make both the name and email fields required by adding the required attribute.", + "title": "Campos requeridos", + "description": "El atributo required evita el envío del formulario si el campo está vacío. ¡El navegador muestra un mensaje de validación automáticamente - sin JavaScript!

Añádelo a cualquier campo que deba rellenarse:
<input type=\"text\" required>", + "task": "Haz que ambos campos (nombre y email) sean requeridos añadiendo el atributo required 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": "
\n \n \n \n \n \n \n \n
", - "solution": "
\n \n \n \n \n \n \n \n
", + "initialCode": "
\n \n \n \n \n \n \n \n
", + "solution": "
\n \n \n \n \n \n \n \n
", "previewContainer": "preview-area", "validations": [ { "type": "attribute_value", "value": { "selector": "input[name='name']", "attr": "required", "value": true }, - "message": "Add the required attribute to the name input" + "message": "Añade required al campo de nombre" }, { "type": "attribute_value", "value": { "selector": "input[name='email']", "attr": "required", "value": true }, - "message": "Add the required attribute to the email input" - } - ] - }, - { - "id": "input-constraints", - "title": "Constraints", - "description": "Control what users can enter:

minlength / maxlength - Text length limits
min / max - Number range
pattern - Regex pattern matching
placeholder - Hint text (not a label!)", - "task": "Add validation to the password input:
1. Add minlength=\"8\" for minimum length
2. Add maxlength=\"20\" for maximum length
3. Add placeholder=\"Enter password\" 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": "
\n \n \n Must be 8-20 characters\n \n \n
", - "solution": "
\n \n \n Must be 8-20 characters\n \n \n
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "attribute_value", - "value": { "selector": "input[type='password']", "attr": "minlength", "value": "8" }, - "message": "Add minlength=\"8\" to the password input" - }, - { - "type": "attribute_value", - "value": { "selector": "input[type='password']", "attr": "maxlength", "value": "20" }, - "message": "Add maxlength=\"20\" to the password input" - }, - { - "type": "attribute_value", - "value": { "selector": "input[type='password']", "attr": "placeholder", "value": null }, - "message": "Add a placeholder to hint what to enter" - } - ] - }, - { - "id": "complete-registration", - "title": "Full Form", - "description": "Build a complete registration form with all validation concepts:

- Required fields marked with *
- Email validation (use type=\"email\")
- Password with length constraints
- Terms checkbox (required)
- 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": "
\n

Create Account

\n \n \n \n \n \n \n \n \n \n \n \n \n \n
", - "solution": "
\n

Create Account

\n \n \n \n \n \n \n \n \n \n \n \n \n \n
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "attribute_value", - "value": { "selector": "#fullname", "attr": "required", "value": true }, - "message": "Make the full name field required" - }, - { - "type": "attribute_value", - "value": { "selector": "#email", "attr": "type", "value": "email" }, - "message": "Set the email input type=\"email\"" - }, - { - "type": "attribute_value", - "value": { "selector": "#email", "attr": "required", "value": true }, - "message": "Make the email field required" - }, - { - "type": "attribute_value", - "value": { "selector": "#password", "attr": "type", "value": "password" }, - "message": "Set the password input type=\"password\"" - }, - { - "type": "attribute_value", - "value": { "selector": "#password", "attr": "required", "value": true }, - "message": "Make the password field required" - }, - { - "type": "attribute_value", - "value": { "selector": "#password", "attr": "minlength", "value": "8" }, - "message": "Add minlength=\"8\" to password" - }, - { - "type": "attribute_value", - "value": { "selector": "#terms", "attr": "required", "value": true }, - "message": "Make the terms checkbox required" + "message": "Añade required al campo de email" } ] } diff --git a/lessons/es/23-html-details-summary.json b/lessons/es/23-html-details-summary.json index ff83fc3..f38a7e9 100644 --- a/lessons/es/23-html-details-summary.json +++ b/lessons/es/23-html-details-summary.json @@ -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 <details> element creates a collapsible section. The <summary> provides the clickable label.

Click the summary to toggle the hidden content - no JavaScript needed!", - "task": "Create a <details> element with:
1. A <summary> saying Click to reveal
2. A <p> with the text This content was hidden!", + "title": "Primer widget", + "description": "El elemento <details> crea una sección plegable. El <summary> proporciona la etiqueta clickeable.

¡Haz clic en el resumen para mostrar el contenido oculto - sin JavaScript!", + "task": "Crea un elemento <details> con:
1. Un <summary> que diga Click to reveal
2. Un <p> con el texto This content was hidden!", "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 <details> element" + "message": "Añade un elemento <details>" }, { "type": "element_exists", "value": "summary", - "message": "Add a <summary> inside the details" + "message": "Añade un <summary> dentro del details" }, { "type": "parent_child", "value": { "parent": "details", "child": "summary" }, - "message": "The <summary> must be inside <details>" + "message": "El <summary> debe estar dentro de <details>" }, { "type": "parent_child", "value": { "parent": "details", "child": "p" }, - "message": "Add a <p> inside <details> for the hidden content" + "message": "Añade un <p> dentro de <details> para el contenido oculto" } ] }, { "id": "details-open-attribute", - "title": "Pre-expanded Details", - "description": "By default, <details> is closed. Add the open attribute to show the content initially.

This is a boolean attribute - just add open without a value.", - "task": "Add the open attribute to the <details> element to show the content by default.", + "title": "Expandido por defecto", + "description": "Por defecto, <details> está cerrado. Añade el atributo open para mostrar el contenido inicialmente.

Este es un atributo booleano - solo añade open sin valor.", + "task": "Añade el atributo open al elemento <details> 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 open attribute to <details>" + "message": "Añade el atributo open a <details>" } ] }, { "id": "faq-accordion", - "title": "FAQ Accordion", - "description": "Multiple <details> elements create an accordion-style FAQ. Each question can be expanded independently.

Pro tip: Type details*3>summary+p and press Tab for Emmet expansion. *3 creates 3 elements, > nests inside, + adds siblings.", - "task": "Create an FAQ section with:
1. An <h1> saying Frequently Asked Questions
2. Three <details> elements, each with a question in <summary> and an answer in <p>", + "title": "Acordeón FAQ", + "description": "Múltiples elementos <details> crean un FAQ estilo acordeón. Cada pregunta puede expandirse independientemente.

Pro tip: Escribe details*3>summary+p y presiona Tab para expansión Emmet. *3 crea 3 elementos, > anida dentro, + añade hermanos.", + "task": "Crea una sección FAQ con:
1. Un <h1> que diga Frequently Asked Questions
2. Tres elementos <details>, cada uno con una pregunta en <summary> y una respuesta en <p>", "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 <h1> heading for the FAQ title" + "message": "Añade un encabezado <h1> para el título del FAQ" }, { "type": "element_count", "value": { "selector": "details", "min": 3 }, - "message": "Create at least 3 <details> elements for the FAQ" + "message": "Crea al menos 3 elementos <details> para el FAQ" }, { "type": "element_count", "value": { "selector": "summary", "min": 3 }, - "message": "Each <details> needs a <summary> for the question" + "message": "Cada <details> necesita un <summary> para la pregunta" }, { "type": "element_count", "value": { "selector": "details p", "min": 3 }, - "message": "Each <details> needs a <p> for the answer" + "message": "Cada <details> necesita un <p> para la respuesta" } ] } diff --git a/lessons/es/24-html-progress-meter.json b/lessons/es/24-html-progress-meter.json index 3a89bc3..b4a7521 100644 --- a/lessons/es/24-html-progress-meter.json +++ b/lessons/es/24-html-progress-meter.json @@ -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 <progress> element shows task completion. Use value for current progress and max for the total.

Note: This is not a self-closing tag! Write <progress>...</progress> with fallback text inside for older browsers.", - "task": "Create a progress bar showing 70% completion:
1. Add a <label> saying Download:
2. Add a <progress> with value=\"70\" and max=\"100\"", + "title": "Barras de progreso", + "description": "El elemento <progress> muestra el progreso de una tarea. Usa value para el progreso actual y max para el total.

Nota: ¡Esto no es una etiqueta de cierre automático! Escribe <progress>...</progress> con texto alternativo dentro para navegadores antiguos.", + "task": "Crea una barra de progreso mostrando 70% de completado:
1. Añade un <label> que diga Download:
2. Añade un <progress> con value=\"70\" y max=\"100\"", "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 <progress> element" + "message": "Añade un elemento <progress>" }, { "type": "attribute_value", "value": { "selector": "progress", "attr": "value", "value": "70" }, - "message": "Set value=\"70\" on the progress element" + "message": "Establece value=\"70\" en el elemento progress" }, { "type": "attribute_value", "value": { "selector": "progress", "attr": "max", "value": "100" }, - "message": "Set max=\"100\" on the progress element" + "message": "Establece max=\"100\" en el elemento progress" }, { "type": "element_exists", "value": "label", - "message": "Add a <label> for the progress bar" + "message": "Añade un <label> para la barra de progreso" } ] }, { "id": "progress-indeterminate", - "title": "Indeterminate Progress", - "description": "When progress is unknown (like loading), omit the value attribute. This creates an animated indeterminate state.

Useful for network requests or processes with unknown duration.", - "task": "Create a loading indicator:
1. Add a <p> saying Loading...
2. Add a <progress> without a value attribute", + "title": "Progreso indeterminado", + "description": "Cuando el progreso es desconocido (como al cargar), omite el atributo value. Esto crea un estado animado indeterminado.

Útil para solicitudes de red o procesos con duración desconocida.", + "task": "Crea un indicador de carga:
1. Añade un <p> que diga Loading...
2. Añade un <progress> 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 <progress> element" + "message": "Añade un elemento <progress>" }, { "type": "element_exists", "value": "p", - "message": "Add a <p> with loading text" + "message": "Añade un <p> con texto de carga" } ] }, { "id": "meter-gauge", - "title": "Meter Gauges", - "description": "The <meter> element displays a scalar value within a range. Use it for measurements like disk space, battery, or ratings.

Set low, high, and optimum to define good/bad ranges - the browser colors it accordingly!", - "task": "Create a battery level meter:
1. Add a <label> saying Battery:
2. Add a <meter> with:
- value=\"0.8\"
- min=\"0\" and max=\"1\"
- low=\"0.2\" and high=\"0.8\"
- optimum=\"1\"", + "title": "Indicadores meter", + "description": "El elemento <meter> muestra un valor escalar dentro de un rango. Úsalo para mediciones como espacio en disco, batería o calificaciones.

Establece low, high y optimum para definir rangos buenos/malos - ¡el navegador lo colorea correspondientemente!", + "task": "Crea un indicador de nivel de batería:
1. Añade un <label> que diga Battery:
2. Añade un <meter> con:
- value=\"0.8\"
- min=\"0\" y max=\"1\"
- low=\"0.2\" y high=\"0.8\"
- optimum=\"1\"", "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 <meter> element" + "message": "Añade un elemento <meter>" }, { "type": "attribute_value", "value": { "selector": "meter", "attr": "value", "value": "0.8" }, - "message": "Set value=\"0.8\" on the meter" + "message": "Establece value=\"0.8\" en el meter" + }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "min", "value": "0" }, + "message": "Establece min=\"0\" en el meter" + }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "max", "value": "1" }, + "message": "Establece max=\"1\" en el meter" }, { "type": "attribute_value", "value": { "selector": "meter", "attr": "low", "value": "0.2" }, - "message": "Set low=\"0.2\" to define the low threshold" + "message": "Establece low=\"0.2\" para definir el umbral bajo" + }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "high", "value": "0.8" }, + "message": "Establece high=\"0.8\" para definir el umbral alto" + }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "optimum", "value": "1" }, + "message": "Establece optimum=\"1\" para indicar el valor óptimo" }, { "type": "element_exists", "value": "label", - "message": "Add a <label> for the meter" + "message": "Añade un <label> para el meter" } ] } diff --git a/lessons/es/25-html-datalist.json b/lessons/es/25-html-datalist.json index 8d33f10..b76da6f 100644 --- a/lessons/es/25-html-datalist.json +++ b/lessons/es/25-html-datalist.json @@ -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 <datalist> element provides autocomplete suggestions for inputs. Connect it using the list attribute on the input matching the datalist's id.

Users can still type freely - suggestions are just helpers!", - "task": "Create a browser selector:
1. Add a <label> saying Browser:
2. Add an <input> with list=\"browsers\"
3. Add a <datalist id=\"browsers\"> with options for Chrome, Firefox, and Safari", + "title": "Campo con sugerencias", + "description": "El elemento <datalist> proporciona sugerencias de autocompletado para campos. Conéctalo usando el atributo list en el input que coincida con el id del datalist.

Los usuarios pueden escribir libremente - ¡las sugerencias son solo ayudas!", + "task": "Crea un selector de navegador:
1. Añade un <label> que diga Navegador:
2. Añade un <input> con list=\"browsers\"
3. Añade un <datalist id=\"browsers\"> 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 <datalist> element" + "message": "Añade un elemento <datalist>" }, { "type": "attribute_value", "value": { "selector": "input", "attr": "list", "value": "browsers" }, - "message": "Connect the input to datalist using list=\"browsers\"" + "message": "Conecta el input al datalist usando list=\"browsers\"" }, { "type": "element_count", "value": { "selector": "option", "min": 3 }, - "message": "Add at least 3 <option> elements inside <datalist>" + "message": "Añade al menos 3 elementos <option> dentro de <datalist>" }, { "type": "element_exists", "value": "label", - "message": "Add a <label> for the input" + "message": "Añade un <label> 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.

The value attribute is what gets entered, and you can add display text after it.", - "task": "Create a country input:
1. Add a <label> saying Country:
2. Add an <input> with list=\"countries\"
3. Add a <datalist id=\"countries\"> 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.

El atributo value es lo que se ingresa, y puedes añadir texto de visualización después.", + "task": "Crea un campo de país:
1. Añade un <label> que diga País:
2. Añade un <input> con list=\"countries\"
3. Añade un <datalist id=\"countries\"> 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 <datalist> element" + "message": "Añade un elemento <datalist>" }, { "type": "attribute_value", "value": { "selector": "datalist", "attr": "id", "value": "countries" }, - "message": "Set id=\"countries\" on the datalist" + "message": "Establece id=\"countries\" en el datalist" }, { "type": "attribute_value", "value": { "selector": "input", "attr": "list", "value": "countries" }, - "message": "Connect the input using list=\"countries\"" + "message": "Conecta el input usando list=\"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" } ] } diff --git a/lessons/es/27-html-dialog.json b/lessons/es/27-html-dialog.json index 4b721c7..5c4e43f 100644 --- a/lessons/es/27-html-dialog.json +++ b/lessons/es/27-html-dialog.json @@ -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 <dialog> element creates a native modal. Add the open attribute to show it.

Use <form method=\"dialog\"> inside to close it when the form submits - no JavaScript needed!", - "task": "Create a dialog with:
1. The open attribute to show it
2. An <h2> saying Welcome!
3. A <p> with a greeting message
4. A <form method=\"dialog\"> with a close button", + "title": "Abrir diálogo", + "description": "El elemento <dialog> crea un modal nativo. Añade el atributo open para mostrarlo.

¡Usa <form method=\"dialog\"> dentro para cerrarlo al enviar el formulario - sin JavaScript!", + "task": "Crea un diálogo con:
1. El atributo open para mostrarlo
2. Un <h2> que diga ¡Bienvenido!
3. Un <p> con un mensaje de saludo
4. Un <form method=\"dialog\"> 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 <dialog> element" + "message": "Añade un elemento <dialog>" }, { "type": "attribute_value", "value": { "selector": "dialog", "attr": "open", "value": true }, - "message": "Add the open attribute to show the dialog" + "message": "Añade el atributo open para mostrar el diálogo" }, { "type": "element_exists", "value": "dialog h2", - "message": "Add an <h2> heading inside the dialog" + "message": "Añade un encabezado <h2> dentro del diálogo" }, { "type": "element_exists", "value": "form[method='dialog']", - "message": "Add a <form method=\"dialog\"> for closing" + "message": "Añade un <form method=\"dialog\"> 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 method=\"dialog\" makes the form close the dialog on submit instead of sending data.

This pattern is perfect for confirmation dialogs, quick inputs, or settings panels.", - "task": "Create a confirmation dialog:
1. Add open to show it
2. An <h2> saying Confirm Delete
3. A <p> asking Are you sure?
4. A <form method=\"dialog\"> with Cancel and Delete buttons", + "title": "Diálogo + Formulario", + "description": "Los diálogos pueden contener formularios completos. El method=\"dialog\" hace que el formulario cierre el diálogo al enviar en lugar de enviar datos.

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:
1. Añade open para mostrarlo
2. Un <h2> que diga Confirmar eliminación
3. Un <p> preguntando ¿Estás seguro?
4. Un <form method=\"dialog\"> 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 <dialog> with the open attribute" + "message": "Añade un <dialog> 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 <form method=\"dialog\"> for the buttons" + "message": "Añade un <form method=\"dialog\"> 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)" } ] } diff --git a/lessons/es/28-html-forms-fieldset.json b/lessons/es/28-html-forms-fieldset.json index add7ef9..f1adca2 100644 --- a/lessons/es/28-html-forms-fieldset.json +++ b/lessons/es/28-html-forms-fieldset.json @@ -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 <fieldset> element groups related form controls together. Add a <legend> as the first child to give the group a title.

This helps with accessibility and visual organization of complex forms.", - "task": "Create a form with a fieldset:
1. A <form> element
2. A <fieldset> inside
3. A <legend> saying Personal Info
4. Two labeled inputs for name and email", + "title": "Agrupar con Fieldset", + "description": "El elemento <fieldset> agrupa controles de formulario relacionados. Añade un <legend> como primer hijo para dar un título al grupo.

Esto ayuda con la accesibilidad y organización visual de formularios complejos.", + "task": "Crea un formulario con un fieldset:
1. Un elemento <form>
2. Un <fieldset> dentro
3. Un <legend> que diga Información Personal
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 <form> element" + "message": "Añade un elemento <form>" }, { "type": "element_exists", "value": "fieldset", - "message": "Add a <fieldset> inside the form" + "message": "Añade un <fieldset> dentro del formulario" }, { "type": "element_exists", "value": "legend", - "message": "Add a <legend> to title your fieldset" + "message": "Añade un <legend> 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 <textarea> element creates a multi-line text input, perfect for longer content like messages or descriptions.

Use rows and cols attributes to set default size.", - "task": "Create a contact form:
1. A <fieldset> with <legend> Contact Us
2. A labeled <input> for email
3. A labeled <textarea> for the message
4. A submit <button>", + "title": "Añadiendo Textarea", + "description": "El elemento <textarea> crea una entrada de texto multilínea, perfecta para contenido largo como mensajes o descripciones.

Usa los atributos rows y cols para establecer el tamaño predeterminado.", + "task": "Crea un formulario de contacto:
1. Un <fieldset> con <legend> Contáctanos
2. Un <input> etiquetado para email
3. Un <textarea> etiquetado para el mensaje
4. Un <button> 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 <fieldset> element" + "message": "Añade un elemento <fieldset>" }, { "type": "element_exists", "value": "legend", - "message": "Add a <legend> element" + "message": "Añade un elemento <legend>" }, { "type": "element_exists", "value": "textarea", - "message": "Add a <textarea> for the message" + "message": "Añade un <textarea> 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 <fieldset> elements to organize different sections.

This improves usability for long forms like registration or checkout pages.", - "task": "Create a registration form with 2 fieldsets:
1. Account Info with username and password inputs
2. Preferences with a textarea for bio
3. A submit button outside the fieldsets", + "title": "Múltiples Fieldsets", + "description": "Los formularios complejos pueden usar múltiples elementos <fieldset> para organizar diferentes secciones.

Esto mejora la usabilidad en formularios largos como registro o checkout.", + "task": "Crea un formulario de registro con 2 fieldsets:
1. Información de Cuenta con campos de usuario y contraseña
2. Preferencias con un textarea para bio
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" } ] } diff --git a/lessons/es/30-html-tables.json b/lessons/es/30-html-tables.json index f4d29cf..05597ee 100644 --- a/lessons/es/30-html-tables.json +++ b/lessons/es/30-html-tables.json @@ -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 <table> with <tr> for rows. Inside rows, use <th> for headers and <td> for data cells.

The <caption> element provides an accessible title for the table.", - "task": "Create a simple table with:
1. A <caption> saying Fruit Prices
2. A header row with Fruit and Price columns
3. At least 2 data rows", + "title": "Tablas de datos", + "description": "Las tablas muestran datos estructurados en filas y columnas. Usa <table> como contenedor, <tr> para filas, <th> para celdas de encabezado y <td> para celdas de datos.

Añade <caption> para un título accesible que describa el contenido de la tabla.", + "task": "Crea una tabla de precios:
1. Un <caption> que diga Pricing
2. Una fila de encabezado con Plan y Price
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": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n
Fruit Prices
FruitPrice
Apple$1.50
Banana$0.75
", + "solution": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n
Pricing
PlanPrice
Basic$9
Pro$29
", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "table", - "message": "Add a <table> element" + "message": "Añade un elemento <table>" }, { "type": "element_exists", "value": "caption", - "message": "Add a <caption> for the table title" + "message": "Añade un <caption> 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 (<th>) 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 <thead> to group header rows and <tbody> to group data rows. This helps browsers and assistive technology understand the table structure.

You can also use <tfoot> for footer rows like totals.", - "task": "Create a structured table:
1. A <caption> with Monthly Sales
2. A <thead> with Month and Revenue headers
3. A <tbody> 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": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Monthly Sales
MonthRevenue
January$12,500
February$14,200
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "element_exists", - "value": "table", - "message": "Add a <table> element" - }, - { - "type": "element_exists", - "value": "caption", - "message": "Add a <caption> element" - }, - { - "type": "element_exists", - "value": "thead", - "message": "Add a <thead> for the header section" - }, - { - "type": "element_exists", - "value": "tbody", - "message": "Add a <tbody> 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 <tfoot> to create a footer section for totals or summary data. The footer stays at the bottom even if tbody has many rows.

Combine all sections for a fully structured, accessible table.", - "task": "Create a complete table:
1. A <caption> with Order Summary
2. A <thead> with Item and Price headers
3. A <tbody> with 2 items
4. A <tfoot> 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": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Order Summary
ItemPrice
Widget$25.00
Gadget$35.00
Total$60.00
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "element_exists", - "value": "table", - "message": "Add a <table> element" - }, - { - "type": "element_exists", - "value": "caption", - "message": "Add a <caption> element" - }, - { - "type": "element_exists", - "value": "thead", - "message": "Add a <thead> section" - }, - { - "type": "element_exists", - "value": "tbody", - "message": "Add a <tbody> section" - }, - { - "type": "element_exists", - "value": "tfoot", - "message": "Add a <tfoot> 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)" } ] } diff --git a/lessons/es/31-html-marquee.json b/lessons/es/31-html-marquee.json index ec3a486..eae3193 100644 --- a/lessons/es/31-html-marquee.json +++ b/lessons/es/31-html-marquee.json @@ -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 <marquee> element creates scrolling text - a classic from the early web! While deprecated, it still works in most browsers.

Note: For production, use CSS animations instead. But for learning and fun, marquee is great!", - "task": "Create a simple marquee:
1. Add a <marquee> element
2. Put some text inside like Welcome to my website!", + "title": "Texto Desplazable", + "description": "El elemento <marquee> crea texto desplazable - ¡un clásico de la web temprana! Aunque está obsoleto, todavía funciona en la mayoría de navegadores.

Nota: Para producción, usa animaciones CSS. ¡Pero para aprender y divertirse, marquee es genial!", + "task": "Crea un marquee simple:
1. Añade un elemento <marquee>
2. Pon texto dentro como ¡Bienvenido a mi sitio web!", "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 <marquee> element" + "message": "Añade un elemento <marquee>" } ] }, { "id": "marquee-direction", - "title": "Direction & Behavior", - "description": "Control the marquee with attributes:
direction: left, right, up, down
behavior: scroll (default), slide (stops at edge), alternate (bounces)
scrollamount: speed (default is 6)", - "task": "Create a bouncing marquee:
1. Add a <marquee> element
2. Set behavior=\"alternate\" to make it bounce
3. Add some fun text", + "title": "Dirección y Comportamiento", + "description": "Controla el marquee con atributos:
direction: left, right, up, down
behavior: scroll (predeterminado), slide (se detiene en el borde), alternate (rebota)
scrollamount: velocidad (predeterminado es 6)", + "task": "Crea un marquee que rebota:
1. Añade un elemento <marquee>
2. Pon behavior=\"alternate\" para hacerlo rebotar
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 <marquee> element" + "message": "Añade un elemento <marquee>" }, { "type": "attribute_value", "value": { "selector": "marquee", "attr": "behavior", "value": "alternate" }, - "message": "Add behavior=\"alternate\" to make it bounce" + "message": "Añade behavior=\"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!

Remember: This is deprecated HTML. Modern sites use CSS animations, but marquee is great for understanding web history.", - "task": "Create a news ticker:
1. A <marquee> with direction=\"left\"
2. Set scrollamount=\"5\" for smooth scrolling
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!

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:
1. Un <marquee> con direction=\"left\"
2. Pon scrollamount=\"5\" para desplazamiento suave
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 <marquee> element" + "message": "Añade un elemento <marquee>" }, { "type": "attribute_value", "value": { "selector": "marquee", "attr": "direction", "value": "left" }, - "message": "Add direction=\"left\" for horizontal scrolling" + "message": "Añade direction=\"left\" para desplazamiento horizontal" }, { "type": "attribute_value", "value": { "selector": "marquee", "attr": "scrollamount", "value": "5" }, - "message": "Add scrollamount=\"5\" for smooth speed" + "message": "Añade scrollamount=\"5\" para velocidad suave" } ] } diff --git a/lessons/es/32-html-svg.json b/lessons/es/32-html-svg.json index 59b9dbb..1f01cdf 100644 --- a/lessons/es/32-html-svg.json +++ b/lessons/es/32-html-svg.json @@ -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 <svg> element is the container, with width and height attributes.

Use <circle> with cx, cy (center) and r (radius) to draw circles.", - "task": "Create an SVG with a circle:
1. An <svg> with width=\"200\" and height=\"200\"
2. A <circle> centered at (100,100) with radius 50
3. Add a fill color", + "title": "Dibujando círculos", + "description": "SVG (Scalable Vector Graphics) permite dibujar formas directamente en HTML. El elemento <svg> es el contenedor con atributos width y height.

Usa <circle> con cx, cy (centro) y r (radio) para dibujar círculos.", + "task": "Crea un SVG con un círculo:
1. Un <svg> con width=\"200\" y height=\"200\"
2. Un <circle> centrado en (100,100) con radio 50
3. Añade un color fill", "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": "\n \n", + "solution": "\n \n", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "svg", - "message": "Add an <svg> element" + "message": "Añade un elemento <svg>" }, { "type": "element_exists", "value": "circle", - "message": "Add a <circle> element inside the SVG" + "message": "Añade un elemento <circle> dentro del SVG" + }, + { + "type": "attribute_value", + "value": { "selector": "svg", "attr": "width", "value": "200" }, + "message": "Establece width=\"200\" en el SVG" + }, + { + "type": "attribute_value", + "value": { "selector": "svg", "attr": "height", "value": "200" }, + "message": "Establece height=\"200\" en el SVG" }, { "type": "attribute_value", "value": { "selector": "circle", "attr": "cx", "value": "100" }, - "message": "Set cx=\"100\" for the circle's horizontal center" + "message": "Establece cx=\"100\" para el centro horizontal del círculo" }, { "type": "attribute_value", "value": { "selector": "circle", "attr": "cy", "value": "100" }, - "message": "Set cy=\"100\" for the circle's vertical center" + "message": "Establece cy=\"100\" para el centro vertical del círculo" + }, + { + "type": "attribute_value", + "value": { "selector": "circle", "attr": "r", "value": "50" }, + "message": "Establece r=\"50\" para el radio del círculo" } ] }, { "id": "svg-rect-line", - "title": "Rectangles & Lines", - "description": "Draw rectangles with <rect> using x, y, width, height.

Draw lines with <line> using x1, y1 (start) and x2, y2 (end). Lines need a stroke color!", - "task": "Create an SVG with:
1. An <svg> (200x150)
2. A <rect> at position (20,20) with size 80x60
3. A <line> from (120,30) to (180,90) with a stroke color", + "title": "Rectángulos y líneas", + "description": "Dibuja rectángulos con <rect> usando x, y, width, height.

Dibuja líneas con <line> usando x1, y1 (inicio) y x2, y2 (fin). ¡Las líneas necesitan un color stroke!", + "task": "Crea un SVG con:
1. Un <svg> (200x150)
2. Un <rect> en posición (20,20) con tamaño 80x60
3. Una <line> 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": "\n \n \n", + "solution": "\n \n \n", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "svg", - "message": "Add an <svg> element" + "message": "Añade un elemento <svg>" }, { "type": "element_exists", "value": "rect", - "message": "Add a <rect> element" + "message": "Añade un elemento <rect>" }, { "type": "element_exists", "value": "line", - "message": "Add a <line> element" + "message": "Añade un elemento <line>" + }, + { + "type": "attribute_value", + "value": { "selector": "svg", "attr": "width", "value": "200" }, + "message": "Establece width=\"200\" en el SVG" + }, + { + "type": "attribute_value", + "value": { "selector": "svg", "attr": "height", "value": "150" }, + "message": "Establece height=\"150\" en el SVG" + }, + { + "type": "attribute_value", + "value": { "selector": "rect", "attr": "x", "value": "20" }, + "message": "Establece x=\"20\" en el rect" + }, + { + "type": "attribute_value", + "value": { "selector": "rect", "attr": "y", "value": "20" }, + "message": "Establece y=\"20\" en el rect" + }, + { + "type": "attribute_value", + "value": { "selector": "rect", "attr": "width", "value": "80" }, + "message": "Establece width=\"80\" en el rect" + }, + { + "type": "attribute_value", + "value": { "selector": "rect", "attr": "height", "value": "60" }, + "message": "Establece height=\"60\" en el rect" + }, + { + "type": "attribute_value", + "value": { "selector": "line", "attr": "x1", "value": "120" }, + "message": "Establece x1=\"120\" en la line" + }, + { + "type": "attribute_value", + "value": { "selector": "line", "attr": "y1", "value": "30" }, + "message": "Establece y1=\"30\" en la line" + }, + { + "type": "attribute_value", + "value": { "selector": "line", "attr": "x2", "value": "180" }, + "message": "Establece x2=\"180\" en la line" + }, + { + "type": "attribute_value", + "value": { "selector": "line", "attr": "y2", "value": "90" }, + "message": "Establece y2=\"90\" en la line" + }, + { + "type": "contains", + "value": "stroke", + "message": "Añade un color stroke a la line" } ] }, { "id": "svg-shapes", - "title": "Multiple Shapes", - "description": "Combine shapes to create simple graphics! Add stroke for outlines and stroke-width for thickness.

Use fill=\"none\" for hollow shapes. Shapes stack in order - later elements appear on top.", - "task": "Create a simple face:
1. An <svg> (200x200)
2. A large <circle> for the face
3. Two small <circle> elements for eyes
4. A <line> for the smile", + "title": "Múltiples formas", + "description": "¡Combina formas para crear gráficos simples! Añade stroke para contornos y stroke-width para el grosor.

Usa fill=\"none\" para formas huecas. Las formas se apilan en orden - los elementos posteriores aparecen encima.", + "task": "Crea una cara simple:
1. Un <svg> (200x200)
2. Un <circle> grande para la cara
3. Dos <circle> pequeños para los ojos
4. Una <line> 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": "\n \n \n \n \n", + "solution": "\n \n \n \n \n", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "svg", - "message": "Add an <svg> element" + "message": "Añade un elemento <svg>" }, { "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 <line> for the smile" + "message": "Añade una <line> para la sonrisa" } ] } diff --git a/lessons/pl/00-welcome.json b/lessons/pl/00-welcome.json index 459920c..28d8058 100644 --- a/lessons/pl/00-welcome.json +++ b/lessons/pl/00-welcome.json @@ -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": "Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required!

What you'll learn:
HTML - Semantic elements, forms, tables, SVG (HTML Block & Inline, HTML Forms, HTML Tables)
CSS - Selectors, box model, flexbox, animations (CSS Selectors, CSS Box Model, CSS Flexbox)
Responsive Design - Media queries and mobile-first layouts

How it works:
1. Read the task in the left panel
2. Write code in the editor
3. See live results in the preview
4. Get instant feedback with hints

Keyboard shortcuts: Ctrl+Z to undo, Ctrl+Shift+Z to redo

More resources:
HTML over JS - Native HTML vs JavaScript solutions
Web Engineering Mandala - JavaScript technology roadmap", - "task": "Write Hello World", + "title": "Rozpocznij", + "description": "Code Crispies to darmowa platforma open-source do nauki tworzenia stron przez praktyczne ćwiczenia. Konto nie jest wymagane!

Czego się nauczysz:
HTML - Elementy semantyczne, formularze, tabele, SVG (HTML Blokowe i Liniowe, HTML Formularze, HTML Tabele)
CSS - Selektory, model pudełkowy, flexbox, animacje (CSS Selektory, CSS Model pudełkowy, CSS Flexbox)
Responsive Design - Media queries i układy mobile-first

Jak to działa:
1. Przeczytaj zadanie w lewym panelu
2. Napisz kod w edytorze
3. Zobacz wyniki na żywo w podglądzie
4. Otrzymaj natychmiastową informację zwrotną ze wskazówkami

Skróty klawiszowe: Ctrl+Z cofnij, Ctrl+Shift+Z ponów

Więcej zasobów:
HTML over JS - Natywny HTML vs rozwiązania JavaScript
Web Engineering Mandala - Mapa technologii JavaScript", + "task": "Napisz Hello World", "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 Hello World" + "message": "Napisz Hello World" } ] }, { "id": "overview", - "title": "Overview", - "description": "You're ready! Open the menu (☰) to explore all modules.

Recommended learning path:
1. HTML Block & Inline - Understand container vs inline elements
2. HTML Forms - Build interactive forms with validation
3. CSS Selectors - Target elements precisely
4. CSS Box Model - Master padding, margin, borders
5. CSS Flexbox - Create flexible layouts
6. CSS Animations - Add motion and transitions

Tips:
• Use Show Expected to see the target result
• Your progress is saved automatically
• Try Emmet in HTML mode: ul>li*3 + Tab

Open Source:
Gitea (Source) · GitHub (Mirror)
• Made by LibreTECH · Michael Czechowski", - "task": "Click Next to continue", + "title": "Przegląd", + "description": "Jesteś gotowy! Otwórz menu (☰) aby odkryć wszystkie moduły.

Zalecana ścieżka nauki:
1. HTML Blokowe i Liniowe - Zrozum elementy kontenerowe vs liniowe
2. HTML Formularze - Twórz interaktywne formularze z walidacją
3. CSS Selektory - Celuj w elementy precyzyjnie
4. CSS Model pudełkowy - Opanuj padding, margin, borders
5. CSS Flexbox - Twórz elastyczne układy
6. CSS Animacje - Dodaj ruch i przejścia

Wskazówki:
• Użyj Pokaż oczekiwane aby zobaczyć docelowy wynik
• Twój postęp jest zapisywany automatycznie
• Wypróbuj Emmet w trybie HTML: ul>li*3 + Tab

Open Source:
Gitea (Source) · GitHub (Mirror)
• Stworzone przez LibreTECH · Michael Czechowski", + "task": "Kliknij Dalej aby kontynuować", "previewHTML": "

Hello World! 🌍

Hallo Welt!

Bonjour le monde!

¡Hola Mundo!

Ciao Mondo!

Olá Mundo!

こんにちは世界!

你好世界!

안녕 세상!

Привет мир!

שלום עולם!

مرحبا بالعالم!

", "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ć" } ] }, diff --git a/lessons/pl/01-box-model.json b/lessons/pl/01-box-model.json index d525bcf..55db4fc 100644 --- a/lessons/pl/01-box-model.json +++ b/lessons/pl/01-box-model.json @@ -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 padding to 1rem to create space between the content and border.", - "previewHTML": "
Box Model Components
", - "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. Padding tworzy przestrzeń między treścią a krawędzią pudełka.

Bez paddingu tekst przylega niezręcznie do ramek. Padding sprawia, że treść jest czytelna i wizualnie zbalansowana.

.card {\n  padding: 1rem;\n}
", + "task": "Ta karta profilu wygląda na ciasną. Dodaj padding: 1rem, aby tekst miał miejsce do oddychania.", + "previewHTML": "

Sarah Chen

Frontend Developer

", + "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 padding: 1rem" + "message": "Ustaw padding: 1rem" } ] }, { "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 border to 2px solid darkslategray.", - "previewHTML": "
This box needs a border
", - "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 border przyjmuje trzy wartości: szerokość, styl i kolor.

Popularne style: solid, dashed, dotted, none", + "task": "Dodaj subtelny lewy akcent do karty za pomocą border-left: 4px solid steelblue.", + "previewHTML": "

Sarah Chen

Frontend Developer

", + "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 border: 2px solid darkslategray", + "value": "border-left:\\s*4px\\s+solid\\s+steelblue", + "message": "Ustaw border-left: 4px solid steelblue", "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 margin to 1rem to create space between this element and its neighbors.", - "previewHTML": "
This box needs margins
Adjacent element
", - "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ń na zewnątrz 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ą margin-bottom: 1rem na .card.", + "previewHTML": "

Sarah Chen

Frontend Developer

Alex Rivera

UX Designer

", + "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 margin: 1rem" + "value": { "property": "margin-bottom", "expected": "1rem" }, + "message": "Ustaw margin-bottom: 1rem" } ] }, { "id": "box-model-4", - "title": "Box Sizing: Border-Box", - "description": "The box-sizing property determines how element dimensions are calculated. The default content-box excludes padding and border from width/height, while border-box includes them, making layout calculations more intuitive.", - "task": "Set box-sizing to border-box so padding and border are included in the width.", - "previewHTML": "
Content-box (default)
Border-box
", - "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 width ustawia tylko szerokość treści. Padding i ramki są dodawane. To powoduje problemy z układem.

box-sizing: border-box 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ą width: 200px. Lewa używa domyślnego rozmiaru (content-box), stając się szersza niż oczekiwano. Napraw prawą kartę za pomocą box-sizing: border-box.", + "previewHTML": "
Content-box
Border-box
", + "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .demo { display: flex; gap: 1rem; } .card { width: 200px; padding: 1rem; border: 4px solid steelblue; background: white; border-radius: 8px; }", "sandboxCSS": "", - "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 box-sizing: border-box" + "message": "Ustaw box-sizing: border-box" } ] }, { "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 margin-bottom to 2rem. Notice the space between paragraphs equals 2rem (not 3rem) due to margin collapse.", - "previewHTML": "

This paragraph has a bottom margin.

This paragraph has a top margin of 1rem.

", - "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:
• 1 wartość: wszystkie strony
• 2 wartości: pionowo | poziomo
• 4 wartości: góra | prawo | dół | lewo", + "task": "Ten przycisk potrzebuje więcej miejsca poziomego niż pionowego. Ustaw padding: 8px 1rem (8px góra/dół, 1rem lewo/prawo).", + "previewHTML": "", + "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 margin-bottom: 2rem" + "type": "regex", + "value": "padding:\\s*8px\\s+1rem", + "message": "Ustaw padding: 8px 1rem", + "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 margin to 1rem 2rem for 1rem top/bottom and 2rem left/right.", - "previewHTML": "
This box needs margins: 1rem top/bottom, 2rem left/right
", - "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ą margin: 0 auto.", + "task": "Wycentruj tę kartę poziomo. Ustaw margin: 0 auto, aby automatycznie obliczyć równe marginesy lewo/prawo.", + "previewHTML": "

Sarah Chen

Frontend Developer

", + "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 margin: 1rem 2rem", + "value": "margin:\\s*0\\s+auto", + "message": "Ustaw margin: 0 auto", "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 padding to 2rem to add equal padding on all sides.", - "previewHTML": "
This box needs equal padding on all sides
", - "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, border-radius zaokrągla rogi ramki elementu. Użyj 50% na kwadratowym elemencie, aby utworzyć koło.", + "task": "Zrób okrągły obrazek awatara za pomocą border-radius: 50%.", + "previewHTML": "
\"Avatar\"

Sarah Chen

Frontend Developer

", + "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 padding: 2rem" + "value": { "property": "border-radius", "expected": "50%" }, + "message": "Ustaw border-radius: 50%" } ] }, { "id": "box-model-8", - "title": "Border on Specific Sides", - "description": "For granular control, you can target specific sides with border-top, border-right, border-bottom, or border-left.", - "task": "Set border-bottom to 4px solid dodgerblue.", - "previewHTML": "
This element needs only a bottom border
", - "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 padding: 1rem, border-left: 4px solid coral i border-radius: 4px.", + "previewHTML": "
New message

You have 3 unread notifications

", + "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 padding: 1rem" + }, { "type": "regex", - "value": "border-bottom:\\s*4px\\s+solid\\s+dodgerblue", - "message": "Set border-bottom: 4px solid dodgerblue", + "value": "border-left:\\s*4px\\s+solid\\s+coral", + "message": "Ustaw border-left: 4px solid coral", "options": { "caseSensitive": false } + }, + { + "type": "property_value", + "value": { "property": "border-radius", "expected": "4px" }, + "message": "Ustaw border-radius: 4px" } ] } diff --git a/lessons/pl/05-units-variables.json b/lessons/pl/05-units-variables.json index 67a9272..098c766 100644 --- a/lessons/pl/05-units-variables.json +++ b/lessons/pl/05-units-variables.json @@ -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 width of .box to 80% and max-width to 37.5rem.", - "previewHTML": "
Resize me!
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { background: #f5f5f5; padding: 1rem; }", + "title": "Relative Units", + "description": "CSS oferuje dwa typy jednostek: absolutne (jak px) i względne (jak % i rem). Jednostki względne dostosowują się do kontekstu, czyniąc layouty elastycznymi i dostępnymi.

Popularne jednostki względne:
% – Względem elementu nadrzędnego
rem – Względem rozmiaru czcionki root (zwykle 16px)
em – Względem rozmiaru czcionki elementu

Popularny wzorzec dla czytelnej treści: ustaw width: 100%, aby wypełnić dostępną przestrzeń, potem max-width: 40rem aby ograniczyć długość linii dla czytelności.", + "task": "Ten tekst artykułu jest zbyt szeroki na dużych ekranach. Dodaj max-width: 40rem dla optymalnej szerokości czytania.", + "previewHTML": "

The Art of Typography

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.

The ideal line length is 45-75 characters per line. At typical font sizes, this works out to roughly 40rem maximum width.

", + "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 width property", "options": { "caseSensitive": false } }, - { "type": "property_value", "value": { "property": "width", "expected": "80%" }, "message": "Set width to 80%" }, - { "type": "contains", "value": "max-width", "message": "Use max-width property", "options": { "caseSensitive": false } }, { "type": "property_value", - "value": { "property": "max-width", "expected": "37.5rem" }, - "message": "Set max-width to 37.5rem" + "value": { "property": "max-width", "expected": "40rem" }, + "message": "Ustaw max-width: 40rem" } ] }, { "id": "units-2", - "title": "CSS Custom Properties", - "description": "Define and reuse variables (--custom properties) to centralize your theme values.", - "task": "Create a --main-color variable in :root with #6200ee and apply it as the border color on .themed.", - "previewHTML": "
Variable Box
", - "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ą --nazwa i używaj z var(--nazwa). Zmienne zdefiniowane na :root są dostępne wszędzie.", + "task": "Zdefiniuj --brand: steelblue w :root, następnie użyj jako koloru background dla .btn.", + "previewHTML": "
", + "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 --main-color in :root", + "value": "--brand", + "message": "Zdefiniuj zmienną --brand", "options": { "caseSensitive": false } }, { "type": "contains", - "value": "var(--main-color)", - "message": "Use var(--main-color)", + "value": "steelblue", + "message": "Ustaw wartość na steelblue", "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 calc() function to combine different units in one expression.", - "task": "Set the width of .sized to calc(100% - 2rem) and min-height to calc(10vh + 1rem).", - "previewHTML": "
Calc Demo
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sized { background: #e8f5e9; padding: 1rem; }", + "title": "calc() Function", + "description": "Funkcja calc() 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 width: calc(100% - 200px) na .main.", + "previewHTML": "

Main Content

This area should fill the remaining width after accounting for the fixed-width sidebar.

", + "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 calc() function", "options": { "caseSensitive": false } }, { "type": "regex", - "value": "width:\\s*calc\\(100% - 2rem\\)", - "message": "Width should be calc(100% - 2rem)", - "options": { "caseSensitive": false } - }, - { - "type": "regex", - "value": "min-height:\\s*calc\\(10vh \\+ 1rem\\)", - "message": "Min-height should be calc(10vh + 1rem)", + "value": "width:\\s*calc\\(\\s*100%\\s*-\\s*200px\\s*\\)", + "message": "Ustaw width: calc(100% - 200px)", "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 .view a width of 50vw and height of 20vh.", - "previewHTML": "
Viewport Box
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .view { background: #ffe0b2; }", + "title": "Viewport Units", + "description": "Jednostki viewport wymiarują elementy względem okna przeglądarki:
vw – 1% szerokości viewport
vh – 1% wysokości viewport

Są idealne dla sekcji pełnoekranowych jak banery hero.", + "task": "Spraw, aby ta sekcja hero wypełniła wysokość viewport ustawiając min-height: 100vh.", + "previewHTML": "

Welcome

Scroll down to explore

", + "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 vw unit", "options": { "caseSensitive": false } }, - { "type": "contains", "value": "vh", "message": "Use vh unit", "options": { "caseSensitive": false } }, - { "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Set width to 50vw" }, - { "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Set height to 20vh" } + { + "type": "property_value", + "value": { "property": "min-height", "expected": "100vh" }, + "message": "Ustaw min-height: 100vh" + } ] } ] diff --git a/lessons/pl/06-transitions-animations.json b/lessons/pl/06-transitions-animations.json index 5f8280d..0d21172 100644 --- a/lessons/pl/06-transitions-animations.json +++ b/lessons/pl/06-transitions-animations.json @@ -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 transition to properties for smooth changes on state changes.

transition: property duration;\n/* e.g. transition: background-color 0.3s; */
", - "task": "Add transition: background-color 0.3s to .btn so the color fades smoothly on hover.", + "description": "Naucz się jak stosować transition do właściwości dla płynnych zmian przy zmianie stanu.

transition: property duration;\n/* np. transition: background-color 0.3s; */
", + "task": "Dodaj transition: background-color 0.3s, aby kolor płynnie zmieniał się przy hover.", "previewHTML": "", "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 transition property", + "message": "Użyj właściwości transition", "options": { "caseSensitive": false } }, { "type": "regex", "value": "transition:\\s*background-color\\s*0\\.3s", - "message": "Set transition: background-color 0.3s", + "message": "Ustaw transition: background-color 0.3s", "options": { "caseSensitive": false } } ] @@ -36,8 +36,8 @@ { "id": "transitions-2", "title": "Timing Funcs", - "description": "Explore easing functions like ease, linear, ease-in, ease-out to control animation pacing.", - "task": "Set transition-timing-function to ease-in-out on .btn.", + "description": "Poznaj funkcje easing jak ease, linear, ease-in, ease-out do kontrolowania tempa animacji.", + "task": "Ustaw transition-timing-function na ease-in-out.", "previewHTML": "", "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 transition-timing-function", + "message": "Użyj transition-timing-function", "options": { "caseSensitive": false } }, { "type": "property_value", "value": { "property": "transition-timing-function", "expected": "ease-in-out" }, - "message": "Set timing to ease-in-out" + "message": "Ustaw timing na ease-in-out" } ] }, { "id": "transitions-3", "title": "Keyframes", - "description": "Create named animations using @keyframes and apply them via the animation shorthand.

@keyframes bounce {\n  50% { transform: translateY(-20px); }\n}\n.ball {\n  animation: bounce 1s infinite;\n}
", - "task": "Define a keyframe at 50% with transform: translateY(-20px) and apply animation: bounce 1s infinite to .ball.", + "description": "Twórz nazwane animacje używając @keyframes i stosuj je przez skrót animation.

@keyframes bounce {\n  50% { transform: translateY(-20px); }\n}\n.ball {\n  animation: bounce 1s infinite;\n}
", + "task": "Zdefiniuj keyframe przy 50% z transform: translateY(-20px) i zastosuj animation: bounce 1s infinite na .ball.", "previewHTML": "
", "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 @keyframes bounce", + "message": "Zdefiniuj @keyframes bounce", "options": { "caseSensitive": false } }, { "type": "regex", "value": "50%.*transform: translateY\\(-20px\\)", - "message": "At 50%, use transform: translateY(-20px)", + "message": "Przy 50%, użyj transform: translateY(-20px)", "options": { "caseSensitive": false } }, { "type": "contains", "value": "animation", - "message": "Use animation property on .ball", + "message": "Użyj właściwości animation na .ball", "options": { "caseSensitive": false } }, { "type": "regex", "value": "animation:.*bounce.*1s.*infinite", - "message": "Apply animation: bounce 1s infinite", + "message": "Zastosuj animation: bounce 1s infinite", "options": { "caseSensitive": false } } ] @@ -103,8 +103,8 @@ { "id": "transitions-4", "title": "Animation Properties", - "description": "Fine-tune animations with animation-delay, animation-iteration-count, animation-direction, and animation-fill-mode.", - "task": "Apply the pulse animation to .box with animation-name: pulse, animation-duration: 2s, animation-delay: 1s, animation-iteration-count: 2, and animation-fill-mode: forwards.", + "description": "Dostosuj animacje za pomocą animation-delay, animation-iteration-count, animation-direction i animation-fill-mode.", + "task": "Zastosuj animację pulse na .box z animation-name: pulse, animation-duration: 2s, animation-delay: 1s, animation-iteration-count: 2 i animation-fill-mode: forwards.", "previewHTML": "
Pulse
", "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 animation-name: pulse" + "message": "Ustaw animation-name: pulse" }, { "type": "property_value", "value": { "property": "animation-duration", "expected": "2s" }, - "message": "Set animation-duration: 2s" + "message": "Ustaw animation-duration: 2s" }, { "type": "property_value", "value": { "property": "animation-delay", "expected": "1s" }, - "message": "Set animation-delay: 1s" + "message": "Ustaw animation-delay: 1s" }, { "type": "property_value", "value": { "property": "animation-iteration-count", "expected": "2" }, - "message": "Set animation-iteration-count: 2" + "message": "Ustaw animation-iteration-count: 2" }, { "type": "property_value", "value": { "property": "animation-fill-mode", "expected": "forwards" }, - "message": "Set animation-fill-mode: forwards" + "message": "Ustaw animation-fill-mode: forwards" } ] } diff --git a/lessons/pl/08-responsive.json b/lessons/pl/08-responsive.json index 0db9b98..273221e 100644 --- a/lessons/pl/08-responsive.json +++ b/lessons/pl/08-responsive.json @@ -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 @media (max-width: 600px) that changes .panel background to lightcoral.", + "description": "Poznaj składnię i przypadki użycia CSS media queries do warunkowego stosowania stylów na podstawie cech viewport.

@media (max-width: 600px) {\n  .panel {\n    background: lightcoral;\n  }\n}
", + "task": "Napisz media query z @media (max-width: 600px), która zmienia tło .panel na lightcoral.", "previewHTML": "
Resize the window
", "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 @media (max-width: 600px)", + "message": "Użyj @media (max-width: 600px)", "options": { "caseSensitive": false } }, { "type": "contains", "value": ".panel", - "message": "Target .panel inside the media query", + "message": "Zaadresuj .panel wewnątrz media query", "options": { "caseSensitive": false } }, { "type": "property_value", "value": { "property": "background", "expected": "lightcoral" }, - "message": "Set background: lightcoral", + "message": "Ustaw background: lightcoral", "options": { "exact": false } } ] @@ -42,8 +42,8 @@ { "id": "responsive-2", "title": "Fluid Type", - "description": "Use relative units like vw to make font sizes scale with the viewport width.", - "task": "Set font-size: 5vw on .text so it scales as the viewport changes.", + "description": "Używaj jednostek względnych jak vw, aby rozmiary czcionek skalowały się z szerokością viewport.", + "task": "Ustaw font-size: 5vw, aby skalowała się ze zmianą viewport.", "previewHTML": "

Fluid Typography

", "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 font-size: 5vw" } + { "type": "property_value", "value": { "property": "font-size", "expected": "5vw" }, "message": "Ustaw font-size: 5vw" } ] }, { "id": "responsive-3", - "title": "Flex Grids", - "description": "Combine CSS Grid with auto-fit or auto-fill for responsive column layouts.", - "task": "Add display: grid, grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)), and gap: 1rem to .cards.", - "previewHTML": "
1
2
3
4
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .cards > div { background: #d1c4e9; padding: 1rem; }", + "title": "Responsive Grid", + "description": "Połącz CSS Grid z auto-fit lub auto-fill dla responsywnych układów kolumnowych, które automatycznie dostosowują liczbę kolumn na podstawie dostępnej przestrzeni.", + "task": "Dodaj display: grid, grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) i gap: 1rem.", + "previewHTML": "

Fast

Lightning quick load times

Secure

Enterprise-grade security

Reliable

99.9% uptime guaranteed

Support

24/7 customer service

", + "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 display: grid" + "message": "Ustaw display: grid" }, { "type": "regex", "value": "repeat\\(auto-fit,\\s*minmax\\(200px,\\s*1fr\\)\\)", - "message": "Use repeat(auto-fit, minmax(200px, 1fr))", + "message": "Użyj repeat(auto-fit, minmax(200px, 1fr))", "options": { "caseSensitive": false } }, { "type": "property_value", "value": { "property": "gap", "expected": "1rem" }, - "message": "Set gap: 1rem" + "message": "Ustaw gap: 1rem" } ] }, { "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 @media (min-width: 768px) that sets .sidebar width to 250px.", + "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 @media (min-width: 768px), która ustawia szerokość .sidebar na 250px.", "previewHTML": "", "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 @media (min-width: 768px)", + "message": "Użyj @media (min-width: 768px)", "options": { "caseSensitive": false } }, { "type": "contains", "value": ".sidebar", - "message": "Target .sidebar inside media query", + "message": "Zaadresuj .sidebar w media query", "options": { "caseSensitive": false } }, { "type": "property_value", "value": { "property": "width", "expected": "250px" }, - "message": "Set width: 250px", + "message": "Ustaw width: 250px", "options": { "exact": false } } ] diff --git a/lessons/pl/20-html-elements.json b/lessons/pl/20-html-elements.json index 4d8553a..d0f6f37 100644 --- a/lessons/pl/20-html-elements.json +++ b/lessons/pl/20-html-elements.json @@ -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:

Block elements (containers) start on a new line and take full width. Examples: <div>, <p>, <h1>, <section>

Inline elements flow within text and only take needed width. Examples: <span>, <a>, <strong>, <em>", - "task": "Wrap the word important with <strong> 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:

Elementy blokowe (kontenery) zaczynają się w nowej linii i zajmują pełną szerokość. Przykłady: <div>, <p>, <h1>, <section>

Elementy liniowe płyną wewnątrz tekstu i zajmują tylko potrzebną szerokość. Przykłady: <span>, <a>, <strong>, <em>", + "task": "Otocz słowo ważne tagami <strong>, 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": "

This is a paragraph with an important word.

", - "solution": "

This is a paragraph with an important word.

", + "initialCode": "

To jest akapit z ważne słowem.

", + "solution": "

To jest akapit z ważne słowem.

", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "p", - "message": "Add a <p> paragraph element" + "message": "Dodaj element akapitu <p>" }, { "type": "parent_child", "value": { "parent": "p", "child": "strong" }, - "message": "Wrap the word important with <strong> tags" + "message": "Otocz słowo ważne tagami <strong>" } ] }, { "id": "semantic-containers", - "title": "Semantic Tags", - "description": "Modern HTML uses semantic containers that describe their content:

<header> - Page or section header
<nav> - Navigation links
<main> - Main content area
<section> - Thematic grouping
<article> - Self-contained content
<footer> - Page or section footer", - "task": "Create a basic page structure:
1. Add a <header> with an <h1> containing the text My Website
2. Add a <main> element with a paragraph saying Welcome to my site!
3. Add a <footer> with a paragraph saying Copyright 2026", + "title": "Tagi semantyczne", + "description": "Nowoczesny HTML używa semantycznych kontenerów, które opisują swoją zawartość:

<header> - Nagłówek strony lub sekcji
<nav> - Linki nawigacyjne
<main> - Główna treść
<section> - Grupa tematyczna
<article> - Samodzielna treść
<footer> - Stopka strony lub sekcji", + "task": "Stwórz podstawową strukturę strony:
1. Dodaj <header> z <h1> zawierającym tekst Moja Strona
2. Dodaj element <main> z akapitem mówiącym Witaj na mojej stronie!
3. Dodaj <footer> z akapitem mówiącym Copyright 2026", "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": "
\n

My Website

\n
\n
\n

Welcome to my site!

\n
\n", + "solution": "
\n

Moja Strona

\n
\n
\n

Witaj na mojej stronie!

\n
\n", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "header", - "message": "Add a <header> element" + "message": "Dodaj element <header>" }, { "type": "element_exists", "value": "main", - "message": "Add a <main> element" + "message": "Dodaj element <main>" }, { "type": "element_exists", "value": "footer", - "message": "Add a <footer> element" + "message": "Dodaj element <footer>" }, { "type": "parent_child", "value": { "parent": "header", "child": "h1" }, - "message": "Add an <h1> heading inside your header" + "message": "Dodaj nagłówek <h1> wewnątrz header" } ] } diff --git a/lessons/pl/21-html-forms-basic.json b/lessons/pl/21-html-forms-basic.json index e92962f..eb78162 100644 --- a/lessons/pl/21-html-forms-basic.json +++ b/lessons/pl/21-html-forms-basic.json @@ -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 <form> wrapper. Inside, use <label> to describe inputs and <input> for user data entry.

The for attribute on labels should match the id on inputs for accessibility.", - "task": "Create a form with:
1. A <label> with the text Name: and for=\"name\" attribute
2. A text <input> with id=\"name\" and name=\"name\" attributes", + "title": "Struktura formularza", + "description": "Każdy formularz potrzebuje wrappera <form>. Wewnątrz używaj <label> do opisywania pól i <input> do wprowadzania danych.

Atrybut for w labelach powinien odpowiadać id pól dla dostępności.", + "task": "Stwórz formularz z:
1. <label> z tekstem Imię: i atrybutem for=\"name\"
2. Tekstowym <input> z atrybutami id=\"name\" i name=\"name\"", "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": "
\n \n \n
", + "solution": "
\n \n \n
", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "form", - "message": "Wrap everything in a <form> element" + "message": "Otocz wszystko elementem <form>" }, { "type": "element_exists", "value": "label", - "message": "Add a <label> for your input" + "message": "Dodaj <label> dla swojego pola" }, { "type": "element_exists", "value": "input", - "message": "Add an <input> element" + "message": "Dodaj element <input>" }, { "type": "attribute_value", "value": { "selector": "label", "attr": "for", "value": null }, - "message": "Add a for attribute to your label" + "message": "Dodaj atrybut for do swojego labela" }, { "type": "attribute_value", "value": { "selector": "input", "attr": "id", "value": null }, - "message": "Add an id attribute to your input" + "message": "Dodaj atrybut id do swojego pola" } ] }, { "id": "input-types", - "title": "Input Types", - "description": "Different input types provide appropriate keyboards and validation:

type=\"text\" - General text
type=\"email\" - Email with @ validation
type=\"password\" - Hidden characters
type=\"number\" - Numeric keyboard
type=\"tel\" - Phone keyboard", - "task": "Create a login form with two fields:
1. An email field: <label for=\"email\">Email:</label> and <input type=\"email\" id=\"email\">
2. A password field: <label for=\"password\">Password:</label> and <input type=\"password\" id=\"password\">", + "title": "Typy pól", + "description": "Różne typy pól zapewniają odpowiednie klawiatury i walidację:

type=\"text\" - Ogólny tekst
type=\"email\" - Email z walidacją @
type=\"password\" - Ukryte znaki
type=\"number\" - Klawiatura numeryczna
type=\"tel\" - Klawiatura telefoniczna", + "task": "Stwórz formularz logowania z dwoma polami:
1. Pole email: <label for=\"email\">Email:</label> i <input type=\"email\" id=\"email\">
2. Pole hasła: <label for=\"password\">Hasło:</label> i <input type=\"password\" id=\"password\">", "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": "
\n \n
", - "solution": "
\n \n \n \n \n \n
", + "solution": "
\n \n \n \n \n \n
", "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:

<button type=\"submit\"> - Preferred, flexible content
<input type=\"submit\"> - Simple text-only button

The button text should be action-oriented (e.g., Sign In, 'Register', 'Send').", - "task": "Add a submit button to the form with the text Sign In.", + "title": "Przycisk wysyłania", + "description": "Formularze potrzebują sposobu na wysłanie danych. Użyj:

<button type=\"submit\"> - Preferowany, elastyczna zawartość
<input type=\"submit\"> - Prosty przycisk tekstowy

Tekst przycisku powinien być zorientowany na akcję (np. Zaloguj, 'Zarejestruj', 'Wyślij').", + "task": "Dodaj przycisk wysyłania do formularza z tekstem Zaloguj.", "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": "
\n \n \n \n \n \n \n
", - "solution": "
\n \n \n \n \n \n \n \n
", + "initialCode": "
\n \n \n \n \n \n \n
", + "solution": "
\n \n \n \n \n \n \n \n
", "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 Sign In" + "value": { "selector": "button", "text": "Zaloguj" }, + "message": "Przycisk powinien wyświetlać Zaloguj" } ] } diff --git a/lessons/pl/22-html-forms-validation.json b/lessons/pl/22-html-forms-validation.json index 72f4cd1..c538abb 100644 --- a/lessons/pl/22-html-forms-validation.json +++ b/lessons/pl/22-html-forms-validation.json @@ -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 required attribute prevents form submission if the field is empty.

Add it to any input that must be filled:
<input type=\"text\" required>

The browser shows a validation message automatically.", - "task": "Make both the name and email fields required by adding the required attribute.", + "title": "Pola wymagane", + "description": "Atrybut required zapobiega wysłaniu formularza, jeśli pole jest puste. Przeglądarka automatycznie pokazuje komunikat walidacji - bez JavaScript!

Dodaj go do każdego pola, które musi być wypełnione:
<input type=\"text\" required>", + "task": "Uczyń oba pola (imię i email) wymaganymi, dodając atrybut required 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": "
\n \n \n \n \n \n \n \n
", - "solution": "
\n \n \n \n \n \n \n \n
", + "initialCode": "
\n \n \n \n \n \n \n \n
", + "solution": "
\n \n \n \n \n \n \n \n
", "previewContainer": "preview-area", "validations": [ { "type": "attribute_value", "value": { "selector": "input[name='name']", "attr": "required", "value": true }, - "message": "Add the required attribute to the name input" + "message": "Dodaj required do pola imienia" }, { "type": "attribute_value", "value": { "selector": "input[name='email']", "attr": "required", "value": true }, - "message": "Add the required attribute to the email input" - } - ] - }, - { - "id": "input-constraints", - "title": "Constraints", - "description": "Control what users can enter:

minlength / maxlength - Text length limits
min / max - Number range
pattern - Regex pattern matching
placeholder - Hint text (not a label!)", - "task": "Add validation to the password input:
1. Add minlength=\"8\" for minimum length
2. Add maxlength=\"20\" for maximum length
3. Add placeholder=\"Enter password\" 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": "
\n \n \n Must be 8-20 characters\n \n \n
", - "solution": "
\n \n \n Must be 8-20 characters\n \n \n
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "attribute_value", - "value": { "selector": "input[type='password']", "attr": "minlength", "value": "8" }, - "message": "Add minlength=\"8\" to the password input" - }, - { - "type": "attribute_value", - "value": { "selector": "input[type='password']", "attr": "maxlength", "value": "20" }, - "message": "Add maxlength=\"20\" to the password input" - }, - { - "type": "attribute_value", - "value": { "selector": "input[type='password']", "attr": "placeholder", "value": null }, - "message": "Add a placeholder to hint what to enter" - } - ] - }, - { - "id": "complete-registration", - "title": "Full Form", - "description": "Build a complete registration form with all validation concepts:

- Required fields marked with *
- Email validation (use type=\"email\")
- Password with length constraints
- Terms checkbox (required)
- 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": "
\n

Create Account

\n \n \n \n \n \n \n \n \n \n \n \n \n \n
", - "solution": "
\n

Create Account

\n \n \n \n \n \n \n \n \n \n \n \n \n \n
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "attribute_value", - "value": { "selector": "#fullname", "attr": "required", "value": true }, - "message": "Make the full name field required" - }, - { - "type": "attribute_value", - "value": { "selector": "#email", "attr": "type", "value": "email" }, - "message": "Set the email input type=\"email\"" - }, - { - "type": "attribute_value", - "value": { "selector": "#email", "attr": "required", "value": true }, - "message": "Make the email field required" - }, - { - "type": "attribute_value", - "value": { "selector": "#password", "attr": "type", "value": "password" }, - "message": "Set the password input type=\"password\"" - }, - { - "type": "attribute_value", - "value": { "selector": "#password", "attr": "required", "value": true }, - "message": "Make the password field required" - }, - { - "type": "attribute_value", - "value": { "selector": "#password", "attr": "minlength", "value": "8" }, - "message": "Add minlength=\"8\" to password" - }, - { - "type": "attribute_value", - "value": { "selector": "#terms", "attr": "required", "value": true }, - "message": "Make the terms checkbox required" + "message": "Dodaj required do pola email" } ] } diff --git a/lessons/pl/23-html-details-summary.json b/lessons/pl/23-html-details-summary.json index ff83fc3..f9d8450 100644 --- a/lessons/pl/23-html-details-summary.json +++ b/lessons/pl/23-html-details-summary.json @@ -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 <details> element creates a collapsible section. The <summary> provides the clickable label.

Click the summary to toggle the hidden content - no JavaScript needed!", - "task": "Create a <details> element with:
1. A <summary> saying Click to reveal
2. A <p> with the text This content was hidden!", + "title": "Pierwszy widget", + "description": "Element <details> tworzy zwijany sekcję. Element <summary> zapewnia klikalną etykietę.

Kliknij podsumowanie, aby przełączyć ukrytą treść - bez JavaScript!", + "task": "Utwórz element <details> z:
1. Elementem <summary> z tekstem Click to reveal
2. Elementem <p> z tekstem This content was hidden!", "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 <details> element" + "message": "Dodaj element <details>" }, { "type": "element_exists", "value": "summary", - "message": "Add a <summary> inside the details" + "message": "Dodaj <summary> wewnątrz details" }, { "type": "parent_child", "value": { "parent": "details", "child": "summary" }, - "message": "The <summary> must be inside <details>" + "message": "Element <summary> musi być wewnątrz <details>" }, { "type": "parent_child", "value": { "parent": "details", "child": "p" }, - "message": "Add a <p> inside <details> for the hidden content" + "message": "Dodaj <p> wewnątrz <details> dla ukrytej treści" } ] }, { "id": "details-open-attribute", - "title": "Pre-expanded Details", - "description": "By default, <details> is closed. Add the open attribute to show the content initially.

This is a boolean attribute - just add open without a value.", - "task": "Add the open attribute to the <details> element to show the content by default.", + "title": "Domyślnie rozwinięte", + "description": "Domyślnie <details> jest zamknięte. Dodaj atrybut open, aby pokazać treść na początku.

To jest atrybut logiczny - po prostu dodaj open bez wartości.", + "task": "Dodaj atrybut open do elementu <details>, 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 open attribute to <details>" + "message": "Dodaj atrybut open do <details>" } ] }, { "id": "faq-accordion", - "title": "FAQ Accordion", - "description": "Multiple <details> elements create an accordion-style FAQ. Each question can be expanded independently.

Pro tip: Type details*3>summary+p and press Tab for Emmet expansion. *3 creates 3 elements, > nests inside, + adds siblings.", - "task": "Create an FAQ section with:
1. An <h1> saying Frequently Asked Questions
2. Three <details> elements, each with a question in <summary> and an answer in <p>", + "title": "Akordeon FAQ", + "description": "Wiele elementów <details> tworzy FAQ w stylu akordeonu. Każde pytanie może być rozwijane niezależnie.

Pro tip: Wpisz details*3>summary+p i naciśnij Tab dla rozwinięcia Emmet. *3 tworzy 3 elementy, > zagnieżdża wewnątrz, + dodaje rodzeństwo.", + "task": "Utwórz sekcję FAQ z:
1. Nagłówkiem <h1> z tekstem Frequently Asked Questions
2. Trzema elementami <details>, każdy z pytaniem w <summary> i odpowiedzią w <p>", "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 <h1> heading for the FAQ title" + "message": "Dodaj nagłówek <h1> dla tytułu FAQ" }, { "type": "element_count", "value": { "selector": "details", "min": 3 }, - "message": "Create at least 3 <details> elements for the FAQ" + "message": "Utwórz co najmniej 3 elementy <details> dla FAQ" }, { "type": "element_count", "value": { "selector": "summary", "min": 3 }, - "message": "Each <details> needs a <summary> for the question" + "message": "Każdy <details> potrzebuje <summary> dla pytania" }, { "type": "element_count", "value": { "selector": "details p", "min": 3 }, - "message": "Each <details> needs a <p> for the answer" + "message": "Każdy <details> potrzebuje <p> dla odpowiedzi" } ] } diff --git a/lessons/pl/24-html-progress-meter.json b/lessons/pl/24-html-progress-meter.json index 3a89bc3..1b0a703 100644 --- a/lessons/pl/24-html-progress-meter.json +++ b/lessons/pl/24-html-progress-meter.json @@ -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 <progress> element shows task completion. Use value for current progress and max for the total.

Note: This is not a self-closing tag! Write <progress>...</progress> with fallback text inside for older browsers.", - "task": "Create a progress bar showing 70% completion:
1. Add a <label> saying Download:
2. Add a <progress> with value=\"70\" and max=\"100\"", + "title": "Paski postępu", + "description": "Element <progress> pokazuje postęp zadania. Użyj value dla aktualnego postępu i max dla całości.

Uwaga: To nie jest samozamykający się tag! Pisz <progress>...</progress> z tekstem zastępczym w środku dla starszych przeglądarek.", + "task": "Utwórz pasek postępu pokazujący 70% ukończenia:
1. Dodaj <label> z tekstem Download:
2. Dodaj <progress> z value=\"70\" i max=\"100\"", "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 <progress> element" + "message": "Dodaj element <progress>" }, { "type": "attribute_value", "value": { "selector": "progress", "attr": "value", "value": "70" }, - "message": "Set value=\"70\" on the progress element" + "message": "Ustaw value=\"70\" w elemencie progress" }, { "type": "attribute_value", "value": { "selector": "progress", "attr": "max", "value": "100" }, - "message": "Set max=\"100\" on the progress element" + "message": "Ustaw max=\"100\" w elemencie progress" }, { "type": "element_exists", "value": "label", - "message": "Add a <label> for the progress bar" + "message": "Dodaj <label> dla paska postępu" } ] }, { "id": "progress-indeterminate", - "title": "Indeterminate Progress", - "description": "When progress is unknown (like loading), omit the value attribute. This creates an animated indeterminate state.

Useful for network requests or processes with unknown duration.", - "task": "Create a loading indicator:
1. Add a <p> saying Loading...
2. Add a <progress> without a value attribute", + "title": "Nieokreślony postęp", + "description": "Gdy postęp jest nieznany (jak przy ładowaniu), pomiń atrybut value. To tworzy animowany stan nieokreślony.

Przydatne dla żądań sieciowych lub procesów o nieznanym czasie trwania.", + "task": "Utwórz wskaźnik ładowania:
1. Dodaj <p> z tekstem Loading...
2. Dodaj <progress> 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 <progress> element" + "message": "Dodaj element <progress>" }, { "type": "element_exists", "value": "p", - "message": "Add a <p> with loading text" + "message": "Dodaj <p> z tekstem ładowania" } ] }, { "id": "meter-gauge", - "title": "Meter Gauges", - "description": "The <meter> element displays a scalar value within a range. Use it for measurements like disk space, battery, or ratings.

Set low, high, and optimum to define good/bad ranges - the browser colors it accordingly!", - "task": "Create a battery level meter:
1. Add a <label> saying Battery:
2. Add a <meter> with:
- value=\"0.8\"
- min=\"0\" and max=\"1\"
- low=\"0.2\" and high=\"0.8\"
- optimum=\"1\"", + "title": "Wskaźniki meter", + "description": "Element <meter> wyświetla wartość skalarną w zakresie. Używaj go do pomiarów jak przestrzeń dyskowa, bateria lub oceny.

Ustaw low, high i optimum, aby zdefiniować dobre/złe zakresy - przeglądarka odpowiednio je koloruje!", + "task": "Utwórz wskaźnik poziomu baterii:
1. Dodaj <label> z tekstem Battery:
2. Dodaj <meter> z:
- value=\"0.8\"
- min=\"0\" i max=\"1\"
- low=\"0.2\" i high=\"0.8\"
- optimum=\"1\"", "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 <meter> element" + "message": "Dodaj element <meter>" }, { "type": "attribute_value", "value": { "selector": "meter", "attr": "value", "value": "0.8" }, - "message": "Set value=\"0.8\" on the meter" + "message": "Ustaw value=\"0.8\" w meter" + }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "min", "value": "0" }, + "message": "Ustaw min=\"0\" w meter" + }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "max", "value": "1" }, + "message": "Ustaw max=\"1\" w meter" }, { "type": "attribute_value", "value": { "selector": "meter", "attr": "low", "value": "0.2" }, - "message": "Set low=\"0.2\" to define the low threshold" + "message": "Ustaw low=\"0.2\", aby zdefiniować niski próg" + }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "high", "value": "0.8" }, + "message": "Ustaw high=\"0.8\", aby zdefiniować wysoki próg" + }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "optimum", "value": "1" }, + "message": "Ustaw optimum=\"1\", aby wskazać optymalną wartość" }, { "type": "element_exists", "value": "label", - "message": "Add a <label> for the meter" + "message": "Dodaj <label> dla meter" } ] } diff --git a/lessons/pl/25-html-datalist.json b/lessons/pl/25-html-datalist.json index 8d33f10..6ad729a 100644 --- a/lessons/pl/25-html-datalist.json +++ b/lessons/pl/25-html-datalist.json @@ -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 <datalist> element provides autocomplete suggestions for inputs. Connect it using the list attribute on the input matching the datalist's id.

Users can still type freely - suggestions are just helpers!", - "task": "Create a browser selector:
1. Add a <label> saying Browser:
2. Add an <input> with list=\"browsers\"
3. Add a <datalist id=\"browsers\"> with options for Chrome, Firefox, and Safari", + "title": "Pole z sugestiami", + "description": "Element <datalist> dostarcza sugestie autouzupełniania dla pól. Połącz go używając atrybutu list na polu, pasującego do id datalist.

Użytkownicy mogą nadal pisać dowolnie - sugestie to tylko pomocnicy!", + "task": "Utwórz selektor przeglądarki:
1. Dodaj <label> z tekstem Przeglądarka:
2. Dodaj <input> z list=\"browsers\"
3. Dodaj <datalist id=\"browsers\"> 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 <datalist> element" + "message": "Dodaj element <datalist>" }, { "type": "attribute_value", "value": { "selector": "input", "attr": "list", "value": "browsers" }, - "message": "Connect the input to datalist using list=\"browsers\"" + "message": "Połącz pole z datalist używając list=\"browsers\"" }, { "type": "element_count", "value": { "selector": "option", "min": 3 }, - "message": "Add at least 3 <option> elements inside <datalist>" + "message": "Dodaj co najmniej 3 elementy <option> wewnątrz <datalist>" }, { "type": "element_exists", "value": "label", - "message": "Add a <label> for the input" + "message": "Dodaj <label> dla pola" } ] }, { "id": "datalist-countries", - "title": "Country Selector", - "description": "Datalists work great for long lists like countries. Users can type to filter suggestions instantly.

The value attribute is what gets entered, and you can add display text after it.", - "task": "Create a country input:
1. Add a <label> saying Country:
2. Add an <input> with list=\"countries\"
3. Add a <datalist id=\"countries\"> 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.

Atrybut value określa, co zostanie wpisane, a po nim możesz dodać tekst wyświetlany.", + "task": "Utwórz pole kraju:
1. Dodaj <label> z tekstem Kraj:
2. Dodaj <input> z list=\"countries\"
3. Dodaj <datalist id=\"countries\"> 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 <datalist> element" + "message": "Dodaj element <datalist>" }, { "type": "attribute_value", "value": { "selector": "datalist", "attr": "id", "value": "countries" }, - "message": "Set id=\"countries\" on the datalist" + "message": "Ustaw id=\"countries\" w datalist" }, { "type": "attribute_value", "value": { "selector": "input", "attr": "list", "value": "countries" }, - "message": "Connect the input using list=\"countries\"" + "message": "Połącz pole używając list=\"countries\"" }, { "type": "element_count", "value": { "selector": "option", "min": 4 }, - "message": "Add at least 4 country options" + "message": "Dodaj co najmniej 4 opcje krajów" } ] } diff --git a/lessons/pl/27-html-dialog.json b/lessons/pl/27-html-dialog.json index 4b721c7..abfbed3 100644 --- a/lessons/pl/27-html-dialog.json +++ b/lessons/pl/27-html-dialog.json @@ -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 <dialog> element creates a native modal. Add the open attribute to show it.

Use <form method=\"dialog\"> inside to close it when the form submits - no JavaScript needed!", - "task": "Create a dialog with:
1. The open attribute to show it
2. An <h2> saying Welcome!
3. A <p> with a greeting message
4. A <form method=\"dialog\"> with a close button", + "title": "Otwórz dialog", + "description": "Element <dialog> tworzy natywne okno modalne. Dodaj atrybut open, aby je wyświetlić.

Użyj <form method=\"dialog\"> wewnątrz, aby zamknąć je przy wysłaniu formularza - bez JavaScript!", + "task": "Utwórz dialog z:
1. Atrybutem open aby go wyświetlić
2. Elementem <h2> z tekstem Witaj!
3. Elementem <p> z wiadomością powitalną
4. Elementem <form method=\"dialog\"> 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 <dialog> element" + "message": "Dodaj element <dialog>" }, { "type": "attribute_value", "value": { "selector": "dialog", "attr": "open", "value": true }, - "message": "Add the open attribute to show the dialog" + "message": "Dodaj atrybut open aby wyświetlić dialog" }, { "type": "element_exists", "value": "dialog h2", - "message": "Add an <h2> heading inside the dialog" + "message": "Dodaj nagłówek <h2> wewnątrz dialogu" }, { "type": "element_exists", "value": "form[method='dialog']", - "message": "Add a <form method=\"dialog\"> for closing" + "message": "Dodaj <form method=\"dialog\"> 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 method=\"dialog\" makes the form close the dialog on submit instead of sending data.

This pattern is perfect for confirmation dialogs, quick inputs, or settings panels.", - "task": "Create a confirmation dialog:
1. Add open to show it
2. An <h2> saying Confirm Delete
3. A <p> asking Are you sure?
4. A <form method=\"dialog\"> with Cancel and Delete buttons", + "title": "Dialog z formularzem", + "description": "Dialogi mogą zawierać pełne formularze. method=\"dialog\" sprawia, że formularz zamyka dialog przy wysłaniu zamiast wysyłać dane.

Ten wzorzec jest idealny dla dialogów potwierdzenia, szybkich wejść lub paneli ustawień.", + "task": "Utwórz dialog potwierdzenia:
1. Dodaj open aby go wyświetlić
2. Element <h2> z tekstem Potwierdź usunięcie
3. Element <p> z pytaniem Czy jesteś pewien?
4. Element <form method=\"dialog\"> 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 <dialog> with the open attribute" + "message": "Dodaj <dialog> 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 <form method=\"dialog\"> for the buttons" + "message": "Dodaj <form method=\"dialog\"> 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ź)" } ] } diff --git a/lessons/pl/28-html-forms-fieldset.json b/lessons/pl/28-html-forms-fieldset.json index add7ef9..4d6ccc6 100644 --- a/lessons/pl/28-html-forms-fieldset.json +++ b/lessons/pl/28-html-forms-fieldset.json @@ -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 <fieldset> element groups related form controls together. Add a <legend> as the first child to give the group a title.

This helps with accessibility and visual organization of complex forms.", - "task": "Create a form with a fieldset:
1. A <form> element
2. A <fieldset> inside
3. A <legend> saying Personal Info
4. Two labeled inputs for name and email", + "title": "Grupowanie z Fieldset", + "description": "Element <fieldset> grupuje powiązane elementy formularza. Dodaj <legend> jako pierwsze dziecko, aby nadać grupie tytuł.

To pomaga w dostępności i wizualnej organizacji złożonych formularzy.", + "task": "Utwórz formularz z fieldset:
1. Element <form>
2. Element <fieldset> wewnątrz
3. Element <legend> z tekstem Dane osobowe
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 <form> element" + "message": "Dodaj element <form>" }, { "type": "element_exists", "value": "fieldset", - "message": "Add a <fieldset> inside the form" + "message": "Dodaj <fieldset> wewnątrz formularza" }, { "type": "element_exists", "value": "legend", - "message": "Add a <legend> to title your fieldset" + "message": "Dodaj <legend> 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 <textarea> element creates a multi-line text input, perfect for longer content like messages or descriptions.

Use rows and cols attributes to set default size.", - "task": "Create a contact form:
1. A <fieldset> with <legend> Contact Us
2. A labeled <input> for email
3. A labeled <textarea> for the message
4. A submit <button>", + "title": "Dodawanie Textarea", + "description": "Element <textarea> tworzy wieloliniowe pole tekstowe, idealne dla dłuższych treści jak wiadomości lub opisy.

Użyj atrybutów rows i cols aby ustawić domyślny rozmiar.", + "task": "Utwórz formularz kontaktowy:
1. Element <fieldset> z <legend> Kontakt
2. Opisane <input> dla e-maila
3. Opisane <textarea> dla wiadomości
4. Przycisk <button> 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 <fieldset> element" + "message": "Dodaj element <fieldset>" }, { "type": "element_exists", "value": "legend", - "message": "Add a <legend> element" + "message": "Dodaj element <legend>" }, { "type": "element_exists", "value": "textarea", - "message": "Add a <textarea> for the message" + "message": "Dodaj <textarea> 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 <fieldset> elements to organize different sections.

This improves usability for long forms like registration or checkout pages.", - "task": "Create a registration form with 2 fieldsets:
1. Account Info with username and password inputs
2. Preferences with a textarea for bio
3. A submit button outside the fieldsets", + "title": "Wiele Fieldsetów", + "description": "Złożone formularze mogą używać wielu elementów <fieldset> do organizacji różnych sekcji.

To poprawia użyteczność długich formularzy jak rejestracja czy koszyk.", + "task": "Utwórz formularz rejestracji z 2 fieldsetami:
1. Dane konta z polami nazwa użytkownika i hasło
2. Preferencje z textarea dla bio
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" } ] } diff --git a/lessons/pl/30-html-tables.json b/lessons/pl/30-html-tables.json index f4d29cf..dc42f25 100644 --- a/lessons/pl/30-html-tables.json +++ b/lessons/pl/30-html-tables.json @@ -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 <table> with <tr> for rows. Inside rows, use <th> for headers and <td> for data cells.

The <caption> element provides an accessible title for the table.", - "task": "Create a simple table with:
1. A <caption> saying Fruit Prices
2. A header row with Fruit and Price columns
3. At least 2 data rows", + "title": "Tabele danych", + "description": "Tabele wyświetlają strukturalne dane w wierszach i kolumnach. Użyj <table> jako kontenera, <tr> dla wierszy, <th> dla komórek nagłówka i <td> dla komórek danych.

Dodaj <caption> dla dostępnego tytułu opisującego zawartość tabeli.", + "task": "Utwórz tabelę cenową:
1. Element <caption> z tekstem Pricing
2. Wiersz nagłówka z Plan i Price
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": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n
Fruit Prices
FruitPrice
Apple$1.50
Banana$0.75
", + "solution": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n
Pricing
PlanPrice
Basic$9
Pro$29
", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "table", - "message": "Add a <table> element" + "message": "Dodaj element <table>" }, { "type": "element_exists", "value": "caption", - "message": "Add a <caption> for the table title" + "message": "Dodaj <caption> 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 (<th>) 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 <thead> to group header rows and <tbody> to group data rows. This helps browsers and assistive technology understand the table structure.

You can also use <tfoot> for footer rows like totals.", - "task": "Create a structured table:
1. A <caption> with Monthly Sales
2. A <thead> with Month and Revenue headers
3. A <tbody> 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": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Monthly Sales
MonthRevenue
January$12,500
February$14,200
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "element_exists", - "value": "table", - "message": "Add a <table> element" - }, - { - "type": "element_exists", - "value": "caption", - "message": "Add a <caption> element" - }, - { - "type": "element_exists", - "value": "thead", - "message": "Add a <thead> for the header section" - }, - { - "type": "element_exists", - "value": "tbody", - "message": "Add a <tbody> 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 <tfoot> to create a footer section for totals or summary data. The footer stays at the bottom even if tbody has many rows.

Combine all sections for a fully structured, accessible table.", - "task": "Create a complete table:
1. A <caption> with Order Summary
2. A <thead> with Item and Price headers
3. A <tbody> with 2 items
4. A <tfoot> 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": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Order Summary
ItemPrice
Widget$25.00
Gadget$35.00
Total$60.00
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "element_exists", - "value": "table", - "message": "Add a <table> element" - }, - { - "type": "element_exists", - "value": "caption", - "message": "Add a <caption> element" - }, - { - "type": "element_exists", - "value": "thead", - "message": "Add a <thead> section" - }, - { - "type": "element_exists", - "value": "tbody", - "message": "Add a <tbody> section" - }, - { - "type": "element_exists", - "value": "tfoot", - "message": "Add a <tfoot> 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)" } ] } diff --git a/lessons/pl/31-html-marquee.json b/lessons/pl/31-html-marquee.json index ec3a486..1eadb01 100644 --- a/lessons/pl/31-html-marquee.json +++ b/lessons/pl/31-html-marquee.json @@ -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 <marquee> element creates scrolling text - a classic from the early web! While deprecated, it still works in most browsers.

Note: For production, use CSS animations instead. But for learning and fun, marquee is great!", - "task": "Create a simple marquee:
1. Add a <marquee> element
2. Put some text inside like Welcome to my website!", + "title": "Przewijający się tekst", + "description": "Element <marquee> tworzy przewijający się tekst - klasyk z wczesnego internetu! Choć przestarzały, nadal działa w większości przeglądarek.

Uwaga: W produkcji używaj animacji CSS. Ale do nauki i zabawy marquee jest świetne!", + "task": "Utwórz prosty marquee:
1. Dodaj element <marquee>
2. Umieść w nim tekst jak Witaj na mojej stronie!", "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 <marquee> element" + "message": "Dodaj element <marquee>" } ] }, { "id": "marquee-direction", - "title": "Direction & Behavior", - "description": "Control the marquee with attributes:
direction: left, right, up, down
behavior: scroll (default), slide (stops at edge), alternate (bounces)
scrollamount: speed (default is 6)", - "task": "Create a bouncing marquee:
1. Add a <marquee> element
2. Set behavior=\"alternate\" to make it bounce
3. Add some fun text", + "title": "Kierunek i zachowanie", + "description": "Kontroluj marquee za pomocą atrybutów:
direction: left, right, up, down
behavior: scroll (domyślnie), slide (zatrzymuje się na krawędzi), alternate (odbija się)
scrollamount: prędkość (domyślnie 6)", + "task": "Utwórz odbijający się marquee:
1. Dodaj element <marquee>
2. Ustaw behavior=\"alternate\" aby się odbijał
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 <marquee> element" + "message": "Dodaj element <marquee>" }, { "type": "attribute_value", "value": { "selector": "marquee", "attr": "behavior", "value": "alternate" }, - "message": "Add behavior=\"alternate\" to make it bounce" + "message": "Dodaj behavior=\"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!

Remember: This is deprecated HTML. Modern sites use CSS animations, but marquee is great for understanding web history.", - "task": "Create a news ticker:
1. A <marquee> with direction=\"left\"
2. Set scrollamount=\"5\" for smooth scrolling
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!

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:
1. Element <marquee> z direction=\"left\"
2. Ustaw scrollamount=\"5\" dla płynnego przewijania
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 <marquee> element" + "message": "Dodaj element <marquee>" }, { "type": "attribute_value", "value": { "selector": "marquee", "attr": "direction", "value": "left" }, - "message": "Add direction=\"left\" for horizontal scrolling" + "message": "Dodaj direction=\"left\" dla poziomego przewijania" }, { "type": "attribute_value", "value": { "selector": "marquee", "attr": "scrollamount", "value": "5" }, - "message": "Add scrollamount=\"5\" for smooth speed" + "message": "Dodaj scrollamount=\"5\" dla płynnej prędkości" } ] } diff --git a/lessons/pl/32-html-svg.json b/lessons/pl/32-html-svg.json index 59b9dbb..2ea0ffb 100644 --- a/lessons/pl/32-html-svg.json +++ b/lessons/pl/32-html-svg.json @@ -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 <svg> element is the container, with width and height attributes.

Use <circle> with cx, cy (center) and r (radius) to draw circles.", - "task": "Create an SVG with a circle:
1. An <svg> with width=\"200\" and height=\"200\"
2. A <circle> centered at (100,100) with radius 50
3. Add a fill color", + "title": "Rysowanie kół", + "description": "SVG (Scalable Vector Graphics) pozwala rysować kształty bezpośrednio w HTML. Element <svg> jest kontenerem z atrybutami width i height.

Użyj <circle> z cx, cy (środek) i r (promień) do rysowania kół.", + "task": "Utwórz SVG z kołem:
1. Element <svg> z width=\"200\" i height=\"200\"
2. Element <circle> wyśrodkowany w (100,100) z promieniem 50
3. Dodaj kolor fill", "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": "\n \n", + "solution": "\n \n", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "svg", - "message": "Add an <svg> element" + "message": "Dodaj element <svg>" }, { "type": "element_exists", "value": "circle", - "message": "Add a <circle> element inside the SVG" + "message": "Dodaj element <circle> wewnątrz SVG" + }, + { + "type": "attribute_value", + "value": { "selector": "svg", "attr": "width", "value": "200" }, + "message": "Ustaw width=\"200\" w SVG" + }, + { + "type": "attribute_value", + "value": { "selector": "svg", "attr": "height", "value": "200" }, + "message": "Ustaw height=\"200\" w SVG" }, { "type": "attribute_value", "value": { "selector": "circle", "attr": "cx", "value": "100" }, - "message": "Set cx=\"100\" for the circle's horizontal center" + "message": "Ustaw cx=\"100\" dla poziomego środka koła" }, { "type": "attribute_value", "value": { "selector": "circle", "attr": "cy", "value": "100" }, - "message": "Set cy=\"100\" for the circle's vertical center" + "message": "Ustaw cy=\"100\" dla pionowego środka koła" + }, + { + "type": "attribute_value", + "value": { "selector": "circle", "attr": "r", "value": "50" }, + "message": "Ustaw r=\"50\" dla promienia koła" } ] }, { "id": "svg-rect-line", - "title": "Rectangles & Lines", - "description": "Draw rectangles with <rect> using x, y, width, height.

Draw lines with <line> using x1, y1 (start) and x2, y2 (end). Lines need a stroke color!", - "task": "Create an SVG with:
1. An <svg> (200x150)
2. A <rect> at position (20,20) with size 80x60
3. A <line> from (120,30) to (180,90) with a stroke color", + "title": "Prostokąty i linie", + "description": "Rysuj prostokąty za pomocą <rect> używając x, y, width, height.

Rysuj linie za pomocą <line> używając x1, y1 (start) i x2, y2 (koniec). Linie potrzebują koloru stroke!", + "task": "Utwórz SVG z:
1. Elementem <svg> (200x150)
2. Elementem <rect> na pozycji (20,20) o rozmiarze 80x60
3. Elementem <line> 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": "\n \n \n", + "solution": "\n \n \n", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "svg", - "message": "Add an <svg> element" + "message": "Dodaj element <svg>" }, { "type": "element_exists", "value": "rect", - "message": "Add a <rect> element" + "message": "Dodaj element <rect>" }, { "type": "element_exists", "value": "line", - "message": "Add a <line> element" + "message": "Dodaj element <line>" + }, + { + "type": "attribute_value", + "value": { "selector": "svg", "attr": "width", "value": "200" }, + "message": "Ustaw width=\"200\" w SVG" + }, + { + "type": "attribute_value", + "value": { "selector": "svg", "attr": "height", "value": "150" }, + "message": "Ustaw height=\"150\" w SVG" + }, + { + "type": "attribute_value", + "value": { "selector": "rect", "attr": "x", "value": "20" }, + "message": "Ustaw x=\"20\" w rect" + }, + { + "type": "attribute_value", + "value": { "selector": "rect", "attr": "y", "value": "20" }, + "message": "Ustaw y=\"20\" w rect" + }, + { + "type": "attribute_value", + "value": { "selector": "rect", "attr": "width", "value": "80" }, + "message": "Ustaw width=\"80\" w rect" + }, + { + "type": "attribute_value", + "value": { "selector": "rect", "attr": "height", "value": "60" }, + "message": "Ustaw height=\"60\" w rect" + }, + { + "type": "attribute_value", + "value": { "selector": "line", "attr": "x1", "value": "120" }, + "message": "Ustaw x1=\"120\" w line" + }, + { + "type": "attribute_value", + "value": { "selector": "line", "attr": "y1", "value": "30" }, + "message": "Ustaw y1=\"30\" w line" + }, + { + "type": "attribute_value", + "value": { "selector": "line", "attr": "x2", "value": "180" }, + "message": "Ustaw x2=\"180\" w line" + }, + { + "type": "attribute_value", + "value": { "selector": "line", "attr": "y2", "value": "90" }, + "message": "Ustaw y2=\"90\" w line" + }, + { + "type": "contains", + "value": "stroke", + "message": "Dodaj kolor stroke do line" } ] }, { "id": "svg-shapes", - "title": "Multiple Shapes", - "description": "Combine shapes to create simple graphics! Add stroke for outlines and stroke-width for thickness.

Use fill=\"none\" for hollow shapes. Shapes stack in order - later elements appear on top.", - "task": "Create a simple face:
1. An <svg> (200x200)
2. A large <circle> for the face
3. Two small <circle> elements for eyes
4. A <line> for the smile", + "title": "Wiele kształtów", + "description": "Łącz kształty, aby tworzyć proste grafiki! Dodaj stroke dla konturów i stroke-width dla grubości.

Użyj fill=\"none\" dla pustych kształtów. Kształty nakładają się w kolejności - późniejsze elementy są na wierzchu.", + "task": "Utwórz prostą twarz:
1. Element <svg> (200x200)
2. Duże <circle> dla twarzy
3. Dwa małe <circle> dla oczu
4. Element <line> 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": "\n \n \n \n \n", + "solution": "\n \n \n \n \n", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "svg", - "message": "Add an <svg> element" + "message": "Dodaj element <svg>" }, { "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 <line> for the smile" + "message": "Dodaj <line> dla uśmiechu" } ] } diff --git a/lessons/uk/00-welcome.json b/lessons/uk/00-welcome.json index 459920c..1e2ca4a 100644 --- a/lessons/uk/00-welcome.json +++ b/lessons/uk/00-welcome.json @@ -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": "Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required!

What you'll learn:
HTML - Semantic elements, forms, tables, SVG (HTML Block & Inline, HTML Forms, HTML Tables)
CSS - Selectors, box model, flexbox, animations (CSS Selectors, CSS Box Model, CSS Flexbox)
Responsive Design - Media queries and mobile-first layouts

How it works:
1. Read the task in the left panel
2. Write code in the editor
3. See live results in the preview
4. Get instant feedback with hints

Keyboard shortcuts: Ctrl+Z to undo, Ctrl+Shift+Z to redo

More resources:
HTML over JS - Native HTML vs JavaScript solutions
Web Engineering Mandala - JavaScript technology roadmap", - "task": "Write Hello World", + "title": "Почати", + "description": "Code Crispies - це безкоштовна платформа з відкритим кодом для вивчення веб-розробки через практичні вправи. Обліковий запис не потрібен!

Що ви вивчите:
HTML - Семантичні елементи, форми, таблиці, SVG (HTML Блокові та рядкові, HTML Форми, HTML Таблиці)
CSS - Селектори, блокова модель, flexbox, анімації (CSS Селектори, CSS Блокова модель, CSS Flexbox)
Адаптивний дизайн - Media queries та mobile-first макети

Як це працює:
1. Прочитайте завдання в лівій панелі
2. Напишіть код в редакторі
3. Побачте результати в попередньому перегляді
4. Отримайте миттєвий зворотний зв'язок з підказками

Гарячі клавіші: Ctrl+Z скасувати, Ctrl+Shift+Z повторити

Більше ресурсів:
HTML over JS - Нативний HTML проти JavaScript рішень
Web Engineering Mandala - Карта JavaScript технологій", + "task": "Напишіть Hello World", "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 Hello World" + "message": "Напишіть Hello World" } ] }, { "id": "overview", - "title": "Overview", - "description": "You're ready! Open the menu (☰) to explore all modules.

Recommended learning path:
1. HTML Block & Inline - Understand container vs inline elements
2. HTML Forms - Build interactive forms with validation
3. CSS Selectors - Target elements precisely
4. CSS Box Model - Master padding, margin, borders
5. CSS Flexbox - Create flexible layouts
6. CSS Animations - Add motion and transitions

Tips:
• Use Show Expected to see the target result
• Your progress is saved automatically
• Try Emmet in HTML mode: ul>li*3 + Tab

Open Source:
Gitea (Source) · GitHub (Mirror)
• Made by LibreTECH · Michael Czechowski", - "task": "Click Next to continue", + "title": "Огляд", + "description": "Ви готові! Відкрийте меню (☰) щоб дослідити всі модулі.

Рекомендований шлях навчання:
1. HTML Блокові та рядкові - Зрозумійте контейнерні vs рядкові елементи
2. HTML Форми - Створюйте інтерактивні форми з валідацією
3. CSS Селектори - Точно вибирайте елементи
4. CSS Блокова модель - Опануйте padding, margin, borders
5. CSS Flexbox - Створюйте гнучкі макети
6. CSS Анімації - Додавайте рух та переходи

Поради:
• Використовуйте Показати очікуване щоб побачити цільовий результат
• Ваш прогрес зберігається автоматично
• Спробуйте Emmet в режимі HTML: ul>li*3 + Tab

Open Source:
Gitea (Source) · GitHub (Mirror)
• Створено LibreTECH · Michael Czechowski", + "task": "Натисніть Далі щоб продовжити", "previewHTML": "

Hello World! 🌍

Hallo Welt!

Bonjour le monde!

¡Hola Mundo!

Ciao Mondo!

Olá Mundo!

こんにちは世界!

你好世界!

안녕 세상!

Привет мир!

שלום עולם!

مرحبا بالعالم!

", "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": "Натисніть Далі щоб продовжити" } ] }, diff --git a/lessons/uk/01-box-model.json b/lessons/uk/01-box-model.json index d525bcf..8478ba0 100644 --- a/lessons/uk/01-box-model.json +++ b/lessons/uk/01-box-model.json @@ -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 padding to 1rem to create space between the content and border.", - "previewHTML": "
Box Model Components
", - "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: lavender; border: 2px dashed slategray; }", + "title": "Padding", + "description": "Кожен елемент у CSS - це блок з чотирма шарами: контент, відступ (padding), межа та поле. Padding створює простір для дихання між вашим контентом і краєм блоку.

Без padding текст незручно притискається до меж. Padding робить контент читабельним і візуально збалансованим.

.card {\n  padding: 1rem;\n}
", + "task": "Ця картка профілю виглядає тісною. Додайте padding: 1rem, щоб текст мав простір для дихання.", + "previewHTML": "

Sarah Chen

Frontend Developer

", + "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 padding: 1rem" + "message": "Встановіть padding: 1rem" } ] }, { "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 border to 2px solid darkslategray.", - "previewHTML": "
This box needs a border
", - "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: mintcream; padding: 1rem; }", + "title": "Borders", + "description": "Межі створюють візуальні границі навколо елементів. Скорочення border приймає три значення: ширину, стиль і колір.

Поширені стилі: solid, dashed, dotted, none", + "task": "Додайте тонкий лівий акцент до картки за допомогою border-left: 4px solid steelblue.", + "previewHTML": "

Sarah Chen

Frontend Developer

", + "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 border: 2px solid darkslategray", + "value": "border-left:\\s*4px\\s+solid\\s+steelblue", + "message": "Встановіть border-left: 4px solid steelblue", "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 margin to 1rem to create space between this element and its neighbors.", - "previewHTML": "
This box needs margins
Adjacent element
", - "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": "Поля створюють простір зовні елемента, відділяючи його від сусідів. Тоді як padding штовхає контент всередину, поля відштовхують інші елементи.", + "task": "Додайте простір між цими двома картками профілю за допомогою margin-bottom: 1rem на .card.", + "previewHTML": "

Sarah Chen

Frontend Developer

Alex Rivera

UX Designer

", + "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 margin: 1rem" + "value": { "property": "margin-bottom", "expected": "1rem" }, + "message": "Встановіть margin-bottom: 1rem" } ] }, { "id": "box-model-4", - "title": "Box Sizing: Border-Box", - "description": "The box-sizing property determines how element dimensions are calculated. The default content-box excludes padding and border from width/height, while border-box includes them, making layout calculations more intuitive.", - "task": "Set box-sizing to border-box so padding and border are included in the width.", - "previewHTML": "
Content-box (default)
Border-box
", - "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": "За замовчуванням width встановлює лише ширину контенту. Padding і межі додаються до загальної суми. Це спричиняє проблеми з макетом.

box-sizing: border-box включає padding і межу у ширину, роблячи розмір передбачуваним. Більшість розробників застосовують це до всіх елементів.", + "task": "Обидві картки мають width: 200px. Ліва використовує стандартний розмір (content-box), стаючи ширшою за очікуване. Виправте праву картку за допомогою box-sizing: border-box.", + "previewHTML": "
Content-box
Border-box
", + "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .demo { display: flex; gap: 1rem; } .card { width: 200px; padding: 1rem; border: 4px solid steelblue; background: white; border-radius: 8px; }", "sandboxCSS": "", - "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 box-sizing: border-box" + "message": "Встановіть box-sizing: border-box" } ] }, { "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 margin-bottom to 2rem. Notice the space between paragraphs equals 2rem (not 3rem) due to margin collapse.", - "previewHTML": "

This paragraph has a bottom margin.

This paragraph has a top margin of 1rem.

", - "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 значення:
• 1 значення: всі сторони
• 2 значення: вертикально | горизонтально
• 4 значення: верх | право | низ | ліво", + "task": "Ця кнопка потребує більше горизонтального простору, ніж вертикального. Встановіть padding: 8px 1rem (8px верх/низ, 1rem ліво/право).", + "previewHTML": "", + "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 margin-bottom: 2rem" + "type": "regex", + "value": "padding:\\s*8px\\s+1rem", + "message": "Встановіть padding: 8px 1rem", + "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 margin to 1rem 2rem for 1rem top/bottom and 2rem left/right.", - "previewHTML": "
This box needs margins: 1rem top/bottom, 2rem left/right
", - "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. Поширений шаблон - горизонтальне центрування блокових елементів за допомогою margin: 0 auto.", + "task": "Відцентруйте цю картку горизонтально. Встановіть margin: 0 auto, щоб автоматично обчислити рівні ліві/праві поля.", + "previewHTML": "

Sarah Chen

Frontend Developer

", + "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 margin: 1rem 2rem", + "value": "margin:\\s*0\\s+auto", + "message": "Встановіть margin: 0 auto", "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 padding to 2rem to add equal padding on all sides.", - "previewHTML": "
This box needs equal padding on all sides
", - "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .padded { background-color: papayawhip; border: 2px solid orange; }", + "title": "Border Radius", + "description": "Хоча не є частиною класичної блокової моделі, border-radius заокруглює кути межі елемента. Використовуйте 50% на квадратному елементі, щоб створити коло.", + "task": "Зробіть зображення аватара круглим за допомогою border-radius: 50%.", + "previewHTML": "
\"Avatar\"

Sarah Chen

Frontend Developer

", + "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 padding: 2rem" + "value": { "property": "border-radius", "expected": "50%" }, + "message": "Встановіть border-radius: 50%" } ] }, { "id": "box-model-8", - "title": "Border on Specific Sides", - "description": "For granular control, you can target specific sides with border-top, border-right, border-bottom, or border-left.", - "task": "Set border-bottom to 4px solid dodgerblue.", - "previewHTML": "
This element needs only a bottom border
", - "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .line { padding: 1rem; background-color: aliceblue; }", + "title": "Complete Card", + "description": "Об'єднаймо все разом. Ця картка сповіщення потребує стилізації, щоб виглядати професійно.", + "task": "Стилізуйте сповіщення: додайте padding: 1rem, border-left: 4px solid coral та border-radius: 4px.", + "previewHTML": "
New message

You have 3 unread notifications

", + "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": "Встановіть padding: 1rem" + }, { "type": "regex", - "value": "border-bottom:\\s*4px\\s+solid\\s+dodgerblue", - "message": "Set border-bottom: 4px solid dodgerblue", + "value": "border-left:\\s*4px\\s+solid\\s+coral", + "message": "Встановіть border-left: 4px solid coral", "options": { "caseSensitive": false } + }, + { + "type": "property_value", + "value": { "property": "border-radius", "expected": "4px" }, + "message": "Встановіть border-radius: 4px" } ] } diff --git a/lessons/uk/05-units-variables.json b/lessons/uk/05-units-variables.json index 67a9272..da86551 100644 --- a/lessons/uk/05-units-variables.json +++ b/lessons/uk/05-units-variables.json @@ -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 width of .box to 80% and max-width to 37.5rem.", - "previewHTML": "
Resize me!
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { background: #f5f5f5; padding: 1rem; }", + "title": "Relative Units", + "description": "CSS пропонує два типи одиниць: абсолютні (як px) та відносні (як % і rem). Відносні одиниці адаптуються до контексту, роблячи макети гнучкими та доступними.

Поширені відносні одиниці:
% – Відносно батьківського елемента
rem – Відносно кореневого розміру шрифту (зазвичай 16px)
em – Відносно розміру шрифту елемента

Поширений патерн для читабельного контенту: встановіть width: 100%, щоб заповнити доступний простір, потім max-width: 40rem для обмеження довжини рядка.", + "task": "Цей текст статті занадто широкий на великих екранах. Додайте max-width: 40rem для оптимальної ширини читання.", + "previewHTML": "

The Art of Typography

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.

The ideal line length is 45-75 characters per line. At typical font sizes, this works out to roughly 40rem maximum width.

", + "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 width property", "options": { "caseSensitive": false } }, - { "type": "property_value", "value": { "property": "width", "expected": "80%" }, "message": "Set width to 80%" }, - { "type": "contains", "value": "max-width", "message": "Use max-width property", "options": { "caseSensitive": false } }, { "type": "property_value", - "value": { "property": "max-width", "expected": "37.5rem" }, - "message": "Set max-width to 37.5rem" + "value": { "property": "max-width", "expected": "40rem" }, + "message": "Встановіть max-width: 40rem" } ] }, { "id": "units-2", - "title": "CSS Custom Properties", - "description": "Define and reuse variables (--custom properties) to centralize your theme values.", - "task": "Create a --main-color variable in :root with #6200ee and apply it as the border color on .themed.", - "previewHTML": "
Variable Box
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .themed { padding: 1rem; border: 0.125rem solid #ddd; }", + "title": "CSS Variables", + "description": "Кастомні властивості CSS (змінні) дозволяють визначати значення для повторного використання. Визначайте їх за допомогою --назва та використовуйте з var(--назва). Змінні, визначені на :root, доступні всюди.", + "task": "Визначте --brand: steelblue в :root, потім використайте як колір background для .btn.", + "previewHTML": "
", + "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 --main-color in :root", + "value": "--brand", + "message": "Визначте змінну --brand", "options": { "caseSensitive": false } }, { "type": "contains", - "value": "var(--main-color)", - "message": "Use var(--main-color)", + "value": "steelblue", + "message": "Встановіть значення steelblue", "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 calc() function to combine different units in one expression.", - "task": "Set the width of .sized to calc(100% - 2rem) and min-height to calc(10vh + 1rem).", - "previewHTML": "
Calc Demo
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sized { background: #e8f5e9; padding: 1rem; }", + "title": "calc() Function", + "description": "Функція calc() дозволяє змішувати різні одиниці в обчисленнях. Це важливо для макетів, що поєднують фіксовані та гнучкі розміри, як макет із сайдбаром.", + "task": "Основний контент повинен заповнити залишок місця після сайдбару 200px. Встановіть width: calc(100% - 200px) на .main.", + "previewHTML": "

Main Content

This area should fill the remaining width after accounting for the fixed-width sidebar.

", + "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 calc() function", "options": { "caseSensitive": false } }, { "type": "regex", - "value": "width:\\s*calc\\(100% - 2rem\\)", - "message": "Width should be calc(100% - 2rem)", - "options": { "caseSensitive": false } - }, - { - "type": "regex", - "value": "min-height:\\s*calc\\(10vh \\+ 1rem\\)", - "message": "Min-height should be calc(10vh + 1rem)", + "value": "width:\\s*calc\\(\\s*100%\\s*-\\s*200px\\s*\\)", + "message": "Встановіть width: calc(100% - 200px)", "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 .view a width of 50vw and height of 20vh.", - "previewHTML": "
Viewport Box
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .view { background: #ffe0b2; }", + "title": "Viewport Units", + "description": "Одиниці viewport розміряють елементи відносно вікна браузера:
vw – 1% ширини viewport
vh – 1% висоти viewport

Вони ідеальні для повноекранних секцій як hero-банери.", + "task": "Зробіть цю hero-секцію висотою з viewport, встановивши min-height: 100vh.", + "previewHTML": "

Welcome

Scroll down to explore

", + "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 vw unit", "options": { "caseSensitive": false } }, - { "type": "contains", "value": "vh", "message": "Use vh unit", "options": { "caseSensitive": false } }, - { "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Set width to 50vw" }, - { "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Set height to 20vh" } + { + "type": "property_value", + "value": { "property": "min-height", "expected": "100vh" }, + "message": "Встановіть min-height: 100vh" + } ] } ] diff --git a/lessons/uk/06-transitions-animations.json b/lessons/uk/06-transitions-animations.json index 5f8280d..1066fdc 100644 --- a/lessons/uk/06-transitions-animations.json +++ b/lessons/uk/06-transitions-animations.json @@ -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 transition to properties for smooth changes on state changes.

transition: property duration;\n/* e.g. transition: background-color 0.3s; */
", - "task": "Add transition: background-color 0.3s to .btn so the color fades smoothly on hover.", + "description": "Навчіться застосовувати transition до властивостей для плавних змін при зміні стану.

transition: property duration;\n/* напр. transition: background-color 0.3s; */
", + "task": "Додайте transition: background-color 0.3s, щоб колір плавно змінювався при наведенні.", "previewHTML": "", "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 transition property", + "message": "Використайте властивість transition", "options": { "caseSensitive": false } }, { "type": "regex", "value": "transition:\\s*background-color\\s*0\\.3s", - "message": "Set transition: background-color 0.3s", + "message": "Встановіть transition: background-color 0.3s", "options": { "caseSensitive": false } } ] @@ -36,8 +36,8 @@ { "id": "transitions-2", "title": "Timing Funcs", - "description": "Explore easing functions like ease, linear, ease-in, ease-out to control animation pacing.", - "task": "Set transition-timing-function to ease-in-out on .btn.", + "description": "Дослідіть функції пом'якшення як ease, linear, ease-in, ease-out для контролю темпу анімації.", + "task": "Встановіть transition-timing-function на ease-in-out.", "previewHTML": "", "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 transition-timing-function", + "message": "Використайте transition-timing-function", "options": { "caseSensitive": false } }, { "type": "property_value", "value": { "property": "transition-timing-function", "expected": "ease-in-out" }, - "message": "Set timing to ease-in-out" + "message": "Встановіть timing на ease-in-out" } ] }, { "id": "transitions-3", "title": "Keyframes", - "description": "Create named animations using @keyframes and apply them via the animation shorthand.

@keyframes bounce {\n  50% { transform: translateY(-20px); }\n}\n.ball {\n  animation: bounce 1s infinite;\n}
", - "task": "Define a keyframe at 50% with transform: translateY(-20px) and apply animation: bounce 1s infinite to .ball.", + "description": "Створюйте іменовані анімації використовуючи @keyframes та застосовуйте їх через скорочення animation.

@keyframes bounce {\n  50% { transform: translateY(-20px); }\n}\n.ball {\n  animation: bounce 1s infinite;\n}
", + "task": "Визначте keyframe при 50% з transform: translateY(-20px) та застосуйте animation: bounce 1s infinite на .ball.", "previewHTML": "
", "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 @keyframes bounce", + "message": "Визначте @keyframes bounce", "options": { "caseSensitive": false } }, { "type": "regex", "value": "50%.*transform: translateY\\(-20px\\)", - "message": "At 50%, use transform: translateY(-20px)", + "message": "При 50%, використайте transform: translateY(-20px)", "options": { "caseSensitive": false } }, { "type": "contains", "value": "animation", - "message": "Use animation property on .ball", + "message": "Використайте властивість animation на .ball", "options": { "caseSensitive": false } }, { "type": "regex", "value": "animation:.*bounce.*1s.*infinite", - "message": "Apply animation: bounce 1s infinite", + "message": "Застосуйте animation: bounce 1s infinite", "options": { "caseSensitive": false } } ] @@ -103,8 +103,8 @@ { "id": "transitions-4", "title": "Animation Properties", - "description": "Fine-tune animations with animation-delay, animation-iteration-count, animation-direction, and animation-fill-mode.", - "task": "Apply the pulse animation to .box with animation-name: pulse, animation-duration: 2s, animation-delay: 1s, animation-iteration-count: 2, and animation-fill-mode: forwards.", + "description": "Налаштуйте анімації за допомогою animation-delay, animation-iteration-count, animation-direction та animation-fill-mode.", + "task": "Застосуйте анімацію pulse до .box з animation-name: pulse, animation-duration: 2s, animation-delay: 1s, animation-iteration-count: 2 та animation-fill-mode: forwards.", "previewHTML": "
Pulse
", "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 animation-name: pulse" + "message": "Встановіть animation-name: pulse" }, { "type": "property_value", "value": { "property": "animation-duration", "expected": "2s" }, - "message": "Set animation-duration: 2s" + "message": "Встановіть animation-duration: 2s" }, { "type": "property_value", "value": { "property": "animation-delay", "expected": "1s" }, - "message": "Set animation-delay: 1s" + "message": "Встановіть animation-delay: 1s" }, { "type": "property_value", "value": { "property": "animation-iteration-count", "expected": "2" }, - "message": "Set animation-iteration-count: 2" + "message": "Встановіть animation-iteration-count: 2" }, { "type": "property_value", "value": { "property": "animation-fill-mode", "expected": "forwards" }, - "message": "Set animation-fill-mode: forwards" + "message": "Встановіть animation-fill-mode: forwards" } ] } diff --git a/lessons/uk/08-responsive.json b/lessons/uk/08-responsive.json index 0db9b98..a432f73 100644 --- a/lessons/uk/08-responsive.json +++ b/lessons/uk/08-responsive.json @@ -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 @media (max-width: 600px) that changes .panel background to lightcoral.", + "description": "Зрозумійте синтаксис та випадки використання CSS media queries для умовного застосування стилів на основі характеристик viewport.

@media (max-width: 600px) {\n  .panel {\n    background: lightcoral;\n  }\n}
", + "task": "Напишіть media query з @media (max-width: 600px), яка змінює фон .panel на lightcoral.", "previewHTML": "
Resize the window
", "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 @media (max-width: 600px)", + "message": "Використайте @media (max-width: 600px)", "options": { "caseSensitive": false } }, { "type": "contains", "value": ".panel", - "message": "Target .panel inside the media query", + "message": "Вкажіть .panel всередині media query", "options": { "caseSensitive": false } }, { "type": "property_value", "value": { "property": "background", "expected": "lightcoral" }, - "message": "Set background: lightcoral", + "message": "Встановіть background: lightcoral", "options": { "exact": false } } ] @@ -42,8 +42,8 @@ { "id": "responsive-2", "title": "Fluid Type", - "description": "Use relative units like vw to make font sizes scale with the viewport width.", - "task": "Set font-size: 5vw on .text so it scales as the viewport changes.", + "description": "Використовуйте відносні одиниці як vw, щоб розміри шрифтів масштабувались з шириною viewport.", + "task": "Встановіть font-size: 5vw, щоб масштабувалась з viewport.", "previewHTML": "

Fluid Typography

", "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 font-size: 5vw" } + { + "type": "property_value", + "value": { "property": "font-size", "expected": "5vw" }, + "message": "Встановіть font-size: 5vw" + } ] }, { "id": "responsive-3", - "title": "Flex Grids", - "description": "Combine CSS Grid with auto-fit or auto-fill for responsive column layouts.", - "task": "Add display: grid, grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)), and gap: 1rem to .cards.", - "previewHTML": "
1
2
3
4
", - "previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .cards > div { background: #d1c4e9; padding: 1rem; }", + "title": "Responsive Grid", + "description": "Поєднайте CSS Grid з auto-fit або auto-fill для адаптивних колонкових макетів, які автоматично налаштовують кількість колонок на основі доступного простору.", + "task": "Додайте display: grid, grid-template-columns: repeat(auto-fit, minmax(200px, 1fr)) та gap: 1rem.", + "previewHTML": "

Fast

Lightning quick load times

Secure

Enterprise-grade security

Reliable

99.9% uptime guaranteed

Support

24/7 customer service

", + "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 display: grid" + "message": "Встановіть display: grid" }, { "type": "regex", "value": "repeat\\(auto-fit,\\s*minmax\\(200px,\\s*1fr\\)\\)", - "message": "Use repeat(auto-fit, minmax(200px, 1fr))", + "message": "Використайте repeat(auto-fit, minmax(200px, 1fr))", "options": { "caseSensitive": false } }, { "type": "property_value", "value": { "property": "gap", "expected": "1rem" }, - "message": "Set gap: 1rem" + "message": "Встановіть gap: 1rem" } ] }, { "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 @media (min-width: 768px) that sets .sidebar width to 250px.", + "description": "Застосуйте підхід mobile-first: пишіть базові стилі для малих екранів і розширюйте для більших viewport.", + "task": "Напишіть media query з @media (min-width: 768px), яка встановлює ширину .sidebar на 250px.", "previewHTML": "", "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 @media (min-width: 768px)", + "message": "Використайте @media (min-width: 768px)", "options": { "caseSensitive": false } }, { "type": "contains", "value": ".sidebar", - "message": "Target .sidebar inside media query", + "message": "Вкажіть .sidebar в media query", "options": { "caseSensitive": false } }, { "type": "property_value", "value": { "property": "width", "expected": "250px" }, - "message": "Set width: 250px", + "message": "Встановіть width: 250px", "options": { "exact": false } } ] diff --git a/lessons/uk/20-html-elements.json b/lessons/uk/20-html-elements.json index 4d8553a..fa180ae 100644 --- a/lessons/uk/20-html-elements.json +++ b/lessons/uk/20-html-elements.json @@ -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:

Block elements (containers) start on a new line and take full width. Examples: <div>, <p>, <h1>, <section>

Inline elements flow within text and only take needed width. Examples: <span>, <a>, <strong>, <em>", - "task": "Wrap the word important with <strong> tags to make it bold. Notice how the paragraph (block) takes full width while strong (inline) flows with text.", + "title": "Блокові vs рядкові елементи", + "description": "HTML елементи поділяються на дві основні категорії:

Блокові елементи (контейнери) починаються з нового рядка і займають повну ширину. Приклади: <div>, <p>, <h1>, <section>

Рядкові елементи розміщуються всередині тексту і займають лише необхідну ширину. Приклади: <span>, <a>, <strong>, <em>", + "task": "Оберніть слово важливе тегами <strong>, щоб зробити його жирним. Зверніть увагу, як абзац (блок) займає повну ширину, тоді як strong (рядковий) тече з текстом.", "previewHTML": "", "previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; } p { background: #e3f2fd; padding: 10px; } strong { background: #ffecb3; }", "sandboxCSS": "", - "initialCode": "

This is a paragraph with an important word.

", - "solution": "

This is a paragraph with an important word.

", + "initialCode": "

Це абзац з важливе словом.

", + "solution": "

Це абзац з важливе словом.

", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "p", - "message": "Add a <p> paragraph element" + "message": "Додайте елемент абзацу <p>" }, { "type": "parent_child", "value": { "parent": "p", "child": "strong" }, - "message": "Wrap the word important with <strong> tags" + "message": "Оберніть слово важливе тегами <strong>" } ] }, { "id": "semantic-containers", - "title": "Semantic Tags", - "description": "Modern HTML uses semantic containers that describe their content:

<header> - Page or section header
<nav> - Navigation links
<main> - Main content area
<section> - Thematic grouping
<article> - Self-contained content
<footer> - Page or section footer", - "task": "Create a basic page structure:
1. Add a <header> with an <h1> containing the text My Website
2. Add a <main> element with a paragraph saying Welcome to my site!
3. Add a <footer> with a paragraph saying Copyright 2026", + "title": "Семантичні теги", + "description": "Сучасний HTML використовує семантичні контейнери, які описують свій вміст:

<header> - Заголовок сторінки або секції
<nav> - Навігаційні посилання
<main> - Основний вміст
<section> - Тематичне групування
<article> - Самостійний вміст
<footer> - Підвал сторінки або секції", + "task": "Створіть базову структуру сторінки:
1. Додайте <header> з <h1>, що містить текст Мій Сайт
2. Додайте елемент <main> з абзацом, що каже Ласкаво просимо на мій сайт!
3. Додайте <footer> з абзацом, що каже Copyright 2026", "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": "
\n

My Website

\n
\n
\n

Welcome to my site!

\n
\n", + "solution": "
\n

Мій Сайт

\n
\n
\n

Ласкаво просимо на мій сайт!

\n
\n", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "header", - "message": "Add a <header> element" + "message": "Додайте елемент <header>" }, { "type": "element_exists", "value": "main", - "message": "Add a <main> element" + "message": "Додайте елемент <main>" }, { "type": "element_exists", "value": "footer", - "message": "Add a <footer> element" + "message": "Додайте елемент <footer>" }, { "type": "parent_child", "value": { "parent": "header", "child": "h1" }, - "message": "Add an <h1> heading inside your header" + "message": "Додайте заголовок <h1> всередині header" } ] } diff --git a/lessons/uk/21-html-forms-basic.json b/lessons/uk/21-html-forms-basic.json index e92962f..d3ac41c 100644 --- a/lessons/uk/21-html-forms-basic.json +++ b/lessons/uk/21-html-forms-basic.json @@ -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 <form> wrapper. Inside, use <label> to describe inputs and <input> for user data entry.

The for attribute on labels should match the id on inputs for accessibility.", - "task": "Create a form with:
1. A <label> with the text Name: and for=\"name\" attribute
2. A text <input> with id=\"name\" and name=\"name\" attributes", + "title": "Структура форми", + "description": "Кожна форма потребує обгортки <form>. Всередині використовуйте <label> для опису полів та <input> для введення даних.

Атрибут for у label має відповідати id полів для доступності.", + "task": "Створіть форму з:
1. <label> з текстом Ім'я: та атрибутом for=\"name\"
2. Текстовим <input> з атрибутами id=\"name\" та name=\"name\"", "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": "
\n \n \n
", + "solution": "
\n \n \n
", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "form", - "message": "Wrap everything in a <form> element" + "message": "Оберніть все елементом <form>" }, { "type": "element_exists", "value": "label", - "message": "Add a <label> for your input" + "message": "Додайте <label> для вашого поля" }, { "type": "element_exists", "value": "input", - "message": "Add an <input> element" + "message": "Додайте елемент <input>" }, { "type": "attribute_value", "value": { "selector": "label", "attr": "for", "value": null }, - "message": "Add a for attribute to your label" + "message": "Додайте атрибут for до вашого label" }, { "type": "attribute_value", "value": { "selector": "input", "attr": "id", "value": null }, - "message": "Add an id attribute to your input" + "message": "Додайте атрибут id до вашого поля" } ] }, { "id": "input-types", - "title": "Input Types", - "description": "Different input types provide appropriate keyboards and validation:

type=\"text\" - General text
type=\"email\" - Email with @ validation
type=\"password\" - Hidden characters
type=\"number\" - Numeric keyboard
type=\"tel\" - Phone keyboard", - "task": "Create a login form with two fields:
1. An email field: <label for=\"email\">Email:</label> and <input type=\"email\" id=\"email\">
2. A password field: <label for=\"password\">Password:</label> and <input type=\"password\" id=\"password\">", + "title": "Типи полів", + "description": "Різні типи полів надають відповідні клавіатури та валідацію:

type=\"text\" - Загальний текст
type=\"email\" - Email з валідацією @
type=\"password\" - Приховані символи
type=\"number\" - Цифрова клавіатура
type=\"tel\" - Телефонна клавіатура", + "task": "Створіть форму входу з двома полями:
1. Поле email: <label for=\"email\">Email:</label> та <input type=\"email\" id=\"email\">
2. Поле пароля: <label for=\"password\">Пароль:</label> та <input type=\"password\" id=\"password\">", "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": "
\n \n
", - "solution": "
\n \n \n \n \n \n
", + "solution": "
\n \n \n \n \n \n
", "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:

<button type=\"submit\"> - Preferred, flexible content
<input type=\"submit\"> - Simple text-only button

The button text should be action-oriented (e.g., Sign In, 'Register', 'Send').", - "task": "Add a submit button to the form with the text Sign In.", + "title": "Кнопка відправки", + "description": "Форми потребують способу відправки даних. Використовуйте:

<button type=\"submit\"> - Переважно, гнучкий вміст
<input type=\"submit\"> - Простий текстовий кнопка

Текст кнопки має бути орієнтованим на дію (напр. Увійти, 'Зареєструватись', 'Надіслати').", + "task": "Додайте кнопку відправки до форми з текстом Увійти.", "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": "
\n \n \n \n \n \n \n
", - "solution": "
\n \n \n \n \n \n \n \n
", + "initialCode": "
\n \n \n \n \n \n \n
", + "solution": "
\n \n \n \n \n \n \n \n
", "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 Sign In" + "value": { "selector": "button", "text": "Увійти" }, + "message": "Кнопка має відображати Увійти" } ] } diff --git a/lessons/uk/22-html-forms-validation.json b/lessons/uk/22-html-forms-validation.json index 72f4cd1..c2cb526 100644 --- a/lessons/uk/22-html-forms-validation.json +++ b/lessons/uk/22-html-forms-validation.json @@ -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 required attribute prevents form submission if the field is empty.

Add it to any input that must be filled:
<input type=\"text\" required>

The browser shows a validation message automatically.", - "task": "Make both the name and email fields required by adding the required attribute.", + "title": "Обов'язкові поля", + "description": "Атрибут required запобігає відправці форми, якщо поле порожнє. Браузер автоматично показує повідомлення валідації - без JavaScript!

Додайте його до будь-якого поля, яке має бути заповнене:
<input type=\"text\" required>", + "task": "Зробіть обидва поля (ім'я та email) обов'язковими, додавши атрибут required до кожного поля.", "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": "
\n \n \n \n \n \n \n \n
", - "solution": "
\n \n \n \n \n \n \n \n
", + "initialCode": "
\n \n \n \n \n \n \n \n
", + "solution": "
\n \n \n \n \n \n \n \n
", "previewContainer": "preview-area", "validations": [ { "type": "attribute_value", "value": { "selector": "input[name='name']", "attr": "required", "value": true }, - "message": "Add the required attribute to the name input" + "message": "Додайте required до поля імені" }, { "type": "attribute_value", "value": { "selector": "input[name='email']", "attr": "required", "value": true }, - "message": "Add the required attribute to the email input" - } - ] - }, - { - "id": "input-constraints", - "title": "Constraints", - "description": "Control what users can enter:

minlength / maxlength - Text length limits
min / max - Number range
pattern - Regex pattern matching
placeholder - Hint text (not a label!)", - "task": "Add validation to the password input:
1. Add minlength=\"8\" for minimum length
2. Add maxlength=\"20\" for maximum length
3. Add placeholder=\"Enter password\" 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": "
\n \n \n Must be 8-20 characters\n \n \n
", - "solution": "
\n \n \n Must be 8-20 characters\n \n \n
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "attribute_value", - "value": { "selector": "input[type='password']", "attr": "minlength", "value": "8" }, - "message": "Add minlength=\"8\" to the password input" - }, - { - "type": "attribute_value", - "value": { "selector": "input[type='password']", "attr": "maxlength", "value": "20" }, - "message": "Add maxlength=\"20\" to the password input" - }, - { - "type": "attribute_value", - "value": { "selector": "input[type='password']", "attr": "placeholder", "value": null }, - "message": "Add a placeholder to hint what to enter" - } - ] - }, - { - "id": "complete-registration", - "title": "Full Form", - "description": "Build a complete registration form with all validation concepts:

- Required fields marked with *
- Email validation (use type=\"email\")
- Password with length constraints
- Terms checkbox (required)
- 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": "
\n

Create Account

\n \n \n \n \n \n \n \n \n \n \n \n \n \n
", - "solution": "
\n

Create Account

\n \n \n \n \n \n \n \n \n \n \n \n \n \n
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "attribute_value", - "value": { "selector": "#fullname", "attr": "required", "value": true }, - "message": "Make the full name field required" - }, - { - "type": "attribute_value", - "value": { "selector": "#email", "attr": "type", "value": "email" }, - "message": "Set the email input type=\"email\"" - }, - { - "type": "attribute_value", - "value": { "selector": "#email", "attr": "required", "value": true }, - "message": "Make the email field required" - }, - { - "type": "attribute_value", - "value": { "selector": "#password", "attr": "type", "value": "password" }, - "message": "Set the password input type=\"password\"" - }, - { - "type": "attribute_value", - "value": { "selector": "#password", "attr": "required", "value": true }, - "message": "Make the password field required" - }, - { - "type": "attribute_value", - "value": { "selector": "#password", "attr": "minlength", "value": "8" }, - "message": "Add minlength=\"8\" to password" - }, - { - "type": "attribute_value", - "value": { "selector": "#terms", "attr": "required", "value": true }, - "message": "Make the terms checkbox required" + "message": "Додайте required до поля email" } ] } diff --git a/lessons/uk/23-html-details-summary.json b/lessons/uk/23-html-details-summary.json index ff83fc3..9a86af9 100644 --- a/lessons/uk/23-html-details-summary.json +++ b/lessons/uk/23-html-details-summary.json @@ -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 <details> element creates a collapsible section. The <summary> provides the clickable label.

Click the summary to toggle the hidden content - no JavaScript needed!", - "task": "Create a <details> element with:
1. A <summary> saying Click to reveal
2. A <p> with the text This content was hidden!", + "title": "Перший віджет", + "description": "Елемент <details> створює згортувану секцію. Елемент <summary> надає клікабельний заголовок.

Натисніть на summary, щоб показати прихований вміст - без JavaScript!", + "task": "Створіть елемент <details> з:
1. Елементом <summary> з текстом Click to reveal
2. Елементом <p> з текстом This content was hidden!", "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 <details> element" + "message": "Додайте елемент <details>" }, { "type": "element_exists", "value": "summary", - "message": "Add a <summary> inside the details" + "message": "Додайте <summary> всередину details" }, { "type": "parent_child", "value": { "parent": "details", "child": "summary" }, - "message": "The <summary> must be inside <details>" + "message": "Елемент <summary> має бути всередині <details>" }, { "type": "parent_child", "value": { "parent": "details", "child": "p" }, - "message": "Add a <p> inside <details> for the hidden content" + "message": "Додайте <p> всередину <details> для прихованого вмісту" } ] }, { "id": "details-open-attribute", - "title": "Pre-expanded Details", - "description": "By default, <details> is closed. Add the open attribute to show the content initially.

This is a boolean attribute - just add open without a value.", - "task": "Add the open attribute to the <details> element to show the content by default.", + "title": "Розгорнуто за замовчуванням", + "description": "За замовчуванням <details> закритий. Додайте атрибут open, щоб показати вміст спочатку.

Це булевий атрибут - просто додайте open без значення.", + "task": "Додайте атрибут open до елемента <details>, щоб показати вміст за замовчуванням.", "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 open attribute to <details>" + "message": "Додайте атрибут open до <details>" } ] }, { "id": "faq-accordion", - "title": "FAQ Accordion", - "description": "Multiple <details> elements create an accordion-style FAQ. Each question can be expanded independently.

Pro tip: Type details*3>summary+p and press Tab for Emmet expansion. *3 creates 3 elements, > nests inside, + adds siblings.", - "task": "Create an FAQ section with:
1. An <h1> saying Frequently Asked Questions
2. Three <details> elements, each with a question in <summary> and an answer in <p>", + "title": "FAQ акордеон", + "description": "Кілька елементів <details> створюють FAQ у стилі акордеону. Кожне питання можна розгортати незалежно.

Порада: Введіть details*3>summary+p і натисніть Tab для розгортання Emmet. *3 створює 3 елементи, > вкладає всередину, + додає сусідні.", + "task": "Створіть секцію FAQ з:
1. Заголовком <h1> з текстом Frequently Asked Questions
2. Трьома елементами <details>, кожен з питанням у <summary> та відповіддю у <p>", "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 <h1> heading for the FAQ title" + "message": "Додайте заголовок <h1> для назви FAQ" }, { "type": "element_count", "value": { "selector": "details", "min": 3 }, - "message": "Create at least 3 <details> elements for the FAQ" + "message": "Створіть принаймні 3 елементи <details> для FAQ" }, { "type": "element_count", "value": { "selector": "summary", "min": 3 }, - "message": "Each <details> needs a <summary> for the question" + "message": "Кожен <details> потребує <summary> для питання" }, { "type": "element_count", "value": { "selector": "details p", "min": 3 }, - "message": "Each <details> needs a <p> for the answer" + "message": "Кожен <details> потребує <p> для відповіді" } ] } diff --git a/lessons/uk/24-html-progress-meter.json b/lessons/uk/24-html-progress-meter.json index 3a89bc3..41edc1c 100644 --- a/lessons/uk/24-html-progress-meter.json +++ b/lessons/uk/24-html-progress-meter.json @@ -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 <progress> element shows task completion. Use value for current progress and max for the total.

Note: This is not a self-closing tag! Write <progress>...</progress> with fallback text inside for older browsers.", - "task": "Create a progress bar showing 70% completion:
1. Add a <label> saying Download:
2. Add a <progress> with value=\"70\" and max=\"100\"", + "title": "Смуги прогресу", + "description": "Елемент <progress> показує виконання завдання. Використовуйте value для поточного прогресу та max для загального.

Примітка: Це не самозакриваючий тег! Пишіть <progress>...</progress> з резервним текстом всередині для старих браузерів.", + "task": "Створіть смугу прогресу, що показує 70% виконання:
1. Додайте <label> з текстом Download:
2. Додайте <progress> з value=\"70\" та max=\"100\"", "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 <progress> element" + "message": "Додайте елемент <progress>" }, { "type": "attribute_value", "value": { "selector": "progress", "attr": "value", "value": "70" }, - "message": "Set value=\"70\" on the progress element" + "message": "Встановіть value=\"70\" в елементі progress" }, { "type": "attribute_value", "value": { "selector": "progress", "attr": "max", "value": "100" }, - "message": "Set max=\"100\" on the progress element" + "message": "Встановіть max=\"100\" в елементі progress" }, { "type": "element_exists", "value": "label", - "message": "Add a <label> for the progress bar" + "message": "Додайте <label> для смуги прогресу" } ] }, { "id": "progress-indeterminate", - "title": "Indeterminate Progress", - "description": "When progress is unknown (like loading), omit the value attribute. This creates an animated indeterminate state.

Useful for network requests or processes with unknown duration.", - "task": "Create a loading indicator:
1. Add a <p> saying Loading...
2. Add a <progress> without a value attribute", + "title": "Невизначений прогрес", + "description": "Коли прогрес невідомий (як при завантаженні), опустіть атрибут value. Це створює анімований невизначений стан.

Корисно для мережевих запитів або процесів з невідомою тривалістю.", + "task": "Створіть індикатор завантаження:
1. Додайте <p> з текстом Loading...
2. Додайте <progress> без атрибута 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 <progress> element" + "message": "Додайте елемент <progress>" }, { "type": "element_exists", "value": "p", - "message": "Add a <p> with loading text" + "message": "Додайте <p> з текстом завантаження" } ] }, { "id": "meter-gauge", - "title": "Meter Gauges", - "description": "The <meter> element displays a scalar value within a range. Use it for measurements like disk space, battery, or ratings.

Set low, high, and optimum to define good/bad ranges - the browser colors it accordingly!", - "task": "Create a battery level meter:
1. Add a <label> saying Battery:
2. Add a <meter> with:
- value=\"0.8\"
- min=\"0\" and max=\"1\"
- low=\"0.2\" and high=\"0.8\"
- optimum=\"1\"", + "title": "Шкали meter", + "description": "Елемент <meter> відображає скалярне значення в діапазоні. Використовуйте його для вимірювань, як-от місце на диску, батарея або рейтинги.

Встановіть low, high та optimum, щоб визначити хороші/погані діапазони - браузер забарвлює відповідно!", + "task": "Створіть шкалу рівня батареї:
1. Додайте <label> з текстом Battery:
2. Додайте <meter> з:
- value=\"0.8\"
- min=\"0\" та max=\"1\"
- low=\"0.2\" та high=\"0.8\"
- optimum=\"1\"", "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 <meter> element" + "message": "Додайте елемент <meter>" }, { "type": "attribute_value", "value": { "selector": "meter", "attr": "value", "value": "0.8" }, - "message": "Set value=\"0.8\" on the meter" + "message": "Встановіть value=\"0.8\" в meter" + }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "min", "value": "0" }, + "message": "Встановіть min=\"0\" в meter" + }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "max", "value": "1" }, + "message": "Встановіть max=\"1\" в meter" }, { "type": "attribute_value", "value": { "selector": "meter", "attr": "low", "value": "0.2" }, - "message": "Set low=\"0.2\" to define the low threshold" + "message": "Встановіть low=\"0.2\", щоб визначити низький поріг" + }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "high", "value": "0.8" }, + "message": "Встановіть high=\"0.8\", щоб визначити високий поріг" + }, + { + "type": "attribute_value", + "value": { "selector": "meter", "attr": "optimum", "value": "1" }, + "message": "Встановіть optimum=\"1\", щоб вказати оптимальне значення" }, { "type": "element_exists", "value": "label", - "message": "Add a <label> for the meter" + "message": "Додайте <label> для meter" } ] } diff --git a/lessons/uk/25-html-datalist.json b/lessons/uk/25-html-datalist.json index 8d33f10..c5d5df9 100644 --- a/lessons/uk/25-html-datalist.json +++ b/lessons/uk/25-html-datalist.json @@ -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 <datalist> element provides autocomplete suggestions for inputs. Connect it using the list attribute on the input matching the datalist's id.

Users can still type freely - suggestions are just helpers!", - "task": "Create a browser selector:
1. Add a <label> saying Browser:
2. Add an <input> with list=\"browsers\"
3. Add a <datalist id=\"browsers\"> with options for Chrome, Firefox, and Safari", + "title": "Поле з підказками", + "description": "Елемент <datalist> надає підказки автозаповнення для полів. З'єднайте його за допомогою атрибута list на полі, що відповідає id datalist.

Користувачі все одно можуть вводити вільно - підказки лише помічники!", + "task": "Створіть селектор браузера:
1. Додайте <label> з текстом Браузер:
2. Додайте <input> з list=\"browsers\"
3. Додайте <datalist id=\"browsers\"> з варіантами 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 <datalist> element" + "message": "Додайте елемент <datalist>" }, { "type": "attribute_value", "value": { "selector": "input", "attr": "list", "value": "browsers" }, - "message": "Connect the input to datalist using list=\"browsers\"" + "message": "З'єднайте поле з datalist за допомогою list=\"browsers\"" }, { "type": "element_count", "value": { "selector": "option", "min": 3 }, - "message": "Add at least 3 <option> elements inside <datalist>" + "message": "Додайте принаймні 3 елементи <option> всередину <datalist>" }, { "type": "element_exists", "value": "label", - "message": "Add a <label> for the input" + "message": "Додайте <label> для поля" } ] }, { "id": "datalist-countries", - "title": "Country Selector", - "description": "Datalists work great for long lists like countries. Users can type to filter suggestions instantly.

The value attribute is what gets entered, and you can add display text after it.", - "task": "Create a country input:
1. Add a <label> saying Country:
2. Add an <input> with list=\"countries\"
3. Add a <datalist id=\"countries\"> with at least 4 country options", + "title": "Селектор країн", + "description": "Datalist чудово працює для довгих списків, як країни. Користувачі можуть вводити, щоб миттєво фільтрувати підказки.

Атрибут value - це те, що вводиться, і ви можете додати текст для відображення після нього.", + "task": "Створіть поле країни:
1. Додайте <label> з текстом Країна:
2. Додайте <input> з list=\"countries\"
3. Додайте <datalist id=\"countries\"> з принаймні 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 <datalist> element" + "message": "Додайте елемент <datalist>" }, { "type": "attribute_value", "value": { "selector": "datalist", "attr": "id", "value": "countries" }, - "message": "Set id=\"countries\" on the datalist" + "message": "Встановіть id=\"countries\" в datalist" }, { "type": "attribute_value", "value": { "selector": "input", "attr": "list", "value": "countries" }, - "message": "Connect the input using list=\"countries\"" + "message": "З'єднайте поле за допомогою list=\"countries\"" }, { "type": "element_count", "value": { "selector": "option", "min": 4 }, - "message": "Add at least 4 country options" + "message": "Додайте принаймні 4 варіанти країн" } ] } diff --git a/lessons/uk/27-html-dialog.json b/lessons/uk/27-html-dialog.json index 4b721c7..c890939 100644 --- a/lessons/uk/27-html-dialog.json +++ b/lessons/uk/27-html-dialog.json @@ -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 <dialog> element creates a native modal. Add the open attribute to show it.

Use <form method=\"dialog\"> inside to close it when the form submits - no JavaScript needed!", - "task": "Create a dialog with:
1. The open attribute to show it
2. An <h2> saying Welcome!
3. A <p> with a greeting message
4. A <form method=\"dialog\"> with a close button", + "title": "Відкрити діалог", + "description": "Елемент <dialog> створює нативне модальне вікно. Додайте атрибут open, щоб показати його.

Використовуйте <form method=\"dialog\"> всередині, щоб закрити його при відправці форми - без JavaScript!", + "task": "Створіть діалог з:
1. Атрибутом open для відображення
2. Елементом <h2> з текстом Вітаємо!
3. Елементом <p> з привітальним повідомленням
4. Елементом <form method=\"dialog\"> з кнопкою закриття", "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 <dialog> element" + "message": "Додайте елемент <dialog>" }, { "type": "attribute_value", "value": { "selector": "dialog", "attr": "open", "value": true }, - "message": "Add the open attribute to show the dialog" + "message": "Додайте атрибут open щоб показати діалог" }, { "type": "element_exists", "value": "dialog h2", - "message": "Add an <h2> heading inside the dialog" + "message": "Додайте заголовок <h2> всередині діалогу" }, { "type": "element_exists", "value": "form[method='dialog']", - "message": "Add a <form method=\"dialog\"> for closing" + "message": "Додайте <form method=\"dialog\"> для закриття" }, { "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 method=\"dialog\" makes the form close the dialog on submit instead of sending data.

This pattern is perfect for confirmation dialogs, quick inputs, or settings panels.", - "task": "Create a confirmation dialog:
1. Add open to show it
2. An <h2> saying Confirm Delete
3. A <p> asking Are you sure?
4. A <form method=\"dialog\"> with Cancel and Delete buttons", + "title": "Діалог + Форма", + "description": "Діалоги можуть містити повні форми. method=\"dialog\" змушує форму закривати діалог при відправці замість відправки даних.

Цей шаблон ідеальний для діалогів підтвердження, швидкого введення або панелей налаштувань.", + "task": "Створіть діалог підтвердження:
1. Додайте open для відображення
2. Елемент <h2> з текстом Підтвердити видалення
3. Елемент <p> з питанням Ви впевнені?
4. Елемент <form method=\"dialog\"> з кнопками Скасувати та Видалити", "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 <dialog> with the open attribute" + "message": "Додайте <dialog> з атрибутом 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 <form method=\"dialog\"> for the buttons" + "message": "Додайте <form method=\"dialog\"> для кнопок" }, { "type": "element_count", "value": { "selector": "dialog button", "min": 2 }, - "message": "Add at least 2 buttons (Cancel and Confirm)" + "message": "Додайте принаймні 2 кнопки (Скасувати та Підтвердити)" } ] } diff --git a/lessons/uk/28-html-forms-fieldset.json b/lessons/uk/28-html-forms-fieldset.json index add7ef9..7e8ee9d 100644 --- a/lessons/uk/28-html-forms-fieldset.json +++ b/lessons/uk/28-html-forms-fieldset.json @@ -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 <fieldset> element groups related form controls together. Add a <legend> as the first child to give the group a title.

This helps with accessibility and visual organization of complex forms.", - "task": "Create a form with a fieldset:
1. A <form> element
2. A <fieldset> inside
3. A <legend> saying Personal Info
4. Two labeled inputs for name and email", + "title": "Групування з Fieldset", + "description": "Елемент <fieldset> групує пов'язані елементи форми разом. Додайте <legend> як перший дочірній елемент, щоб дати групі заголовок.

Це допомагає з доступністю та візуальною організацією складних форм.", + "task": "Створіть форму з fieldset:
1. Елемент <form>
2. Елемент <fieldset> всередині
3. Елемент <legend> з текстом Особисті дані
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 <form> element" + "message": "Додайте елемент <form>" }, { "type": "element_exists", "value": "fieldset", - "message": "Add a <fieldset> inside the form" + "message": "Додайте <fieldset> всередину форми" }, { "type": "element_exists", "value": "legend", - "message": "Add a <legend> to title your fieldset" + "message": "Додайте <legend> як заголовок 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 <textarea> element creates a multi-line text input, perfect for longer content like messages or descriptions.

Use rows and cols attributes to set default size.", - "task": "Create a contact form:
1. A <fieldset> with <legend> Contact Us
2. A labeled <input> for email
3. A labeled <textarea> for the message
4. A submit <button>", + "title": "Додавання Textarea", + "description": "Елемент <textarea> створює багаторядкове текстове поле, ідеальне для довшого вмісту як повідомлення або описи.

Використовуйте атрибути rows та cols для встановлення розміру за замовчуванням.", + "task": "Створіть контактну форму:
1. Елемент <fieldset> з <legend> Зв'яжіться з нами
2. Підписане <input> для email
3. Підписане <textarea> для повідомлення
4. Кнопка <button> відправки", "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 <fieldset> element" + "message": "Додайте елемент <fieldset>" }, { "type": "element_exists", "value": "legend", - "message": "Add a <legend> element" + "message": "Додайте елемент <legend>" }, { "type": "element_exists", "value": "textarea", - "message": "Add a <textarea> for the message" + "message": "Додайте <textarea> для повідомлення" }, { "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 <fieldset> elements to organize different sections.

This improves usability for long forms like registration or checkout pages.", - "task": "Create a registration form with 2 fieldsets:
1. Account Info with username and password inputs
2. Preferences with a textarea for bio
3. A submit button outside the fieldsets", + "title": "Кілька Fieldsetів", + "description": "Складні форми можуть використовувати кілька елементів <fieldset> для організації різних секцій.

Це покращує зручність використання довгих форм як реєстрація або оформлення замовлення.", + "task": "Створіть форму реєстрації з 2 fieldsetами:
1. Дані облікового запису з полями ім'я користувача та пароль
2. Налаштування з textarea для біо
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 поля введення" } ] } diff --git a/lessons/uk/30-html-tables.json b/lessons/uk/30-html-tables.json index f4d29cf..18209b5 100644 --- a/lessons/uk/30-html-tables.json +++ b/lessons/uk/30-html-tables.json @@ -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 <table> with <tr> for rows. Inside rows, use <th> for headers and <td> for data cells.

The <caption> element provides an accessible title for the table.", - "task": "Create a simple table with:
1. A <caption> saying Fruit Prices
2. A header row with Fruit and Price columns
3. At least 2 data rows", + "title": "Таблиці даних", + "description": "Таблиці відображають структуровані дані в рядках і стовпцях. Використовуйте <table> як контейнер, <tr> для рядків, <th> для комірок заголовка та <td> для комірок даних.

Додайте <caption> для доступного заголовка, що описує вміст таблиці.", + "task": "Створіть таблицю цін:
1. Елемент <caption> з текстом Pricing
2. Рядок заголовка з Plan та Price
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": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n
Fruit Prices
FruitPrice
Apple$1.50
Banana$0.75
", + "solution": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n
Pricing
PlanPrice
Basic$9
Pro$29
", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "table", - "message": "Add a <table> element" + "message": "Додайте елемент <table>" }, { "type": "element_exists", "value": "caption", - "message": "Add a <caption> for the table title" + "message": "Додайте <caption> для заголовка таблиці" }, { "type": "element_count", "value": { "selector": "th", "min": 2 }, - "message": "Add at least 2 header cells (th)" + "message": "Додайте комірки заголовка (<th>) для 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 <thead> to group header rows and <tbody> to group data rows. This helps browsers and assistive technology understand the table structure.

You can also use <tfoot> for footer rows like totals.", - "task": "Create a structured table:
1. A <caption> with Monthly Sales
2. A <thead> with Month and Revenue headers
3. A <tbody> 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": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Monthly Sales
MonthRevenue
January$12,500
February$14,200
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "element_exists", - "value": "table", - "message": "Add a <table> element" - }, - { - "type": "element_exists", - "value": "caption", - "message": "Add a <caption> element" - }, - { - "type": "element_exists", - "value": "thead", - "message": "Add a <thead> for the header section" - }, - { - "type": "element_exists", - "value": "tbody", - "message": "Add a <tbody> 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 <tfoot> to create a footer section for totals or summary data. The footer stays at the bottom even if tbody has many rows.

Combine all sections for a fully structured, accessible table.", - "task": "Create a complete table:
1. A <caption> with Order Summary
2. A <thead> with Item and Price headers
3. A <tbody> with 2 items
4. A <tfoot> 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": "\n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n \n
Order Summary
ItemPrice
Widget$25.00
Gadget$35.00
Total$60.00
", - "previewContainer": "preview-area", - "validations": [ - { - "type": "element_exists", - "value": "table", - "message": "Add a <table> element" - }, - { - "type": "element_exists", - "value": "caption", - "message": "Add a <caption> element" - }, - { - "type": "element_exists", - "value": "thead", - "message": "Add a <thead> section" - }, - { - "type": "element_exists", - "value": "tbody", - "message": "Add a <tbody> section" - }, - { - "type": "element_exists", - "value": "tfoot", - "message": "Add a <tfoot> 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 рядки даних)" } ] } diff --git a/lessons/uk/31-html-marquee.json b/lessons/uk/31-html-marquee.json index ec3a486..9284f1c 100644 --- a/lessons/uk/31-html-marquee.json +++ b/lessons/uk/31-html-marquee.json @@ -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 <marquee> element creates scrolling text - a classic from the early web! While deprecated, it still works in most browsers.

Note: For production, use CSS animations instead. But for learning and fun, marquee is great!", - "task": "Create a simple marquee:
1. Add a <marquee> element
2. Put some text inside like Welcome to my website!", + "title": "Біжучий текст", + "description": "Елемент <marquee> створює біжучий текст - класика раннього вебу! Хоча він застарілий, все ще працює в більшості браузерів.

Примітка: Для продакшену використовуйте CSS-анімації. Але для навчання та розваги marquee чудовий!", + "task": "Створіть простий marquee:
1. Додайте елемент <marquee>
2. Помістіть текст всередину, наприклад Ласкаво просимо на мій сайт!", "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 <marquee> element" + "message": "Додайте елемент <marquee>" } ] }, { "id": "marquee-direction", - "title": "Direction & Behavior", - "description": "Control the marquee with attributes:
direction: left, right, up, down
behavior: scroll (default), slide (stops at edge), alternate (bounces)
scrollamount: speed (default is 6)", - "task": "Create a bouncing marquee:
1. Add a <marquee> element
2. Set behavior=\"alternate\" to make it bounce
3. Add some fun text", + "title": "Напрямок та поведінка", + "description": "Керуйте marquee за допомогою атрибутів:
direction: left, right, up, down
behavior: scroll (за замовчуванням), slide (зупиняється на краю), alternate (відбивається)
scrollamount: швидкість (за замовчуванням 6)", + "task": "Створіть marquee що відбивається:
1. Додайте елемент <marquee>
2. Встановіть behavior=\"alternate\" щоб він відбивався
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 <marquee> element" + "message": "Додайте елемент <marquee>" }, { "type": "attribute_value", "value": { "selector": "marquee", "attr": "behavior", "value": "alternate" }, - "message": "Add behavior=\"alternate\" to make it bounce" + "message": "Додайте behavior=\"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!

Remember: This is deprecated HTML. Modern sites use CSS animations, but marquee is great for understanding web history.", - "task": "Create a news ticker:
1. A <marquee> with direction=\"left\"
2. Set scrollamount=\"5\" for smooth scrolling
3. Add a breaking news headline inside", + "title": "Ретро-стрічка новин", + "description": "Поєднайте кілька атрибутів marquee для класичного ефекту стрічки новин. Ви навіть можете помістити кілька елементів всередину!

Пам'ятайте: Це застарілий HTML. Сучасні сайти використовують CSS-анімації, але marquee чудовий для розуміння історії вебу.", + "task": "Створіть стрічку новин:
1. Елемент <marquee> з direction=\"left\"
2. Встановіть scrollamount=\"5\" для плавного прокручування
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 <marquee> element" + "message": "Додайте елемент <marquee>" }, { "type": "attribute_value", "value": { "selector": "marquee", "attr": "direction", "value": "left" }, - "message": "Add direction=\"left\" for horizontal scrolling" + "message": "Додайте direction=\"left\" для горизонтального прокручування" }, { "type": "attribute_value", "value": { "selector": "marquee", "attr": "scrollamount", "value": "5" }, - "message": "Add scrollamount=\"5\" for smooth speed" + "message": "Додайте scrollamount=\"5\" для плавної швидкості" } ] } diff --git a/lessons/uk/32-html-svg.json b/lessons/uk/32-html-svg.json index 59b9dbb..2fce5f2 100644 --- a/lessons/uk/32-html-svg.json +++ b/lessons/uk/32-html-svg.json @@ -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 <svg> element is the container, with width and height attributes.

Use <circle> with cx, cy (center) and r (radius) to draw circles.", - "task": "Create an SVG with a circle:
1. An <svg> with width=\"200\" and height=\"200\"
2. A <circle> centered at (100,100) with radius 50
3. Add a fill color", + "title": "Малювання кіл", + "description": "SVG (Scalable Vector Graphics) дозволяє малювати фігури безпосередньо в HTML. Елемент <svg> є контейнером з атрибутами width та height.

Використовуйте <circle> з cx, cy (центр) та r (радіус) для малювання кіл.", + "task": "Створіть SVG з колом:
1. Елемент <svg> з width=\"200\" та height=\"200\"
2. Елемент <circle> з центром у (100,100) та радіусом 50
3. Додайте колір fill", "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": "\n \n", + "solution": "\n \n", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "svg", - "message": "Add an <svg> element" + "message": "Додайте елемент <svg>" }, { "type": "element_exists", "value": "circle", - "message": "Add a <circle> element inside the SVG" + "message": "Додайте елемент <circle> всередину SVG" + }, + { + "type": "attribute_value", + "value": { "selector": "svg", "attr": "width", "value": "200" }, + "message": "Встановіть width=\"200\" в SVG" + }, + { + "type": "attribute_value", + "value": { "selector": "svg", "attr": "height", "value": "200" }, + "message": "Встановіть height=\"200\" в SVG" }, { "type": "attribute_value", "value": { "selector": "circle", "attr": "cx", "value": "100" }, - "message": "Set cx=\"100\" for the circle's horizontal center" + "message": "Встановіть cx=\"100\" для горизонтального центру кола" }, { "type": "attribute_value", "value": { "selector": "circle", "attr": "cy", "value": "100" }, - "message": "Set cy=\"100\" for the circle's vertical center" + "message": "Встановіть cy=\"100\" для вертикального центру кола" + }, + { + "type": "attribute_value", + "value": { "selector": "circle", "attr": "r", "value": "50" }, + "message": "Встановіть r=\"50\" для радіуса кола" } ] }, { "id": "svg-rect-line", - "title": "Rectangles & Lines", - "description": "Draw rectangles with <rect> using x, y, width, height.

Draw lines with <line> using x1, y1 (start) and x2, y2 (end). Lines need a stroke color!", - "task": "Create an SVG with:
1. An <svg> (200x150)
2. A <rect> at position (20,20) with size 80x60
3. A <line> from (120,30) to (180,90) with a stroke color", + "title": "Прямокутники та лінії", + "description": "Малюйте прямокутники за допомогою <rect> використовуючи x, y, width, height.

Малюйте лінії за допомогою <line> використовуючи x1, y1 (початок) та x2, y2 (кінець). Лінії потребують колір stroke!", + "task": "Створіть SVG з:
1. Елементом <svg> (200x150)
2. Елементом <rect> на позиції (20,20) розміром 80x60
3. Елементом <line> від (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": "\n \n \n", + "solution": "\n \n \n", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "svg", - "message": "Add an <svg> element" + "message": "Додайте елемент <svg>" }, { "type": "element_exists", "value": "rect", - "message": "Add a <rect> element" + "message": "Додайте елемент <rect>" }, { "type": "element_exists", "value": "line", - "message": "Add a <line> element" + "message": "Додайте елемент <line>" + }, + { + "type": "attribute_value", + "value": { "selector": "svg", "attr": "width", "value": "200" }, + "message": "Встановіть width=\"200\" в SVG" + }, + { + "type": "attribute_value", + "value": { "selector": "svg", "attr": "height", "value": "150" }, + "message": "Встановіть height=\"150\" в SVG" + }, + { + "type": "attribute_value", + "value": { "selector": "rect", "attr": "x", "value": "20" }, + "message": "Встановіть x=\"20\" в rect" + }, + { + "type": "attribute_value", + "value": { "selector": "rect", "attr": "y", "value": "20" }, + "message": "Встановіть y=\"20\" в rect" + }, + { + "type": "attribute_value", + "value": { "selector": "rect", "attr": "width", "value": "80" }, + "message": "Встановіть width=\"80\" в rect" + }, + { + "type": "attribute_value", + "value": { "selector": "rect", "attr": "height", "value": "60" }, + "message": "Встановіть height=\"60\" в rect" + }, + { + "type": "attribute_value", + "value": { "selector": "line", "attr": "x1", "value": "120" }, + "message": "Встановіть x1=\"120\" в line" + }, + { + "type": "attribute_value", + "value": { "selector": "line", "attr": "y1", "value": "30" }, + "message": "Встановіть y1=\"30\" в line" + }, + { + "type": "attribute_value", + "value": { "selector": "line", "attr": "x2", "value": "180" }, + "message": "Встановіть x2=\"180\" в line" + }, + { + "type": "attribute_value", + "value": { "selector": "line", "attr": "y2", "value": "90" }, + "message": "Встановіть y2=\"90\" в line" + }, + { + "type": "contains", + "value": "stroke", + "message": "Додайте колір stroke до line" } ] }, { "id": "svg-shapes", - "title": "Multiple Shapes", - "description": "Combine shapes to create simple graphics! Add stroke for outlines and stroke-width for thickness.

Use fill=\"none\" for hollow shapes. Shapes stack in order - later elements appear on top.", - "task": "Create a simple face:
1. An <svg> (200x200)
2. A large <circle> for the face
3. Two small <circle> elements for eyes
4. A <line> for the smile", + "title": "Кілька фігур", + "description": "Комбінуйте фігури для створення простої графіки! Додайте stroke для контурів та stroke-width для товщини.

Використовуйте fill=\"none\" для порожніх фігур. Фігури накладаються в порядку - пізніші елементи з'являються зверху.", + "task": "Створіть просте обличчя:
1. Елемент <svg> (200x200)
2. Велике <circle> для обличчя
3. Два маленьких <circle> для очей
4. Елемент <line> для посмішки", "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": "\n \n \n \n \n", + "solution": "\n \n \n \n \n", "previewContainer": "preview-area", "validations": [ { "type": "element_exists", "value": "svg", - "message": "Add an <svg> element" + "message": "Додайте елемент <svg>" }, { "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 <line> for the smile" + "message": "Додайте <line> для посмішки" } ] }