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

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

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

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

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "welcome",
"title": "Code Crispies",
"description": "Welcome to Code Crispies - your interactive web development learning platform",
"description": "Ласкаво просимо до Code Crispies - вашої інтерактивної платформи для вивчення веб-розробки",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "get-started",
"title": "Get Started",
"description": "<strong>Code Crispies</strong> is a free, open-source platform for learning web development through hands-on exercises. No account required!<br><br><strong>What you'll learn:</strong><br>• <strong>HTML</strong> - Semantic elements, forms, tables, SVG (<em>HTML Block & Inline</em>, <em>HTML Forms</em>, <em>HTML Tables</em>)<br>• <strong>CSS</strong> - Selectors, box model, flexbox, animations (<em>CSS Selectors</em>, <em>CSS Box Model</em>, <em>CSS Flexbox</em>)<br>• <strong>Responsive Design</strong> - Media queries and mobile-first layouts<br><br><strong>How it works:</strong><br>1. Read the task in the left panel<br>2. Write code in the editor<br>3. See live results in the preview<br>4. Get instant feedback with hints<br><br><strong>Keyboard shortcuts:</strong> <kbd>Ctrl+Z</kbd> to undo, <kbd>Ctrl+Shift+Z</kbd> to redo<br><br><strong>More resources:</strong><br>• <a href=\"https://nextlevelshit.github.io/html-over-js/\" target=\"_blank\">HTML over JS</a> - Native HTML vs JavaScript solutions<br>• <a href=\"https://nextlevelshit.github.io/web-engineering-mandala/\" target=\"_blank\">Web Engineering Mandala</a> - JavaScript technology roadmap",
"task": "Write <code>Hello World</code>",
"title": "Почати",
"description": "<strong>Code Crispies</strong> - це безкоштовна платформа з відкритим кодом для вивчення веб-розробки через практичні вправи. Обліковий запис не потрібен!<br><br><strong>Що ви вивчите:</strong><br>• <strong>HTML</strong> - Семантичні елементи, форми, таблиці, SVG (<em>HTML Блокові та рядкові</em>, <em>HTML Форми</em>, <em>HTML Таблиці</em>)<br>• <strong>CSS</strong> - Селектори, блокова модель, flexbox, анімації (<em>CSS Селектори</em>, <em>CSS Блокова модель</em>, <em>CSS Flexbox</em>)<br>• <strong>Адаптивний дизайн</strong> - Media queries та mobile-first макети<br><br><strong>Як це працює:</strong><br>1. Прочитайте завдання в лівій панелі<br>2. Напишіть код в редакторі<br>3. Побачте результати в попередньому перегляді<br>4. Отримайте миттєвий зворотний зв'язок з підказками<br><br><strong>Гарячі клавіші:</strong> <kbd>Ctrl+Z</kbd> скасувати, <kbd>Ctrl+Shift+Z</kbd> повторити<br><br><strong>Більше ресурсів:</strong><br>• <a href=\"https://nextlevelshit.github.io/html-over-js/\" target=\"_blank\">HTML over JS</a> - Нативний HTML проти JavaScript рішень<br>• <a href=\"https://nextlevelshit.github.io/web-engineering-mandala/\" target=\"_blank\">Web Engineering Mandala</a> - Карта JavaScript технологій",
"task": "Напишіть <code>Hello World</code>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 20px; text-align: center; font-size: 2rem; color: #6366f1; font-weight: bold; }",
"sandboxCSS": "",
@@ -21,15 +21,15 @@
{
"type": "contains",
"value": "Hello World",
"message": "Write <code>Hello World</code>"
"message": "Напишіть <code>Hello World</code>"
}
]
},
{
"id": "overview",
"title": "Overview",
"description": "<strong>You're ready!</strong> Open the menu (☰) to explore all modules.<br><br><strong>Recommended learning path:</strong><br>1. <em>HTML Block & Inline</em> - Understand container vs inline elements<br>2. <em>HTML Forms</em> - Build interactive forms with validation<br>3. <em>CSS Selectors</em> - Target elements precisely<br>4. <em>CSS Box Model</em> - Master padding, margin, borders<br>5. <em>CSS Flexbox</em> - Create flexible layouts<br>6. <em>CSS Animations</em> - Add motion and transitions<br><br><strong>Tips:</strong><br>• Use <em>Show Expected</em> to see the target result<br>• Your progress is saved automatically<br>• Try Emmet in HTML mode: <kbd>ul>li*3</kbd> + Tab<br><br><strong>Open Source:</strong><br>• <a href=\"https://git.librete.ch/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> - Зрозумійте контейнерні vs рядкові елементи<br>2. <em>HTML Форми</em> - Створюйте інтерактивні форми з валідацією<br>3. <em>CSS Селектори</em> - Точно вибирайте елементи<br>4. <em>CSS Блокова модель</em> - Опануйте padding, margin, borders<br>5. <em>CSS Flexbox</em> - Створюйте гнучкі макети<br>6. <em>CSS Анімації</em> - Додавайте рух та переходи<br><br><strong>Поради:</strong><br>• Використовуйте <em>Показати очікуване</em> щоб побачити цільовий результат<br>• Ваш прогрес зберігається автоматично<br>• Спробуйте Emmet в режимі HTML: <kbd>ul>li*3</kbd> + Tab<br><br><strong>Open Source:</strong><br>• <a href=\"https://git.librete.ch/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": "Натисніть Далі щоб продовжити"
}
]
},

View File

@@ -2,18 +2,18 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "box-model",
"title": "CSS Box Model",
"description": "Master the fundamental principles of space management in web design through the CSS box model. This module explores how content, padding, borders, and margins combine to create layout structures that are both visually appealing and structurally sound.",
"description": "Опануйте фундаментальні принципи управління простором у веб-дизайні через блокову модель CSS. Цей модуль досліджує, як контент, відступи, межі та поля поєднуються для створення структур макету.",
"difficulty": "beginner",
"lessons": [
{
"id": "box-model-1",
"title": "Box Model Components",
"description": "The CSS box model consists of four concentric layers: content area (innermost), padding, border, and margin (outermost). Understanding how these components interact is essential for precise layout control.",
"task": "Set <kbd>padding</kbd> to <kbd>1rem</kbd> to create space between the content and border.",
"previewHTML": "<div class=\"box\">Box Model Components</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: lavender; border: 2px dashed slategray; }",
"title": "Padding",
"description": "Кожен елемент у CSS - це блок з чотирма шарами: контент, відступ (padding), межа та поле. <strong>Padding</strong> створює простір для дихання між вашим контентом і краєм блоку.<br><br>Без padding текст незручно притискається до меж. Padding робить контент читабельним і візуально збалансованим.<br><br><pre>.card {\n padding: 1rem;\n}</pre>",
"task": "Ця картка профілю виглядає тісною. Додайте <kbd>padding: 1rem</kbd>, щоб текст мав простір для дихання.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
"sandboxCSS": "",
"codePrefix": ".box {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "padding: 1rem;",
@@ -22,62 +22,62 @@
{
"type": "property_value",
"value": { "property": "padding", "expected": "1rem" },
"message": "Set <kbd>padding: 1rem</kbd>"
"message": "Встановіть <kbd>padding: 1rem</kbd>"
}
]
},
{
"id": "box-model-2",
"title": "Adding Borders",
"description": "Borders outline an element, creating visual separation from surrounding content. The border shorthand accepts three values: width, style, and color.",
"task": "Set <kbd>border</kbd> to <kbd>2px solid darkslategray</kbd>.",
"previewHTML": "<div class=\"box\">This box needs a border</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .box { background-color: mintcream; padding: 1rem; }",
"title": "Borders",
"description": "Межі створюють візуальні границі навколо елементів. Скорочення <kbd>border</kbd> приймає три значення: ширину, стиль і колір.<br><br>Поширені стилі: <kbd>solid</kbd>, <kbd>dashed</kbd>, <kbd>dotted</kbd>, <kbd>none</kbd>",
"task": "Додайте тонкий лівий акцент до картки за допомогою <kbd>border-left: 4px solid steelblue</kbd>.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
"sandboxCSS": "",
"codePrefix": ".box {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "border: 2px solid darkslategray;",
"solution": "border-left: 4px solid steelblue;",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "border:\\s*2px\\s+solid\\s+darkslategray",
"message": "Set <kbd>border: 2px solid darkslategray</kbd>",
"value": "border-left:\\s*4px\\s+solid\\s+steelblue",
"message": "Встановіть <kbd>border-left: 4px solid steelblue</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-3",
"title": "Adding Margins",
"description": "Margins create space between elements, controlling how they relate to one another within a layout. Unlike padding (which affects internal spacing), margins exist outside the element's border.",
"task": "Set <kbd>margin</kbd> to <kbd>1rem</kbd> to create space between this element and its neighbors.",
"previewHTML": "<div class=\"container\"><div class=\"outer\">This box needs margins</div><div class=\"neighbor\">Adjacent element</div></div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .outer { background-color: plum; padding: 1rem; border: 2px solid orchid; } .neighbor { background-color: lightblue; padding: 1rem; border: 2px solid steelblue; }",
"title": "Margins",
"description": "Поля створюють простір <em>зовні</em> елемента, відділяючи його від сусідів. Тоді як padding штовхає контент всередину, поля відштовхують інші елементи.",
"task": "Додайте простір між цими двома картками профілю за допомогою <kbd>margin-bottom: 1rem</kbd> на <kbd>.card</kbd>.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article><article class=\"card\"><h3>Alex Rivera</h3><p>UX Designer</p></article>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; border-left: 4px solid steelblue; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
"sandboxCSS": "",
"codePrefix": ".outer {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "margin: 1rem;",
"solution": "margin-bottom: 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "margin", "expected": "1rem" },
"message": "Set <kbd>margin: 1rem</kbd>"
"value": { "property": "margin-bottom", "expected": "1rem" },
"message": "Встановіть <kbd>margin-bottom: 1rem</kbd>"
}
]
},
{
"id": "box-model-4",
"title": "Box Sizing: Border-Box",
"description": "The <kbd>box-sizing</kbd> property determines how element dimensions are calculated. The default <kbd>content-box</kbd> excludes padding and border from width/height, while <kbd>border-box</kbd> includes them, making layout calculations more intuitive.",
"task": "Set <kbd>box-sizing</kbd> to <kbd>border-box</kbd> so padding and border are included in the width.",
"previewHTML": "<div class=\"sizing-demo\"><div class=\"box default\">Content-box (default)</div><div class=\"box sized\">Border-box</div></div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .sizing-demo { display: flex; gap: 1rem; } .box { width: 200px; padding: 1rem; border: 4px solid teal; background: lightcyan; } .default { box-sizing: content-box; }",
"title": "Box Sizing",
"description": "За замовчуванням <kbd>width</kbd> встановлює лише ширину контенту. Padding і межі додаються до загальної суми. Це спричиняє проблеми з макетом.<br><br><kbd>box-sizing: border-box</kbd> включає padding і межу у ширину, роблячи розмір передбачуваним. Більшість розробників застосовують це до всіх елементів.",
"task": "Обидві картки мають <kbd>width: 200px</kbd>. Ліва використовує стандартний розмір (content-box), стаючи ширшою за очікуване. Виправте праву картку за допомогою <kbd>box-sizing: border-box</kbd>.",
"previewHTML": "<div class=\"demo\"><article class=\"card\">Content-box</article><article class=\"card fix\">Border-box</article></div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .demo { display: flex; gap: 1rem; } .card { width: 200px; padding: 1rem; border: 4px solid steelblue; background: white; border-radius: 8px; }",
"sandboxCSS": "",
"codePrefix": ".sized {\n ",
"codePrefix": ".fix {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "box-sizing: border-box;",
@@ -86,93 +86,104 @@
{
"type": "property_value",
"value": { "property": "box-sizing", "expected": "border-box" },
"message": "Set <kbd>box-sizing: border-box</kbd>"
"message": "Встановіть <kbd>box-sizing: border-box</kbd>"
}
]
},
{
"id": "box-model-5",
"title": "Margin Collapse",
"description": "When two vertical margins meet, they collapse to the larger value instead of adding up. Understanding this behavior is crucial for consistent vertical spacing.",
"task": "Set <kbd>margin-bottom</kbd> to <kbd>2rem</kbd>. Notice the space between paragraphs equals 2rem (not 3rem) due to margin collapse.",
"previewHTML": "<div class=\"collapse-demo\"><p class=\"first\">This paragraph has a bottom margin.</p><p class=\"second\">This paragraph has a top margin of 1rem.</p></div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .collapse-demo { border: 1px solid silver; padding: 8px; background: ghostwhite; } .second { margin-top: 1rem; background: linen; }",
"title": "Padding Shorthand",
"description": "Padding приймає 1-4 значення:<br>• 1 значення: всі сторони<br>• 2 значення: вертикально | горизонтально<br>• 4 значення: верх | право | низ | ліво",
"task": "Ця кнопка потребує більше горизонтального простору, ніж вертикального. Встановіть <kbd>padding: 8px 1rem</kbd> (8px верх/низ, 1rem ліво/право).",
"previewHTML": "<button class=\"btn\">Follow</button>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .btn { background: steelblue; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 1rem; }",
"sandboxCSS": "",
"codePrefix": ".first {\n ",
"codePrefix": ".btn {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "margin-bottom: 2rem;",
"solution": "padding: 8px 1rem;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "margin-bottom", "expected": "2rem" },
"message": "Set <kbd>margin-bottom: 2rem</kbd>"
"type": "regex",
"value": "padding:\\s*8px\\s+1rem",
"message": "Встановіть <kbd>padding: 8px 1rem</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-6",
"title": "Margin Shorthand Notation",
"description": "The margin shorthand can set all four sides at once. Two values set vertical (top/bottom) and horizontal (left/right) margins respectively.",
"task": "Set <kbd>margin</kbd> to <kbd>1rem 2rem</kbd> for 1rem top/bottom and 2rem left/right.",
"previewHTML": "<div class=\"container\"><div class=\"spaced\">This box needs margins: 1rem top/bottom, 2rem left/right</div></div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .container { background-color: whitesmoke; padding: 8px; } .spaced { background-color: honeydew; border: 2px solid mediumseagreen; padding: 1rem; }",
"title": "Margin Shorthand",
"description": "Margin використовує той самий шаблон скорочення, що й padding. Поширений шаблон - горизонтальне центрування блокових елементів за допомогою <kbd>margin: 0 auto</kbd>.",
"task": "Відцентруйте цю картку горизонтально. Встановіть <kbd>margin: 0 auto</kbd>, щоб автоматично обчислити рівні ліві/праві поля.",
"previewHTML": "<article class=\"card\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { width: 250px; background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; border-left: 4px solid steelblue; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
"sandboxCSS": "",
"codePrefix": ".spaced {\n ",
"codePrefix": ".card {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "margin: 1rem 2rem;",
"solution": "margin: 0 auto;",
"previewContainer": "preview-area",
"validations": [
{
"type": "regex",
"value": "margin:\\s*1rem\\s+2rem",
"message": "Set <kbd>margin: 1rem 2rem</kbd>",
"value": "margin:\\s*0\\s+auto",
"message": "Встановіть <kbd>margin: 0 auto</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "box-model-7",
"title": "Padding Shorthand Notation",
"description": "Like margin, padding shorthand allows setting all sides at once. A single value applies to all four sides equally.",
"task": "Set <kbd>padding</kbd> to <kbd>2rem</kbd> to add equal padding on all sides.",
"previewHTML": "<div class=\"padded\">This box needs equal padding on all sides</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .padded { background-color: papayawhip; border: 2px solid orange; }",
"title": "Border Radius",
"description": "Хоча не є частиною класичної блокової моделі, <kbd>border-radius</kbd> заокруглює кути межі елемента. Використовуйте <kbd>50%</kbd> на квадратному елементі, щоб створити коло.",
"task": "Зробіть зображення аватара круглим за допомогою <kbd>border-radius: 50%</kbd>.",
"previewHTML": "<article class=\"card\"><img class=\"avatar\" src=\"data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 100 100'%3E%3Ccircle cx='50' cy='35' r='25' fill='%23666'/%3E%3Ccircle cx='50' cy='90' r='40' fill='%23666'/%3E%3C/svg%3E\" alt=\"Avatar\"><h3>Sarah Chen</h3><p>Frontend Developer</p></article>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; background: #f5f5f5; } .card { background: white; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); padding: 1rem; text-align: center; } .avatar { width: 80px; height: 80px; background: #ddd; margin-bottom: 8px; } .card h3 { margin: 0 0 4px; } .card p { margin: 0; color: #666; }",
"sandboxCSS": "",
"codePrefix": ".padded {\n ",
"codePrefix": ".avatar {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "padding: 2rem;",
"solution": "border-radius: 50%;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "padding", "expected": "2rem" },
"message": "Set <kbd>padding: 2rem</kbd>"
"value": { "property": "border-radius", "expected": "50%" },
"message": "Встановіть <kbd>border-radius: 50%</kbd>"
}
]
},
{
"id": "box-model-8",
"title": "Border on Specific Sides",
"description": "For granular control, you can target specific sides with <kbd>border-top</kbd>, <kbd>border-right</kbd>, <kbd>border-bottom</kbd>, or <kbd>border-left</kbd>.",
"task": "Set <kbd>border-bottom</kbd> to <kbd>4px solid dodgerblue</kbd>.",
"previewHTML": "<div class=\"line\">This element needs only a bottom border</div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .line { padding: 1rem; background-color: aliceblue; }",
"title": "Complete Card",
"description": "Об'єднаймо все разом. Ця картка сповіщення потребує стилізації, щоб виглядати професійно.",
"task": "Стилізуйте сповіщення: додайте <kbd>padding: 1rem</kbd>, <kbd>border-left: 4px solid coral</kbd> та <kbd>border-radius: 4px</kbd>.",
"previewHTML": "<div class=\"alert\"><strong>New message</strong><p>You have 3 unread notifications</p></div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .alert { background: seashell; } .alert strong { color: coral; } .alert p { margin: 4px 0 0; color: #666; }",
"sandboxCSS": "",
"codePrefix": ".line {\n ",
"codePrefix": ".alert {\n ",
"initialCode": "",
"codeSuffix": "\n}",
"solution": "border-bottom: 4px solid dodgerblue;",
"solution": "padding: 1rem;\n border-left: 4px solid coral;\n border-radius: 4px;",
"previewContainer": "preview-area",
"validations": [
{
"type": "property_value",
"value": { "property": "padding", "expected": "1rem" },
"message": "Встановіть <kbd>padding: 1rem</kbd>"
},
{
"type": "regex",
"value": "border-bottom:\\s*4px\\s+solid\\s+dodgerblue",
"message": "Set <kbd>border-bottom: 4px solid dodgerblue</kbd>",
"value": "border-left:\\s*4px\\s+solid\\s+coral",
"message": "Встановіть <kbd>border-left: 4px solid coral</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "border-radius", "expected": "4px" },
"message": "Встановіть <kbd>border-radius: 4px</kbd>"
}
]
}

View File

@@ -1,115 +1,100 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "units-variables",
"title": "CSS Units & Variables",
"description": "Understand the variety of CSS measurement units and how to define and use custom properties for maintainable styles.",
"title": "Одиниці CSS та змінні",
"description": "Зрозумійте різноманітність одиниць вимірювання CSS та як визначати й використовувати кастомні властивості для стилів, які легко підтримувати.",
"difficulty": "beginner",
"lessons": [
{
"id": "units-1",
"title": "Absolute vs. Relative Units",
"description": "Learn the difference between px, rem, em, %, and vw/vh for flexible, responsive layouts.",
"task": "Set the <kbd>width</kbd> of <kbd>.box</kbd> to <kbd>80%</kbd> and <kbd>max-width</kbd> to <kbd>37.5rem</kbd>.",
"previewHTML": "<div class=\"box\">Resize me!</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .box { background: #f5f5f5; padding: 1rem; }",
"title": "Relative Units",
"description": "CSS пропонує два типи одиниць: <em>абсолютні</em> (як <kbd>px</kbd>) та <em>відносні</em> (як <kbd>%</kbd> і <kbd>rem</kbd>). Відносні одиниці адаптуються до контексту, роблячи макети гнучкими та доступними.<br><br><strong>Поширені відносні одиниці:</strong><br>• <kbd>%</kbd> Відносно батьківського елемента<br>• <kbd>rem</kbd> Відносно кореневого розміру шрифту (зазвичай 16px)<br>• <kbd>em</kbd> Відносно розміру шрифту елемента<br><br>Поширений патерн для читабельного контенту: встановіть <kbd>width: 100%</kbd>, щоб заповнити доступний простір, потім <kbd>max-width: 40rem</kbd> для обмеження довжини рядка.",
"task": "Цей текст статті занадто широкий на великих екранах. Додайте <kbd>max-width: 40rem</kbd> для оптимальної ширини читання.",
"previewHTML": "<article class=\"article\"><h2>The Art of Typography</h2><p>Good typography is invisible. When text is set well, readers absorb information without noticing the design decisions that make it comfortable to read. Line length is crucial—too wide and eyes get lost, too narrow and reading becomes choppy.</p><p>The ideal line length is 45-75 characters per line. At typical font sizes, this works out to roughly 40rem maximum width.</p></article>",
"previewBaseCSS": "body { font-family: Georgia, serif; padding: 1rem; background: #f9f9f9; } .article { background: white; padding: 2rem; box-shadow: 0 1px 3px rgba(0,0,0,0.1); } .article h2 { margin: 0 0 1rem; color: #333; } .article p { margin: 0 0 1rem; line-height: 1.6; color: #444; } .article p:last-child { margin-bottom: 0; }",
"sandboxCSS": "",
"codePrefix": "/* Set flexible sizing */\n.box {",
"codePrefix": ".article {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: 80%;\n max-width: 37.5rem;",
"codeSuffix": "\n}",
"solution": "max-width: 40rem;",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "width", "message": "Use <kbd>width</kbd> property", "options": { "caseSensitive": false } },
{ "type": "property_value", "value": { "property": "width", "expected": "80%" }, "message": "Set width to <kbd>80%</kbd>" },
{ "type": "contains", "value": "max-width", "message": "Use <kbd>max-width</kbd> property", "options": { "caseSensitive": false } },
{
"type": "property_value",
"value": { "property": "max-width", "expected": "37.5rem" },
"message": "Set max-width to <kbd>37.5rem</kbd>"
"value": { "property": "max-width", "expected": "40rem" },
"message": "Встановіть <kbd>max-width: 40rem</kbd>"
}
]
},
{
"id": "units-2",
"title": "CSS Custom Properties",
"description": "Define and reuse variables (--custom properties) to centralize your theme values.",
"task": "Create a <kbd>--main-color</kbd> variable in <kbd>:root</kbd> with <kbd>#6200ee</kbd> and apply it as the border color on <kbd>.themed</kbd>.",
"previewHTML": "<div class=\"themed\">Variable Box</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .themed { padding: 1rem; border: 0.125rem solid #ddd; }",
"title": "CSS Variables",
"description": "Кастомні властивості CSS (змінні) дозволяють визначати значення для повторного використання. Визначайте їх за допомогою <kbd>--назва</kbd> та використовуйте з <kbd>var(--назва)</kbd>. Змінні, визначені на <kbd>:root</kbd>, доступні всюди.",
"task": "Визначте <kbd>--brand: steelblue</kbd> в <kbd>:root</kbd>, потім використайте як колір <kbd>background</kbd> для <kbd>.btn</kbd>.",
"previewHTML": "<div class=\"actions\"><button class=\"btn\">Subscribe</button><button class=\"btn\">Learn More</button></div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .actions { display: flex; gap: 1rem; } .btn { color: white; border: none; padding: 12px 24px; border-radius: 6px; font-size: 1rem; cursor: pointer; background: #ccc; }",
"sandboxCSS": "",
"codePrefix": "/* Define and use a CSS variable */\n:root {",
"codePrefix": ":root {\n ",
"initialCode": "",
"codeSuffix": "}\n.themed { }",
"solution": " --main-color: #6200ee;\n}\n.themed {\n border-color: var(--main-color);",
"codeSuffix": "\n}\n\n.btn {\n background: var(--brand);\n}",
"solution": "--brand: steelblue;",
"previewContainer": "preview-area",
"validations": [
{
"type": "contains",
"value": "--main-color",
"message": "Define <kbd>--main-color</kbd> in :root",
"value": "--brand",
"message": "Визначте змінну <kbd>--brand</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "contains",
"value": "var(--main-color)",
"message": "Use <kbd>var(--main-color)</kbd>",
"value": "steelblue",
"message": "Встановіть значення <kbd>steelblue</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "property_value",
"value": { "property": "border", "expected": "var(--main-color)" },
"message": "Apply variable to border color",
"options": { "exact": false }
}
]
},
{
"id": "units-3",
"title": "Unit Calculations (calc)",
"description": "Use the <kbd>calc()</kbd> function to combine different units in one expression.",
"task": "Set the <kbd>width</kbd> of <kbd>.sized</kbd> to <kbd>calc(100% - 2rem)</kbd> and <kbd>min-height</kbd> to <kbd>calc(10vh + 1rem)</kbd>.",
"previewHTML": "<div class=\"sized\">Calc Demo</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .sized { background: #e8f5e9; padding: 1rem; }",
"title": "calc() Function",
"description": "Функція <kbd>calc()</kbd> дозволяє змішувати різні одиниці в обчисленнях. Це важливо для макетів, що поєднують фіксовані та гнучкі розміри, як макет із сайдбаром.",
"task": "Основний контент повинен заповнити залишок місця після сайдбару 200px. Встановіть <kbd>width: calc(100% - 200px)</kbd> на <kbd>.main</kbd>.",
"previewHTML": "<div class=\"layout\"><aside class=\"sidebar\">Sidebar<br>Navigation</aside><main class=\"main\"><h2>Main Content</h2><p>This area should fill the remaining width after accounting for the fixed-width sidebar.</p></main></div>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; padding: 1rem; } .layout { display: flex; gap: 1rem; } .sidebar { width: 200px; background: #1a1a2e; color: white; padding: 1rem; border-radius: 8px; flex-shrink: 0; } .main { background: white; padding: 1rem; border-radius: 8px; box-shadow: 0 2px 4px rgba(0,0,0,0.1); } .main h2 { margin: 0 0 8px; } .main p { margin: 0; color: #666; }",
"sandboxCSS": "",
"codePrefix": "/* Use calc for dynamic sizing */\n.sized {",
"codePrefix": ".main {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: calc(100% - 2rem);\n min-height: calc(10vh + 1rem);",
"codeSuffix": "\n}",
"solution": "width: calc(100% - 200px);",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "calc", "message": "Use <kbd>calc()</kbd> function", "options": { "caseSensitive": false } },
{
"type": "regex",
"value": "width:\\s*calc\\(100% - 2rem\\)",
"message": "Width should be <kbd>calc(100% - 2rem)</kbd>",
"options": { "caseSensitive": false }
},
{
"type": "regex",
"value": "min-height:\\s*calc\\(10vh \\+ 1rem\\)",
"message": "Min-height should be <kbd>calc(10vh + 1rem)</kbd>",
"value": "width:\\s*calc\\(\\s*100%\\s*-\\s*200px\\s*\\)",
"message": "Встановіть <kbd>width: calc(100% - 200px)</kbd>",
"options": { "caseSensitive": false }
}
]
},
{
"id": "units-4",
"title": "Viewport & Responsive Units",
"description": "Control layouts relative to viewport size with vw, vh, and vmin/vmax units.",
"task": "Give <kbd>.view</kbd> a <kbd>width</kbd> of <kbd>50vw</kbd> and <kbd>height</kbd> of <kbd>20vh</kbd>.",
"previewHTML": "<div class=\"view\">Viewport Box</div>",
"previewBaseCSS": "body { font-family: sans-serif; padding: 1rem; } .view { background: #ffe0b2; }",
"title": "Viewport Units",
"description": "Одиниці viewport розміряють елементи відносно вікна браузера:<br>• <kbd>vw</kbd> 1% ширини viewport<br>• <kbd>vh</kbd> 1% висоти viewport<br><br>Вони ідеальні для повноекранних секцій як hero-банери.",
"task": "Зробіть цю hero-секцію висотою з viewport, встановивши <kbd>min-height: 100vh</kbd>.",
"previewHTML": "<section class=\"hero\"><h1>Welcome</h1><p>Scroll down to explore</p></section>",
"previewBaseCSS": "body { font-family: system-ui, sans-serif; margin: 0; } .hero { background: linear-gradient(135deg, #1a1a2e 0%, steelblue 100%); color: white; display: flex; flex-direction: column; align-items: center; justify-content: center; text-align: center; padding: 2rem; } .hero h1 { margin: 0 0 1rem; font-size: 2.5rem; } .hero p { margin: 0; opacity: 0.8; }",
"sandboxCSS": "",
"codePrefix": "/* Use viewport units */\n.view {",
"codePrefix": ".hero {\n ",
"initialCode": "",
"codeSuffix": "}",
"solution": " width: 50vw;\n height: 20vh;",
"codeSuffix": "\n}",
"solution": "min-height: 100vh;",
"previewContainer": "preview-area",
"validations": [
{ "type": "contains", "value": "vw", "message": "Use <kbd>vw</kbd> unit", "options": { "caseSensitive": false } },
{ "type": "contains", "value": "vh", "message": "Use <kbd>vh</kbd> unit", "options": { "caseSensitive": false } },
{ "type": "property_value", "value": { "property": "width", "expected": "50vw" }, "message": "Set width to <kbd>50vw</kbd>" },
{ "type": "property_value", "value": { "property": "height", "expected": "20vh" }, "message": "Set height to <kbd>20vh</kbd>" }
{
"type": "property_value",
"value": { "property": "min-height", "expected": "100vh" },
"message": "Встановіть <kbd>min-height: 100vh</kbd>"
}
]
}
]

View File

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

View File

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

View File

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

View File

@@ -1,100 +1,100 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-forms-basic",
"title": "HTML Forms",
"description": "Learn to create forms with various input types",
"title": "HTML Форми",
"description": "Навчіться створювати форми з різними типами полів",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "form-structure",
"title": "Form Structure",
"description": "Every form needs a <kbd>&lt;form&gt;</kbd> wrapper. Inside, use <kbd>&lt;label&gt;</kbd> to describe inputs and <kbd>&lt;input&gt;</kbd> for user data entry.<br><br>The <kbd>for</kbd> attribute on labels should match the <kbd>id</kbd> on inputs for accessibility.",
"task": "Create a form with:<br>1. A <kbd>&lt;label&gt;</kbd> with the text <code>Name:</code> and <kbd>for=\"name\"</kbd> attribute<br>2. A text <kbd>&lt;input&gt;</kbd> with <kbd>id=\"name\"</kbd> and <kbd>name=\"name\"</kbd> attributes",
"title": "Структура форми",
"description": "Кожна форма потребує обгортки <kbd>&lt;form&gt;</kbd>. Всередині використовуйте <kbd>&lt;label&gt;</kbd> для опису полів та <kbd>&lt;input&gt;</kbd> для введення даних.<br><br>Атрибут <kbd>for</kbd> у label має відповідати <kbd>id</kbd> полів для доступності.",
"task": "Створіть форму з:<br>1. <kbd>&lt;label&gt;</kbd> з текстом <code>Ім'я:</code> та атрибутом <kbd>for=\"name\"</kbd><br>2. Текстовим <kbd>&lt;input&gt;</kbd> з атрибутами <kbd>id=\"name\"</kbd> та <kbd>name=\"name\"</kbd>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-bottom: 5px; font-weight: 500; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
"sandboxCSS": "",
"initialCode": "",
"solution": "<form>\n <label for=\"name\">Name:</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n</form>",
"solution": "<form>\n <label for=\"name\">Ім'я:</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n</form>",
"previewContainer": "preview-area",
"validations": [
{
"type": "element_exists",
"value": "form",
"message": "Wrap everything in a <kbd>&lt;form&gt;</kbd> element"
"message": "Оберніть все елементом <kbd>&lt;form&gt;</kbd>"
},
{
"type": "element_exists",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for your input"
"message": "Додайте <kbd>&lt;label&gt;</kbd> для вашого поля"
},
{
"type": "element_exists",
"value": "input",
"message": "Add an <kbd>&lt;input&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;input&gt;</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "label", "attr": "for", "value": null },
"message": "Add a <kbd>for</kbd> attribute to your label"
"message": "Додайте атрибут <kbd>for</kbd> до вашого label"
},
{
"type": "attribute_value",
"value": { "selector": "input", "attr": "id", "value": null },
"message": "Add an <kbd>id</kbd> attribute to your input"
"message": "Додайте атрибут <kbd>id</kbd> до вашого поля"
}
]
},
{
"id": "input-types",
"title": "Input Types",
"description": "Different input types provide appropriate keyboards and validation:<br><br><kbd>type=\"text\"</kbd> - General text<br><kbd>type=\"email\"</kbd> - Email with @ validation<br><kbd>type=\"password\"</kbd> - Hidden characters<br><kbd>type=\"number\"</kbd> - Numeric keyboard<br><kbd>type=\"tel\"</kbd> - Phone keyboard",
"task": "Create a login form with two fields:<br>1. An email field: <kbd>&lt;label for=\"email\"&gt;Email:&lt;/label&gt;</kbd> and <kbd>&lt;input type=\"email\" id=\"email\"&gt;</kbd><br>2. A password field: <kbd>&lt;label for=\"password\"&gt;Password:&lt;/label&gt;</kbd> and <kbd>&lt;input type=\"password\" id=\"password\"&gt;</kbd>",
"title": "Типи полів",
"description": "Різні типи полів надають відповідні клавіатури та валідацію:<br><br><kbd>type=\"text\"</kbd> - Загальний текст<br><kbd>type=\"email\"</kbd> - Email з валідацією @<br><kbd>type=\"password\"</kbd> - Приховані символи<br><kbd>type=\"number\"</kbd> - Цифрова клавіатура<br><kbd>type=\"tel\"</kbd> - Телефонна клавіатура",
"task": "Створіть форму входу з двома полями:<br>1. Поле email: <kbd>&lt;label for=\"email\"&gt;Email:&lt;/label&gt;</kbd> та <kbd>&lt;input type=\"email\" id=\"email\"&gt;</kbd><br>2. Поле пароля: <kbd>&lt;label for=\"password\"&gt;Пароль:&lt;/label&gt;</kbd> та <kbd>&lt;input type=\"password\" id=\"password\"&gt;</kbd>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; }",
"sandboxCSS": "",
"initialCode": "<form>\n \n</form>",
"solution": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\">\n</form>",
"solution": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <label for=\"password\">Пароль:</label>\n <input type=\"password\" id=\"password\" name=\"password\">\n</form>",
"previewContainer": "preview-area",
"validations": [
{
"type": "element_exists",
"value": "input[type='email']",
"message": "Add an input with type=\"email\""
"message": "Додайте поле з type=\"email\""
},
{
"type": "element_exists",
"value": "input[type='password']",
"message": "Add an input with type=\"password\""
"message": "Додайте поле з type=\"password\""
},
{
"type": "element_count",
"value": { "selector": "label", "min": 2 },
"message": "Add labels for both inputs"
"message": "Додайте label для обох полів"
}
]
},
{
"id": "submit-button",
"title": "Submit Button",
"description": "Forms need a way to submit data. Use:<br><br><kbd>&lt;button type=\"submit\"&gt;</kbd> - Preferred, flexible content<br><kbd>&lt;input type=\"submit\"&gt;</kbd> - Simple text-only button<br><br>The button text should be action-oriented (e.g., <code>Sign In</code>, 'Register', 'Send').",
"task": "Add a submit button to the form with the text <code>Sign In</code>.",
"title": "Кнопка відправки",
"description": "Форми потребують способу відправки даних. Використовуйте:<br><br><kbd>&lt;button type=\"submit\"&gt;</kbd> - Переважно, гнучкий вміст<br><kbd>&lt;input type=\"submit\"&gt;</kbd> - Простий текстовий кнопка<br><br>Текст кнопки має бути орієнтованим на дію (напр. <code>Увійти</code>, 'Зареєструватись', 'Надіслати').",
"task": "Додайте кнопку відправки до форми з текстом <code>Увійти</code>.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 300px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-child { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } button { width: 100%; margin-top: 20px; padding: 10px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; } button:hover { background: #1565c0; }",
"sandboxCSS": "",
"initialCode": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\">\n \n</form>",
"solution": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\">\n \n <button type=\"submit\">Sign In</button>\n</form>",
"initialCode": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">Пароль:</label>\n <input type=\"password\" id=\"password\">\n \n</form>",
"solution": "<form>\n <label for=\"email\">Email:</label>\n <input type=\"email\" id=\"email\">\n \n <label for=\"password\">Пароль:</label>\n <input type=\"password\" id=\"password\">\n \n <button type=\"submit\">Увійти</button>\n</form>",
"previewContainer": "preview-area",
"validations": [
{
"type": "element_exists",
"value": "button[type='submit'], input[type='submit']",
"message": "Add a submit button to your form"
"message": "Додайте кнопку відправки до форми"
},
{
"type": "element_text",
"value": { "selector": "button", "text": "Sign In" },
"message": "The button should say <kbd>Sign In</kbd>"
"value": { "selector": "button", "text": "Увійти" },
"message": "Кнопка має відображати <kbd>Увійти</kbd>"
}
]
}

View File

@@ -1,110 +1,32 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-forms-validation",
"title": "HTML Validation",
"description": "Learn HTML5 built-in form validation attributes",
"title": "Валідація форм",
"description": "Використовуйте вбудовану валідацію HTML5 для кращого досвіду користувача",
"mode": "html",
"difficulty": "intermediate",
"difficulty": "beginner",
"lessons": [
{
"id": "required-fields",
"title": "Required Fields",
"description": "The <kbd>required</kbd> attribute prevents form submission if the field is empty.<br><br>Add it to any input that must be filled:<br><kbd>&lt;input type=\"text\" required&gt;</kbd><br><br>The browser shows a validation message automatically.",
"task": "Make both the name and email fields required by adding the <kbd>required</kbd> attribute.",
"title": "Обов'язкові поля",
"description": "Атрибут <kbd>required</kbd> запобігає відправці форми, якщо поле порожнє. Браузер автоматично показує повідомлення валідації - без JavaScript!<br><br>Додайте його до будь-якого поля, яке має бути заповнене:<br><kbd>&lt;input type=\"text\" required&gt;</kbd>",
"task": "Зробіть обидва поля (ім'я та email) обов'язковими, додавши атрибут <kbd>required</kbd> до кожного поля.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 350px; } label { display: block; margin-top: 15px; margin-bottom: 5px; } label:first-of-type { margin-top: 0; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } input:invalid { border-color: #d32f2f; } button { margin-top: 20px; padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; }",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 320px; } label { display: block; margin-top: 15px; margin-bottom: 5px; font-weight: 500; } label:first-of-type { margin-top: 0; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; box-sizing: border-box; } input:focus { outline: 2px solid steelblue; border-color: transparent; } button { margin-top: 20px; padding: 10px 20px; background: steelblue; color: white; border: none; border-radius: 6px; cursor: pointer; font-weight: 500; }",
"sandboxCSS": "",
"initialCode": "<form>\n <label for=\"name\">Name: *</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n \n <label for=\"email\">Email: *</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <button type=\"submit\">Submit</button>\n</form>",
"solution": "<form>\n <label for=\"name\">Name: *</label>\n <input type=\"text\" id=\"name\" name=\"name\" required>\n \n <label for=\"email\">Email: *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <button type=\"submit\">Submit</button>\n</form>",
"initialCode": "<form>\n <label for=\"name\">Ім'я *</label>\n <input type=\"text\" id=\"name\" name=\"name\">\n \n <label for=\"email\">Email *</label>\n <input type=\"email\" id=\"email\" name=\"email\">\n \n <button type=\"submit\">Надіслати</button>\n</form>",
"solution": "<form>\n <label for=\"name\">Ім'я *</label>\n <input type=\"text\" id=\"name\" name=\"name\" required>\n \n <label for=\"email\">Email *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <button type=\"submit\">Надіслати</button>\n</form>",
"previewContainer": "preview-area",
"validations": [
{
"type": "attribute_value",
"value": { "selector": "input[name='name']", "attr": "required", "value": true },
"message": "Add the <kbd>required</kbd> attribute to the name input"
"message": "Додайте <kbd>required</kbd> до поля імені"
},
{
"type": "attribute_value",
"value": { "selector": "input[name='email']", "attr": "required", "value": true },
"message": "Add the <kbd>required</kbd> attribute to the email input"
}
]
},
{
"id": "input-constraints",
"title": "Constraints",
"description": "Control what users can enter:<br><br><kbd>minlength</kbd> / <kbd>maxlength</kbd> - Text length limits<br><kbd>min</kbd> / <kbd>max</kbd> - Number range<br><kbd>pattern</kbd> - Regex pattern matching<br><kbd>placeholder</kbd> - Hint text (not a label!)",
"task": "Add validation to the password input:<br>1. Add <kbd>minlength=\"8\"</kbd> for minimum length<br>2. Add <kbd>maxlength=\"20\"</kbd> for maximum length<br>3. Add <kbd>placeholder=\"Enter password\"</kbd> as a hint",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 350px; } label { display: block; margin-bottom: 5px; } input { width: 100%; padding: 8px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; } input:invalid:not(:placeholder-shown) { border-color: #d32f2f; } small { display: block; font-size: 12px; color: #666; margin-top: 4px; } button { margin-top: 20px; padding: 10px 20px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; }",
"sandboxCSS": "",
"initialCode": "<form>\n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required aria-describedby=\"password-hint\">\n <small id=\"password-hint\">Must be 8-20 characters</small>\n \n <button type=\"submit\">Create Account</button>\n</form>",
"solution": "<form>\n <label for=\"password\">Password:</label>\n <input type=\"password\" id=\"password\" name=\"password\" required minlength=\"8\" maxlength=\"20\" placeholder=\"Enter password\" aria-describedby=\"password-hint\">\n <small id=\"password-hint\">Must be 8-20 characters</small>\n \n <button type=\"submit\">Create Account</button>\n</form>",
"previewContainer": "preview-area",
"validations": [
{
"type": "attribute_value",
"value": { "selector": "input[type='password']", "attr": "minlength", "value": "8" },
"message": "Add <kbd>minlength</kbd>=\"8\" to the password input"
},
{
"type": "attribute_value",
"value": { "selector": "input[type='password']", "attr": "maxlength", "value": "20" },
"message": "Add <kbd>maxlength</kbd>=\"20\" to the password input"
},
{
"type": "attribute_value",
"value": { "selector": "input[type='password']", "attr": "placeholder", "value": null },
"message": "Add a <kbd>placeholder</kbd> to hint what to enter"
}
]
},
{
"id": "complete-registration",
"title": "Full Form",
"description": "Build a complete registration form with all validation concepts:<br><br>- Required fields marked with *<br>- Email validation (use type=\"email\")<br>- Password with length constraints<br>- Terms checkbox (required)<br>- Submit button",
"task": "Complete the registration form. Add required attributes, proper input types, and validation constraints.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } form { max-width: 400px; background: #f5f5f5; padding: 25px; border-radius: 8px; } h2 { margin-top: 0; margin-bottom: 20px; } label { display: block; margin-top: 15px; margin-bottom: 5px; font-weight: 500; } input[type='text'], input[type='email'], input[type='password'] { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 4px; box-sizing: border-box; font-size: 14px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; } input[type='checkbox'] { width: auto; margin-right: 8px; vertical-align: middle; } label:has(input[type='checkbox']) { display: flex; align-items: center; font-weight: normal; } button { width: 100%; margin-top: 20px; padding: 12px; background: #1976d2; color: white; border: none; border-radius: 4px; cursor: pointer; font-size: 16px; font-weight: 500; } button:hover { background: #1565c0; }",
"sandboxCSS": "",
"initialCode": "<form>\n <h2>Create Account</h2>\n \n <label for=\"fullname\">Full Name *</label>\n <input type=\"text\" id=\"fullname\" name=\"fullname\">\n \n <label for=\"email\">Email *</label>\n <input id=\"email\" name=\"email\">\n \n <label for=\"password\">Password *</label>\n <input id=\"password\" name=\"password\">\n \n <label>\n <input type=\"checkbox\" id=\"terms\" name=\"terms\">\n I agree to the Terms of Service *\n </label>\n \n <button type=\"submit\">Register</button>\n</form>",
"solution": "<form>\n <h2>Create Account</h2>\n \n <label for=\"fullname\">Full Name *</label>\n <input type=\"text\" id=\"fullname\" name=\"fullname\" required>\n \n <label for=\"email\">Email *</label>\n <input type=\"email\" id=\"email\" name=\"email\" required>\n \n <label for=\"password\">Password *</label>\n <input type=\"password\" id=\"password\" name=\"password\" required minlength=\"8\">\n \n <label>\n <input type=\"checkbox\" id=\"terms\" name=\"terms\" required>\n I agree to the Terms of Service *\n </label>\n \n <button type=\"submit\">Register</button>\n</form>",
"previewContainer": "preview-area",
"validations": [
{
"type": "attribute_value",
"value": { "selector": "#fullname", "attr": "required", "value": true },
"message": "Make the full name field <kbd>required</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "#email", "attr": "type", "value": "email" },
"message": "Set the email input <kbd>type=\"email\"</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "#email", "attr": "required", "value": true },
"message": "Make the email field <kbd>required</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "#password", "attr": "type", "value": "password" },
"message": "Set the password input <kbd>type=\"password\"</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "#password", "attr": "required", "value": true },
"message": "Make the password field <kbd>required</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "#password", "attr": "minlength", "value": "8" },
"message": "Add <kbd>minlength</kbd>=\"8\" to password"
},
{
"type": "attribute_value",
"value": { "selector": "#terms", "attr": "required", "value": true },
"message": "Make the terms checkbox <kbd>required</kbd>"
"message": "Додайте <kbd>required</kbd> до поля email"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-details-summary",
"title": "HTML Details & Summary",
"description": "Create expandable content sections without JavaScript",
"description": "Створюйте розгортувані секції без JavaScript",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "details-summary-basic",
"title": "First Widget",
"description": "The <kbd>&lt;details&gt;</kbd> element creates a collapsible section. The <kbd>&lt;summary&gt;</kbd> provides the clickable label.<br><br>Click the summary to toggle the hidden content - no JavaScript needed!",
"task": "Create a <kbd>&lt;details&gt;</kbd> element with:<br>1. A <kbd>&lt;summary&gt;</kbd> saying <code>Click to reveal</code><br>2. A <kbd>&lt;p&gt;</kbd> with the text <code>This content was hidden!</code>",
"title": "Перший віджет",
"description": "Елемент <kbd>&lt;details&gt;</kbd> створює згортувану секцію. Елемент <kbd>&lt;summary&gt;</kbd> надає клікабельний заголовок.<br><br>Натисніть на summary, щоб показати прихований вміст - без JavaScript!",
"task": "Створіть елемент <kbd>&lt;details&gt;</kbd> з:<br>1. Елементом <kbd>&lt;summary&gt;</kbd> з текстом <code>Click to reveal</code><br>2. Елементом <kbd>&lt;p&gt;</kbd> з текстом <code>This content was hidden!</code>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; } summary { font-weight: 600; cursor: pointer; } summary:hover { color: #1976d2; } details p { margin-top: 15px; color: #666; }",
"sandboxCSS": "",
@@ -21,30 +21,30 @@
{
"type": "element_exists",
"value": "details",
"message": "Add a <kbd>&lt;details&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;details&gt;</kbd>"
},
{
"type": "element_exists",
"value": "summary",
"message": "Add a <kbd>&lt;summary&gt;</kbd> inside the details"
"message": "Додайте <kbd>&lt;summary&gt;</kbd> всередину details"
},
{
"type": "parent_child",
"value": { "parent": "details", "child": "summary" },
"message": "The <kbd>&lt;summary&gt;</kbd> must be inside <kbd>&lt;details&gt;</kbd>"
"message": "Елемент <kbd>&lt;summary&gt;</kbd> має бути всередині <kbd>&lt;details&gt;</kbd>"
},
{
"type": "parent_child",
"value": { "parent": "details", "child": "p" },
"message": "Add a <kbd>&lt;p&gt;</kbd> inside <kbd>&lt;details&gt;</kbd> for the hidden content"
"message": "Додайте <kbd>&lt;p&gt;</kbd> всередину <kbd>&lt;details&gt;</kbd> для прихованого вмісту"
}
]
},
{
"id": "details-open-attribute",
"title": "Pre-expanded Details",
"description": "By default, <kbd>&lt;details&gt;</kbd> is closed. Add the <kbd>open</kbd> attribute to show the content initially.<br><br>This is a boolean attribute - just add <kbd>open</kbd> without a value.",
"task": "Add the <kbd>open</kbd> attribute to the <kbd>&lt;details&gt;</kbd> element to show the content by default.",
"title": "Розгорнуто за замовчуванням",
"description": "За замовчуванням <kbd>&lt;details&gt;</kbd> закритий. Додайте атрибут <kbd>open</kbd>, щоб показати вміст спочатку.<br><br>Це булевий атрибут - просто додайте <kbd>open</kbd> без значення.",
"task": "Додайте атрибут <kbd>open</kbd> до елемента <kbd>&lt;details&gt;</kbd>, щоб показати вміст за замовчуванням.",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } details { border: 1px solid #ddd; border-radius: 8px; padding: 15px; background: #f9f9f9; } summary { font-weight: 600; cursor: pointer; } details p { margin-top: 15px; color: #666; }",
"sandboxCSS": "",
@@ -55,15 +55,15 @@
{
"type": "attribute_value",
"value": { "selector": "details", "attr": "open", "value": true },
"message": "Add the <kbd>open</kbd> attribute to <kbd>&lt;details&gt;</kbd>"
"message": "Додайте атрибут <kbd>open</kbd> до <kbd>&lt;details&gt;</kbd>"
}
]
},
{
"id": "faq-accordion",
"title": "FAQ Accordion",
"description": "Multiple <kbd>&lt;details&gt;</kbd> elements create an accordion-style FAQ. Each question can be expanded independently.<br><br><b>Pro tip:</b> Type <kbd>details*3&gt;summary+p</kbd> and press Tab for Emmet expansion. <kbd>*3</kbd> creates 3 elements, <kbd>&gt;</kbd> nests inside, <kbd>+</kbd> adds siblings.",
"task": "Create an FAQ section with:<br>1. An <kbd>&lt;h1&gt;</kbd> saying <code>Frequently Asked Questions</code><br>2. Three <kbd>&lt;details&gt;</kbd> elements, each with a question in <kbd>&lt;summary&gt;</kbd> and an answer in <kbd>&lt;p&gt;</kbd>",
"title": "FAQ акордеон",
"description": "Кілька елементів <kbd>&lt;details&gt;</kbd> створюють FAQ у стилі акордеону. Кожне питання можна розгортати незалежно.<br><br><b>Порада:</b> Введіть <kbd>details*3&gt;summary+p</kbd> і натисніть Tab для розгортання Emmet. <kbd>*3</kbd> створює 3 елементи, <kbd>&gt;</kbd> вкладає всередину, <kbd>+</kbd> додає сусідні.",
"task": "Створіть секцію FAQ з:<br>1. Заголовком <kbd>&lt;h1&gt;</kbd> з текстом <code>Frequently Asked Questions</code><br>2. Трьома елементами <kbd>&lt;details&gt;</kbd>, кожен з питанням у <kbd>&lt;summary&gt;</kbd> та відповіддю у <kbd>&lt;p&gt;</kbd>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; min-height: 100vh; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); display: flex; flex-direction: column; justify-content: center; padding: 40px; margin: 0; box-sizing: border-box; } h1 { font-size: 2.5rem; color: #4a4a4a; text-align: center; margin: 0 0 30px 0; } details { background: white; border-radius: 12px; margin-bottom: 15px; padding: 20px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } summary { font-size: 1.3rem; font-weight: 600; color: #5c5c5c; cursor: pointer; list-style: none; } summary::before { content: '▸ '; color: #fcb69f; } details[open] summary::before { content: '▾ '; } details p { margin: 15px 0 0 0; color: #666; line-height: 1.6; }",
"sandboxCSS": "",
@@ -74,22 +74,22 @@
{
"type": "element_exists",
"value": "h1",
"message": "Add an <kbd>&lt;h1&gt;</kbd> heading for the FAQ title"
"message": "Додайте заголовок <kbd>&lt;h1&gt;</kbd> для назви FAQ"
},
{
"type": "element_count",
"value": { "selector": "details", "min": 3 },
"message": "Create at least 3 <kbd>&lt;details&gt;</kbd> elements for the FAQ"
"message": "Створіть принаймні 3 елементи <kbd>&lt;details&gt;</kbd> для FAQ"
},
{
"type": "element_count",
"value": { "selector": "summary", "min": 3 },
"message": "Each <kbd>&lt;details&gt;</kbd> needs a <kbd>&lt;summary&gt;</kbd> for the question"
"message": "Кожен <kbd>&lt;details&gt;</kbd> потребує <kbd>&lt;summary&gt;</kbd> для питання"
},
{
"type": "element_count",
"value": { "selector": "details p", "min": 3 },
"message": "Each <kbd>&lt;details&gt;</kbd> needs a <kbd>&lt;p&gt;</kbd> for the answer"
"message": "Кожен <kbd>&lt;details&gt;</kbd> потребує <kbd>&lt;p&gt;</kbd> для відповіді"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-progress-meter",
"title": "HTML Progress & Meter",
"description": "Display completion status and scalar measurements natively",
"description": "Відображайте статус виконання та вимірювання нативно",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "progress-basic",
"title": "Progress Bars",
"description": "The <kbd>&lt;progress&gt;</kbd> element shows task completion. Use <kbd>value</kbd> for current progress and <kbd>max</kbd> for the total.<br><br><b>Note:</b> This is not a self-closing tag! Write <kbd>&lt;progress&gt;...&lt;/progress&gt;</kbd> with fallback text inside for older browsers.",
"task": "Create a progress bar showing 70% completion:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Download:</code><br>2. Add a <kbd>&lt;progress&gt;</kbd> with <kbd>value=\"70\"</kbd> and <kbd>max=\"100\"</kbd>",
"title": "Смуги прогресу",
"description": "Елемент <kbd>&lt;progress&gt;</kbd> показує виконання завдання. Використовуйте <kbd>value</kbd> для поточного прогресу та <kbd>max</kbd> для загального.<br><br><b>Примітка:</b> Це не самозакриваючий тег! Пишіть <kbd>&lt;progress&gt;...&lt;/progress&gt;</kbd> з резервним текстом всередині для старих браузерів.",
"task": "Створіть смугу прогресу, що показує 70% виконання:<br>1. Додайте <kbd>&lt;label&gt;</kbd> з текстом <code>Download:</code><br>2. Додайте <kbd>&lt;progress&gt;</kbd> з <kbd>value=\"70\"</kbd> та <kbd>max=\"100\"</kbd>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } progress { width: 100%; height: 20px; border-radius: 10px; } progress::-webkit-progress-bar { background: #e0e0e0; border-radius: 10px; } progress::-webkit-progress-value { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; } progress::-moz-progress-bar { background: linear-gradient(90deg, #4caf50, #8bc34a); border-radius: 10px; }",
"sandboxCSS": "",
@@ -21,30 +21,30 @@
{
"type": "element_exists",
"value": "progress",
"message": "Add a <kbd>&lt;progress&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;progress&gt;</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "progress", "attr": "value", "value": "70" },
"message": "Set <kbd>value=</kbd>\"70\" on the progress element"
"message": "Встановіть <kbd>value=</kbd>\"70\" в елементі progress"
},
{
"type": "attribute_value",
"value": { "selector": "progress", "attr": "max", "value": "100" },
"message": "Set <kbd>max=</kbd>\"100\" on the progress element"
"message": "Встановіть <kbd>max=</kbd>\"100\" в елементі progress"
},
{
"type": "element_exists",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for the progress bar"
"message": "Додайте <kbd>&lt;label&gt;</kbd> для смуги прогресу"
}
]
},
{
"id": "progress-indeterminate",
"title": "Indeterminate Progress",
"description": "When progress is unknown (like loading), omit the <kbd>value</kbd> attribute. This creates an animated indeterminate state.<br><br>Useful for network requests or processes with unknown duration.",
"task": "Create a loading indicator:<br>1. Add a <kbd>&lt;p&gt;</kbd> saying <code>Loading...</code><br>2. Add a <kbd>&lt;progress&gt;</kbd> without a value attribute",
"title": "Невизначений прогрес",
"description": "Коли прогрес невідомий (як при завантаженні), опустіть атрибут <kbd>value</kbd>. Це створює анімований невизначений стан.<br><br>Корисно для мережевих запитів або процесів з невідомою тривалістю.",
"task": "Створіть індикатор завантаження:<br>1. Додайте <kbd>&lt;p&gt;</kbd> з текстом <code>Loading...</code><br>2. Додайте <kbd>&lt;progress&gt;</kbd> без атрибута value",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } p { margin-bottom: 10px; color: #666; } progress { width: 100%; height: 8px; border-radius: 4px; } progress::-webkit-progress-bar { background: #e0e0e0; border-radius: 4px; }",
"sandboxCSS": "",
@@ -55,20 +55,20 @@
{
"type": "element_exists",
"value": "progress",
"message": "Add a <kbd>&lt;progress&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;progress&gt;</kbd>"
},
{
"type": "element_exists",
"value": "p",
"message": "Add a <kbd>&lt;p&gt;</kbd> with loading text"
"message": "Додайте <kbd>&lt;p&gt;</kbd> з текстом завантаження"
}
]
},
{
"id": "meter-gauge",
"title": "Meter Gauges",
"description": "The <kbd>&lt;meter&gt;</kbd> element displays a scalar value within a range. Use it for measurements like disk space, battery, or ratings.<br><br>Set <kbd>low</kbd>, <kbd>high</kbd>, and <kbd>optimum</kbd> to define good/bad ranges - the browser colors it accordingly!",
"task": "Create a battery level meter:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Battery:</code><br>2. Add a <kbd>&lt;meter&gt;</kbd> with:<br> - <kbd>value=\"0.8\"</kbd><br> - <kbd>min=\"0\"</kbd> and <kbd>max=\"1\"</kbd><br> - <kbd>low=\"0.2\"</kbd> and <kbd>high=\"0.8\"</kbd><br> - <kbd>optimum=\"1\"</kbd>",
"title": "Шкали meter",
"description": "Елемент <kbd>&lt;meter&gt;</kbd> відображає скалярне значення в діапазоні. Використовуйте його для вимірювань, як-от місце на диску, батарея або рейтинги.<br><br>Встановіть <kbd>low</kbd>, <kbd>high</kbd> та <kbd>optimum</kbd>, щоб визначити хороші/погані діапазони - браузер забарвлює відповідно!",
"task": "Створіть шкалу рівня батареї:<br>1. Додайте <kbd>&lt;label&gt;</kbd> з текстом <code>Battery:</code><br>2. Додайте <kbd>&lt;meter&gt;</kbd> з:<br> - <kbd>value=\"0.8\"</kbd><br> - <kbd>min=\"0\"</kbd> та <kbd>max=\"1\"</kbd><br> - <kbd>low=\"0.2\"</kbd> та <kbd>high=\"0.8\"</kbd><br> - <kbd>optimum=\"1\"</kbd>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } meter { width: 100%; height: 25px; }",
"sandboxCSS": "",
@@ -79,22 +79,42 @@
{
"type": "element_exists",
"value": "meter",
"message": "Add a <kbd>&lt;meter&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;meter&gt;</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "meter", "attr": "value", "value": "0.8" },
"message": "Set <kbd>value=</kbd>\"0.8\" on the meter"
"message": "Встановіть <kbd>value=</kbd>\"0.8\" в meter"
},
{
"type": "attribute_value",
"value": { "selector": "meter", "attr": "min", "value": "0" },
"message": "Встановіть <kbd>min=</kbd>\"0\" в meter"
},
{
"type": "attribute_value",
"value": { "selector": "meter", "attr": "max", "value": "1" },
"message": "Встановіть <kbd>max=</kbd>\"1\" в meter"
},
{
"type": "attribute_value",
"value": { "selector": "meter", "attr": "low", "value": "0.2" },
"message": "Set <kbd>low=</kbd>\"0.2\" to define the low threshold"
"message": "Встановіть <kbd>low=</kbd>\"0.2\", щоб визначити низький поріг"
},
{
"type": "attribute_value",
"value": { "selector": "meter", "attr": "high", "value": "0.8" },
"message": "Встановіть <kbd>high=</kbd>\"0.8\", щоб визначити високий поріг"
},
{
"type": "attribute_value",
"value": { "selector": "meter", "attr": "optimum", "value": "1" },
"message": "Встановіть <kbd>optimum=</kbd>\"1\", щоб вказати оптимальне значення"
},
{
"type": "element_exists",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for the meter"
"message": "Додайте <kbd>&lt;label&gt;</kbd> для meter"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-datalist",
"title": "Datalist",
"description": "Provide suggestions for text inputs without JavaScript",
"description": "Додайте підказки для текстових полів без JavaScript",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "datalist-basic",
"title": "Input with Suggestions",
"description": "The <kbd>&lt;datalist&gt;</kbd> element provides autocomplete suggestions for inputs. Connect it using the <kbd>list</kbd> attribute on the input matching the datalist's <kbd>id</kbd>.<br><br>Users can still type freely - suggestions are just helpers!",
"task": "Create a browser selector:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Browser:</code><br>2. Add an <kbd>&lt;input&gt;</kbd> with <kbd>list=\"browsers\"</kbd><br>3. Add a <kbd>&lt;datalist id=\"browsers\"&gt;</kbd> with options for Chrome, Firefox, and Safari",
"title": "Поле з підказками",
"description": "Елемент <kbd>&lt;datalist&gt;</kbd> надає підказки автозаповнення для полів. З'єднайте його за допомогою атрибута <kbd>list</kbd> на полі, що відповідає <kbd>id</kbd> datalist.<br><br>Користувачі все одно можуть вводити вільно - підказки лише помічники!",
"task": "Створіть селектор браузера:<br>1. Додайте <kbd>&lt;label&gt;</kbd> з текстом <code>Браузер:</code><br>2. Додайте <kbd>&lt;input&gt;</kbd> з <kbd>list=\"browsers\"</kbd><br>3. Додайте <kbd>&lt;datalist id=\"browsers\"&gt;</kbd> з варіантами Chrome, Firefox та Safari",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; } label { display: block; margin-bottom: 8px; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ccc; border-radius: 6px; font-size: 16px; } input:focus { outline: 2px solid #1976d2; border-color: transparent; }",
"sandboxCSS": "",
@@ -21,30 +21,30 @@
{
"type": "element_exists",
"value": "datalist",
"message": "Add a <kbd>&lt;datalist&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;datalist&gt;</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "input", "attr": "list", "value": "browsers" },
"message": "Connect the input to datalist using <kbd>list=</kbd>\"browsers\""
"message": "З'єднайте поле з datalist за допомогою <kbd>list=</kbd>\"browsers\""
},
{
"type": "element_count",
"value": { "selector": "option", "min": 3 },
"message": "Add at least 3 <kbd>&lt;option&gt;</kbd> elements inside <kbd>&lt;datalist&gt;</kbd>"
"message": "Додайте принаймні 3 елементи <kbd>&lt;option&gt;</kbd> всередину <kbd>&lt;datalist&gt;</kbd>"
},
{
"type": "element_exists",
"value": "label",
"message": "Add a <kbd>&lt;label&gt;</kbd> for the input"
"message": "Додайте <kbd>&lt;label&gt;</kbd> для поля"
}
]
},
{
"id": "datalist-countries",
"title": "Country Selector",
"description": "Datalists work great for long lists like countries. Users can type to filter suggestions instantly.<br><br>The <kbd>value</kbd> attribute is what gets entered, and you can add display text after it.",
"task": "Create a country input:<br>1. Add a <kbd>&lt;label&gt;</kbd> saying <code>Country:</code><br>2. Add an <kbd>&lt;input&gt;</kbd> with <kbd>list=\"countries\"</kbd><br>3. Add a <kbd>&lt;datalist id=\"countries\"&gt;</kbd> with at least 4 country options",
"title": "Селектор країн",
"description": "Datalist чудово працює для довгих списків, як країни. Користувачі можуть вводити, щоб миттєво фільтрувати підказки.<br><br>Атрибут <kbd>value</kbd> - це те, що вводиться, і ви можете додати текст для відображення після нього.",
"task": "Створіть поле країни:<br>1. Додайте <kbd>&lt;label&gt;</kbd> з текстом <code>Країна:</code><br>2. Додайте <kbd>&lt;input&gt;</kbd> з <kbd>list=\"countries\"</kbd><br>3. Додайте <kbd>&lt;datalist id=\"countries\"&gt;</kbd> з принаймні 4 варіантами країн",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 30px; background: linear-gradient(135deg, #e0f7fa 0%, #b2ebf2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } label { display: block; margin-bottom: 10px; font-weight: 600; color: #00695c; } input { width: 100%; padding: 12px 15px; border: 2px solid #26a69a; border-radius: 8px; font-size: 16px; background: white; } input:focus { outline: none; border-color: #00695c; box-shadow: 0 0 0 3px rgba(38,166,154,0.2); }",
"sandboxCSS": "",
@@ -55,22 +55,22 @@
{
"type": "element_exists",
"value": "datalist",
"message": "Add a <kbd>&lt;datalist&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;datalist&gt;</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "datalist", "attr": "id", "value": "countries" },
"message": "Set <kbd>id=</kbd>\"countries\" on the datalist"
"message": "Встановіть <kbd>id=</kbd>\"countries\" в datalist"
},
{
"type": "attribute_value",
"value": { "selector": "input", "attr": "list", "value": "countries" },
"message": "Connect the input using <kbd>list=</kbd>\"countries\""
"message": "З'єднайте поле за допомогою <kbd>list=</kbd>\"countries\""
},
{
"type": "element_count",
"value": { "selector": "option", "min": 4 },
"message": "Add at least 4 country options"
"message": "Додайте принаймні 4 варіанти країн"
}
]
}

View File

@@ -1,16 +1,16 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-dialog",
"title": "Dialogs",
"description": "Create modal dialogs without JavaScript libraries",
"title": "Діалоги",
"description": "Створюйте модальні діалоги без JavaScript бібліотек",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "dialog-basic",
"title": "Open Dialog",
"description": "The <kbd>&lt;dialog&gt;</kbd> element creates a native modal. Add the <kbd>open</kbd> attribute to show it.<br><br>Use <kbd>&lt;form method=\"dialog\"&gt;</kbd> inside to close it when the form submits - no JavaScript needed!",
"task": "Create a dialog with:<br>1. The <kbd>open</kbd> attribute to show it<br>2. An <kbd>&lt;h2&gt;</kbd> saying <code>Welcome!</code><br>3. A <kbd>&lt;p&gt;</kbd> with a greeting message<br>4. A <kbd>&lt;form method=\"dialog\"&gt;</kbd> with a close button",
"title": "Відкрити діалог",
"description": "Елемент <kbd>&lt;dialog&gt;</kbd> створює нативне модальне вікно. Додайте атрибут <kbd>open</kbd>, щоб показати його.<br><br>Використовуйте <kbd>&lt;form method=\"dialog\"&gt;</kbd> всередині, щоб закрити його при відправці форми - без JavaScript!",
"task": "Створіть діалог з:<br>1. Атрибутом <kbd>open</kbd> для відображення<br>2. Елементом <kbd>&lt;h2&gt;</kbd> з текстом <code>Вітаємо!</code><br>3. Елементом <kbd>&lt;p&gt;</kbd> з привітальним повідомленням<br>4. Елементом <kbd>&lt;form method=\"dialog\"&gt;</kbd> з кнопкою закриття",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: #f5f5f5; } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 400px; } dialog::backdrop { background: rgba(0,0,0,0.5); } dialog h2 { margin: 0 0 15px 0; color: #1976d2; } dialog p { color: #666; margin: 0 0 20px 0; } dialog button { background: #1976d2; color: white; border: none; padding: 10px 25px; border-radius: 6px; cursor: pointer; font-size: 16px; } dialog button:hover { background: #1565c0; }",
"sandboxCSS": "",
@@ -21,35 +21,35 @@
{
"type": "element_exists",
"value": "dialog",
"message": "Add a <kbd>&lt;dialog&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;dialog&gt;</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "dialog", "attr": "open", "value": true },
"message": "Add the <kbd>open</kbd> attribute to show the dialog"
"message": "Додайте атрибут <kbd>open</kbd> щоб показати діалог"
},
{
"type": "element_exists",
"value": "dialog h2",
"message": "Add an <kbd>&lt;h2&gt;</kbd> heading inside the dialog"
"message": "Додайте заголовок <kbd>&lt;h2&gt;</kbd> всередині діалогу"
},
{
"type": "element_exists",
"value": "form[method='dialog']",
"message": "Add a <kbd>&lt;form method=\"dialog\"&gt;</kbd> for closing"
"message": "Додайте <kbd>&lt;form method=\"dialog\"&gt;</kbd> для закриття"
},
{
"type": "element_exists",
"value": "dialog button",
"message": "Add a close button inside the form"
"message": "Додайте кнопку закриття всередині форми"
}
]
},
{
"id": "dialog-form",
"title": "Dialog + Form",
"description": "Dialogs can contain full forms. The <kbd>method=\"dialog\"</kbd> makes the form close the dialog on submit instead of sending data.<br><br>This pattern is perfect for confirmation dialogs, quick inputs, or settings panels.",
"task": "Create a confirmation dialog:<br>1. Add <kbd>open</kbd> to show it<br>2. An <kbd>&lt;h2&gt;</kbd> saying <code>Confirm Delete</code><br>3. A <kbd>&lt;p&gt;</kbd> asking <code>Are you sure?</code><br>4. A <kbd>&lt;form method=\"dialog\"&gt;</kbd> with Cancel and Delete buttons",
"title": "Діалог + Форма",
"description": "Діалоги можуть містити повні форми. <kbd>method=\"dialog\"</kbd> змушує форму закривати діалог при відправці замість відправки даних.<br><br>Цей шаблон ідеальний для діалогів підтвердження, швидкого введення або панелей налаштувань.",
"task": "Створіть діалог підтвердження:<br>1. Додайте <kbd>open</kbd> для відображення<br>2. Елемент <kbd>&lt;h2&gt;</kbd> з текстом <code>Підтвердити видалення</code><br>3. Елемент <kbd>&lt;p&gt;</kbd> з питанням <code>Ви впевнені?</code><br>4. Елемент <kbd>&lt;form method=\"dialog\"&gt;</kbd> з кнопками Скасувати та Видалити",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; min-height: 200px; background: linear-gradient(135deg, #fce4ec 0%, #f8bbd9 100%); } dialog { border: none; border-radius: 12px; box-shadow: 0 10px 40px rgba(0,0,0,0.2); padding: 25px; max-width: 350px; text-align: center; } dialog h2 { margin: 0 0 10px 0; color: #c62828; } dialog p { color: #666; margin: 0 0 20px 0; } dialog form { display: flex; gap: 10px; justify-content: center; } dialog button { padding: 10px 20px; border-radius: 6px; cursor: pointer; font-size: 14px; border: none; } dialog button:first-child { background: #e0e0e0; color: #333; } dialog button:last-child { background: #c62828; color: white; } dialog button:hover { opacity: 0.9; }",
"sandboxCSS": "",
@@ -60,22 +60,22 @@
{
"type": "element_exists",
"value": "dialog[open]",
"message": "Add a <kbd>&lt;dialog&gt;</kbd> with the open attribute"
"message": "Додайте <kbd>&lt;dialog&gt;</kbd> з атрибутом open"
},
{
"type": "element_exists",
"value": "dialog h2",
"message": "Add a heading to the dialog"
"message": "Додайте заголовок до діалогу"
},
{
"type": "element_exists",
"value": "form[method='dialog']",
"message": "Add a <kbd>&lt;form method=\"dialog\"&gt;</kbd> for the buttons"
"message": "Додайте <kbd>&lt;form method=\"dialog\"&gt;</kbd> для кнопок"
},
{
"type": "element_count",
"value": { "selector": "dialog button", "min": 2 },
"message": "Add at least 2 buttons (Cancel and Confirm)"
"message": "Додайте принаймні 2 кнопки (Скасувати та Підтвердити)"
}
]
}

View File

@@ -1,16 +1,16 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-forms-fieldset",
"title": "Fieldsets",
"description": "Group form controls with fieldset and legend elements",
"title": "Fieldset",
"description": "Групуйте елементи форми за допомогою fieldset та legend",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "fieldset-basic",
"title": "Grouping with Fieldset",
"description": "The <kbd>&lt;fieldset&gt;</kbd> element groups related form controls together. Add a <kbd>&lt;legend&gt;</kbd> as the first child to give the group a title.<br><br>This helps with accessibility and visual organization of complex forms.",
"task": "Create a form with a fieldset:<br>1. A <kbd>&lt;form&gt;</kbd> element<br>2. A <kbd>&lt;fieldset&gt;</kbd> inside<br>3. A <kbd>&lt;legend&gt;</kbd> saying <code>Personal Info</code><br>4. Two labeled inputs for name and email",
"title": "Групування з Fieldset",
"description": "Елемент <kbd>&lt;fieldset&gt;</kbd> групує пов'язані елементи форми разом. Додайте <kbd>&lt;legend&gt;</kbd> як перший дочірній елемент, щоб дати групі заголовок.<br><br>Це допомагає з доступністю та візуальною організацією складних форм.",
"task": "Створіть форму з fieldset:<br>1. Елемент <kbd>&lt;form&gt;</kbd><br>2. Елемент <kbd>&lt;fieldset&gt;</kbd> всередині<br>3. Елемент <kbd>&lt;legend&gt;</kbd> з текстом <code>Особисті дані</code><br>4. Два підписаних поля для імені та email",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f0f4f8; } form { max-width: 400px; } fieldset { border: 2px solid #3498db; border-radius: 10px; padding: 20px; background: white; } legend { color: #3498db; font-weight: 600; padding: 0 10px; font-size: 1.1rem; } label { display: block; margin: 15px 0 5px; color: #333; font-weight: 500; } input { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; } input:focus { outline: 2px solid #3498db; border-color: transparent; }",
"sandboxCSS": "",
@@ -21,35 +21,35 @@
{
"type": "element_exists",
"value": "form",
"message": "Add a <kbd>&lt;form&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;form&gt;</kbd>"
},
{
"type": "element_exists",
"value": "fieldset",
"message": "Add a <kbd>&lt;fieldset&gt;</kbd> inside the form"
"message": "Додайте <kbd>&lt;fieldset&gt;</kbd> всередину форми"
},
{
"type": "element_exists",
"value": "legend",
"message": "Add a <kbd>&lt;legend&gt;</kbd> to title your fieldset"
"message": "Додайте <kbd>&lt;legend&gt;</kbd> як заголовок fieldset"
},
{
"type": "element_count",
"value": { "selector": "label", "min": 2 },
"message": "Add at least 2 labels"
"message": "Додайте принаймні 2 мітки"
},
{
"type": "element_count",
"value": { "selector": "input", "min": 2 },
"message": "Add at least 2 input fields"
"message": "Додайте принаймні 2 поля введення"
}
]
},
{
"id": "fieldset-textarea",
"title": "Adding Textarea",
"description": "The <kbd>&lt;textarea&gt;</kbd> element creates a multi-line text input, perfect for longer content like messages or descriptions.<br><br>Use <kbd>rows</kbd> and <kbd>cols</kbd> attributes to set default size.",
"task": "Create a contact form:<br>1. A <kbd>&lt;fieldset&gt;</kbd> with <kbd>&lt;legend&gt;</kbd> <code>Contact Us</code><br>2. A labeled <kbd>&lt;input&gt;</kbd> for email<br>3. A labeled <kbd>&lt;textarea&gt;</kbd> for the message<br>4. A submit <kbd>&lt;button&gt;</kbd>",
"title": "Додавання Textarea",
"description": "Елемент <kbd>&lt;textarea&gt;</kbd> створює багаторядкове текстове поле, ідеальне для довшого вмісту як повідомлення або описи.<br><br>Використовуйте атрибути <kbd>rows</kbd> та <kbd>cols</kbd> для встановлення розміру за замовчуванням.",
"task": "Створіть контактну форму:<br>1. Елемент <kbd>&lt;fieldset&gt;</kbd> з <kbd>&lt;legend&gt;</kbd> <code>Зв'яжіться з нами</code><br>2. Підписане <kbd>&lt;input&gt;</kbd> для email<br>3. Підписане <kbd>&lt;textarea&gt;</kbd> для повідомлення<br>4. Кнопка <kbd>&lt;button&gt;</kbd> відправки",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } form { max-width: 450px; margin: 0 auto; } fieldset { border: none; border-radius: 15px; padding: 30px; background: white; box-shadow: 0 10px 40px rgba(0,0,0,0.2); } legend { color: #667eea; font-weight: 700; padding: 0; font-size: 1.5rem; margin-bottom: 10px; } label { display: block; margin: 20px 0 8px; color: #333; font-weight: 500; } input, textarea { width: 100%; padding: 12px; border: 2px solid #e0e0e0; border-radius: 8px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: none; border-color: #667eea; } textarea { resize: vertical; min-height: 100px; } button { margin-top: 20px; width: 100%; padding: 14px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); color: white; border: none; border-radius: 8px; font-size: 16px; font-weight: 600; cursor: pointer; } button:hover { opacity: 0.9; }",
"sandboxCSS": "",
@@ -60,35 +60,35 @@
{
"type": "element_exists",
"value": "fieldset",
"message": "Add a <kbd>&lt;fieldset&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;fieldset&gt;</kbd>"
},
{
"type": "element_exists",
"value": "legend",
"message": "Add a <kbd>&lt;legend&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;legend&gt;</kbd>"
},
{
"type": "element_exists",
"value": "textarea",
"message": "Add a <kbd>&lt;textarea&gt;</kbd> for the message"
"message": "Додайте <kbd>&lt;textarea&gt;</kbd> для повідомлення"
},
{
"type": "element_exists",
"value": "button",
"message": "Add a submit button"
"message": "Додайте кнопку відправки"
},
{
"type": "element_exists",
"value": "input",
"message": "Add an input field for email"
"message": "Додайте поле для email"
}
]
},
{
"id": "fieldset-multiple",
"title": "Multiple Fieldsets",
"description": "Complex forms can use multiple <kbd>&lt;fieldset&gt;</kbd> elements to organize different sections.<br><br>This improves usability for long forms like registration or checkout pages.",
"task": "Create a registration form with 2 fieldsets:<br>1. <code>Account Info</code> with username and password inputs<br>2. <code>Preferences</code> with a textarea for bio<br>3. A submit button outside the fieldsets",
"title": "Кілька Fieldsetів",
"description": "Складні форми можуть використовувати кілька елементів <kbd>&lt;fieldset&gt;</kbd> для організації різних секцій.<br><br>Це покращує зручність використання довгих форм як реєстрація або оформлення замовлення.",
"task": "Створіть форму реєстрації з 2 fieldsetами:<br>1. <code>Дані облікового запису</code> з полями ім'я користувача та пароль<br>2. <code>Налаштування</code> з textarea для біо<br>3. Кнопка відправки поза fieldsetами",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } form { max-width: 500px; } fieldset { border: 1px solid #ddd; border-radius: 10px; padding: 20px; margin-bottom: 20px; background: white; } legend { color: #2c3e50; font-weight: 600; padding: 0 10px; } label { display: block; margin: 15px 0 5px; color: #555; } input, textarea { width: 100%; padding: 10px; border: 1px solid #ddd; border-radius: 6px; font-size: 16px; box-sizing: border-box; font-family: inherit; } input:focus, textarea:focus { outline: 2px solid #3498db; border-color: transparent; } textarea { resize: vertical; min-height: 80px; } button { width: 100%; padding: 14px; background: #2c3e50; color: white; border: none; border-radius: 8px; font-size: 16px; cursor: pointer; } button:hover { background: #34495e; }",
"sandboxCSS": "",
@@ -99,27 +99,27 @@
{
"type": "element_count",
"value": { "selector": "fieldset", "min": 2 },
"message": "Create at least 2 fieldsets"
"message": "Створіть принаймні 2 fieldseti"
},
{
"type": "element_count",
"value": { "selector": "legend", "min": 2 },
"message": "Add a legend to each fieldset"
"message": "Додайте legend до кожного fieldseta"
},
{
"type": "element_exists",
"value": "textarea",
"message": "Add a textarea for the bio"
"message": "Додайте textarea для біо"
},
{
"type": "element_exists",
"value": "button",
"message": "Add a submit button"
"message": "Додайте кнопку відправки"
},
{
"type": "element_count",
"value": { "selector": "input", "min": 2 },
"message": "Add at least 2 input fields"
"message": "Додайте принаймні 2 поля введення"
}
]
}

View File

@@ -1,125 +1,42 @@
{
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-tables",
"title": "HTML Tables",
"description": "Create structured data tables with headers and captions",
"title": "Таблиці HTML",
"description": "Створюйте структуровані таблиці даних із семантичною розміткою",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "table-basic",
"title": "Basic Table Structure",
"description": "Tables use <kbd>&lt;table&gt;</kbd> with <kbd>&lt;tr&gt;</kbd> for rows. Inside rows, use <kbd>&lt;th&gt;</kbd> for headers and <kbd>&lt;td&gt;</kbd> for data cells.<br><br>The <kbd>&lt;caption&gt;</kbd> element provides an accessible title for the table.",
"task": "Create a simple table with:<br>1. A <kbd>&lt;caption&gt;</kbd> saying <code>Fruit Prices</code><br>2. A header row with <code>Fruit</code> and <code>Price</code> columns<br>3. At least 2 data rows",
"title": "Таблиці даних",
"description": "Таблиці відображають структуровані дані в рядках і стовпцях. Використовуйте <kbd>&lt;table&gt;</kbd> як контейнер, <kbd>&lt;tr&gt;</kbd> для рядків, <kbd>&lt;th&gt;</kbd> для комірок заголовка та <kbd>&lt;td&gt;</kbd> для комірок даних.<br><br>Додайте <kbd>&lt;caption&gt;</kbd> для доступного заголовка, що описує вміст таблиці.",
"task": "Створіть таблицю цін:<br>1. Елемент <kbd>&lt;caption&gt;</kbd> з текстом <code>Pricing</code><br>2. Рядок заголовка з <code>Plan</code> та <code>Price</code><br>3. Два рядки даних для Basic ($9) та Pro ($29)",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px; font-weight: 600; font-size: 1.2rem; color: #333; background: #f8f9fa; } th, td { padding: 12px 20px; text-align: left; border-bottom: 1px solid #eee; } th { background: #3498db; color: white; font-weight: 500; } tr:hover { background: #f8f9fa; } tr:last-child td { border-bottom: none; }",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; } table { border-collapse: collapse; width: 100%; max-width: 350px; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 16px; font-weight: 600; font-size: 1.1rem; color: #333; background: #f8f9fa; } th, td { padding: 14px 20px; text-align: left; border-bottom: 1px solid #eee; } th { background: steelblue; color: white; font-weight: 500; } tr:last-child td { border-bottom: none; } tr:hover td { background: #f8f9fa; }",
"sandboxCSS": "",
"initialCode": "",
"solution": "<table>\n <caption>Fruit Prices</caption>\n <tr>\n <th>Fruit</th>\n <th>Price</th>\n </tr>\n <tr>\n <td>Apple</td>\n <td>$1.50</td>\n </tr>\n <tr>\n <td>Banana</td>\n <td>$0.75</td>\n </tr>\n</table>",
"solution": "<table>\n <caption>Pricing</caption>\n <tr>\n <th>Plan</th>\n <th>Price</th>\n </tr>\n <tr>\n <td>Basic</td>\n <td>$9</td>\n </tr>\n <tr>\n <td>Pro</td>\n <td>$29</td>\n </tr>\n</table>",
"previewContainer": "preview-area",
"validations": [
{
"type": "element_exists",
"value": "table",
"message": "Add a <kbd>&lt;table&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;table&gt;</kbd>"
},
{
"type": "element_exists",
"value": "caption",
"message": "Add a <kbd>&lt;caption&gt;</kbd> for the table title"
"message": "Додайте <kbd>&lt;caption&gt;</kbd> для заголовка таблиці"
},
{
"type": "element_count",
"value": { "selector": "th", "min": 2 },
"message": "Add at least 2 header cells (th)"
"message": "Додайте комірки заголовка (<kbd>&lt;th&gt;</kbd>) для Plan та Price"
},
{
"type": "element_count",
"value": { "selector": "tr", "min": 3 },
"message": "Add at least 3 rows (1 header + 2 data rows)"
}
]
},
{
"id": "table-thead-tbody",
"title": "Table Head & Body",
"description": "Use <kbd>&lt;thead&gt;</kbd> to group header rows and <kbd>&lt;tbody&gt;</kbd> to group data rows. This helps browsers and assistive technology understand the table structure.<br><br>You can also use <kbd>&lt;tfoot&gt;</kbd> for footer rows like totals.",
"task": "Create a structured table:<br>1. A <kbd>&lt;caption&gt;</kbd> with <code>Monthly Sales</code><br>2. A <kbd>&lt;thead&gt;</kbd> with Month and Revenue headers<br>3. A <kbd>&lt;tbody&gt;</kbd> with at least 2 data rows",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 100vh; margin: 0; box-sizing: border-box; } table { border-collapse: collapse; width: 100%; max-width: 450px; background: white; border-radius: 12px; overflow: hidden; box-shadow: 0 10px 30px rgba(0,0,0,0.2); } caption { padding: 20px; font-weight: 700; font-size: 1.3rem; color: white; background: transparent; text-shadow: 0 2px 4px rgba(0,0,0,0.2); caption-side: top; } thead { background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); } th { padding: 15px 20px; text-align: left; color: white; font-weight: 500; } tbody tr { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } td { padding: 15px 20px; color: #333; } tbody tr:last-child { border-bottom: none; }",
"sandboxCSS": "",
"initialCode": "",
"solution": "<table>\n <caption>Monthly Sales</caption>\n <thead>\n <tr>\n <th>Month</th>\n <th>Revenue</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>January</td>\n <td>$12,500</td>\n </tr>\n <tr>\n <td>February</td>\n <td>$14,200</td>\n </tr>\n </tbody>\n</table>",
"previewContainer": "preview-area",
"validations": [
{
"type": "element_exists",
"value": "table",
"message": "Add a <kbd>&lt;table&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "caption",
"message": "Add a <kbd>&lt;caption&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "thead",
"message": "Add a <kbd>&lt;thead&gt;</kbd> for the header section"
},
{
"type": "element_exists",
"value": "tbody",
"message": "Add a <kbd>&lt;tbody&gt;</kbd> for the data rows"
},
{
"type": "element_count",
"value": { "selector": "tbody tr", "min": 2 },
"message": "Add at least 2 data rows in tbody"
}
]
},
{
"id": "table-complete",
"title": "Complete Table with Footer",
"description": "Add <kbd>&lt;tfoot&gt;</kbd> to create a footer section for totals or summary data. The footer stays at the bottom even if tbody has many rows.<br><br>Combine all sections for a fully structured, accessible table.",
"task": "Create a complete table:<br>1. A <kbd>&lt;caption&gt;</kbd> with <code>Order Summary</code><br>2. A <kbd>&lt;thead&gt;</kbd> with Item and Price headers<br>3. A <kbd>&lt;tbody&gt;</kbd> with 2 items<br>4. A <kbd>&lt;tfoot&gt;</kbd> with a Total row",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #fafafa; } table { border-collapse: collapse; width: 100%; max-width: 400px; background: white; border-radius: 10px; overflow: hidden; box-shadow: 0 4px 15px rgba(0,0,0,0.1); } caption { padding: 15px 20px; font-weight: 600; font-size: 1.1rem; color: #333; text-align: left; background: #f8f9fa; border-bottom: 2px solid #eee; } th, td { padding: 12px 20px; text-align: left; } thead th { background: #2c3e50; color: white; } tbody td { border-bottom: 1px solid #eee; } tbody tr:hover { background: #f8f9fa; } tfoot { background: #ecf0f1; font-weight: 600; } tfoot td { border-top: 2px solid #2c3e50; color: #2c3e50; }",
"sandboxCSS": "",
"initialCode": "",
"solution": "<table>\n <caption>Order Summary</caption>\n <thead>\n <tr>\n <th>Item</th>\n <th>Price</th>\n </tr>\n </thead>\n <tbody>\n <tr>\n <td>Widget</td>\n <td>$25.00</td>\n </tr>\n <tr>\n <td>Gadget</td>\n <td>$35.00</td>\n </tr>\n </tbody>\n <tfoot>\n <tr>\n <td>Total</td>\n <td>$60.00</td>\n </tr>\n </tfoot>\n</table>",
"previewContainer": "preview-area",
"validations": [
{
"type": "element_exists",
"value": "table",
"message": "Add a <kbd>&lt;table&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "caption",
"message": "Add a <kbd>&lt;caption&gt;</kbd> element"
},
{
"type": "element_exists",
"value": "thead",
"message": "Add a <kbd>&lt;thead&gt;</kbd> section"
},
{
"type": "element_exists",
"value": "tbody",
"message": "Add a <kbd>&lt;tbody&gt;</kbd> section"
},
{
"type": "element_exists",
"value": "tfoot",
"message": "Add a <kbd>&lt;tfoot&gt;</kbd> section for the total"
},
{
"type": "element_count",
"value": { "selector": "tbody tr", "min": 2 },
"message": "Add at least 2 item rows in tbody"
"message": "Додайте 3 рядки (1 заголовок + 2 рядки даних)"
}
]
}

View File

@@ -2,15 +2,15 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-marquee",
"title": "HTML Marquee",
"description": "Create scrolling text with the classic (deprecated but fun!) marquee element",
"description": "Створюйте біжучий текст за допомогою класичного (застарілого але веселого!) елемента marquee",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "marquee-basic",
"title": "Scrolling Text",
"description": "The <kbd>&lt;marquee&gt;</kbd> element creates scrolling text - a classic from the early web! While deprecated, it still works in most browsers.<br><br>Note: For production, use CSS animations instead. But for learning and fun, marquee is great!",
"task": "Create a simple marquee:<br>1. Add a <kbd>&lt;marquee&gt;</kbd> element<br>2. Put some text inside like <code>Welcome to my website!</code>",
"title": "Біжучий текст",
"description": "Елемент <kbd>&lt;marquee&gt;</kbd> створює біжучий текст - класика раннього вебу! Хоча він застарілий, все ще працює в більшості браузерів.<br><br>Примітка: Для продакшену використовуйте CSS-анімації. Але для навчання та розваги marquee чудовий!",
"task": "Створіть простий marquee:<br>1. Додайте елемент <kbd>&lt;marquee&gt;</kbd><br>2. Помістіть текст всередину, наприклад <code>Ласкаво просимо на мій сайт!</code>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #0f0c29 0%, #302b63 50%, #24243e 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2rem; color: #00ff00; text-shadow: 0 0 10px #00ff00, 0 0 20px #00ff00; font-family: 'Courier New', monospace; }",
"sandboxCSS": "",
@@ -21,15 +21,15 @@
{
"type": "element_exists",
"value": "marquee",
"message": "Add a <kbd>&lt;marquee&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;marquee&gt;</kbd>"
}
]
},
{
"id": "marquee-direction",
"title": "Direction & Behavior",
"description": "Control the marquee with attributes:<br>• <kbd>direction</kbd>: left, right, up, down<br>• <kbd>behavior</kbd>: scroll (default), slide (stops at edge), alternate (bounces)<br>• <kbd>scrollamount</kbd>: speed (default is 6)",
"task": "Create a bouncing marquee:<br>1. Add a <kbd>&lt;marquee&gt;</kbd> element<br>2. Set <kbd>behavior=\"alternate\"</kbd> to make it bounce<br>3. Add some fun text",
"title": "Напрямок та поведінка",
"description": "Керуйте marquee за допомогою атрибутів:<br>• <kbd>direction</kbd>: left, right, up, down<br>• <kbd>behavior</kbd>: scroll (за замовчуванням), slide (зупиняється на краю), alternate (відбивається)<br>• <kbd>scrollamount</kbd>: швидкість (за замовчуванням 6)",
"task": "Створіть marquee що відбивається:<br>1. Додайте елемент <kbd>&lt;marquee&gt;</kbd><br>2. Встановіть <kbd>behavior=\"alternate\"</kbd> щоб він відбивався<br>3. Додайте веселий текст",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ff6b6b 0%, #feca57 100%); min-height: 150px; display: flex; align-items: center; } marquee { font-size: 2.5rem; color: white; text-shadow: 2px 2px 4px rgba(0,0,0,0.3); font-weight: bold; }",
"sandboxCSS": "",
@@ -40,20 +40,20 @@
{
"type": "element_exists",
"value": "marquee",
"message": "Add a <kbd>&lt;marquee&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;marquee&gt;</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "marquee", "attr": "behavior", "value": "alternate" },
"message": "Add <kbd>behavior=</kbd>\"alternate\" to make it bounce"
"message": "Додайте <kbd>behavior=</kbd>\"alternate\" щоб він відбивався"
}
]
},
{
"id": "marquee-retro",
"title": "Retro News Ticker",
"description": "Combine multiple marquee attributes for a classic news ticker effect. You can even put multiple elements inside!<br><br>Remember: This is deprecated HTML. Modern sites use CSS animations, but marquee is great for understanding web history.",
"task": "Create a news ticker:<br>1. A <kbd>&lt;marquee&gt;</kbd> with <kbd>direction=\"left\"</kbd><br>2. Set <kbd>scrollamount=\"5\"</kbd> for smooth scrolling<br>3. Add a breaking news headline inside",
"title": "Ретро-стрічка новин",
"description": "Поєднайте кілька атрибутів marquee для класичного ефекту стрічки новин. Ви навіть можете помістити кілька елементів всередину!<br><br>Пам'ятайте: Це застарілий HTML. Сучасні сайти використовують CSS-анімації, але marquee чудовий для розуміння історії вебу.",
"task": "Створіть стрічку новин:<br>1. Елемент <kbd>&lt;marquee&gt;</kbd> з <kbd>direction=\"left\"</kbd><br>2. Встановіть <kbd>scrollamount=\"5\"</kbd> для плавного прокручування<br>3. Додайте всередину заголовок термінових новин",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 0; margin: 0; background: #1a1a2e; } marquee { background: linear-gradient(90deg, #c0392b 0%, #e74c3c 50%, #c0392b 100%); padding: 15px 0; font-size: 1.3rem; color: white; font-weight: 500; text-transform: uppercase; letter-spacing: 1px; border-top: 3px solid #f1c40f; border-bottom: 3px solid #f1c40f; }",
"sandboxCSS": "",
@@ -64,17 +64,17 @@
{
"type": "element_exists",
"value": "marquee",
"message": "Add a <kbd>&lt;marquee&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;marquee&gt;</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "marquee", "attr": "direction", "value": "left" },
"message": "Add <kbd>direction=</kbd>\"left\" for horizontal scrolling"
"message": "Додайте <kbd>direction=</kbd>\"left\" для горизонтального прокручування"
},
{
"type": "attribute_value",
"value": { "selector": "marquee", "attr": "scrollamount", "value": "5" },
"message": "Add <kbd>scrollamount=</kbd>\"5\" for smooth speed"
"message": "Додайте <kbd>scrollamount=</kbd>\"5\" для плавної швидкості"
}
]
}

View File

@@ -2,99 +2,169 @@
"$schema": "../../schemas/code-crispies-module-schema.json",
"id": "html-svg",
"title": "HTML SVG",
"description": "Draw scalable vector graphics directly in HTML",
"description": "Малюйте масштабовану векторну графіку безпосередньо в HTML",
"mode": "html",
"difficulty": "beginner",
"lessons": [
{
"id": "svg-circle",
"title": "Drawing Circles",
"description": "SVG (Scalable Vector Graphics) lets you draw shapes directly in HTML. The <kbd>&lt;svg&gt;</kbd> element is the container, with <kbd>width</kbd> and <kbd>height</kbd> attributes.<br><br>Use <kbd>&lt;circle&gt;</kbd> with <kbd>cx</kbd>, <kbd>cy</kbd> (center) and <kbd>r</kbd> (radius) to draw circles.",
"task": "Create an SVG with a circle:<br>1. An <kbd>&lt;svg&gt;</kbd> with width=\"200\" and height=\"200\"<br>2. A <kbd>&lt;circle&gt;</kbd> centered at (100,100) with radius 50<br>3. Add a <kbd>fill</kbd> color",
"title": "Малювання кіл",
"description": "SVG (Scalable Vector Graphics) дозволяє малювати фігури безпосередньо в HTML. Елемент <kbd>&lt;svg&gt;</kbd> є контейнером з атрибутами <kbd>width</kbd> та <kbd>height</kbd>.<br><br>Використовуйте <kbd>&lt;circle&gt;</kbd> з <kbd>cx</kbd>, <kbd>cy</kbd> (центр) та <kbd>r</kbd> (радіус) для малювання кіл.",
"task": "Створіть SVG з колом:<br>1. Елемент <kbd>&lt;svg&gt;</kbd> з width=\"200\" та height=\"200\"<br>2. Елемент <kbd>&lt;circle&gt;</kbd> з центром у (100,100) та радіусом 50<br>3. Додайте колір <kbd>fill</kbd>",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: #f5f5f5; display: flex; justify-content: center; } svg { background: white; border-radius: 10px; box-shadow: 0 4px 15px rgba(0,0,0,0.1); }",
"sandboxCSS": "",
"initialCode": "",
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"50\" fill=\"#3498db\" />\n</svg>",
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"50\" fill=\"steelblue\" />\n</svg>",
"previewContainer": "preview-area",
"validations": [
{
"type": "element_exists",
"value": "svg",
"message": "Add an <kbd>&lt;svg&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;svg&gt;</kbd>"
},
{
"type": "element_exists",
"value": "circle",
"message": "Add a <kbd>&lt;circle&gt;</kbd> element inside the SVG"
"message": "Додайте елемент <kbd>&lt;circle&gt;</kbd> всередину SVG"
},
{
"type": "attribute_value",
"value": { "selector": "svg", "attr": "width", "value": "200" },
"message": "Встановіть <kbd>width=</kbd>\"200\" в SVG"
},
{
"type": "attribute_value",
"value": { "selector": "svg", "attr": "height", "value": "200" },
"message": "Встановіть <kbd>height=</kbd>\"200\" в SVG"
},
{
"type": "attribute_value",
"value": { "selector": "circle", "attr": "cx", "value": "100" },
"message": "Set <kbd>cx=</kbd>\"100\" for the circle's horizontal center"
"message": "Встановіть <kbd>cx=</kbd>\"100\" для горизонтального центру кола"
},
{
"type": "attribute_value",
"value": { "selector": "circle", "attr": "cy", "value": "100" },
"message": "Set <kbd>cy=</kbd>\"100\" for the circle's vertical center"
"message": "Встановіть <kbd>cy=</kbd>\"100\" для вертикального центру кола"
},
{
"type": "attribute_value",
"value": { "selector": "circle", "attr": "r", "value": "50" },
"message": "Встановіть <kbd>r=</kbd>\"50\" для радіуса кола"
}
]
},
{
"id": "svg-rect-line",
"title": "Rectangles & Lines",
"description": "Draw rectangles with <kbd>&lt;rect&gt;</kbd> using <kbd>x</kbd>, <kbd>y</kbd>, <kbd>width</kbd>, <kbd>height</kbd>.<br><br>Draw lines with <kbd>&lt;line&gt;</kbd> using <kbd>x1</kbd>, <kbd>y1</kbd> (start) and <kbd>x2</kbd>, <kbd>y2</kbd> (end). Lines need a <kbd>stroke</kbd> color!",
"task": "Create an SVG with:<br>1. An <kbd>&lt;svg&gt;</kbd> (200x150)<br>2. A <kbd>&lt;rect&gt;</kbd> at position (20,20) with size 80x60<br>3. A <kbd>&lt;line&gt;</kbd> from (120,30) to (180,90) with a stroke color",
"title": "Прямокутники та лінії",
"description": "Малюйте прямокутники за допомогою <kbd>&lt;rect&gt;</kbd> використовуючи <kbd>x</kbd>, <kbd>y</kbd>, <kbd>width</kbd>, <kbd>height</kbd>.<br><br>Малюйте лінії за допомогою <kbd>&lt;line&gt;</kbd> використовуючи <kbd>x1</kbd>, <kbd>y1</kbd> (початок) та <kbd>x2</kbd>, <kbd>y2</kbd> (кінець). Лінії потребують колір <kbd>stroke</kbd>!",
"task": "Створіть SVG з:<br>1. Елементом <kbd>&lt;svg&gt;</kbd> (200x150)<br>2. Елементом <kbd>&lt;rect&gt;</kbd> на позиції (20,20) розміром 80x60<br>3. Елементом <kbd>&lt;line&gt;</kbd> від (120,30) до (180,90) з кольором stroke",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #667eea 0%, #764ba2 100%); min-height: 200px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 12px; box-shadow: 0 10px 30px rgba(0,0,0,0.2); }",
"sandboxCSS": "",
"initialCode": "",
"solution": "<svg width=\"200\" height=\"150\">\n <rect x=\"20\" y=\"20\" width=\"80\" height=\"60\" fill=\"#e74c3c\" />\n <line x1=\"120\" y1=\"30\" x2=\"180\" y2=\"90\" stroke=\"#2c3e50\" stroke-width=\"3\" />\n</svg>",
"solution": "<svg width=\"200\" height=\"150\">\n <rect x=\"20\" y=\"20\" width=\"80\" height=\"60\" fill=\"tomato\" />\n <line x1=\"120\" y1=\"30\" x2=\"180\" y2=\"90\" stroke=\"slategray\" stroke-width=\"3\" />\n</svg>",
"previewContainer": "preview-area",
"validations": [
{
"type": "element_exists",
"value": "svg",
"message": "Add an <kbd>&lt;svg&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;svg&gt;</kbd>"
},
{
"type": "element_exists",
"value": "rect",
"message": "Add a <kbd>&lt;rect&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;rect&gt;</kbd>"
},
{
"type": "element_exists",
"value": "line",
"message": "Add a <kbd>&lt;line&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;line&gt;</kbd>"
},
{
"type": "attribute_value",
"value": { "selector": "svg", "attr": "width", "value": "200" },
"message": "Встановіть <kbd>width=</kbd>\"200\" в SVG"
},
{
"type": "attribute_value",
"value": { "selector": "svg", "attr": "height", "value": "150" },
"message": "Встановіть <kbd>height=</kbd>\"150\" в SVG"
},
{
"type": "attribute_value",
"value": { "selector": "rect", "attr": "x", "value": "20" },
"message": "Встановіть <kbd>x=</kbd>\"20\" в rect"
},
{
"type": "attribute_value",
"value": { "selector": "rect", "attr": "y", "value": "20" },
"message": "Встановіть <kbd>y=</kbd>\"20\" в rect"
},
{
"type": "attribute_value",
"value": { "selector": "rect", "attr": "width", "value": "80" },
"message": "Встановіть <kbd>width=</kbd>\"80\" в rect"
},
{
"type": "attribute_value",
"value": { "selector": "rect", "attr": "height", "value": "60" },
"message": "Встановіть <kbd>height=</kbd>\"60\" в rect"
},
{
"type": "attribute_value",
"value": { "selector": "line", "attr": "x1", "value": "120" },
"message": "Встановіть <kbd>x1=</kbd>\"120\" в line"
},
{
"type": "attribute_value",
"value": { "selector": "line", "attr": "y1", "value": "30" },
"message": "Встановіть <kbd>y1=</kbd>\"30\" в line"
},
{
"type": "attribute_value",
"value": { "selector": "line", "attr": "x2", "value": "180" },
"message": "Встановіть <kbd>x2=</kbd>\"180\" в line"
},
{
"type": "attribute_value",
"value": { "selector": "line", "attr": "y2", "value": "90" },
"message": "Встановіть <kbd>y2=</kbd>\"90\" в line"
},
{
"type": "contains",
"value": "stroke",
"message": "Додайте колір <kbd>stroke</kbd> до line"
}
]
},
{
"id": "svg-shapes",
"title": "Multiple Shapes",
"description": "Combine shapes to create simple graphics! Add <kbd>stroke</kbd> for outlines and <kbd>stroke-width</kbd> for thickness.<br><br>Use <kbd>fill=\"none\"</kbd> for hollow shapes. Shapes stack in order - later elements appear on top.",
"task": "Create a simple face:<br>1. An <kbd>&lt;svg&gt;</kbd> (200x200)<br>2. A large <kbd>&lt;circle&gt;</kbd> for the face<br>3. Two small <kbd>&lt;circle&gt;</kbd> elements for eyes<br>4. A <kbd>&lt;line&gt;</kbd> for the smile",
"title": "Кілька фігур",
"description": "Комбінуйте фігури для створення простої графіки! Додайте <kbd>stroke</kbd> для контурів та <kbd>stroke-width</kbd> для товщини.<br><br>Використовуйте <kbd>fill=\"none\"</kbd> для порожніх фігур. Фігури накладаються в порядку - пізніші елементи з'являються зверху.",
"task": "Створіть просте обличчя:<br>1. Елемент <kbd>&lt;svg&gt;</kbd> (200x200)<br>2. Велике <kbd>&lt;circle&gt;</kbd> для обличчя<br>3. Два маленьких <kbd>&lt;circle&gt;</kbd> для очей<br>4. Елемент <kbd>&lt;line&gt;</kbd> для посмішки",
"previewHTML": "",
"previewBaseCSS": "body { font-family: system-ui; padding: 20px; background: linear-gradient(135deg, #ffecd2 0%, #fcb69f 100%); min-height: 250px; display: flex; justify-content: center; align-items: center; } svg { background: white; border-radius: 15px; box-shadow: 0 10px 30px rgba(0,0,0,0.15); }",
"sandboxCSS": "",
"initialCode": "",
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"80\" fill=\"#f1c40f\" stroke=\"#e67e22\" stroke-width=\"4\" />\n <circle cx=\"70\" cy=\"80\" r=\"10\" fill=\"#2c3e50\" />\n <circle cx=\"130\" cy=\"80\" r=\"10\" fill=\"#2c3e50\" />\n <line x1=\"60\" y1=\"130\" x2=\"140\" y2=\"130\" stroke=\"#2c3e50\" stroke-width=\"4\" stroke-linecap=\"round\" />\n</svg>",
"solution": "<svg width=\"200\" height=\"200\">\n <circle cx=\"100\" cy=\"100\" r=\"80\" fill=\"gold\" stroke=\"orange\" stroke-width=\"4\" />\n <circle cx=\"70\" cy=\"80\" r=\"10\" fill=\"darkslategray\" />\n <circle cx=\"130\" cy=\"80\" r=\"10\" fill=\"darkslategray\" />\n <line x1=\"60\" y1=\"130\" x2=\"140\" y2=\"130\" stroke=\"darkslategray\" stroke-width=\"4\" stroke-linecap=\"round\" />\n</svg>",
"previewContainer": "preview-area",
"validations": [
{
"type": "element_exists",
"value": "svg",
"message": "Add an <kbd>&lt;svg&gt;</kbd> element"
"message": "Додайте елемент <kbd>&lt;svg&gt;</kbd>"
},
{
"type": "element_count",
"value": { "selector": "circle", "min": 3 },
"message": "Add at least 3 circles (1 face + 2 eyes)"
"message": "Додайте принаймні 3 кола (1 обличчя + 2 ока)"
},
{
"type": "element_exists",
"value": "line",
"message": "Add a <kbd>&lt;line&gt;</kbd> for the smile"
"message": "Додайте <kbd>&lt;line&gt;</kbd> для посмішки"
}
]
}