fix(i18n): sync all lesson translations with English source
Synchronizes 72 lesson files across 5 languages (de, pl, es, ar, uk) to match the English source. This ensures code, solutions, and validations are identical while only title, description, task, and message fields are translated. Changes include: - Box model lessons (01-box-model.json) - Units and variables (05-units-variables.json) - Transitions and animations (06-transitions-animations.json) - Responsive design (08-responsive.json) - HTML elements (20-html-elements.json) - HTML forms basic and validation (21, 22) - HTML details/summary, progress/meter (23, 24) - HTML datalist, dialog, fieldset (25, 27, 28) - HTML tables and SVG (30, 32) - HTML marquee (31) - Welcome module (00-welcome.json) Fixes validation inconsistencies and removes extra content that exceeded English source. German translations were largely correct; Polish, Spanish, Arabic, and Ukrainian required full translations.
This commit is contained in:
@@ -2,15 +2,15 @@
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "welcome",
|
||||
"title": "Code Crispies",
|
||||
"description": "Welcome to Code Crispies - your interactive web development learning platform",
|
||||
"description": "مرحباً بك في Code Crispies - منصتك التفاعلية لتعلم تطوير الويب",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "get-started",
|
||||
"title": "Get Started",
|
||||
"description": "<strong>Code Crispies</strong> is a free, open-source platform for learning web development through hands-on exercises. No account required!<br><br><strong>What you'll learn:</strong><br>• <strong>HTML</strong> - Semantic elements, forms, tables, SVG (<em>HTML Block & Inline</em>, <em>HTML Forms</em>, <em>HTML Tables</em>)<br>• <strong>CSS</strong> - Selectors, box model, flexbox, animations (<em>CSS Selectors</em>, <em>CSS Box Model</em>, <em>CSS Flexbox</em>)<br>• <strong>Responsive Design</strong> - Media queries and mobile-first layouts<br><br><strong>How it works:</strong><br>1. Read the task in the left panel<br>2. Write code in the editor<br>3. See live results in the preview<br>4. Get instant feedback with hints<br><br><strong>Keyboard shortcuts:</strong> <kbd>Ctrl+Z</kbd> to undo, <kbd>Ctrl+Shift+Z</kbd> to redo<br><br><strong>More resources:</strong><br>• <a href=\"https://nextlevelshit.github.io/html-over-js/\" target=\"_blank\">HTML over JS</a> - Native HTML vs JavaScript solutions<br>• <a href=\"https://nextlevelshit.github.io/web-engineering-mandala/\" target=\"_blank\">Web Engineering Mandala</a> - JavaScript technology roadmap",
|
||||
"task": "Write <code>Hello World</code>",
|
||||
"title": "ابدأ",
|
||||
"description": "<strong>Code Crispies</strong> منصة مجانية ومفتوحة المصدر لتعلم تطوير الويب من خلال التمارين العملية. لا حاجة لحساب!<br><br><strong>ما ستتعلمه:</strong><br>• <strong>HTML</strong> - العناصر الدلالية، النماذج، الجداول، SVG (<em>HTML كتلي وسطري</em>، <em>HTML النماذج</em>، <em>HTML الجداول</em>)<br>• <strong>CSS</strong> - المحددات، نموذج الصندوق، flexbox، الحركات (<em>CSS المحددات</em>، <em>CSS نموذج الصندوق</em>، <em>CSS Flexbox</em>)<br>• <strong>التصميم المتجاوب</strong> - استعلامات الوسائط وتخطيطات mobile-first<br><br><strong>كيف يعمل:</strong><br>1. اقرأ المهمة في اللوحة اليسرى<br>2. اكتب الكود في المحرر<br>3. شاهد النتائج مباشرة في المعاينة<br>4. احصل على ملاحظات فورية مع تلميحات<br><br><strong>اختصارات لوحة المفاتيح:</strong> <kbd>Ctrl+Z</kbd> تراجع، <kbd>Ctrl+Shift+Z</kbd> إعادة<br><br><strong>المزيد من الموارد:</strong><br>• <a href=\"https://nextlevelshit.github.io/html-over-js/\" target=\"_blank\">HTML over JS</a> - HTML الأصلي مقابل حلول JavaScript<br>• <a href=\"https://nextlevelshit.github.io/web-engineering-mandala/\" target=\"_blank\">Web Engineering Mandala</a> - خارطة تقنيات JavaScript",
|
||||
"task": "اكتب <code>Hello World</code>",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 2rem; color: #6366f1; font-weight: bold; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -21,15 +21,15 @@
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "Hello World",
|
||||
"message": "Write <code>Hello World</code>"
|
||||
"message": "اكتب <code>Hello World</code>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "overview",
|
||||
"title": "Overview",
|
||||
"description": "<strong>You're ready!</strong> Open the menu (☰) to explore all modules.<br><br><strong>Recommended learning path:</strong><br>1. <em>HTML Block & Inline</em> - Understand container vs inline elements<br>2. <em>HTML Forms</em> - Build interactive forms with validation<br>3. <em>CSS Selectors</em> - Target elements precisely<br>4. <em>CSS Box Model</em> - Master padding, margin, borders<br>5. <em>CSS Flexbox</em> - Create flexible layouts<br>6. <em>CSS Animations</em> - Add motion and transitions<br><br><strong>Tips:</strong><br>• Use <em>Show Expected</em> to see the target result<br>• Your progress is saved automatically<br>• Try Emmet in HTML mode: <kbd>ul>li*3</kbd> + Tab<br><br><strong>Open Source:</strong><br>• <a href=\"https://git.librete.ch/public/code-crispies\" target=\"_blank\">Gitea (Source)</a> · <a href=\"https://github.com/nextlevelshit/code-crispies\" target=\"_blank\">GitHub (Mirror)</a><br>• Made by <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a> · <a href=\"https://www.linkedin.com/in/michael-werner-czechowski\" target=\"_blank\">Michael Czechowski</a>",
|
||||
"task": "Click Next to continue",
|
||||
"title": "نظرة عامة",
|
||||
"description": "<strong>أنت جاهز!</strong> افتح القائمة (☰) لاستكشاف جميع الوحدات.<br><br><strong>مسار التعلم الموصى به:</strong><br>1. <em>HTML كتلي وسطري</em> - افهم عناصر الحاوية مقابل السطرية<br>2. <em>HTML النماذج</em> - أنشئ نماذج تفاعلية مع التحقق<br>3. <em>CSS المحددات</em> - استهدف العناصر بدقة<br>4. <em>CSS نموذج الصندوق</em> - أتقن padding، margin، borders<br>5. <em>CSS Flexbox</em> - أنشئ تخطيطات مرنة<br>6. <em>CSS الحركات</em> - أضف الحركة والانتقالات<br><br><strong>نصائح:</strong><br>• استخدم <em>إظهار المتوقع</em> لرؤية النتيجة المستهدفة<br>• يُحفظ تقدمك تلقائياً<br>• جرب Emmet في وضع HTML: <kbd>ul>li*3</kbd> + Tab<br><br><strong>مفتوح المصدر:</strong><br>• <a href=\"https://git.librete.ch/public/code-crispies\" target=\"_blank\">Gitea (Source)</a> · <a href=\"https://github.com/nextlevelshit/code-crispies\" target=\"_blank\">GitHub (Mirror)</a><br>• صُنع بواسطة <a href=\"https://librete.ch\" target=\"_blank\">LibreTECH</a> · <a href=\"https://www.linkedin.com/in/michael-werner-czechowski\" target=\"_blank\">Michael Czechowski</a>",
|
||||
"task": "انقر التالي للمتابعة",
|
||||
"previewHTML": "<p>Hello World! 🌍</p><p>Hallo Welt!</p><p>Bonjour le monde!</p><p>¡Hola Mundo!</p><p>Ciao Mondo!</p><p>Olá Mundo!</p><p>こんにちは世界!</p><p>你好世界!</p><p>안녕 세상!</p><p>Привет мир!</p><p dir=\"rtl\">שלום עולם!</p><p dir=\"rtl\">مرحبا بالعالم!</p>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 12px; } p { margin: 6px 0; padding: 6px 12px; border-radius: 6px; font-weight: 600; font-size: 0.95em; } p:nth-child(1) { background: #fef3c7; color: #92400e; font-size: 1.2em; } p:nth-child(2) { background: #dcfce7; color: #166534; } p:nth-child(3) { background: #dbeafe; color: #1e40af; } p:nth-child(4) { background: #fce7f3; color: #9d174d; } p:nth-child(5) { background: #e0e7ff; color: #4338ca; } p:nth-child(6) { background: #fef9c3; color: #854d0e; } p:nth-child(7) { background: #fee2e2; color: #991b1b; } p:nth-child(8) { background: #f3e8ff; color: #7c3aed; } p:nth-child(9) { background: #ccfbf1; color: #0f766e; } p:nth-child(10) { background: #fae8ff; color: #86198f; } p:nth-child(11) { background: #fef3c7; color: #b45309; } p:nth-child(12) { background: #d1fae5; color: #047857; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -39,8 +39,8 @@
|
||||
"validations": [
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "Hello World",
|
||||
"message": "Click Next to continue"
|
||||
"value": "Hello",
|
||||
"message": "انقر التالي للمتابعة"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
@@ -2,18 +2,18 @@
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "box-model",
|
||||
"title": "CSS Box Model",
|
||||
"description": "Master the fundamental principles of space management in web design through the CSS box model. This module explores how content, padding, borders, and margins combine to create layout structures that are both visually appealing and structurally sound.",
|
||||
"description": "أتقن المبادئ الأساسية لإدارة المساحة في تصميم الويب من خلال نموذج الصندوق CSS. يستكشف هذا الوحدة كيف يتحد المحتوى والحشو والحدود والهوامش لإنشاء هياكل التخطيط.",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "box-model-1",
|
||||
"title": "Box Model Components",
|
||||
"description": "The CSS box model consists of four concentric layers: content area (innermost), padding, border, and margin (outermost). Understanding how these components interact is essential for precise layout control.",
|
||||
"task": "Set <kbd>padding</kbd> to <kbd>1rem</kbd> to create space between the content and border.",
|
||||
"previewHTML": "<div class=\"box\">Box Model Components</div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: lavender; border: 2px dashed slategray; }",
|
||||
"title": "Padding",
|
||||
"description": "كل عنصر في CSS هو صندوق بأربع طبقات: المحتوى، الحشو (padding)، الحدود، والهامش. <strong>Padding</strong> يخلق مساحة تنفس بين محتواك وحافة الصندوق.<br><br>بدون padding، يضغط النص بشكل محرج على الحدود. Padding يجعل المحتوى قابلاً للقراءة ومتوازناً بصرياً.<br><br><pre>.card {\n padding: 1rem;\n}</pre>",
|
||||
"task": "بطاقة الملف الشخصي هذه تبدو ضيقة. أضف <kbd>padding: 1rem</kbd> ليكون للنص مجال للتنفس.",
|
||||
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".box {\n ",
|
||||
"codePrefix": ".card {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "padding: 1rem;",
|
||||
@@ -22,62 +22,62 @@
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "padding", "expected": "1rem" },
|
||||
"message": "Set <kbd>padding: 1rem</kbd>"
|
||||
"message": "اضبط <kbd>padding: 1rem</kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "box-model-2",
|
||||
"title": "Adding Borders",
|
||||
"description": "Borders outline an element, creating visual separation from surrounding content. The border shorthand accepts three values: width, style, and color.",
|
||||
"task": "Set <kbd>border</kbd> to <kbd>2px solid darkslategray</kbd>.",
|
||||
"previewHTML": "<div class=\"box\">This box needs a border</div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: mintcream; padding: 1rem; }",
|
||||
"title": "Borders",
|
||||
"description": "الحدود تنشئ حدوداً مرئية حول العناصر. اختصار <kbd>border</kbd> يقبل ثلاث قيم: العرض، النمط، واللون.<br><br>الأنماط الشائعة: <kbd>solid</kbd>، <kbd>dashed</kbd>، <kbd>dotted</kbd>، <kbd>none</kbd>",
|
||||
"task": "أضف لمسة يسارية خفيفة للبطاقة باستخدام <kbd>border-left: 4px solid steelblue</kbd>.",
|
||||
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".box {\n ",
|
||||
"codePrefix": ".card {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "border: 2px solid darkslategray;",
|
||||
"solution": "border-left: 4px solid steelblue;",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "border:\\s*2px\\s+solid\\s+darkslategray",
|
||||
"message": "Set <kbd>border: 2px solid darkslategray</kbd>",
|
||||
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
|
||||
"message": "اضبط <kbd>border-left: 4px solid steelblue</kbd>",
|
||||
"options": { "caseSensitive": false }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "box-model-3",
|
||||
"title": "Adding Margins",
|
||||
"description": "Margins create space between elements, controlling how they relate to one another within a layout. Unlike padding (which affects internal spacing), margins exist outside the element's border.",
|
||||
"task": "Set <kbd>margin</kbd> to <kbd>1rem</kbd> to create space between this element and its neighbors.",
|
||||
"previewHTML": "<div class=\"container\"><div class=\"outer\">This box needs margins</div><div class=\"neighbor\">Adjacent element</div></div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .outer { background-color: plum; padding: 1rem; border: 2px solid orchid; } .neighbor { background-color: lightblue; padding: 1rem; border: 2px solid steelblue; }",
|
||||
"title": "Margins",
|
||||
"description": "الهوامش تنشئ مساحة <em>خارج</em> العنصر، تفصله عن جيرانه. بينما يدفع padding المحتوى للداخل، الهوامش تدفع العناصر الأخرى بعيداً.",
|
||||
"task": "أضف مساحة بين بطاقتي الملف الشخصي هاتين باستخدام <kbd>margin-bottom: 1rem</kbd> على <kbd>.card</kbd>.",
|
||||
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article><article class=\"card\"><h3>Alex Rivera</h3><p>UX Designer</p></article>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; border-left: 4px solid steelblue; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".outer {\n ",
|
||||
"codePrefix": ".card {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "margin: 1rem;",
|
||||
"solution": "margin-bottom: 1rem;",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "margin", "expected": "1rem" },
|
||||
"message": "Set <kbd>margin: 1rem</kbd>"
|
||||
"value": { "property": "margin-bottom", "expected": "1rem" },
|
||||
"message": "اضبط <kbd>margin-bottom: 1rem</kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "box-model-4",
|
||||
"title": "Box Sizing: Border-Box",
|
||||
"description": "The <kbd>box-sizing</kbd> property determines how element dimensions are calculated. The default <kbd>content-box</kbd> excludes padding and border from width/height, while <kbd>border-box</kbd> includes them, making layout calculations more intuitive.",
|
||||
"task": "Set <kbd>box-sizing</kbd> to <kbd>border-box</kbd> so padding and border are included in the width.",
|
||||
"previewHTML": "<div class=\"sizing-demo\"><div class=\"box default\">Content-box (default)</div><div class=\"box sized\">Border-box</div></div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .sizing-demo { display: flex; gap: 1rem; } .box { width: 200px; padding: 1rem; border: 4px solid teal; background: lightcyan; } .default { box-sizing: content-box; }",
|
||||
"title": "Box Sizing",
|
||||
"description": "افتراضياً، <kbd>width</kbd> يحدد فقط عرض المحتوى. Padding والحدود تُضاف للمجموع. هذا يسبب مشاكل في التخطيط.<br><br><kbd>box-sizing: border-box</kbd> يشمل padding والحدود في العرض، مما يجعل التحجيم متوقعاً. معظم المطورين يطبقون هذا على جميع العناصر.",
|
||||
"task": "كلا البطاقتين لهما <kbd>width: 200px</kbd>. اليسرى تستخدم التحجيم الافتراضي (content-box)، مما يجعلها أعرض من المتوقع. أصلح البطاقة اليمنى باستخدام <kbd>box-sizing: border-box</kbd>.",
|
||||
"previewHTML": "<div class=\"demo\"><article class=\"card\">Content-box</article><article class=\"card fix\">Border-box</article></div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .demo { display: flex; gap: 1rem; } .card { width: 200px; padding: 1rem; border: 4px solid steelblue; background: white; border-radius: 8px; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".sized {\n ",
|
||||
"codePrefix": ".fix {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "box-sizing: border-box;",
|
||||
@@ -86,93 +86,104 @@
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "box-sizing", "expected": "border-box" },
|
||||
"message": "Set <kbd>box-sizing: border-box</kbd>"
|
||||
"message": "اضبط <kbd>box-sizing: border-box</kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "box-model-5",
|
||||
"title": "Margin Collapse",
|
||||
"description": "When two vertical margins meet, they collapse to the larger value instead of adding up. Understanding this behavior is crucial for consistent vertical spacing.",
|
||||
"task": "Set <kbd>margin-bottom</kbd> to <kbd>2rem</kbd>. Notice the space between paragraphs equals 2rem (not 3rem) due to margin collapse.",
|
||||
"previewHTML": "<div class=\"collapse-demo\"><p class=\"first\">This paragraph has a bottom margin.</p><p class=\"second\">This paragraph has a top margin of 1rem.</p></div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .collapse-demo { border: 1px solid silver; padding: 8px; background: ghostwhite; } .second { margin-top: 1rem; background: linen; }",
|
||||
"title": "Padding Shorthand",
|
||||
"description": "Padding يقبل 1-4 قيم:<br>• قيمة واحدة: جميع الجوانب<br>• قيمتان: عمودي | أفقي<br>• 4 قيم: أعلى | يمين | أسفل | يسار",
|
||||
"task": "هذا الزر يحتاج مساحة أفقية أكثر من العمودية. اضبط <kbd>padding: 8px 1rem</kbd> (8px أعلى/أسفل، 1rem يسار/يمين).",
|
||||
"previewHTML": "<button class=\"btn\">Follow</button>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .btn { background: steelblue; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".first {\n ",
|
||||
"codePrefix": ".btn {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "margin-bottom: 2rem;",
|
||||
"solution": "padding: 8px 1rem;",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "margin-bottom", "expected": "2rem" },
|
||||
"message": "Set <kbd>margin-bottom: 2rem</kbd>"
|
||||
"type": "regex",
|
||||
"value": "padding:\\s*8px\\s+1rem",
|
||||
"message": "اضبط <kbd>padding: 8px 1rem</kbd>",
|
||||
"options": { "caseSensitive": false }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "box-model-6",
|
||||
"title": "Margin Shorthand Notation",
|
||||
"description": "The margin shorthand can set all four sides at once. Two values set vertical (top/bottom) and horizontal (left/right) margins respectively.",
|
||||
"task": "Set <kbd>margin</kbd> to <kbd>1rem 2rem</kbd> for 1rem top/bottom and 2rem left/right.",
|
||||
"previewHTML": "<div class=\"container\"><div class=\"spaced\">This box needs margins: 1rem top/bottom, 2rem left/right</div></div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .spaced { background-color: honeydew; border: 2px solid mediumseagreen; padding: 1rem; }",
|
||||
"title": "Margin Shorthand",
|
||||
"description": "Margin يستخدم نفس نمط الاختصار مثل padding. نمط شائع هو توسيط عناصر الكتلة أفقياً باستخدام <kbd>margin: 0 auto</kbd>.",
|
||||
"task": "وسّط هذه البطاقة أفقياً. اضبط <kbd>margin: 0 auto</kbd> لحساب هوامش يسار/يمين متساوية تلقائياً.",
|
||||
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { width: 250px; background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; border-left: 4px solid steelblue; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".spaced {\n ",
|
||||
"codePrefix": ".card {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "margin: 1rem 2rem;",
|
||||
"solution": "margin: 0 auto;",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "margin:\\s*1rem\\s+2rem",
|
||||
"message": "Set <kbd>margin: 1rem 2rem</kbd>",
|
||||
"value": "margin:\\s*0\\s+auto",
|
||||
"message": "اضبط <kbd>margin: 0 auto</kbd>",
|
||||
"options": { "caseSensitive": false }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "box-model-7",
|
||||
"title": "Padding Shorthand Notation",
|
||||
"description": "Like margin, padding shorthand allows setting all sides at once. A single value applies to all four sides equally.",
|
||||
"task": "Set <kbd>padding</kbd> to <kbd>2rem</kbd> to add equal padding on all sides.",
|
||||
"previewHTML": "<div class=\"padded\">This box needs equal padding on all sides</div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .padded { background-color: papayawhip; border: 2px solid orange; }",
|
||||
"title": "Border Radius",
|
||||
"description": "على الرغم من أنه ليس جزءاً من نموذج الصندوق الكلاسيكي، <kbd>border-radius</kbd> يُدوّر زوايا صندوق حدود العنصر. استخدم <kbd>50%</kbd> على عنصر مربع لإنشاء دائرة.",
|
||||
"task": "اجعل صورة الأفاتار دائرية باستخدام <kbd>border-radius: 50%</kbd>.",
|
||||
"previewHTML": "<article class=\"card\"><img class=\"avatar\" src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='35' r='25' fill='%23666'/%3E%3Ccircle cx='50' cy='90' r='40' fill='%23666'/%3E%3C/svg%3E\" alt=\"Avatar\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; text-align: center; } .avatar { width: 80px; height: 80px; background: #ddd; margin-bottom: 8px; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".padded {\n ",
|
||||
"codePrefix": ".avatar {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "padding: 2rem;",
|
||||
"solution": "border-radius: 50%;",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "padding", "expected": "2rem" },
|
||||
"message": "Set <kbd>padding: 2rem</kbd>"
|
||||
"value": { "property": "border-radius", "expected": "50%" },
|
||||
"message": "اضبط <kbd>border-radius: 50%</kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "box-model-8",
|
||||
"title": "Border on Specific Sides",
|
||||
"description": "For granular control, you can target specific sides with <kbd>border-top</kbd>, <kbd>border-right</kbd>, <kbd>border-bottom</kbd>, or <kbd>border-left</kbd>.",
|
||||
"task": "Set <kbd>border-bottom</kbd> to <kbd>4px solid dodgerblue</kbd>.",
|
||||
"previewHTML": "<div class=\"line\">This element needs only a bottom border</div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .line { padding: 1rem; background-color: aliceblue; }",
|
||||
"title": "Complete Card",
|
||||
"description": "لنجمع كل شيء معاً. بطاقة الإشعار هذه تحتاج تنسيقاً لتبدو احترافية.",
|
||||
"task": "نسّق الإشعار: أضف <kbd>padding: 1rem</kbd>، <kbd>border-left: 4px solid coral</kbd>، و<kbd>border-radius: 4px</kbd>.",
|
||||
"previewHTML": "<div class=\"alert\"><strong>New message</strong><p>You have 3 unread notifications</p></div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .alert { background: seashell; } .alert strong { color: coral; } .alert p { margin: 4px 0 0; color: #666; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": ".line {\n ",
|
||||
"codePrefix": ".alert {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "border-bottom: 4px solid dodgerblue;",
|
||||
"solution": "padding: 1rem;\n border-left: 4px solid coral;\n border-radius: 4px;",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "padding", "expected": "1rem" },
|
||||
"message": "اضبط <kbd>padding: 1rem</kbd>"
|
||||
},
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "border-bottom:\\s*4px\\s+solid\\s+dodgerblue",
|
||||
"message": "Set <kbd>border-bottom: 4px solid dodgerblue</kbd>",
|
||||
"value": "border-left:\\s*4px\\s+solid\\s+coral",
|
||||
"message": "اضبط <kbd>border-left: 4px solid coral</kbd>",
|
||||
"options": { "caseSensitive": false }
|
||||
},
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "border-radius", "expected": "4px" },
|
||||
"message": "اضبط <kbd>border-radius: 4px</kbd>"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,115 +1,100 @@
|
||||
{
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "units-variables",
|
||||
"title": "CSS Units & Variables",
|
||||
"description": "Understand the variety of CSS measurement units and how to define and use custom properties for maintainable styles.",
|
||||
"title": "وحدات CSS والمتغيرات",
|
||||
"description": "افهم تنوع وحدات القياس في CSS وكيفية تعريف واستخدام الخصائص المخصصة لأنماط قابلة للصيانة.",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "units-1",
|
||||
"title": "Absolute vs. Relative Units",
|
||||
"description": "Learn the difference between px, rem, em, %, and vw/vh for flexible, responsive layouts.",
|
||||
"task": "Set the <kbd>width</kbd> of <kbd>.box</kbd> to <kbd>80%</kbd> and <kbd>max-width</kbd> to <kbd>37.5rem</kbd>.",
|
||||
"previewHTML": "<div class=\"box\">Resize me!</div>",
|
||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { background: #f5f5f5; padding: 1rem; }",
|
||||
"title": "Relative Units",
|
||||
"description": "يقدم CSS نوعين من الوحدات: <em>مطلقة</em> (مثل <kbd>px</kbd>) و<em>نسبية</em> (مثل <kbd>%</kbd> و <kbd>rem</kbd>). الوحدات النسبية تتكيف مع سياقها، مما يجعل التخطيطات مرنة وسهلة الوصول.<br><br><strong>الوحدات النسبية الشائعة:</strong><br>• <kbd>%</kbd> – نسبة للعنصر الأب<br>• <kbd>rem</kbd> – نسبة لحجم خط الجذر (عادة 16px)<br>• <kbd>em</kbd> – نسبة لحجم خط العنصر<br><br>نمط شائع للمحتوى القابل للقراءة: اضبط <kbd>width: 100%</kbd> لملء المساحة المتاحة، ثم <kbd>max-width: 40rem</kbd> لتحديد طول السطر للقراءة.",
|
||||
"task": "نص هذه المقالة عريض جداً على الشاشات الكبيرة. أضف <kbd>max-width: 40rem</kbd> للحصول على عرض قراءة مثالي.",
|
||||
"previewHTML": "<article class=\"article\"><h2>The Art of Typography</h2><p>Good typography is invisible. When text is set well, readers absorb information without noticing the design decisions that make it comfortable to read. Line length is crucial—too wide and eyes get lost, too narrow and reading becomes choppy.</p><p>The ideal line length is 45-75 characters per line. At typical font sizes, this works out to roughly 40rem maximum width.</p></article>",
|
||||
"previewBaseCSS": "body { font-family: Georgia, serif; padding: 1rem; background: #f9f9f9; } .article { background: white; padding: 2rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .article h2 { margin: 0 0 1rem; color: #333; } .article p { margin: 0 0 1rem; line-height: 1.6; color: #444; } .article p:last-child { margin-bottom: 0; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": "/* Set flexible sizing */\n.box {",
|
||||
"codePrefix": ".article {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "}",
|
||||
"solution": " width: 80%;\n max-width: 37.5rem;",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "max-width: 40rem;",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{ "type": "contains", "value": "width", "message": "Use <kbd>width</kbd> property", "options": { "caseSensitive": false } },
|
||||
{ "type": "property_value", "value": { "property": "width", "expected": "80%" }, "message": "Set width to <kbd>80%</kbd>" },
|
||||
{ "type": "contains", "value": "max-width", "message": "Use <kbd>max-width</kbd> property", "options": { "caseSensitive": false } },
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "max-width", "expected": "37.5rem" },
|
||||
"message": "Set max-width to <kbd>37.5rem</kbd>"
|
||||
"value": { "property": "max-width", "expected": "40rem" },
|
||||
"message": "اضبط <kbd>max-width: 40rem</kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "units-2",
|
||||
"title": "CSS Custom Properties",
|
||||
"description": "Define and reuse variables (--custom properties) to centralize your theme values.",
|
||||
"task": "Create a <kbd>--main-color</kbd> variable in <kbd>:root</kbd> with <kbd>#6200ee</kbd> and apply it as the border color on <kbd>.themed</kbd>.",
|
||||
"previewHTML": "<div class=\"themed\">Variable Box</div>",
|
||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .themed { padding: 1rem; border: 0.125rem solid #ddd; }",
|
||||
"title": "CSS Variables",
|
||||
"description": "الخصائص المخصصة في CSS (المتغيرات) تتيح لك تعريف قيم قابلة لإعادة الاستخدام. عرّفها بـ <kbd>--اسم</kbd> واستخدمها بـ <kbd>var(--اسم)</kbd>. المتغيرات المعرّفة على <kbd>:root</kbd> متاحة في كل مكان.",
|
||||
"task": "عرّف <kbd>--brand: steelblue</kbd> في <kbd>:root</kbd>، ثم استخدمها كلون <kbd>background</kbd> لـ <kbd>.btn</kbd>.",
|
||||
"previewHTML": "<div class=\"actions\"><button class=\"btn\">Subscribe</button><button class=\"btn\">Learn More</button></div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .actions { display: flex; gap: 1rem; } .btn { color: white; border: none; padding: 12px 24px; border-radius: 6px; font-size: 1rem; cursor: pointer; background: #ccc; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": "/* Define and use a CSS variable */\n:root {",
|
||||
"codePrefix": ":root {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "}\n.themed { }",
|
||||
"solution": " --main-color: #6200ee;\n}\n.themed {\n border-color: var(--main-color);",
|
||||
"codeSuffix": "\n}\n\n.btn {\n background: var(--brand);\n}",
|
||||
"solution": "--brand: steelblue;",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "--main-color",
|
||||
"message": "Define <kbd>--main-color</kbd> in :root",
|
||||
"value": "--brand",
|
||||
"message": "عرّف المتغير <kbd>--brand</kbd>",
|
||||
"options": { "caseSensitive": false }
|
||||
},
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "var(--main-color)",
|
||||
"message": "Use <kbd>var(--main-color)</kbd>",
|
||||
"value": "steelblue",
|
||||
"message": "اضبط القيمة على <kbd>steelblue</kbd>",
|
||||
"options": { "caseSensitive": false }
|
||||
},
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "border", "expected": "var(--main-color)" },
|
||||
"message": "Apply variable to border color",
|
||||
"options": { "exact": false }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "units-3",
|
||||
"title": "Unit Calculations (calc)",
|
||||
"description": "Use the <kbd>calc()</kbd> function to combine different units in one expression.",
|
||||
"task": "Set the <kbd>width</kbd> of <kbd>.sized</kbd> to <kbd>calc(100% - 2rem)</kbd> and <kbd>min-height</kbd> to <kbd>calc(10vh + 1rem)</kbd>.",
|
||||
"previewHTML": "<div class=\"sized\">Calc Demo</div>",
|
||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sized { background: #e8f5e9; padding: 1rem; }",
|
||||
"title": "calc() Function",
|
||||
"description": "دالة <kbd>calc()</kbd> تتيح لك خلط وحدات مختلفة في الحسابات. هذا ضروري للتخطيطات التي تجمع بين الأحجام الثابتة والمرنة، مثل تخطيط الشريط الجانبي.",
|
||||
"task": "المحتوى الرئيسي يجب أن يملأ المساحة المتبقية بعد الشريط الجانبي 200px. اضبط <kbd>width: calc(100% - 200px)</kbd> على <kbd>.main</kbd>.",
|
||||
"previewHTML": "<div class=\"layout\"><aside class=\"sidebar\">Sidebar<br>Navigation</aside><main class=\"main\"><h2>Main Content</h2><p>This area should fill the remaining width after accounting for the fixed-width sidebar.</p></main></div>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .layout { display: flex; gap: 1rem; } .sidebar { width: 200px; background: #1a1a2e; color: white; padding: 1rem; border-radius: 8px; flex-shrink: 0; } .main { background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .main h2 { margin: 0 0 8px; } .main p { margin: 0; color: #666; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": "/* Use calc for dynamic sizing */\n.sized {",
|
||||
"codePrefix": ".main {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "}",
|
||||
"solution": " width: calc(100% - 2rem);\n min-height: calc(10vh + 1rem);",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "width: calc(100% - 200px);",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{ "type": "contains", "value": "calc", "message": "Use <kbd>calc()</kbd> function", "options": { "caseSensitive": false } },
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "width:\\s*calc\\(100% - 2rem\\)",
|
||||
"message": "Width should be <kbd>calc(100% - 2rem)</kbd>",
|
||||
"options": { "caseSensitive": false }
|
||||
},
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "min-height:\\s*calc\\(10vh \\+ 1rem\\)",
|
||||
"message": "Min-height should be <kbd>calc(10vh + 1rem)</kbd>",
|
||||
"value": "width:\\s*calc\\(\\s*100%\\s*-\\s*200px\\s*\\)",
|
||||
"message": "اضبط <kbd>width: calc(100% - 200px)</kbd>",
|
||||
"options": { "caseSensitive": false }
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "units-4",
|
||||
"title": "Viewport & Responsive Units",
|
||||
"description": "Control layouts relative to viewport size with vw, vh, and vmin/vmax units.",
|
||||
"task": "Give <kbd>.view</kbd> a <kbd>width</kbd> of <kbd>50vw</kbd> and <kbd>height</kbd> of <kbd>20vh</kbd>.",
|
||||
"previewHTML": "<div class=\"view\">Viewport Box</div>",
|
||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .view { background: #ffe0b2; }",
|
||||
"title": "Viewport Units",
|
||||
"description": "وحدات العرض تحدد حجم العناصر نسبة لنافذة المتصفح:<br>• <kbd>vw</kbd> – 1% من عرض العرض<br>• <kbd>vh</kbd> – 1% من ارتفاع العرض<br><br>هذه مثالية للأقسام بملء الشاشة مثل لافتات hero.",
|
||||
"task": "اجعل قسم hero هذا يملأ ارتفاع العرض بضبط <kbd>min-height: 100vh</kbd>.",
|
||||
"previewHTML": "<section class=\"hero\"><h1>Welcome</h1><p>Scroll down to explore</p></section>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .hero { background: linear-gradient(135deg, #1a1a2e 0%, steelblue 100%); color: white; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 2rem; } .hero h1 { margin: 0 0 1rem; font-size: 2.5rem; } .hero p { margin: 0; opacity: 0.8; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": "/* Use viewport units */\n.view {",
|
||||
"codePrefix": ".hero {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "}",
|
||||
"solution": " width: 50vw;\n height: 20vh;",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "min-height: 100vh;",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{ "type": "contains", "value": "vw", "message": "Use <kbd>vw</kbd> unit", "options": { "caseSensitive": false } },
|
||||
{ "type": "contains", "value": "vh", "message": "Use <kbd>vh</kbd> unit", "options": { "caseSensitive": false } },
|
||||
{ "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Set width to <kbd>50vw</kbd>" },
|
||||
{ "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Set height to <kbd>20vh</kbd>" }
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "min-height", "expected": "100vh" },
|
||||
"message": "اضبط <kbd>min-height: 100vh</kbd>"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
|
||||
@@ -1,15 +1,15 @@
|
||||
{
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "transitions-animations",
|
||||
"title": "CSS Animations",
|
||||
"description": "Bring interactivity to your UI by smoothly transitioning properties and creating keyframe-driven animations.",
|
||||
"title": "حركات CSS",
|
||||
"description": "أضف التفاعل لواجهتك من خلال انتقالات الخصائص السلسة والحركات المبنية على keyframes.",
|
||||
"difficulty": "intermediate",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "transitions-1",
|
||||
"title": "Transitions",
|
||||
"description": "Learn how to apply <kbd>transition</kbd> to properties for smooth changes on state changes.<br><br><pre>transition: property duration;\n/* e.g. transition: background-color 0.3s; */</pre>",
|
||||
"task": "Add <kbd>transition: background-color 0.3s</kbd> to <kbd>.btn</kbd> so the color fades smoothly on hover.",
|
||||
"description": "تعلم كيفية تطبيق <kbd>transition</kbd> على الخصائص للتغييرات السلسة عند تغيير الحالة.<br><br><pre>transition: property duration;\n/* مثال: transition: background-color 0.3s; */</pre>",
|
||||
"task": "أضف <kbd>transition: background-color 0.3s</kbd> ليتغير اللون بسلاسة عند التمرير.",
|
||||
"previewHTML": "<button class=\"btn\">Hover Me</button>",
|
||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: black; color: white; padding: 0.5rem 1rem; border: none; cursor: pointer; } .btn:hover { background: white; color: black; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -22,13 +22,13 @@
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "transition",
|
||||
"message": "Use the <kbd>transition</kbd> property",
|
||||
"message": "استخدم خاصية <kbd>transition</kbd>",
|
||||
"options": { "caseSensitive": false }
|
||||
},
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "transition:\\s*background-color\\s*0\\.3s",
|
||||
"message": "Set <kbd>transition: background-color 0.3s</kbd>",
|
||||
"message": "اضبط <kbd>transition: background-color 0.3s</kbd>",
|
||||
"options": { "caseSensitive": false }
|
||||
}
|
||||
]
|
||||
@@ -36,8 +36,8 @@
|
||||
{
|
||||
"id": "transitions-2",
|
||||
"title": "Timing Funcs",
|
||||
"description": "Explore easing functions like <kbd>ease</kbd>, <kbd>linear</kbd>, <kbd>ease-in</kbd>, <kbd>ease-out</kbd> to control animation pacing.",
|
||||
"task": "Set <kbd>transition-timing-function</kbd> to <kbd>ease-in-out</kbd> on <kbd>.btn</kbd>.",
|
||||
"description": "استكشف دوال التسهيل مثل <kbd>ease</kbd>، <kbd>linear</kbd>، <kbd>ease-in</kbd>، <kbd>ease-out</kbd> للتحكم في إيقاع الحركة.",
|
||||
"task": "اضبط <kbd>transition-timing-function</kbd> على <kbd>ease-in-out</kbd>.",
|
||||
"previewHTML": "<button class=\"btn\">Timing</button>",
|
||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .btn { background: navy; color: gold; padding: 0.5rem 1rem; border: none; cursor: pointer; transition: all 0.5s; } .btn:hover { background: gold; color: navy; transform: scale(1.1); }",
|
||||
"sandboxCSS": "",
|
||||
@@ -50,21 +50,21 @@
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "transition-timing-function",
|
||||
"message": "Use <kbd>transition-timing-function</kbd>",
|
||||
"message": "استخدم <kbd>transition-timing-function</kbd>",
|
||||
"options": { "caseSensitive": false }
|
||||
},
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "transition-timing-function", "expected": "ease-in-out" },
|
||||
"message": "Set timing to <kbd>ease-in-out</kbd>"
|
||||
"message": "اضبط التوقيت على <kbd>ease-in-out</kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "transitions-3",
|
||||
"title": "Keyframes",
|
||||
"description": "Create named animations using <kbd>@keyframes</kbd> and apply them via the <kbd>animation</kbd> shorthand.<br><br><pre>@keyframes bounce {\n 50% { transform: translateY(-20px); }\n}\n.ball {\n animation: bounce 1s infinite;\n}</pre>",
|
||||
"task": "Define a keyframe at <kbd>50%</kbd> with <kbd>transform: translateY(-20px)</kbd> and apply <kbd>animation: bounce 1s infinite</kbd> to <kbd>.ball</kbd>.",
|
||||
"description": "أنشئ حركات مسماة باستخدام <kbd>@keyframes</kbd> وطبّقها عبر اختصار <kbd>animation</kbd>.<br><br><pre>@keyframes bounce {\n 50% { transform: translateY(-20px); }\n}\n.ball {\n animation: bounce 1s infinite;\n}</pre>",
|
||||
"task": "عرّف keyframe عند <kbd>50%</kbd> مع <kbd>transform: translateY(-20px)</kbd> وطبّق <kbd>animation: bounce 1s infinite</kbd> على <kbd>.ball</kbd>.",
|
||||
"previewHTML": "<div class=\"ball\"></div>",
|
||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .ball { width: 50px; height: 50px; background: crimson; border-radius: 50%; margin: 2rem auto; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -77,25 +77,25 @@
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "@keyframes bounce",
|
||||
"message": "Define <kbd>@keyframes bounce</kbd>",
|
||||
"message": "عرّف <kbd>@keyframes bounce</kbd>",
|
||||
"options": { "caseSensitive": false }
|
||||
},
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "50%.*transform: translateY\\(-20px\\)",
|
||||
"message": "At <kbd>50%</kbd>, use <kbd>transform: translateY(-20px)</kbd>",
|
||||
"message": "عند <kbd>50%</kbd>، استخدم <kbd>transform: translateY(-20px)</kbd>",
|
||||
"options": { "caseSensitive": false }
|
||||
},
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "animation",
|
||||
"message": "Use <kbd>animation</kbd> property on <kbd>.ball</kbd>",
|
||||
"message": "استخدم خاصية <kbd>animation</kbd> على <kbd>.ball</kbd>",
|
||||
"options": { "caseSensitive": false }
|
||||
},
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "animation:.*bounce.*1s.*infinite",
|
||||
"message": "Apply <kbd>animation: bounce 1s infinite</kbd>",
|
||||
"message": "طبّق <kbd>animation: bounce 1s infinite</kbd>",
|
||||
"options": { "caseSensitive": false }
|
||||
}
|
||||
]
|
||||
@@ -103,8 +103,8 @@
|
||||
{
|
||||
"id": "transitions-4",
|
||||
"title": "Animation Properties",
|
||||
"description": "Fine-tune animations with <kbd>animation-delay</kbd>, <kbd>animation-iteration-count</kbd>, <kbd>animation-direction</kbd>, and <kbd>animation-fill-mode</kbd>.",
|
||||
"task": "Apply the <kbd>pulse</kbd> animation to <kbd>.box</kbd> with <kbd>animation-name: pulse</kbd>, <kbd>animation-duration: 2s</kbd>, <kbd>animation-delay: 1s</kbd>, <kbd>animation-iteration-count: 2</kbd>, and <kbd>animation-fill-mode: forwards</kbd>.",
|
||||
"description": "اضبط الحركات بـ <kbd>animation-delay</kbd>، <kbd>animation-iteration-count</kbd>، <kbd>animation-direction</kbd>، و <kbd>animation-fill-mode</kbd>.",
|
||||
"task": "طبّق حركة <kbd>pulse</kbd> على <kbd>.box</kbd> مع <kbd>animation-name: pulse</kbd>، <kbd>animation-duration: 2s</kbd>، <kbd>animation-delay: 1s</kbd>، <kbd>animation-iteration-count: 2</kbd>، و <kbd>animation-fill-mode: forwards</kbd>.",
|
||||
"previewHTML": "<div class=\"box\">Pulse</div>",
|
||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { width: 100px; height: 100px; background: black; color: white; display: flex; align-items: center; justify-content: center; margin: 2rem auto; } @keyframes pulse { 0% { background: black; color: white; transform: scale(1); } 50% { background: white; color: black; transform: scale(1.2); } 100% { background: limegreen; color: black; transform: scale(1); } }",
|
||||
"sandboxCSS": "",
|
||||
@@ -117,27 +117,27 @@
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "animation-name", "expected": "pulse" },
|
||||
"message": "Set <kbd>animation-name: pulse</kbd>"
|
||||
"message": "اضبط <kbd>animation-name: pulse</kbd>"
|
||||
},
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "animation-duration", "expected": "2s" },
|
||||
"message": "Set <kbd>animation-duration: 2s</kbd>"
|
||||
"message": "اضبط <kbd>animation-duration: 2s</kbd>"
|
||||
},
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "animation-delay", "expected": "1s" },
|
||||
"message": "Set <kbd>animation-delay: 1s</kbd>"
|
||||
"message": "اضبط <kbd>animation-delay: 1s</kbd>"
|
||||
},
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "animation-iteration-count", "expected": "2" },
|
||||
"message": "Set <kbd>animation-iteration-count: 2</kbd>"
|
||||
"message": "اضبط <kbd>animation-iteration-count: 2</kbd>"
|
||||
},
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "animation-fill-mode", "expected": "forwards" },
|
||||
"message": "Set <kbd>animation-fill-mode: forwards</kbd>"
|
||||
"message": "اضبط <kbd>animation-fill-mode: forwards</kbd>"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,14 +2,14 @@
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "responsive-design",
|
||||
"title": "CSS Responsive Design",
|
||||
"description": "Make your layouts adapt to different screen sizes using media queries and fluid design techniques.",
|
||||
"description": "اجعل تخطيطاتك تتكيف مع أحجام الشاشات المختلفة باستخدام media queries وتقنيات التصميم المرن.",
|
||||
"difficulty": "intermediate",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "responsive-1",
|
||||
"title": "Media Queries",
|
||||
"description": "Understand the syntax and use cases for CSS media queries to apply styles conditionally based on viewport characteristics.",
|
||||
"task": "Write a media query with <kbd>@media (max-width: 600px)</kbd> that changes <kbd>.panel</kbd> background to <kbd>lightcoral</kbd>.",
|
||||
"description": "افهم صياغة واستخدامات CSS media queries لتطبيق الأنماط شرطياً بناءً على خصائص viewport.<br><br><pre>@media (max-width: 600px) {\n .panel {\n background: lightcoral;\n }\n}</pre>",
|
||||
"task": "اكتب media query باستخدام <kbd>@media (max-width: 600px)</kbd> لتغيير خلفية <kbd>.panel</kbd> إلى <kbd>lightcoral</kbd>.",
|
||||
"previewHTML": "<div class=\"panel\">Resize the window</div>",
|
||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .panel { padding: 1rem; background: lightblue; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -22,19 +22,19 @@
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "@media\\s*\\(max-width:\\s*600px\\)",
|
||||
"message": "Use <kbd>@media (max-width: 600px)</kbd>",
|
||||
"message": "استخدم <kbd>@media (max-width: 600px)</kbd>",
|
||||
"options": { "caseSensitive": false }
|
||||
},
|
||||
{
|
||||
"type": "contains",
|
||||
"value": ".panel",
|
||||
"message": "Target <kbd>.panel</kbd> inside the media query",
|
||||
"message": "استهدف <kbd>.panel</kbd> داخل media query",
|
||||
"options": { "caseSensitive": false }
|
||||
},
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "background", "expected": "lightcoral" },
|
||||
"message": "Set <kbd>background: lightcoral</kbd>",
|
||||
"message": "اضبط <kbd>background: lightcoral</kbd>",
|
||||
"options": { "exact": false }
|
||||
}
|
||||
]
|
||||
@@ -42,8 +42,8 @@
|
||||
{
|
||||
"id": "responsive-2",
|
||||
"title": "Fluid Type",
|
||||
"description": "Use relative units like <kbd>vw</kbd> to make font sizes scale with the viewport width.",
|
||||
"task": "Set <kbd>font-size: 5vw</kbd> on <kbd>.text</kbd> so it scales as the viewport changes.",
|
||||
"description": "استخدم وحدات نسبية مثل <kbd>vw</kbd> لجعل أحجام الخطوط تتناسب مع عرض viewport.",
|
||||
"task": "اضبط <kbd>font-size: 5vw</kbd> لتتغير مع viewport.",
|
||||
"previewHTML": "<p class=\"text\">Fluid Typography</p>",
|
||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -53,46 +53,46 @@
|
||||
"solution": " font-size: 5vw;",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{ "type": "property_value", "value": { "property": "font-size", "expected": "5vw" }, "message": "Set <kbd>font-size: 5vw</kbd>" }
|
||||
{ "type": "property_value", "value": { "property": "font-size", "expected": "5vw" }, "message": "اضبط <kbd>font-size: 5vw</kbd>" }
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "responsive-3",
|
||||
"title": "Flex Grids",
|
||||
"description": "Combine CSS Grid with <kbd>auto-fit</kbd> or <kbd>auto-fill</kbd> for responsive column layouts.",
|
||||
"task": "Add <kbd>display: grid</kbd>, <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd>, and <kbd>gap: 1rem</kbd> to <kbd>.cards</kbd>.",
|
||||
"previewHTML": "<div class=\"cards\"><div>1</div><div>2</div><div>3</div><div>4</div></div>",
|
||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .cards > div { background: #d1c4e9; padding: 1rem; }",
|
||||
"title": "Responsive Grid",
|
||||
"description": "ادمج CSS Grid مع <kbd>auto-fit</kbd> أو <kbd>auto-fill</kbd> لتخطيطات أعمدة متجاوبة تضبط عدد الأعمدة تلقائياً بناءً على المساحة المتاحة.",
|
||||
"task": "أضف <kbd>display: grid</kbd> و <kbd>grid-template-columns: repeat(auto-fit, minmax(200px, 1fr))</kbd> و <kbd>gap: 1rem</kbd>.",
|
||||
"previewHTML": "<section class=\"features\"><article class=\"feature\"><h3>Fast</h3><p>Lightning quick load times</p></article><article class=\"feature\"><h3>Secure</h3><p>Enterprise-grade security</p></article><article class=\"feature\"><h3>Reliable</h3><p>99.9% uptime guaranteed</p></article><article class=\"feature\"><h3>Support</h3><p>24/7 customer service</p></article></section>",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .feature { background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .feature h3 { margin: 0 0 8px; color: steelblue; } .feature p { margin: 0; color: #666; font-size: 0.9rem; }",
|
||||
"sandboxCSS": "",
|
||||
"codePrefix": "/* Create a responsive grid */\n.cards {",
|
||||
"codePrefix": ".features {\n ",
|
||||
"initialCode": "",
|
||||
"codeSuffix": "}",
|
||||
"solution": " display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
|
||||
"codeSuffix": "\n}",
|
||||
"solution": "display: grid;\n grid-template-columns: repeat(auto-fit, minmax(200px, 1fr));\n gap: 1rem;",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "display", "expected": "grid" },
|
||||
"message": "Set <kbd>display: grid</kbd>"
|
||||
"message": "اضبط <kbd>display: grid</kbd>"
|
||||
},
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "repeat\\(auto-fit,\\s*minmax\\(200px,\\s*1fr\\)\\)",
|
||||
"message": "Use <kbd>repeat(auto-fit, minmax(200px, 1fr))</kbd>",
|
||||
"message": "استخدم <kbd>repeat(auto-fit, minmax(200px, 1fr))</kbd>",
|
||||
"options": { "caseSensitive": false }
|
||||
},
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "gap", "expected": "1rem" },
|
||||
"message": "Set <kbd>gap: 1rem</kbd>"
|
||||
"message": "اضبط <kbd>gap: 1rem</kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "responsive-4",
|
||||
"title": "Mobile-First",
|
||||
"description": "Adopt a mobile-first approach by writing base styles for small screens and enhancing for larger viewports.",
|
||||
"task": "Write a media query with <kbd>@media (min-width: 768px)</kbd> that sets <kbd>.sidebar</kbd> width to <kbd>250px</kbd>.",
|
||||
"description": "اتبع نهج mobile-first بكتابة أنماط أساسية للشاشات الصغيرة وتحسينها لـ viewports أكبر.",
|
||||
"task": "اكتب media query باستخدام <kbd>@media (min-width: 768px)</kbd> لضبط عرض <kbd>.sidebar</kbd> إلى <kbd>250px</kbd>.",
|
||||
"previewHTML": "<aside class=\"sidebar\">Sidebar</aside>",
|
||||
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sidebar { background: #c8e6c9; padding: 1rem; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -105,19 +105,19 @@
|
||||
{
|
||||
"type": "regex",
|
||||
"value": "@media\\s*\\(min-width:\\s*768px\\)",
|
||||
"message": "Use <kbd>@media (min-width: 768px)</kbd>",
|
||||
"message": "استخدم <kbd>@media (min-width: 768px)</kbd>",
|
||||
"options": { "caseSensitive": false }
|
||||
},
|
||||
{
|
||||
"type": "contains",
|
||||
"value": ".sidebar",
|
||||
"message": "Target <kbd>.sidebar</kbd> inside media query",
|
||||
"message": "استهدف <kbd>.sidebar</kbd> في media query",
|
||||
"options": { "caseSensitive": false }
|
||||
},
|
||||
{
|
||||
"type": "property_value",
|
||||
"value": { "property": "width", "expected": "250px" },
|
||||
"message": "Set <kbd>width: 250px</kbd>",
|
||||
"message": "اضبط <kbd>width: 250px</kbd>",
|
||||
"options": { "exact": false }
|
||||
}
|
||||
]
|
||||
|
||||
@@ -2,65 +2,65 @@
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-elements",
|
||||
"title": "HTML Block & Inline",
|
||||
"description": "Understanding the fundamental difference between container (block) and inline elements",
|
||||
"description": "فهم الفرق الأساسي بين عناصر الحاويات (الكتلية) والعناصر السطرية",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "block-vs-inline-intro",
|
||||
"title": "Block vs Inline Elements",
|
||||
"description": "HTML elements fall into two main categories:<br><br><strong>Block elements</strong> (containers) start on a new line and take full width. Examples: <kbd><div></kbd>, <kbd><p></kbd>, <kbd><h1></kbd>, <kbd><section></kbd><br><br><strong>Inline elements</strong> flow within text and only take needed width. Examples: <kbd><span></kbd>, <kbd><a></kbd>, <kbd><strong></kbd>, <kbd><em></kbd>",
|
||||
"task": "Wrap the word <kbd>important</kbd> with <kbd><strong></kbd> tags to make it bold. Notice how the paragraph (block) takes full width while strong (inline) flows with text.",
|
||||
"title": "العناصر الكتلية vs السطرية",
|
||||
"description": "تنقسم عناصر HTML إلى فئتين رئيسيتين:<br><br><strong>العناصر الكتلية</strong> (الحاويات) تبدأ في سطر جديد وتأخذ العرض الكامل. أمثلة: <kbd><div></kbd>, <kbd><p></kbd>, <kbd><h1></kbd>, <kbd><section></kbd><br><br><strong>العناصر السطرية</strong> تتدفق داخل النص وتأخذ العرض المطلوب فقط. أمثلة: <kbd><span></kbd>, <kbd><a></kbd>, <kbd><strong></kbd>, <kbd><em></kbd>",
|
||||
"task": "أحط الكلمة <kbd>مهمة</kbd> بوسوم <kbd><strong></kbd> لجعلها عريضة. لاحظ كيف يأخذ الفقرة (كتلي) العرض الكامل بينما strong (سطري) يتدفق مع النص.",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; } p { background: #e3f2fd; padding: 10px; } strong { background: #ffecb3; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<p>This is a paragraph with an important word.</p>",
|
||||
"solution": "<p>This is a paragraph with an <strong>important</strong> word.</p>",
|
||||
"initialCode": "<p>هذه فقرة تحتوي على كلمة مهمة.</p>",
|
||||
"solution": "<p>هذه فقرة تحتوي على كلمة <strong>مهمة</strong>.</p>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "p",
|
||||
"message": "Add a <kbd><p></kbd> paragraph element"
|
||||
"message": "أضف عنصر فقرة <kbd><p></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "parent_child",
|
||||
"value": { "parent": "p", "child": "strong" },
|
||||
"message": "Wrap the word <kbd>important</kbd> with <kbd><strong></kbd> tags"
|
||||
"message": "أحط الكلمة <kbd>مهمة</kbd> بوسوم <kbd><strong></kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "semantic-containers",
|
||||
"title": "Semantic Tags",
|
||||
"description": "Modern HTML uses semantic containers that describe their content:<br><br><kbd><header></kbd> - Page or section header<br><kbd><nav></kbd> - Navigation links<br><kbd><main></kbd> - Main content area<br><kbd><section></kbd> - Thematic grouping<br><kbd><article></kbd> - Self-contained content<br><kbd><footer></kbd> - Page or section footer",
|
||||
"task": "Create a basic page structure:<br>1. Add a <kbd><header></kbd> with an <kbd><h1></kbd> containing the text <code>My Website</code><br>2. Add a <kbd><main></kbd> element with a paragraph saying <code>Welcome to my site!</code><br>3. Add a <kbd><footer></kbd> with a paragraph saying <code>Copyright 2026</code>",
|
||||
"title": "الوسوم الدلالية",
|
||||
"description": "يستخدم HTML الحديث حاويات دلالية تصف محتواها:<br><br><kbd><header></kbd> - رأس الصفحة أو القسم<br><kbd><nav></kbd> - روابط التنقل<br><kbd><main></kbd> - منطقة المحتوى الرئيسي<br><kbd><section></kbd> - تجميع موضوعي<br><kbd><article></kbd> - محتوى مستقل<br><kbd><footer></kbd> - تذييل الصفحة أو القسم",
|
||||
"task": "أنشئ هيكل صفحة أساسي:<br>1. أضف <kbd><header></kbd> مع <kbd><h1></kbd> يحتوي على النص <code>موقعي</code><br>2. أضف عنصر <kbd><main></kbd> مع فقرة تقول <code>مرحباً بك في موقعي!</code><br>3. أضف <kbd><footer></kbd> مع فقرة تقول <code>Copyright 2026</code>",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; margin: 0; } header { background: #1976d2; color: white; padding: 15px; } main { padding: 20px; min-height: 100px; } footer { background: #424242; color: white; padding: 10px; text-align: center; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "",
|
||||
"solution": "<header>\n <h1>My Website</h1>\n</header>\n<main>\n <p>Welcome to my site!</p>\n</main>\n<footer>\n <p>Copyright 2026</p>\n</footer>",
|
||||
"solution": "<header>\n <h1>موقعي</h1>\n</header>\n<main>\n <p>مرحباً بك في موقعي!</p>\n</main>\n<footer>\n <p>Copyright 2026</p>\n</footer>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "header",
|
||||
"message": "Add a <kbd><header></kbd> element"
|
||||
"message": "أضف عنصر <kbd><header></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "main",
|
||||
"message": "Add a <kbd><main></kbd> element"
|
||||
"message": "أضف عنصر <kbd><main></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "footer",
|
||||
"message": "Add a <kbd><footer></kbd> element"
|
||||
"message": "أضف عنصر <kbd><footer></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "parent_child",
|
||||
"value": { "parent": "header", "child": "h1" },
|
||||
"message": "Add an <kbd><h1></kbd> heading inside your header"
|
||||
"message": "أضف عنوان <kbd><h1></kbd> داخل header"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,100 +1,100 @@
|
||||
{
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-forms-basic",
|
||||
"title": "HTML Forms",
|
||||
"description": "Learn to create forms with various input types",
|
||||
"title": "نماذج HTML",
|
||||
"description": "تعلم إنشاء النماذج بأنواع حقول مختلفة",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "form-structure",
|
||||
"title": "Form Structure",
|
||||
"description": "Every form needs a <kbd><form></kbd> wrapper. Inside, use <kbd><label></kbd> to describe inputs and <kbd><input></kbd> for user data entry.<br><br>The <kbd>for</kbd> attribute on labels should match the <kbd>id</kbd> on inputs for accessibility.",
|
||||
"task": "Create a form with:<br>1. A <kbd><label></kbd> with the text <code>Name:</code> and <kbd>for=\"name\"</kbd> attribute<br>2. A text <kbd><input></kbd> with <kbd>id=\"name\"</kbd> and <kbd>name=\"name\"</kbd> attributes",
|
||||
"title": "هيكل النموذج",
|
||||
"description": "كل نموذج يحتاج غلاف <kbd><form></kbd>. بداخله، استخدم <kbd><label></kbd> لوصف الحقول و <kbd><input></kbd> لإدخال البيانات.<br><br>سمة <kbd>for</kbd> في التسميات يجب أن تطابق <kbd>id</kbd> في الحقول للوصولية.",
|
||||
"task": "أنشئ نموذجاً مع:<br>1. <kbd><label></kbd> بالنص <code>الاسم:</code> وسمة <kbd>for=\"name\"</kbd><br>2. <kbd><input></kbd> نصي بسمات <kbd>id=\"name\"</kbd> و <kbd>name=\"name\"</kbd>",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-bottom: 5px; font-weight: 500; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "",
|
||||
"solution": "<form>\n <label for=\"name\">Name:</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n</form>",
|
||||
"solution": "<form>\n <label for=\"name\">الاسم:</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n</form>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "form",
|
||||
"message": "Wrap everything in a <kbd><form></kbd> element"
|
||||
"message": "أحط كل شيء بعنصر <kbd><form></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "label",
|
||||
"message": "Add a <kbd><label></kbd> for your input"
|
||||
"message": "أضف <kbd><label></kbd> لحقلك"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "input",
|
||||
"message": "Add an <kbd><input></kbd> element"
|
||||
"message": "أضف عنصر <kbd><input></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "label", "attr": "for", "value": null },
|
||||
"message": "Add a <kbd>for</kbd> attribute to your label"
|
||||
"message": "أضف سمة <kbd>for</kbd> للتسمية"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "input", "attr": "id", "value": null },
|
||||
"message": "Add an <kbd>id</kbd> attribute to your input"
|
||||
"message": "أضف سمة <kbd>id</kbd> لحقلك"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "input-types",
|
||||
"title": "Input Types",
|
||||
"description": "Different input types provide appropriate keyboards and validation:<br><br><kbd>type=\"text\"</kbd> - General text<br><kbd>type=\"email\"</kbd> - Email with @ validation<br><kbd>type=\"password\"</kbd> - Hidden characters<br><kbd>type=\"number\"</kbd> - Numeric keyboard<br><kbd>type=\"tel\"</kbd> - Phone keyboard",
|
||||
"task": "Create a login form with two fields:<br>1. An email field: <kbd><label for=\"email\">Email:</label></kbd> and <kbd><input type=\"email\" id=\"email\"></kbd><br>2. A password field: <kbd><label for=\"password\">Password:</label></kbd> and <kbd><input type=\"password\" id=\"password\"></kbd>",
|
||||
"title": "أنواع الحقول",
|
||||
"description": "أنواع الحقول المختلفة توفر لوحات مفاتيح وتحقق مناسب:<br><br><kbd>type=\"text\"</kbd> - نص عام<br><kbd>type=\"email\"</kbd> - بريد إلكتروني مع تحقق @<br><kbd>type=\"password\"</kbd> - أحرف مخفية<br><kbd>type=\"number\"</kbd> - لوحة مفاتيح رقمية<br><kbd>type=\"tel\"</kbd> - لوحة مفاتيح هاتف",
|
||||
"task": "أنشئ نموذج تسجيل دخول بحقلين:<br>1. حقل بريد: <kbd><label for=\"email\">البريد:</label></kbd> و <kbd><input type=\"email\" id=\"email\"></kbd><br>2. حقل كلمة مرور: <kbd><label for=\"password\">كلمة المرور:</label></kbd> و <kbd><input type=\"password\" id=\"password\"></kbd>",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<form>\n \n</form>",
|
||||
"solution": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\">\n</form>",
|
||||
"solution": "<form>\n <label for=\"email\">البريد:</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <label for=\"password\">كلمة المرور:</label>\n <input type=\"password\" id=\"password\" name=\"password\">\n</form>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "input[type='email']",
|
||||
"message": "Add an input with type=\"email\""
|
||||
"message": "أضف حقل بـ type=\"email\""
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "input[type='password']",
|
||||
"message": "Add an input with type=\"password\""
|
||||
"message": "أضف حقل بـ type=\"password\""
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "label", "min": 2 },
|
||||
"message": "Add labels for both inputs"
|
||||
"message": "أضف تسميات لكلا الحقلين"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "submit-button",
|
||||
"title": "Submit Button",
|
||||
"description": "Forms need a way to submit data. Use:<br><br><kbd><button type=\"submit\"></kbd> - Preferred, flexible content<br><kbd><input type=\"submit\"></kbd> - Simple text-only button<br><br>The button text should be action-oriented (e.g., <code>Sign In</code>, 'Register', 'Send').",
|
||||
"task": "Add a submit button to the form with the text <code>Sign In</code>.",
|
||||
"title": "زر الإرسال",
|
||||
"description": "النماذج تحتاج طريقة لإرسال البيانات. استخدم:<br><br><kbd><button type=\"submit\"></kbd> - مفضل، محتوى مرن<br><kbd><input type=\"submit\"></kbd> - زر نص بسيط<br><br>نص الزر يجب أن يكون موجه للعمل (مثل <code>تسجيل الدخول</code>، 'التسجيل'، 'إرسال').",
|
||||
"task": "أضف زر إرسال للنموذج بالنص <code>تسجيل الدخول</code>.",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } button { width: 100%; margin-top: 20px; padding: 10px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; } button:hover { background: #1565c0; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\">\n \n</form>",
|
||||
"solution": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\">\n \n <button type=\"submit\">Sign In</button>\n</form>",
|
||||
"initialCode": "<form>\n <label for=\"email\">البريد:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">كلمة المرور:</label>\n <input type=\"password\" id=\"password\">\n \n</form>",
|
||||
"solution": "<form>\n <label for=\"email\">البريد:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">كلمة المرور:</label>\n <input type=\"password\" id=\"password\">\n \n <button type=\"submit\">تسجيل الدخول</button>\n</form>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "button[type='submit'], input[type='submit']",
|
||||
"message": "Add a submit button to your form"
|
||||
"message": "أضف زر إرسال لنموذجك"
|
||||
},
|
||||
{
|
||||
"type": "element_text",
|
||||
"value": { "selector": "button", "text": "Sign In" },
|
||||
"message": "The button should say <kbd>Sign In</kbd>"
|
||||
"value": { "selector": "button", "text": "تسجيل الدخول" },
|
||||
"message": "يجب أن يعرض الزر <kbd>تسجيل الدخول</kbd>"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,110 +1,32 @@
|
||||
{
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-forms-validation",
|
||||
"title": "HTML Validation",
|
||||
"description": "Learn HTML5 built-in form validation attributes",
|
||||
"title": "تحقق النماذج",
|
||||
"description": "استخدم تحقق HTML5 المدمج لتجربة مستخدم أفضل",
|
||||
"mode": "html",
|
||||
"difficulty": "intermediate",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "required-fields",
|
||||
"title": "Required Fields",
|
||||
"description": "The <kbd>required</kbd> attribute prevents form submission if the field is empty.<br><br>Add it to any input that must be filled:<br><kbd><input type=\"text\" required></kbd><br><br>The browser shows a validation message automatically.",
|
||||
"task": "Make both the name and email fields required by adding the <kbd>required</kbd> attribute.",
|
||||
"title": "الحقول المطلوبة",
|
||||
"description": "سمة <kbd>required</kbd> تمنع إرسال النموذج إذا كان الحقل فارغاً. يعرض المتصفح رسالة تحقق تلقائياً - بدون JavaScript!<br><br>أضفها لأي حقل يجب ملؤه:<br><kbd><input type=\"text\" required></kbd>",
|
||||
"task": "اجعل كلا الحقلين (الاسم والبريد) مطلوبين بإضافة سمة <kbd>required</kbd> لكل حقل.",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 350px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-of-type { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } input:invalid { border-color: #d32f2f; } button { margin-top: 20px; padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; }",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 320px; } label { display: block; margin-top: 15px; margin-bottom: 5px; font-weight: 500; } label:first-of-type { margin-top: 0; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; box-sizing: border-box; } input:focus { outline: 2px solid steelblue; border-color: transparent; } button { margin-top: 20px; padding: 10px 20px; background: steelblue; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 500; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<form>\n <label for=\"name\">Name: *</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n \n <label for=\"email\">Email: *</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <button type=\"submit\">Submit</button>\n</form>",
|
||||
"solution": "<form>\n <label for=\"name\">Name: *</label>\n <input type=\"text\" id=\"name\" name=\"name\" required>\n \n <label for=\"email\">Email: *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <button type=\"submit\">Submit</button>\n</form>",
|
||||
"initialCode": "<form>\n <label for=\"name\">الاسم *</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n \n <label for=\"email\">البريد *</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <button type=\"submit\">إرسال</button>\n</form>",
|
||||
"solution": "<form>\n <label for=\"name\">الاسم *</label>\n <input type=\"text\" id=\"name\" name=\"name\" required>\n \n <label for=\"email\">البريد *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <button type=\"submit\">إرسال</button>\n</form>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "input[name='name']", "attr": "required", "value": true },
|
||||
"message": "Add the <kbd>required</kbd> attribute to the name input"
|
||||
"message": "أضف <kbd>required</kbd> لحقل الاسم"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "input[name='email']", "attr": "required", "value": true },
|
||||
"message": "Add the <kbd>required</kbd> attribute to the email input"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "input-constraints",
|
||||
"title": "Constraints",
|
||||
"description": "Control what users can enter:<br><br><kbd>minlength</kbd> / <kbd>maxlength</kbd> - Text length limits<br><kbd>min</kbd> / <kbd>max</kbd> - Number range<br><kbd>pattern</kbd> - Regex pattern matching<br><kbd>placeholder</kbd> - Hint text (not a label!)",
|
||||
"task": "Add validation to the password input:<br>1. Add <kbd>minlength=\"8\"</kbd> for minimum length<br>2. Add <kbd>maxlength=\"20\"</kbd> for maximum length<br>3. Add <kbd>placeholder=\"Enter password\"</kbd> as a hint",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 350px; } label { display: block; margin-bottom: 5px; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } input:invalid:not(:placeholder-shown) { border-color: #d32f2f; } small { display: block; font-size: 12px; color: #666; margin-top: 4px; } button { margin-top: 20px; padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<form>\n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required aria-describedby=\"password-hint\">\n <small id=\"password-hint\">Must be 8-20 characters</small>\n \n <button type=\"submit\">Create Account</button>\n</form>",
|
||||
"solution": "<form>\n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required minlength=\"8\" maxlength=\"20\" placeholder=\"Enter password\" aria-describedby=\"password-hint\">\n <small id=\"password-hint\">Must be 8-20 characters</small>\n \n <button type=\"submit\">Create Account</button>\n</form>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "input[type='password']", "attr": "minlength", "value": "8" },
|
||||
"message": "Add <kbd>minlength</kbd>=\"8\" to the password input"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "input[type='password']", "attr": "maxlength", "value": "20" },
|
||||
"message": "Add <kbd>maxlength</kbd>=\"20\" to the password input"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "input[type='password']", "attr": "placeholder", "value": null },
|
||||
"message": "Add a <kbd>placeholder</kbd> to hint what to enter"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "complete-registration",
|
||||
"title": "Full Form",
|
||||
"description": "Build a complete registration form with all validation concepts:<br><br>- Required fields marked with *<br>- Email validation (use type=\"email\")<br>- Password with length constraints<br>- Terms checkbox (required)<br>- Submit button",
|
||||
"task": "Complete the registration form. Add required attributes, proper input types, and validation constraints.",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 400px; background: #f5f5f5; padding: 25px; border-radius: 8px; } h2 { margin-top: 0; margin-bottom: 20px; } label { display: block; margin-top: 15px; margin-bottom: 5px; font-weight: 500; } input[type='text'], input[type='email'], input[type='password'] { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; font-size: 14px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; } input[type='checkbox'] { width: auto; margin-right: 8px; vertical-align: middle; } label:has(input[type='checkbox']) { display: flex; align-items: center; font-weight: normal; } button { width: 100%; margin-top: 20px; padding: 12px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: 500; } button:hover { background: #1565c0; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "<form>\n <h2>Create Account</h2>\n \n <label for=\"fullname\">Full Name *</label>\n <input type=\"text\" id=\"fullname\" name=\"fullname\">\n \n <label for=\"email\">Email *</label>\n <input id=\"email\" name=\"email\">\n \n <label for=\"password\">Password *</label>\n <input id=\"password\" name=\"password\">\n \n <label>\n <input type=\"checkbox\" id=\"terms\" name=\"terms\">\n I agree to the Terms of Service *\n </label>\n \n <button type=\"submit\">Register</button>\n</form>",
|
||||
"solution": "<form>\n <h2>Create Account</h2>\n \n <label for=\"fullname\">Full Name *</label>\n <input type=\"text\" id=\"fullname\" name=\"fullname\" required>\n \n <label for=\"email\">Email *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <label for=\"password\">Password *</label>\n <input type=\"password\" id=\"password\" name=\"password\" required minlength=\"8\">\n \n <label>\n <input type=\"checkbox\" id=\"terms\" name=\"terms\" required>\n I agree to the Terms of Service *\n </label>\n \n <button type=\"submit\">Register</button>\n</form>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "#fullname", "attr": "required", "value": true },
|
||||
"message": "Make the full name field <kbd>required</kbd>"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "#email", "attr": "type", "value": "email" },
|
||||
"message": "Set the email input <kbd>type=\"email\"</kbd>"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "#email", "attr": "required", "value": true },
|
||||
"message": "Make the email field <kbd>required</kbd>"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "#password", "attr": "type", "value": "password" },
|
||||
"message": "Set the password input <kbd>type=\"password\"</kbd>"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "#password", "attr": "required", "value": true },
|
||||
"message": "Make the password field <kbd>required</kbd>"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "#password", "attr": "minlength", "value": "8" },
|
||||
"message": "Add <kbd>minlength</kbd>=\"8\" to password"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "#terms", "attr": "required", "value": true },
|
||||
"message": "Make the terms checkbox <kbd>required</kbd>"
|
||||
"message": "أضف <kbd>required</kbd> لحقل البريد"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-details-summary",
|
||||
"title": "HTML Details & Summary",
|
||||
"description": "Create expandable content sections without JavaScript",
|
||||
"description": "أنشئ أقسام قابلة للتوسيع بدون JavaScript",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "details-summary-basic",
|
||||
"title": "First Widget",
|
||||
"description": "The <kbd><details></kbd> element creates a collapsible section. The <kbd><summary></kbd> provides the clickable label.<br><br>Click the summary to toggle the hidden content - no JavaScript needed!",
|
||||
"task": "Create a <kbd><details></kbd> element with:<br>1. A <kbd><summary></kbd> saying <code>Click to reveal</code><br>2. A <kbd><p></kbd> with the text <code>This content was hidden!</code>",
|
||||
"title": "أول عنصر تفاعلي",
|
||||
"description": "عنصر <kbd><details></kbd> ينشئ قسماً قابلاً للطي. عنصر <kbd><summary></kbd> يوفر التسمية القابلة للنقر.<br><br>انقر على الملخص لإظهار المحتوى المخفي - بدون JavaScript!",
|
||||
"task": "أنشئ عنصر <kbd><details></kbd> مع:<br>1. عنصر <kbd><summary></kbd> يقول <code>Click to reveal</code><br>2. عنصر <kbd><p></kbd> بالنص <code>This content was hidden!</code>",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; } summary { font-weight: 600; cursor: pointer; } summary:hover { color: #1976d2; } details p { margin-top: 15px; color: #666; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -21,30 +21,30 @@
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "details",
|
||||
"message": "Add a <kbd><details></kbd> element"
|
||||
"message": "أضف عنصر <kbd><details></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "summary",
|
||||
"message": "Add a <kbd><summary></kbd> inside the details"
|
||||
"message": "أضف <kbd><summary></kbd> داخل details"
|
||||
},
|
||||
{
|
||||
"type": "parent_child",
|
||||
"value": { "parent": "details", "child": "summary" },
|
||||
"message": "The <kbd><summary></kbd> must be inside <kbd><details></kbd>"
|
||||
"message": "يجب أن يكون <kbd><summary></kbd> داخل <kbd><details></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "parent_child",
|
||||
"value": { "parent": "details", "child": "p" },
|
||||
"message": "Add a <kbd><p></kbd> inside <kbd><details></kbd> for the hidden content"
|
||||
"message": "أضف <kbd><p></kbd> داخل <kbd><details></kbd> للمحتوى المخفي"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "details-open-attribute",
|
||||
"title": "Pre-expanded Details",
|
||||
"description": "By default, <kbd><details></kbd> is closed. Add the <kbd>open</kbd> attribute to show the content initially.<br><br>This is a boolean attribute - just add <kbd>open</kbd> without a value.",
|
||||
"task": "Add the <kbd>open</kbd> attribute to the <kbd><details></kbd> element to show the content by default.",
|
||||
"title": "موسع افتراضياً",
|
||||
"description": "افتراضياً، <kbd><details></kbd> مغلق. أضف سمة <kbd>open</kbd> لإظهار المحتوى في البداية.<br><br>هذه سمة منطقية - فقط أضف <kbd>open</kbd> بدون قيمة.",
|
||||
"task": "أضف سمة <kbd>open</kbd> لعنصر <kbd><details></kbd> لإظهار المحتوى افتراضياً.",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; background: #f9f9f9; } summary { font-weight: 600; cursor: pointer; } details p { margin-top: 15px; color: #666; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -55,15 +55,15 @@
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "details", "attr": "open", "value": true },
|
||||
"message": "Add the <kbd>open</kbd> attribute to <kbd><details></kbd>"
|
||||
"message": "أضف سمة <kbd>open</kbd> إلى <kbd><details></kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "faq-accordion",
|
||||
"title": "FAQ Accordion",
|
||||
"description": "Multiple <kbd><details></kbd> elements create an accordion-style FAQ. Each question can be expanded independently.<br><br><b>Pro tip:</b> Type <kbd>details*3>summary+p</kbd> and press Tab for Emmet expansion. <kbd>*3</kbd> creates 3 elements, <kbd>></kbd> nests inside, <kbd>+</kbd> adds siblings.",
|
||||
"task": "Create an FAQ section with:<br>1. An <kbd><h1></kbd> saying <code>Frequently Asked Questions</code><br>2. Three <kbd><details></kbd> elements, each with a question in <kbd><summary></kbd> and an answer in <kbd><p></kbd>",
|
||||
"title": "أكورديون FAQ",
|
||||
"description": "عناصر <kbd><details></kbd> المتعددة تنشئ FAQ بأسلوب الأكورديون. كل سؤال يمكن توسيعه بشكل مستقل.<br><br><b>نصيحة:</b> اكتب <kbd>details*3>summary+p</kbd> واضغط Tab لتوسيع Emmet. <kbd>*3</kbd> ينشئ 3 عناصر، <kbd>></kbd> يضع بالداخل، <kbd>+</kbd> يضيف أشقاء.",
|
||||
"task": "أنشئ قسم FAQ مع:<br>1. عنصر <kbd><h1></kbd> يقول <code>Frequently Asked Questions</code><br>2. ثلاثة عناصر <kbd><details></kbd>، كل واحد بسؤال في <kbd><summary></kbd> وإجابة في <kbd><p></kbd>",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; min-height: 100vh; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); display: flex; flex-direction: column; justify-content: center; padding: 40px; margin: 0; box-sizing: border-box; } h1 { font-size: 2.5rem; color: #4a4a4a; text-align: center; margin: 0 0 30px 0; } details { background: white; border-radius: 12px; margin-bottom: 15px; padding: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } summary { font-size: 1.3rem; font-weight: 600; color: #5c5c5c; cursor: pointer; list-style: none; } summary::before { content: '▸ '; color: #fcb69f; } details[open] summary::before { content: '▾ '; } details p { margin: 15px 0 0 0; color: #666; line-height: 1.6; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -74,22 +74,22 @@
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "h1",
|
||||
"message": "Add an <kbd><h1></kbd> heading for the FAQ title"
|
||||
"message": "أضف عنوان <kbd><h1></kbd> لعنوان FAQ"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "details", "min": 3 },
|
||||
"message": "Create at least 3 <kbd><details></kbd> elements for the FAQ"
|
||||
"message": "أنشئ على الأقل 3 عناصر <kbd><details></kbd> للـ FAQ"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "summary", "min": 3 },
|
||||
"message": "Each <kbd><details></kbd> needs a <kbd><summary></kbd> for the question"
|
||||
"message": "كل <kbd><details></kbd> يحتاج <kbd><summary></kbd> للسؤال"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "details p", "min": 3 },
|
||||
"message": "Each <kbd><details></kbd> needs a <kbd><p></kbd> for the answer"
|
||||
"message": "كل <kbd><details></kbd> يحتاج <kbd><p></kbd> للإجابة"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-progress-meter",
|
||||
"title": "HTML Progress & Meter",
|
||||
"description": "Display completion status and scalar measurements natively",
|
||||
"description": "اعرض حالة الإكمال والقياسات بشكل أصلي",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "progress-basic",
|
||||
"title": "Progress Bars",
|
||||
"description": "The <kbd><progress></kbd> element shows task completion. Use <kbd>value</kbd> for current progress and <kbd>max</kbd> for the total.<br><br><b>Note:</b> This is not a self-closing tag! Write <kbd><progress>...</progress></kbd> with fallback text inside for older browsers.",
|
||||
"task": "Create a progress bar showing 70% completion:<br>1. Add a <kbd><label></kbd> saying <code>Download:</code><br>2. Add a <kbd><progress></kbd> with <kbd>value=\"70\"</kbd> and <kbd>max=\"100\"</kbd>",
|
||||
"title": "أشرطة التقدم",
|
||||
"description": "عنصر <kbd><progress></kbd> يُظهر إكمال المهمة. استخدم <kbd>value</kbd> للتقدم الحالي و <kbd>max</kbd> للإجمالي.<br><br><b>ملاحظة:</b> هذا ليس وسماً ذاتي الإغلاق! اكتب <kbd><progress>...</progress></kbd> مع نص بديل بالداخل للمتصفحات القديمة.",
|
||||
"task": "أنشئ شريط تقدم يُظهر 70% إكمال:<br>1. أضف <kbd><label></kbd> يقول <code>Download:</code><br>2. أضف <kbd><progress></kbd> مع <kbd>value=\"70\"</kbd> و <kbd>max=\"100\"</kbd>",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } progress { width: 100%; height: 20px; border-radius: 10px; } progress::-webkit-progress-bar { background: #e0e0e0; border-radius: 10px; } progress::-webkit-progress-value { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; } progress::-moz-progress-bar { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -21,30 +21,30 @@
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "progress",
|
||||
"message": "Add a <kbd><progress></kbd> element"
|
||||
"message": "أضف عنصر <kbd><progress></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "progress", "attr": "value", "value": "70" },
|
||||
"message": "Set <kbd>value=</kbd>\"70\" on the progress element"
|
||||
"message": "عيّن <kbd>value=</kbd>\"70\" في عنصر progress"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "progress", "attr": "max", "value": "100" },
|
||||
"message": "Set <kbd>max=</kbd>\"100\" on the progress element"
|
||||
"message": "عيّن <kbd>max=</kbd>\"100\" في عنصر progress"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "label",
|
||||
"message": "Add a <kbd><label></kbd> for the progress bar"
|
||||
"message": "أضف <kbd><label></kbd> لشريط التقدم"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "progress-indeterminate",
|
||||
"title": "Indeterminate Progress",
|
||||
"description": "When progress is unknown (like loading), omit the <kbd>value</kbd> attribute. This creates an animated indeterminate state.<br><br>Useful for network requests or processes with unknown duration.",
|
||||
"task": "Create a loading indicator:<br>1. Add a <kbd><p></kbd> saying <code>Loading...</code><br>2. Add a <kbd><progress></kbd> without a value attribute",
|
||||
"title": "تقدم غير محدد",
|
||||
"description": "عندما يكون التقدم غير معروف (مثل التحميل)، احذف سمة <kbd>value</kbd>. هذا ينشئ حالة متحركة غير محددة.<br><br>مفيد لطلبات الشبكة أو العمليات ذات المدة غير المعروفة.",
|
||||
"task": "أنشئ مؤشر تحميل:<br>1. أضف <kbd><p></kbd> يقول <code>Loading...</code><br>2. أضف <kbd><progress></kbd> بدون سمة value",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } p { margin-bottom: 10px; color: #666; } progress { width: 100%; height: 8px; border-radius: 4px; } progress::-webkit-progress-bar { background: #e0e0e0; border-radius: 4px; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -55,20 +55,20 @@
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "progress",
|
||||
"message": "Add a <kbd><progress></kbd> element"
|
||||
"message": "أضف عنصر <kbd><progress></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "p",
|
||||
"message": "Add a <kbd><p></kbd> with loading text"
|
||||
"message": "أضف <kbd><p></kbd> مع نص التحميل"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "meter-gauge",
|
||||
"title": "Meter Gauges",
|
||||
"description": "The <kbd><meter></kbd> element displays a scalar value within a range. Use it for measurements like disk space, battery, or ratings.<br><br>Set <kbd>low</kbd>, <kbd>high</kbd>, and <kbd>optimum</kbd> to define good/bad ranges - the browser colors it accordingly!",
|
||||
"task": "Create a battery level meter:<br>1. Add a <kbd><label></kbd> saying <code>Battery:</code><br>2. Add a <kbd><meter></kbd> with:<br> - <kbd>value=\"0.8\"</kbd><br> - <kbd>min=\"0\"</kbd> and <kbd>max=\"1\"</kbd><br> - <kbd>low=\"0.2\"</kbd> and <kbd>high=\"0.8\"</kbd><br> - <kbd>optimum=\"1\"</kbd>",
|
||||
"title": "مقاييس meter",
|
||||
"description": "عنصر <kbd><meter></kbd> يعرض قيمة قياسية ضمن نطاق. استخدمه للقياسات مثل مساحة القرص، البطارية، أو التقييمات.<br><br>عيّن <kbd>low</kbd> و <kbd>high</kbd> و <kbd>optimum</kbd> لتحديد النطاقات الجيدة/السيئة - المتصفح يلونها وفقاً لذلك!",
|
||||
"task": "أنشئ مقياس مستوى البطارية:<br>1. أضف <kbd><label></kbd> يقول <code>Battery:</code><br>2. أضف <kbd><meter></kbd> مع:<br> - <kbd>value=\"0.8\"</kbd><br> - <kbd>min=\"0\"</kbd> و <kbd>max=\"1\"</kbd><br> - <kbd>low=\"0.2\"</kbd> و <kbd>high=\"0.8\"</kbd><br> - <kbd>optimum=\"1\"</kbd>",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -79,22 +79,42 @@
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "meter",
|
||||
"message": "Add a <kbd><meter></kbd> element"
|
||||
"message": "أضف عنصر <kbd><meter></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "meter", "attr": "value", "value": "0.8" },
|
||||
"message": "Set <kbd>value=</kbd>\"0.8\" on the meter"
|
||||
"message": "عيّن <kbd>value=</kbd>\"0.8\" في meter"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "meter", "attr": "min", "value": "0" },
|
||||
"message": "عيّن <kbd>min=</kbd>\"0\" في meter"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "meter", "attr": "max", "value": "1" },
|
||||
"message": "عيّن <kbd>max=</kbd>\"1\" في meter"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "meter", "attr": "low", "value": "0.2" },
|
||||
"message": "Set <kbd>low=</kbd>\"0.2\" to define the low threshold"
|
||||
"message": "عيّن <kbd>low=</kbd>\"0.2\" لتحديد العتبة المنخفضة"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "meter", "attr": "high", "value": "0.8" },
|
||||
"message": "عيّن <kbd>high=</kbd>\"0.8\" لتحديد العتبة العالية"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "meter", "attr": "optimum", "value": "1" },
|
||||
"message": "عيّن <kbd>optimum=</kbd>\"1\" للإشارة إلى القيمة المثلى"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "label",
|
||||
"message": "Add a <kbd><label></kbd> for the meter"
|
||||
"message": "أضف <kbd><label></kbd> للـ meter"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-datalist",
|
||||
"title": "Datalist",
|
||||
"description": "Provide suggestions for text inputs without JavaScript",
|
||||
"title": "قائمة البيانات",
|
||||
"description": "قدم اقتراحات لحقول النص بدون JavaScript",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "datalist-basic",
|
||||
"title": "Input with Suggestions",
|
||||
"description": "The <kbd><datalist></kbd> element provides autocomplete suggestions for inputs. Connect it using the <kbd>list</kbd> attribute on the input matching the datalist's <kbd>id</kbd>.<br><br>Users can still type freely - suggestions are just helpers!",
|
||||
"task": "Create a browser selector:<br>1. Add a <kbd><label></kbd> saying <code>Browser:</code><br>2. Add an <kbd><input></kbd> with <kbd>list=\"browsers\"</kbd><br>3. Add a <kbd><datalist id=\"browsers\"></kbd> with options for Chrome, Firefox, and Safari",
|
||||
"title": "حقل مع اقتراحات",
|
||||
"description": "عنصر <kbd><datalist></kbd> يوفر اقتراحات الإكمال التلقائي للحقول. اربطه باستخدام سمة <kbd>list</kbd> في الحقل بما يتطابق مع <kbd>id</kbd> قائمة البيانات.<br><br>يمكن للمستخدمين الكتابة بحرية - الاقتراحات مجرد مساعدات!",
|
||||
"task": "أنشئ محدد متصفح:<br>1. أضف <kbd><label></kbd> يقول <code>المتصفح:</code><br>2. أضف <kbd><input></kbd> مع <kbd>list=\"browsers\"</kbd><br>3. أضف <kbd><datalist id=\"browsers\"></kbd> مع خيارات Chrome و Firefox و Safari",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; font-size: 16px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -21,30 +21,30 @@
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "datalist",
|
||||
"message": "Add a <kbd><datalist></kbd> element"
|
||||
"message": "أضف عنصر <kbd><datalist></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "input", "attr": "list", "value": "browsers" },
|
||||
"message": "Connect the input to datalist using <kbd>list=</kbd>\"browsers\""
|
||||
"message": "اربط الحقل بقائمة البيانات باستخدام <kbd>list=</kbd>\"browsers\""
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "option", "min": 3 },
|
||||
"message": "Add at least 3 <kbd><option></kbd> elements inside <kbd><datalist></kbd>"
|
||||
"message": "أضف على الأقل 3 عناصر <kbd><option></kbd> داخل <kbd><datalist></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "label",
|
||||
"message": "Add a <kbd><label></kbd> for the input"
|
||||
"message": "أضف <kbd><label></kbd> للحقل"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "datalist-countries",
|
||||
"title": "Country Selector",
|
||||
"description": "Datalists work great for long lists like countries. Users can type to filter suggestions instantly.<br><br>The <kbd>value</kbd> attribute is what gets entered, and you can add display text after it.",
|
||||
"task": "Create a country input:<br>1. Add a <kbd><label></kbd> saying <code>Country:</code><br>2. Add an <kbd><input></kbd> with <kbd>list=\"countries\"</kbd><br>3. Add a <kbd><datalist id=\"countries\"></kbd> with at least 4 country options",
|
||||
"title": "محدد الدول",
|
||||
"description": "قوائم البيانات تعمل بشكل رائع للقوائم الطويلة مثل الدول. يمكن للمستخدمين الكتابة لتصفية الاقتراحات فوراً.<br><br>سمة <kbd>value</kbd> هي ما يتم إدخاله، ويمكنك إضافة نص عرض بعدها.",
|
||||
"task": "أنشئ حقل دولة:<br>1. أضف <kbd><label></kbd> يقول <code>الدولة:</code><br>2. أضف <kbd><input></kbd> مع <kbd>list=\"countries\"</kbd><br>3. أضف <kbd><datalist id=\"countries\"></kbd> مع 4 خيارات دول على الأقل",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 30px; background: linear-gradient(135deg, #e0f7fa 0%, #b2ebf2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } label { display: block; margin-bottom: 10px; font-weight: 600; color: #00695c; } input { width: 100%; padding: 12px 15px; border: 2px solid #26a69a; border-radius: 8px; font-size: 16px; background: white; } input:focus { outline: none; border-color: #00695c; box-shadow: 0 0 0 3px rgba(38,166,154,0.2); }",
|
||||
"sandboxCSS": "",
|
||||
@@ -55,22 +55,22 @@
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "datalist",
|
||||
"message": "Add a <kbd><datalist></kbd> element"
|
||||
"message": "أضف عنصر <kbd><datalist></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "datalist", "attr": "id", "value": "countries" },
|
||||
"message": "Set <kbd>id=</kbd>\"countries\" on the datalist"
|
||||
"message": "عيّن <kbd>id=</kbd>\"countries\" في قائمة البيانات"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "input", "attr": "list", "value": "countries" },
|
||||
"message": "Connect the input using <kbd>list=</kbd>\"countries\""
|
||||
"message": "اربط الحقل باستخدام <kbd>list=</kbd>\"countries\""
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "option", "min": 4 },
|
||||
"message": "Add at least 4 country options"
|
||||
"message": "أضف على الأقل 4 خيارات دول"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-dialog",
|
||||
"title": "Dialogs",
|
||||
"description": "Create modal dialogs without JavaScript libraries",
|
||||
"title": "مربعات الحوار",
|
||||
"description": "أنشئ مربعات حوار نموذجية بدون مكتبات JavaScript",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "dialog-basic",
|
||||
"title": "Open Dialog",
|
||||
"description": "The <kbd><dialog></kbd> element creates a native modal. Add the <kbd>open</kbd> attribute to show it.<br><br>Use <kbd><form method=\"dialog\"></kbd> inside to close it when the form submits - no JavaScript needed!",
|
||||
"task": "Create a dialog with:<br>1. The <kbd>open</kbd> attribute to show it<br>2. An <kbd><h2></kbd> saying <code>Welcome!</code><br>3. A <kbd><p></kbd> with a greeting message<br>4. A <kbd><form method=\"dialog\"></kbd> with a close button",
|
||||
"title": "فتح مربع الحوار",
|
||||
"description": "عنصر <kbd><dialog></kbd> ينشئ نافذة نموذجية أصلية. أضف سمة <kbd>open</kbd> لعرضها.<br><br>استخدم <kbd><form method=\"dialog\"></kbd> بداخلها لإغلاقها عند إرسال النموذج - بدون JavaScript!",
|
||||
"task": "أنشئ مربع حوار مع:<br>1. سمة <kbd>open</kbd> لعرضه<br>2. عنصر <kbd><h2></kbd> يقول <code>أهلاً!</code><br>3. عنصر <kbd><p></kbd> مع رسالة ترحيب<br>4. عنصر <kbd><form method=\"dialog\"></kbd> مع زر إغلاق",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: #f5f5f5; } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 400px; } dialog::backdrop { background: rgba(0,0,0,0.5); } dialog h2 { margin: 0 0 15px 0; color: #1976d2; } dialog p { color: #666; margin: 0 0 20px 0; } dialog button { background: #1976d2; color: white; border: none; padding: 10px 25px; border-radius: 6px; cursor: pointer; font-size: 16px; } dialog button:hover { background: #1565c0; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -21,35 +21,35 @@
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "dialog",
|
||||
"message": "Add a <kbd><dialog></kbd> element"
|
||||
"message": "أضف عنصر <kbd><dialog></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "dialog", "attr": "open", "value": true },
|
||||
"message": "Add the <kbd>open</kbd> attribute to show the dialog"
|
||||
"message": "أضف سمة <kbd>open</kbd> لعرض مربع الحوار"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "dialog h2",
|
||||
"message": "Add an <kbd><h2></kbd> heading inside the dialog"
|
||||
"message": "أضف عنوان <kbd><h2></kbd> داخل مربع الحوار"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "form[method='dialog']",
|
||||
"message": "Add a <kbd><form method=\"dialog\"></kbd> for closing"
|
||||
"message": "أضف <kbd><form method=\"dialog\"></kbd> للإغلاق"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "dialog button",
|
||||
"message": "Add a close button inside the form"
|
||||
"message": "أضف زر إغلاق داخل النموذج"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "dialog-form",
|
||||
"title": "Dialog + Form",
|
||||
"description": "Dialogs can contain full forms. The <kbd>method=\"dialog\"</kbd> makes the form close the dialog on submit instead of sending data.<br><br>This pattern is perfect for confirmation dialogs, quick inputs, or settings panels.",
|
||||
"task": "Create a confirmation dialog:<br>1. Add <kbd>open</kbd> to show it<br>2. An <kbd><h2></kbd> saying <code>Confirm Delete</code><br>3. A <kbd><p></kbd> asking <code>Are you sure?</code><br>4. A <kbd><form method=\"dialog\"></kbd> with Cancel and Delete buttons",
|
||||
"title": "حوار + نموذج",
|
||||
"description": "مربعات الحوار يمكن أن تحتوي على نماذج كاملة. <kbd>method=\"dialog\"</kbd> يجعل النموذج يغلق مربع الحوار عند الإرسال بدلاً من إرسال البيانات.<br><br>هذا النمط مثالي لحوارات التأكيد، المدخلات السريعة، أو لوحات الإعدادات.",
|
||||
"task": "أنشئ مربع حوار تأكيد:<br>1. أضف <kbd>open</kbd> لعرضه<br>2. عنصر <kbd><h2></kbd> يقول <code>تأكيد الحذف</code><br>3. عنصر <kbd><p></kbd> يسأل <code>هل أنت متأكد؟</code><br>4. عنصر <kbd><form method=\"dialog\"></kbd> مع أزرار إلغاء وحذف",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: linear-gradient(135deg, #fce4ec 0%, #f8bbd9 100%); } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 350px; text-align: center; } dialog h2 { margin: 0 0 10px 0; color: #c62828; } dialog p { color: #666; margin: 0 0 20px 0; } dialog form { display: flex; gap: 10px; justify-content: center; } dialog button { padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; border: none; } dialog button:first-child { background: #e0e0e0; color: #333; } dialog button:last-child { background: #c62828; color: white; } dialog button:hover { opacity: 0.9; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -60,22 +60,22 @@
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "dialog[open]",
|
||||
"message": "Add a <kbd><dialog></kbd> with the open attribute"
|
||||
"message": "أضف <kbd><dialog></kbd> مع سمة open"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "dialog h2",
|
||||
"message": "Add a heading to the dialog"
|
||||
"message": "أضف عنواناً لمربع الحوار"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "form[method='dialog']",
|
||||
"message": "Add a <kbd><form method=\"dialog\"></kbd> for the buttons"
|
||||
"message": "أضف <kbd><form method=\"dialog\"></kbd> للأزرار"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "dialog button", "min": 2 },
|
||||
"message": "Add at least 2 buttons (Cancel and Confirm)"
|
||||
"message": "أضف على الأقل زرين (إلغاء وتأكيد)"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,16 +1,16 @@
|
||||
{
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-forms-fieldset",
|
||||
"title": "Fieldsets",
|
||||
"description": "Group form controls with fieldset and legend elements",
|
||||
"title": "مجموعة الحقول",
|
||||
"description": "جمّع عناصر التحكم في النموذج باستخدام fieldset و legend",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "fieldset-basic",
|
||||
"title": "Grouping with Fieldset",
|
||||
"description": "The <kbd><fieldset></kbd> element groups related form controls together. Add a <kbd><legend></kbd> as the first child to give the group a title.<br><br>This helps with accessibility and visual organization of complex forms.",
|
||||
"task": "Create a form with a fieldset:<br>1. A <kbd><form></kbd> element<br>2. A <kbd><fieldset></kbd> inside<br>3. A <kbd><legend></kbd> saying <code>Personal Info</code><br>4. Two labeled inputs for name and email",
|
||||
"title": "التجميع مع Fieldset",
|
||||
"description": "عنصر <kbd><fieldset></kbd> يجمع عناصر التحكم المرتبطة في النموذج معاً. أضف <kbd><legend></kbd> كأول عنصر فرعي لإعطاء المجموعة عنواناً.<br><br>هذا يساعد في إمكانية الوصول والتنظيم البصري للنماذج المعقدة.",
|
||||
"task": "أنشئ نموذجاً مع fieldset:<br>1. عنصر <kbd><form></kbd><br>2. عنصر <kbd><fieldset></kbd> بداخله<br>3. عنصر <kbd><legend></kbd> يقول <code>المعلومات الشخصية</code><br>4. حقلين مُعنونين للاسم والبريد الإلكتروني",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f0f4f8; } form { max-width: 400px; } fieldset { border: 2px solid #3498db; border-radius: 10px; padding: 20px; background: white; } legend { color: #3498db; font-weight: 600; padding: 0 10px; font-size: 1.1rem; } label { display: block; margin: 15px 0 5px; color: #333; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; } input:focus { outline: 2px solid #3498db; border-color: transparent; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -21,35 +21,35 @@
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "form",
|
||||
"message": "Add a <kbd><form></kbd> element"
|
||||
"message": "أضف عنصر <kbd><form></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "fieldset",
|
||||
"message": "Add a <kbd><fieldset></kbd> inside the form"
|
||||
"message": "أضف <kbd><fieldset></kbd> داخل النموذج"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "legend",
|
||||
"message": "Add a <kbd><legend></kbd> to title your fieldset"
|
||||
"message": "أضف <kbd><legend></kbd> لعنونة مجموعة الحقول"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "label", "min": 2 },
|
||||
"message": "Add at least 2 labels"
|
||||
"message": "أضف على الأقل عنوانين"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "input", "min": 2 },
|
||||
"message": "Add at least 2 input fields"
|
||||
"message": "أضف على الأقل حقلي إدخال"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "fieldset-textarea",
|
||||
"title": "Adding Textarea",
|
||||
"description": "The <kbd><textarea></kbd> element creates a multi-line text input, perfect for longer content like messages or descriptions.<br><br>Use <kbd>rows</kbd> and <kbd>cols</kbd> attributes to set default size.",
|
||||
"task": "Create a contact form:<br>1. A <kbd><fieldset></kbd> with <kbd><legend></kbd> <code>Contact Us</code><br>2. A labeled <kbd><input></kbd> for email<br>3. A labeled <kbd><textarea></kbd> for the message<br>4. A submit <kbd><button></kbd>",
|
||||
"title": "إضافة Textarea",
|
||||
"description": "عنصر <kbd><textarea></kbd> ينشئ حقل إدخال نص متعدد الأسطر، مثالي للمحتوى الطويل مثل الرسائل أو الأوصاف.<br><br>استخدم سمات <kbd>rows</kbd> و <kbd>cols</kbd> لتعيين الحجم الافتراضي.",
|
||||
"task": "أنشئ نموذج اتصال:<br>1. عنصر <kbd><fieldset></kbd> مع <kbd><legend></kbd> <code>تواصل معنا</code><br>2. حقل <kbd><input></kbd> مُعنون للبريد الإلكتروني<br>3. حقل <kbd><textarea></kbd> مُعنون للرسالة<br>4. زر <kbd><button></kbd> للإرسال",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } form { max-width: 450px; margin: 0 auto; } fieldset { border: none; border-radius: 15px; padding: 30px; background: white; box-shadow: 0 10px 40px rgba(0,0,0,0.2); } legend { color: #667eea; font-weight: 700; padding: 0; font-size: 1.5rem; margin-bottom: 10px; } label { display: block; margin: 20px 0 8px; color: #333; font-weight: 500; } input, textarea { width: 100%; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: none; border-color: #667eea; } textarea { resize: vertical; min-height: 100px; } button { margin-top: 20px; width: 100%; padding: 14px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; } button:hover { opacity: 0.9; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -60,35 +60,35 @@
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "fieldset",
|
||||
"message": "Add a <kbd><fieldset></kbd> element"
|
||||
"message": "أضف عنصر <kbd><fieldset></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "legend",
|
||||
"message": "Add a <kbd><legend></kbd> element"
|
||||
"message": "أضف عنصر <kbd><legend></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "textarea",
|
||||
"message": "Add a <kbd><textarea></kbd> for the message"
|
||||
"message": "أضف <kbd><textarea></kbd> للرسالة"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "button",
|
||||
"message": "Add a submit button"
|
||||
"message": "أضف زر إرسال"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "input",
|
||||
"message": "Add an input field for email"
|
||||
"message": "أضف حقل للبريد الإلكتروني"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "fieldset-multiple",
|
||||
"title": "Multiple Fieldsets",
|
||||
"description": "Complex forms can use multiple <kbd><fieldset></kbd> elements to organize different sections.<br><br>This improves usability for long forms like registration or checkout pages.",
|
||||
"task": "Create a registration form with 2 fieldsets:<br>1. <code>Account Info</code> with username and password inputs<br>2. <code>Preferences</code> with a textarea for bio<br>3. A submit button outside the fieldsets",
|
||||
"title": "مجموعات حقول متعددة",
|
||||
"description": "النماذج المعقدة يمكنها استخدام عناصر <kbd><fieldset></kbd> متعددة لتنظيم أقسام مختلفة.<br><br>هذا يحسن قابلية الاستخدام للنماذج الطويلة مثل التسجيل أو الدفع.",
|
||||
"task": "أنشئ نموذج تسجيل مع 2 مجموعات حقول:<br>1. <code>معلومات الحساب</code> مع حقول اسم المستخدم وكلمة المرور<br>2. <code>التفضيلات</code> مع textarea للسيرة الذاتية<br>3. زر إرسال خارج مجموعات الحقول",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } form { max-width: 500px; } fieldset { border: 1px solid #ddd; border-radius: 10px; padding: 20px; margin-bottom: 20px; background: white; } legend { color: #2c3e50; font-weight: 600; padding: 0 10px; } label { display: block; margin: 15px 0 5px; color: #555; } input, textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: 2px solid #3498db; border-color: transparent; } textarea { resize: vertical; min-height: 80px; } button { width: 100%; padding: 14px; background: #2c3e50; color: white; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; } button:hover { background: #34495e; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -99,27 +99,27 @@
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "fieldset", "min": 2 },
|
||||
"message": "Create at least 2 fieldsets"
|
||||
"message": "أنشئ على الأقل 2 مجموعات حقول"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "legend", "min": 2 },
|
||||
"message": "Add a legend to each fieldset"
|
||||
"message": "أضف legend لكل مجموعة حقول"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "textarea",
|
||||
"message": "Add a textarea for the bio"
|
||||
"message": "أضف textarea للسيرة الذاتية"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "button",
|
||||
"message": "Add a submit button"
|
||||
"message": "أضف زر إرسال"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "input", "min": 2 },
|
||||
"message": "Add at least 2 input fields"
|
||||
"message": "أضف على الأقل حقلي إدخال"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -1,125 +1,42 @@
|
||||
{
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-tables",
|
||||
"title": "HTML Tables",
|
||||
"description": "Create structured data tables with headers and captions",
|
||||
"title": "جداول HTML",
|
||||
"description": "أنشئ جداول بيانات منظمة بترميز دلالي",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "table-basic",
|
||||
"title": "Basic Table Structure",
|
||||
"description": "Tables use <kbd><table></kbd> with <kbd><tr></kbd> for rows. Inside rows, use <kbd><th></kbd> for headers and <kbd><td></kbd> for data cells.<br><br>The <kbd><caption></kbd> element provides an accessible title for the table.",
|
||||
"task": "Create a simple table with:<br>1. A <kbd><caption></kbd> saying <code>Fruit Prices</code><br>2. A header row with <code>Fruit</code> and <code>Price</code> columns<br>3. At least 2 data rows",
|
||||
"title": "جداول البيانات",
|
||||
"description": "الجداول تعرض البيانات المنظمة في صفوف وأعمدة. استخدم <kbd><table></kbd> كحاوية، <kbd><tr></kbd> للصفوف، <kbd><th></kbd> لخلايا الرأس و <kbd><td></kbd> لخلايا البيانات.<br><br>أضف <kbd><caption></kbd> لعنوان قابل للوصول يصف محتوى الجدول.",
|
||||
"task": "أنشئ جدول أسعار:<br>1. عنصر <kbd><caption></kbd> يقول <code>Pricing</code><br>2. صف رأس مع <code>Plan</code> و <code>Price</code><br>3. صفين بيانات لـ Basic ($9) و Pro ($29)",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px; font-weight: 600; font-size: 1.2rem; color: #333; background: #f8f9fa; } th, td { padding: 12px 20px; text-align: left; border-bottom: 1px solid #eee; } th { background: #3498db; color: white; font-weight: 500; } tr:hover { background: #f8f9fa; } tr:last-child td { border-bottom: none; }",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; } table { border-collapse: collapse; width: 100%; max-width: 350px; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 16px; font-weight: 600; font-size: 1.1rem; color: #333; background: #f8f9fa; } th, td { padding: 14px 20px; text-align: left; border-bottom: 1px solid #eee; } th { background: steelblue; color: white; font-weight: 500; } tr:last-child td { border-bottom: none; } tr:hover td { background: #f8f9fa; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "",
|
||||
"solution": "<table>\n <caption>Fruit Prices</caption>\n <tr>\n <th>Fruit</th>\n <th>Price</th>\n </tr>\n <tr>\n <td>Apple</td>\n <td>$1.50</td>\n </tr>\n <tr>\n <td>Banana</td>\n <td>$0.75</td>\n </tr>\n</table>",
|
||||
"solution": "<table>\n <caption>Pricing</caption>\n <tr>\n <th>Plan</th>\n <th>Price</th>\n </tr>\n <tr>\n <td>Basic</td>\n <td>$9</td>\n </tr>\n <tr>\n <td>Pro</td>\n <td>$29</td>\n </tr>\n</table>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "table",
|
||||
"message": "Add a <kbd><table></kbd> element"
|
||||
"message": "أضف عنصر <kbd><table></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "caption",
|
||||
"message": "Add a <kbd><caption></kbd> for the table title"
|
||||
"message": "أضف <kbd><caption></kbd> لعنوان الجدول"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "th", "min": 2 },
|
||||
"message": "Add at least 2 header cells (th)"
|
||||
"message": "أضف خلايا رأس (<kbd><th></kbd>) لـ Plan و Price"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "tr", "min": 3 },
|
||||
"message": "Add at least 3 rows (1 header + 2 data rows)"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "table-thead-tbody",
|
||||
"title": "Table Head & Body",
|
||||
"description": "Use <kbd><thead></kbd> to group header rows and <kbd><tbody></kbd> to group data rows. This helps browsers and assistive technology understand the table structure.<br><br>You can also use <kbd><tfoot></kbd> for footer rows like totals.",
|
||||
"task": "Create a structured table:<br>1. A <kbd><caption></kbd> with <code>Monthly Sales</code><br>2. A <kbd><thead></kbd> with Month and Revenue headers<br>3. A <kbd><tbody></kbd> with at least 2 data rows",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } table { border-collapse: collapse; width: 100%; max-width: 450px; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.2); } caption { padding: 20px; font-weight: 700; font-size: 1.3rem; color: white; background: transparent; text-shadow: 0 2px 4px rgba(0,0,0,0.2); caption-side: top; } thead { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } th { padding: 15px 20px; text-align: left; color: white; font-weight: 500; } tbody tr { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } td { padding: 15px 20px; color: #333; } tbody tr:last-child { border-bottom: none; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "",
|
||||
"solution": "<table>\n <caption>Monthly Sales</caption>\n <thead>\n <tr>\n <th>Month</th>\n <th>Revenue</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>January</td>\n <td>$12,500</td>\n </tr>\n <tr>\n <td>February</td>\n <td>$14,200</td>\n </tr>\n </tbody>\n</table>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "table",
|
||||
"message": "Add a <kbd><table></kbd> element"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "caption",
|
||||
"message": "Add a <kbd><caption></kbd> element"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "thead",
|
||||
"message": "Add a <kbd><thead></kbd> for the header section"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "tbody",
|
||||
"message": "Add a <kbd><tbody></kbd> for the data rows"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "tbody tr", "min": 2 },
|
||||
"message": "Add at least 2 data rows in tbody"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "table-complete",
|
||||
"title": "Complete Table with Footer",
|
||||
"description": "Add <kbd><tfoot></kbd> to create a footer section for totals or summary data. The footer stays at the bottom even if tbody has many rows.<br><br>Combine all sections for a fully structured, accessible table.",
|
||||
"task": "Create a complete table:<br>1. A <kbd><caption></kbd> with <code>Order Summary</code><br>2. A <kbd><thead></kbd> with Item and Price headers<br>3. A <kbd><tbody></kbd> with 2 items<br>4. A <kbd><tfoot></kbd> with a Total row",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px 20px; font-weight: 600; font-size: 1.1rem; color: #333; text-align: left; background: #f8f9fa; border-bottom: 2px solid #eee; } th, td { padding: 12px 20px; text-align: left; } thead th { background: #2c3e50; color: white; } tbody td { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } tfoot { background: #ecf0f1; font-weight: 600; } tfoot td { border-top: 2px solid #2c3e50; color: #2c3e50; }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "",
|
||||
"solution": "<table>\n <caption>Order Summary</caption>\n <thead>\n <tr>\n <th>Item</th>\n <th>Price</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Widget</td>\n <td>$25.00</td>\n </tr>\n <tr>\n <td>Gadget</td>\n <td>$35.00</td>\n </tr>\n </tbody>\n <tfoot>\n <tr>\n <td>Total</td>\n <td>$60.00</td>\n </tr>\n </tfoot>\n</table>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "table",
|
||||
"message": "Add a <kbd><table></kbd> element"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "caption",
|
||||
"message": "Add a <kbd><caption></kbd> element"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "thead",
|
||||
"message": "Add a <kbd><thead></kbd> section"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "tbody",
|
||||
"message": "Add a <kbd><tbody></kbd> section"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "tfoot",
|
||||
"message": "Add a <kbd><tfoot></kbd> section for the total"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "tbody tr", "min": 2 },
|
||||
"message": "Add at least 2 item rows in tbody"
|
||||
"message": "أضف 3 صفوف (1 رأس + 2 صفوف بيانات)"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,15 +2,15 @@
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-marquee",
|
||||
"title": "HTML Marquee",
|
||||
"description": "Create scrolling text with the classic (deprecated but fun!) marquee element",
|
||||
"description": "أنشئ نصاً متحركاً باستخدام عنصر marquee الكلاسيكي (قديم لكنه ممتع!)",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "marquee-basic",
|
||||
"title": "Scrolling Text",
|
||||
"description": "The <kbd><marquee></kbd> element creates scrolling text - a classic from the early web! While deprecated, it still works in most browsers.<br><br>Note: For production, use CSS animations instead. But for learning and fun, marquee is great!",
|
||||
"task": "Create a simple marquee:<br>1. Add a <kbd><marquee></kbd> element<br>2. Put some text inside like <code>Welcome to my website!</code>",
|
||||
"title": "النص المتحرك",
|
||||
"description": "عنصر <kbd><marquee></kbd> ينشئ نصاً متحركاً - كلاسيكي من الويب القديم! رغم أنه قديم، لا يزال يعمل في معظم المتصفحات.<br><br>ملاحظة: للإنتاج، استخدم حركات CSS بدلاً منه. لكن للتعلم والمرح، marquee رائع!",
|
||||
"task": "أنشئ marquee بسيط:<br>1. أضف عنصر <kbd><marquee></kbd><br>2. ضع نصاً بداخله مثل <code>مرحباً بك في موقعي!</code>",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2rem; color: #00ff00; text-shadow: 0 0 10px #00ff00, 0 0 20px #00ff00; font-family: 'Courier New', monospace; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -21,15 +21,15 @@
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "marquee",
|
||||
"message": "Add a <kbd><marquee></kbd> element"
|
||||
"message": "أضف عنصر <kbd><marquee></kbd>"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "marquee-direction",
|
||||
"title": "Direction & Behavior",
|
||||
"description": "Control the marquee with attributes:<br>• <kbd>direction</kbd>: left, right, up, down<br>• <kbd>behavior</kbd>: scroll (default), slide (stops at edge), alternate (bounces)<br>• <kbd>scrollamount</kbd>: speed (default is 6)",
|
||||
"task": "Create a bouncing marquee:<br>1. Add a <kbd><marquee></kbd> element<br>2. Set <kbd>behavior=\"alternate\"</kbd> to make it bounce<br>3. Add some fun text",
|
||||
"title": "الاتجاه والسلوك",
|
||||
"description": "تحكم في marquee باستخدام السمات:<br>• <kbd>direction</kbd>: left، right، up، down<br>• <kbd>behavior</kbd>: scroll (افتراضي)، slide (يتوقف عند الحافة)، alternate (يرتد)<br>• <kbd>scrollamount</kbd>: السرعة (الافتراضي 6)",
|
||||
"task": "أنشئ marquee مرتداً:<br>1. أضف عنصر <kbd><marquee></kbd><br>2. اضبط <kbd>behavior=\"alternate\"</kbd> ليرتد<br>3. أضف نصاً ممتعاً",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2.5rem; color: white; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); font-weight: bold; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -40,20 +40,20 @@
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "marquee",
|
||||
"message": "Add a <kbd><marquee></kbd> element"
|
||||
"message": "أضف عنصر <kbd><marquee></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "marquee", "attr": "behavior", "value": "alternate" },
|
||||
"message": "Add <kbd>behavior=</kbd>\"alternate\" to make it bounce"
|
||||
"message": "أضف <kbd>behavior=</kbd>\"alternate\" ليرتد"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "marquee-retro",
|
||||
"title": "Retro News Ticker",
|
||||
"description": "Combine multiple marquee attributes for a classic news ticker effect. You can even put multiple elements inside!<br><br>Remember: This is deprecated HTML. Modern sites use CSS animations, but marquee is great for understanding web history.",
|
||||
"task": "Create a news ticker:<br>1. A <kbd><marquee></kbd> with <kbd>direction=\"left\"</kbd><br>2. Set <kbd>scrollamount=\"5\"</kbd> for smooth scrolling<br>3. Add a breaking news headline inside",
|
||||
"title": "شريط أخبار كلاسيكي",
|
||||
"description": "اجمع عدة سمات marquee لتأثير شريط أخبار كلاسيكي. يمكنك حتى وضع عناصر متعددة بداخله!<br><br>تذكر: هذا HTML قديم. المواقع الحديثة تستخدم حركات CSS، لكن marquee رائع لفهم تاريخ الويب.",
|
||||
"task": "أنشئ شريط أخبار:<br>1. عنصر <kbd><marquee></kbd> مع <kbd>direction=\"left\"</kbd><br>2. اضبط <kbd>scrollamount=\"5\"</kbd> للتمرير السلس<br>3. أضف عنواناً إخبارياً عاجلاً بداخله",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 0; margin: 0; background: #1a1a2e; } marquee { background: linear-gradient(90deg, #c0392b 0%, #e74c3c 50%, #c0392b 100%); padding: 15px 0; font-size: 1.3rem; color: white; font-weight: 500; text-transform: uppercase; letter-spacing: 1px; border-top: 3px solid #f1c40f; border-bottom: 3px solid #f1c40f; }",
|
||||
"sandboxCSS": "",
|
||||
@@ -64,17 +64,17 @@
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "marquee",
|
||||
"message": "Add a <kbd><marquee></kbd> element"
|
||||
"message": "أضف عنصر <kbd><marquee></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "marquee", "attr": "direction", "value": "left" },
|
||||
"message": "Add <kbd>direction=</kbd>\"left\" for horizontal scrolling"
|
||||
"message": "أضف <kbd>direction=</kbd>\"left\" للتمرير الأفقي"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "marquee", "attr": "scrollamount", "value": "5" },
|
||||
"message": "Add <kbd>scrollamount=</kbd>\"5\" for smooth speed"
|
||||
"message": "أضف <kbd>scrollamount=</kbd>\"5\" لسرعة سلسة"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
@@ -2,99 +2,169 @@
|
||||
"$schema": "../../schemas/code-crispies-module-schema.json",
|
||||
"id": "html-svg",
|
||||
"title": "HTML SVG",
|
||||
"description": "Draw scalable vector graphics directly in HTML",
|
||||
"description": "ارسم رسومات متجهة قابلة للتحجيم مباشرة في HTML",
|
||||
"mode": "html",
|
||||
"difficulty": "beginner",
|
||||
"lessons": [
|
||||
{
|
||||
"id": "svg-circle",
|
||||
"title": "Drawing Circles",
|
||||
"description": "SVG (Scalable Vector Graphics) lets you draw shapes directly in HTML. The <kbd><svg></kbd> element is the container, with <kbd>width</kbd> and <kbd>height</kbd> attributes.<br><br>Use <kbd><circle></kbd> with <kbd>cx</kbd>, <kbd>cy</kbd> (center) and <kbd>r</kbd> (radius) to draw circles.",
|
||||
"task": "Create an SVG with a circle:<br>1. An <kbd><svg></kbd> with width=\"200\" and height=\"200\"<br>2. A <kbd><circle></kbd> centered at (100,100) with radius 50<br>3. Add a <kbd>fill</kbd> color",
|
||||
"title": "رسم الدوائر",
|
||||
"description": "SVG (Scalable Vector Graphics) يتيح لك رسم الأشكال مباشرة في HTML. عنصر <kbd><svg></kbd> هو الحاوية بسمات <kbd>width</kbd> و <kbd>height</kbd>.<br><br>استخدم <kbd><circle></kbd> مع <kbd>cx</kbd> و <kbd>cy</kbd> (المركز) و <kbd>r</kbd> (نصف القطر) لرسم الدوائر.",
|
||||
"task": "أنشئ SVG مع دائرة:<br>1. عنصر <kbd><svg></kbd> بـ width=\"200\" و height=\"200\"<br>2. عنصر <kbd><circle></kbd> متمركز عند (100,100) بنصف قطر 50<br>3. أضف لون <kbd>fill</kbd>",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; display: flex; justify-content: center; } svg { background: white; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "",
|
||||
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"50\" fill=\"#3498db\" />\n</svg>",
|
||||
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"50\" fill=\"steelblue\" />\n</svg>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "svg",
|
||||
"message": "Add an <kbd><svg></kbd> element"
|
||||
"message": "أضف عنصر <kbd><svg></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "circle",
|
||||
"message": "Add a <kbd><circle></kbd> element inside the SVG"
|
||||
"message": "أضف عنصر <kbd><circle></kbd> داخل SVG"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "svg", "attr": "width", "value": "200" },
|
||||
"message": "عيّن <kbd>width=</kbd>\"200\" في SVG"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "svg", "attr": "height", "value": "200" },
|
||||
"message": "عيّن <kbd>height=</kbd>\"200\" في SVG"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "circle", "attr": "cx", "value": "100" },
|
||||
"message": "Set <kbd>cx=</kbd>\"100\" for the circle's horizontal center"
|
||||
"message": "عيّن <kbd>cx=</kbd>\"100\" للمركز الأفقي للدائرة"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "circle", "attr": "cy", "value": "100" },
|
||||
"message": "Set <kbd>cy=</kbd>\"100\" for the circle's vertical center"
|
||||
"message": "عيّن <kbd>cy=</kbd>\"100\" للمركز العمودي للدائرة"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "circle", "attr": "r", "value": "50" },
|
||||
"message": "عيّن <kbd>r=</kbd>\"50\" لنصف قطر الدائرة"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "svg-rect-line",
|
||||
"title": "Rectangles & Lines",
|
||||
"description": "Draw rectangles with <kbd><rect></kbd> using <kbd>x</kbd>, <kbd>y</kbd>, <kbd>width</kbd>, <kbd>height</kbd>.<br><br>Draw lines with <kbd><line></kbd> using <kbd>x1</kbd>, <kbd>y1</kbd> (start) and <kbd>x2</kbd>, <kbd>y2</kbd> (end). Lines need a <kbd>stroke</kbd> color!",
|
||||
"task": "Create an SVG with:<br>1. An <kbd><svg></kbd> (200x150)<br>2. A <kbd><rect></kbd> at position (20,20) with size 80x60<br>3. A <kbd><line></kbd> from (120,30) to (180,90) with a stroke color",
|
||||
"title": "المستطيلات والخطوط",
|
||||
"description": "ارسم المستطيلات باستخدام <kbd><rect></kbd> مع <kbd>x</kbd> و <kbd>y</kbd> و <kbd>width</kbd> و <kbd>height</kbd>.<br><br>ارسم الخطوط باستخدام <kbd><line></kbd> مع <kbd>x1</kbd> و <kbd>y1</kbd> (البداية) و <kbd>x2</kbd> و <kbd>y2</kbd> (النهاية). الخطوط تحتاج لون <kbd>stroke</kbd>!",
|
||||
"task": "أنشئ SVG مع:<br>1. عنصر <kbd><svg></kbd> (200x150)<br>2. عنصر <kbd><rect></kbd> في الموضع (20,20) بحجم 80x60<br>3. عنصر <kbd><line></kbd> من (120,30) إلى (180,90) مع لون stroke",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 200px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "",
|
||||
"solution": "<svg width=\"200\" height=\"150\">\n <rect x=\"20\" y=\"20\" width=\"80\" height=\"60\" fill=\"#e74c3c\" />\n <line x1=\"120\" y1=\"30\" x2=\"180\" y2=\"90\" stroke=\"#2c3e50\" stroke-width=\"3\" />\n</svg>",
|
||||
"solution": "<svg width=\"200\" height=\"150\">\n <rect x=\"20\" y=\"20\" width=\"80\" height=\"60\" fill=\"tomato\" />\n <line x1=\"120\" y1=\"30\" x2=\"180\" y2=\"90\" stroke=\"slategray\" stroke-width=\"3\" />\n</svg>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "svg",
|
||||
"message": "Add an <kbd><svg></kbd> element"
|
||||
"message": "أضف عنصر <kbd><svg></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "rect",
|
||||
"message": "Add a <kbd><rect></kbd> element"
|
||||
"message": "أضف عنصر <kbd><rect></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "line",
|
||||
"message": "Add a <kbd><line></kbd> element"
|
||||
"message": "أضف عنصر <kbd><line></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "svg", "attr": "width", "value": "200" },
|
||||
"message": "عيّن <kbd>width=</kbd>\"200\" في SVG"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "svg", "attr": "height", "value": "150" },
|
||||
"message": "عيّن <kbd>height=</kbd>\"150\" في SVG"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "rect", "attr": "x", "value": "20" },
|
||||
"message": "عيّن <kbd>x=</kbd>\"20\" في rect"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "rect", "attr": "y", "value": "20" },
|
||||
"message": "عيّن <kbd>y=</kbd>\"20\" في rect"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "rect", "attr": "width", "value": "80" },
|
||||
"message": "عيّن <kbd>width=</kbd>\"80\" في rect"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "rect", "attr": "height", "value": "60" },
|
||||
"message": "عيّن <kbd>height=</kbd>\"60\" في rect"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "line", "attr": "x1", "value": "120" },
|
||||
"message": "عيّن <kbd>x1=</kbd>\"120\" في line"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "line", "attr": "y1", "value": "30" },
|
||||
"message": "عيّن <kbd>y1=</kbd>\"30\" في line"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "line", "attr": "x2", "value": "180" },
|
||||
"message": "عيّن <kbd>x2=</kbd>\"180\" في line"
|
||||
},
|
||||
{
|
||||
"type": "attribute_value",
|
||||
"value": { "selector": "line", "attr": "y2", "value": "90" },
|
||||
"message": "عيّن <kbd>y2=</kbd>\"90\" في line"
|
||||
},
|
||||
{
|
||||
"type": "contains",
|
||||
"value": "stroke",
|
||||
"message": "أضف لون <kbd>stroke</kbd> إلى line"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": "svg-shapes",
|
||||
"title": "Multiple Shapes",
|
||||
"description": "Combine shapes to create simple graphics! Add <kbd>stroke</kbd> for outlines and <kbd>stroke-width</kbd> for thickness.<br><br>Use <kbd>fill=\"none\"</kbd> for hollow shapes. Shapes stack in order - later elements appear on top.",
|
||||
"task": "Create a simple face:<br>1. An <kbd><svg></kbd> (200x200)<br>2. A large <kbd><circle></kbd> for the face<br>3. Two small <kbd><circle></kbd> elements for eyes<br>4. A <kbd><line></kbd> for the smile",
|
||||
"title": "أشكال متعددة",
|
||||
"description": "ادمج الأشكال لإنشاء رسومات بسيطة! أضف <kbd>stroke</kbd> للحدود و <kbd>stroke-width</kbd> للسمك.<br><br>استخدم <kbd>fill=\"none\"</kbd> للأشكال المجوفة. الأشكال تتراكم بالترتيب - العناصر اللاحقة تظهر في الأعلى.",
|
||||
"task": "أنشئ وجهاً بسيطاً:<br>1. عنصر <kbd><svg></kbd> (200x200)<br>2. <kbd><circle></kbd> كبير للوجه<br>3. اثنين <kbd><circle></kbd> صغيرين للعيون<br>4. عنصر <kbd><line></kbd> للابتسامة",
|
||||
"previewHTML": "",
|
||||
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); min-height: 250px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); }",
|
||||
"sandboxCSS": "",
|
||||
"initialCode": "",
|
||||
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"80\" fill=\"#f1c40f\" stroke=\"#e67e22\" stroke-width=\"4\" />\n <circle cx=\"70\" cy=\"80\" r=\"10\" fill=\"#2c3e50\" />\n <circle cx=\"130\" cy=\"80\" r=\"10\" fill=\"#2c3e50\" />\n <line x1=\"60\" y1=\"130\" x2=\"140\" y2=\"130\" stroke=\"#2c3e50\" stroke-width=\"4\" stroke-linecap=\"round\" />\n</svg>",
|
||||
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"80\" fill=\"gold\" stroke=\"orange\" stroke-width=\"4\" />\n <circle cx=\"70\" cy=\"80\" r=\"10\" fill=\"darkslategray\" />\n <circle cx=\"130\" cy=\"80\" r=\"10\" fill=\"darkslategray\" />\n <line x1=\"60\" y1=\"130\" x2=\"140\" y2=\"130\" stroke=\"darkslategray\" stroke-width=\"4\" stroke-linecap=\"round\" />\n</svg>",
|
||||
"previewContainer": "preview-area",
|
||||
"validations": [
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "svg",
|
||||
"message": "Add an <kbd><svg></kbd> element"
|
||||
"message": "أضف عنصر <kbd><svg></kbd>"
|
||||
},
|
||||
{
|
||||
"type": "element_count",
|
||||
"value": { "selector": "circle", "min": 3 },
|
||||
"message": "Add at least 3 circles (1 face + 2 eyes)"
|
||||
"message": "أضف على الأقل 3 دوائر (1 للوجه + 2 للعيون)"
|
||||
},
|
||||
{
|
||||
"type": "element_exists",
|
||||
"value": "line",
|
||||
"message": "Add a <kbd><line></kbd> for the smile"
|
||||
"message": "أضف <kbd><line></kbd> للابتسامة"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user