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
",
+ "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-boxBorder-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": "
",
- "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": "
",
- "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: 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: sans-serif; padding: 1rem; } .view { background: #ffe0b2; }",
+ "title": "Viewport Units",
+ "description": "وحدات العرض تحدد حجم العناصر نسبة لنافذة المتصفح: • vw – 1% من عرض العرض • vh – 1% من ارتفاع العرض
هذه مثالية للأقسام بملء الشاشة مثل لافتات hero.",
+ "task": "اجعل قسم hero هذا يملأ ارتفاع العرض بضبط min-height: 100vh.",
+ "previewHTML": "
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 على الخصائص للتغييرات السلسة عند تغيير الحالة.
",
- "task": "Define a keyframe at 50% with transform: translateY(-20px) and apply animation: bounce 1s infinite to .ball.",
+ "description": "أنشئ حركات مسماة باستخدام @keyframes وطبّقها عبر اختصار animation.
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": "",
+ "solution": "",
"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": "",
- "solution": "",
+ "solution": "",
"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:
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": "النماذج تحتاج طريقة لإرسال البيانات. استخدم:
نص الزر يجب أن يكون موجه للعمل (مثل تسجيل الدخول، 'التسجيل'، 'إرسال').",
+ "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": "",
- "solution": "",
+ "initialCode": "",
+ "solution": "",
"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!
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> ينشئ حقل إدخال نص متعدد الأسطر، مثالي للمحتوى الطويل مثل الرسائل أو الأوصاف.
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> لخلايا البيانات.
",
"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
Monthly Sales
\n \n
\n
Month
\n
Revenue
\n
\n \n \n
\n
January
\n
$12,500
\n
\n
\n
February
\n
$14,200
\n
\n \n
",
- "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
Order Summary
\n \n
\n
Item
\n
Price
\n
\n \n \n
\n
Widget
\n
$25.00
\n
\n
\n
Gadget
\n
$35.00
\n
\n \n \n
\n
Total
\n
$60.00
\n
\n \n
",
- "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": "",
+ "solution": "",
"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.
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": "",
+ "solution": "",
"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": "
",
- "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-boxBorder-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": "
",
- "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": "
",
- "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: 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: 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": "
",
+ "title": "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 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": "
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!
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.
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
",
"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": "
",
- "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-boxBorder-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": "
",
- "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": "
",
- "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: 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: 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": "
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.
",
- "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.
",
- "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": "",
+ "solution": "",
"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": "",
- "solution": "",
+ "solution": "",
"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:
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": "",
- "solution": "",
+ "initialCode": "",
+ "solution": "",
"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!
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
Fruit Prices
\n
\n
Fruit
\n
Price
\n
\n
\n
Apple
\n
$1.50
\n
\n
\n
Banana
\n
$0.75
\n
\n
",
+ "solution": "
\n
Pricing
\n
\n
Plan
\n
Price
\n
\n
\n
Basic
\n
$9
\n
\n
\n
Pro
\n
$29
\n
\n
",
"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
Monthly Sales
\n \n
\n
Month
\n
Revenue
\n
\n \n \n
\n
January
\n
$12,500
\n
\n
\n
February
\n
$14,200
\n
\n \n
",
- "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
Order Summary
\n \n
\n
Item
\n
Price
\n
\n \n \n
\n
Widget
\n
$25.00
\n
\n
\n
Gadget
\n
$35.00
\n
\n \n \n
\n
Total
\n
$60.00
\n
\n \n
",
- "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": "",
+ "solution": "",
"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": "",
+ "solution": "",
"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": "",
+ "solution": "",
"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
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
",
- "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-boxBorder-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": "
",
+ "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: 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: 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": "
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": "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.
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": "",
+ "solution": "",
"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": "",
- "solution": "",
+ "solution": "",
"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:
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:
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": "",
- "solution": "",
+ "initialCode": "",
+ "solution": "",
"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!
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.
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.
",
"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
Monthly Sales
\n \n
\n
Month
\n
Revenue
\n
\n \n \n
\n
January
\n
$12,500
\n
\n
\n
February
\n
$14,200
\n
\n \n
",
- "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
Order Summary
\n \n
\n
Item
\n
Price
\n
\n \n \n
\n
Widget
\n
$25.00
\n
\n
\n
Gadget
\n
$35.00
\n
\n \n \n
\n
Total
\n
$60.00
\n
\n \n
",
- "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": "",
+ "solution": "",
"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.
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": "",
+ "solution": "",
"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. Отримайте миттєвий зворотний зв'язок з підказками
Рекомендований шлях навчання: 1. HTML Блокові та рядкові - Зрозумійте контейнерні vs рядкові елементи 2. HTML Форми - Створюйте інтерактивні форми з валідацією 3. CSS Селектори - Точно вибирайте елементи 4. CSS Блокова модель - Опануйте padding, margin, borders 5. CSS Flexbox - Створюйте гнучкі макети 6. CSS Анімації - Додавайте рух та переходи
Поради: • Використовуйте Показати очікуване щоб побачити цільовий результат • Ваш прогрес зберігається автоматично • Спробуйте Emmet в режимі HTML: ul>li*3 + Tab
",
"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": "
",
- "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-boxBorder-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": "
",
+ "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: 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.
Вони ідеальні для повноекранних секцій як hero-банери.",
+ "task": "Зробіть цю hero-секцію висотою з viewport, встановивши min-height: 100vh.",
+ "previewHTML": "
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 до властивостей для плавних змін при зміні стану.
",
- "task": "Define a keyframe at 50% with transform: translateY(-20px) and apply animation: bounce 1s infinite to .ball.",
+ "description": "Створюйте іменовані анімації використовуючи @keyframes та застосовуйте їх через скорочення animation.
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": "",
+ "solution": "",
"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": "",
- "solution": "",
+ "solution": "",
"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:
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": "Форми потребують способу відправки даних. Використовуйте:
Текст кнопки має бути орієнтованим на дію (напр. Увійти, 'Зареєструватись', 'Надіслати').",
+ "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": "",
- "solution": "",
+ "initialCode": "",
+ "solution": "",
"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!
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> для комірок даних.
",
"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
Monthly Sales
\n \n
\n
Month
\n
Revenue
\n
\n \n \n
\n
January
\n
$12,500
\n
\n
\n
February
\n
$14,200
\n
\n \n
",
- "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
Order Summary
\n \n
\n
Item
\n
Price
\n
\n \n \n
\n
Widget
\n
$25.00
\n
\n
\n
Gadget
\n
$35.00
\n
\n \n \n
\n
Total
\n
$60.00
\n
\n \n
",
- "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": "",
+ "solution": "",
"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.
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": "",
+ "solution": "",
"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> для посмішки"
}
]
}