diff --git a/lessons/00-basic-selectors.json b/lessons/00-basic-selectors.json index 24f3cef..e5a0730 100644 --- a/lessons/00-basic-selectors.json +++ b/lessons/00-basic-selectors.json @@ -9,19 +9,35 @@ "id": "introduction-to-selectors", "title": "What is a CSS Selector?", "description": "A CSS selector is the first part of a CSS rule that tells the browser which HTML elements should receive the styles defined in the declaration block. Selectors are essentially patterns that match against elements in your HTML document. Understanding selectors is fundamental because they determine which elements your CSS rules will affect. The element or elements targeted by a selector are referred to as the 'subject of the selector.' Mastering different selector types gives you precise control over your web page styling.", - "task": "Complete the CSS rule by writing a selector that targets all paragraph elements (p) in the document. This basic type selector will apply the defined style (blue color) to every paragraph on the page.", + "task": "Write a CSS rule using a type selector that targets all paragraph elements (p) in the document. Make the text blue by setting the color property to blue.", "previewHTML": "

Introduction to CSS Selectors

\n

This paragraph should turn blue.

\n
This div element should remain unchanged.
\n

This second paragraph should also turn blue.

", "previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }", "sandboxCSS": "h1, p, div { padding: 8px; margin-bottom: 10px; border: 1px dashed #ccc; }", "codePrefix": "/* Write a type selector to target all paragraph elements */\n", "initialCode": "", - "codeSuffix": " {\n color: blue;\n}", + "codeSuffix": "", "previewContainer": "preview-area", "validations": [ { "type": "regex", - "value": "^\\s*p\\s*$", - "message": "Use the 'p' selector to target all paragraph elements", + "value": "p", + "message": "Create a rule that targets all paragraph elements with 'p'", + "options": { + "caseSensitive": false + } + }, + { + "type": "regex", + "value": "p\\s*{[^}]*color:[^}]*}", + "message": "Create a rule that targets all paragraph elements with 'p' and sets their color", + "options": { + "caseSensitive": false + } + }, + { + "type": "regex", + "value": "p\\s*{[^}]*color:\\s*blue;?[^}]*}", + "message": "Create a rule that targets all paragraph elements with 'p' and sets their color to blue", "options": { "caseSensitive": false } @@ -32,18 +48,18 @@ "id": "type-selectors", "title": "Type Selectors: Targeting HTML Elements", "description": "Type selectors (also called tag name selectors or element selectors) target HTML elements based on their tag name. For example, 'p' selects all paragraph elements, 'h1' selects all level-one headings, and 'div' selects all division elements. Type selectors are the most fundamental way to select elements, applying styles consistently to all instances of a particular HTML element throughout your document. They provide a broad brush for styling your page and are often the starting point for more specific styling using other selector types.", - "task": "Write type selectors to target specific HTML elements. Create three separate rules: one for h2 headings (making them purple), one for span elements (giving them a yellow background), and one for strong elements (making them red). This exercise demonstrates how type selectors can target different elements independently.", + "task": "Write three separate CSS rules using type selectors to target specific HTML elements: make h2 headings purple, give span elements a yellow background, and make strong elements red.", "previewHTML": "

Type Selectors Example

\n

Regular paragraph text with a highlighted span that should have a yellow background.

\n

Another paragraph with strong important text that should be red.

\n

Another Heading

", "previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }", "sandboxCSS": "h2, p, span, strong { padding: 3px; }", "codePrefix": "/* Write three separate type selectors below */\n\n/* 1. Make h2 headings purple */\n", - "initialCode": "\n\n/* 2. Give spans a yellow background */\n\n\n/* 3. Make strong elements red */\n", + "initialCode": "", "codeSuffix": "", "previewContainer": "preview-area", "validations": [ { "type": "regex", - "value": "h2\\s*{[^}]*color:\\s*purple", + "value": "h2\\s*{[^}]*color:\\s*purple;?[^}]*}", "message": "Create a rule for h2 elements with color: purple", "options": { "caseSensitive": false @@ -51,7 +67,7 @@ }, { "type": "regex", - "value": "span\\s*{[^}]*background(-color)?:\\s*yellow", + "value": "span\\s*{[^}]*background(-color)?:\\s*yellow;?[^}]*}", "message": "Create a rule for span elements with background-color: yellow", "options": { "caseSensitive": false @@ -59,7 +75,7 @@ }, { "type": "regex", - "value": "strong\\s*{[^}]*color:\\s*red", + "value": "strong\\s*{[^}]*color:\\s*red;?[^}]*}", "message": "Create a rule for strong elements with color: red", "options": { "caseSensitive": false @@ -71,19 +87,32 @@ "id": "class-selectors", "title": "Class Selectors: Styling Element Groups", "description": "Class selectors target elements with a specific class attribute value. They begin with a dot (.) followed by the class name. Classes are powerful because they allow you to apply the same styles to multiple elements regardless of their type. An HTML element can have multiple classes (separated by spaces in the class attribute), and a class can be applied to any number of elements. This flexibility makes class selectors one of the most commonly used methods for applying styles in CSS, allowing for modular and reusable styling across your website.", - "task": "Create a class selector that targets elements with the class 'highlight'. Write a CSS rule that gives these elements a yellow background and bold text. Notice how the class selector applies the style to different HTML elements that share the same class.", + "task": "Create a CSS rule using a class selector that targets elements with the class 'highlight'. Give these elements a yellow background and bold text.", "previewHTML": "

Using Class Selectors

\n

This is a regular paragraph, but this span has the highlight class applied to it.

\n

This entire paragraph has the highlight class.

\n", "previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }", "sandboxCSS": "h2, p, li { padding: 5px; margin-bottom: 10px; }", "codePrefix": "/* Create a class selector for elements with the 'highlight' class */\n", "initialCode": "", - "codeSuffix": " {\n background-color: yellow;\n font-weight: bold;\n}", + "codeSuffix": "", "previewContainer": "preview-area", "validations": [ + { + "type": "contains", + "value": ".highlight", + "message": "Use the '.highlight' class selector" + }, { "type": "regex", - "value": "^\\.highlight$", - "message": "Use '.highlight' to select elements with the highlight class", + "value": "\\.highlight\\s*{[^}]*background(-color)?:\\s*yellow;[^}]*}", + "message": "Use '.highlight' class selector with background-color: yellow", + "options": { + "caseSensitive": true + } + }, + { + "type": "regex", + "value": "\\.highlight\\s*{[^}]*font-weight:\\s*bold;[^}]*}", + "message": "Add font-weight: bold to the .highlight rule", "options": { "caseSensitive": true } @@ -94,19 +123,35 @@ "id": "multiple-classes", "title": "Working with Multiple Classes", "description": "HTML elements can have multiple classes applied simultaneously, allowing for composable and modular CSS designs. When an element has multiple classes, it will receive styles from all matching class selectors. This approach enables you to build a library of reusable CSS classes that can be combined in different ways. You can also target elements that have a specific combination of classes by chaining class selectors together without spaces (e.g., '.class1.class2'). This technique lets you create conditional styles that only apply when certain classes appear together.", - "task": "Complete the CSS rule that targets elements with both 'card' and 'featured' classes. This selector should match only elements that have both classes applied simultaneously. Set the border-color to gold to make featured cards stand out from regular cards.", + "task": "Complete the CSS rule that targets elements with both 'card' and 'featured' classes by chaining the selectors. Set the border-color to gold and the background-color to lemonchiffon to make featured cards stand out.", "previewHTML": "

Multiple Class Combinations

\n
Regular Card
\n
Featured Card
\n
Just Featured (not a card)
", "previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } .card { border: 2px solid #ccc; padding: 15px; margin-bottom: 10px; border-radius: 5px; }", "sandboxCSS": "", "codePrefix": "/* The .card class already has basic styling */\n/* Now target elements with BOTH classes: 'card' AND 'featured' */\n", "initialCode": "", - "codeSuffix": " {\n border-color: gold;\n background-color: #fffaf0; /* Light gold background */\n}", + "codeSuffix": "", "previewContainer": "preview-area", "validations": [ + { + "type": "contains", + "value": ".card.featured", + "message": "Use '.card.featured' selector (no space between them)", + "options": { + "caseSensitive": true + } + }, { "type": "regex", - "value": "^\\.card\\.featured$", - "message": "Use '.card.featured' to select elements with both classes (no space between them)", + "value": "\\.card\\.featured\\s*{[^}]*border-color:\\s*gold;?[^}]*}", + "message": "Use '.card.featured' selector and set border-color: gold", + "options": { + "caseSensitive": true + } + }, + { + "type": "regex", + "value": "\\.card\\.featured\\s*{[^}]*background(-color)?:\\s*lemonchiffon;?[^}]*}", + "message": "Add background-color: lemonchiffon to your .card.featured rule", "options": { "caseSensitive": true } @@ -117,19 +162,24 @@ "id": "class-with-type", "title": "Combining Type and Class Selectors", "description": "You can combine type selectors with class selectors to target specific HTML elements that have a certain class. This creates a more specific selector that only matches when both conditions are true: the element is of the specified type AND it has the specified class. For example, 'p.note' would select paragraph elements with the class 'note', but would not select divs or spans with that same class. This approach allows you to apply different styles to the same class when it appears on different element types.", - "task": "Create a CSS rule that specifically targets elements with the class 'highlight'. This combined selector should make those elements have an orange background, while other elements with the highlight class remain untouched.", + "task": "Create a CSS rule that specifically targets elements with the class 'highlight'. Make those elements have an orange background, while other elements with the highlight class remain untouched.", "previewHTML": "

Type and Class Combinations

\n

This paragraph has a highlighted span that should have an orange background.

\n

This paragraph has the highlight class but should NOT have an orange background.

", "previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } .highlight { font-weight: bold; }", "sandboxCSS": "h2, p, span { padding: 5px; }", "codePrefix": "/* The .highlight class already sets font-weight to bold */\n/* Now target ONLY span elements with the highlight class */\n", "initialCode": "", - "codeSuffix": " {\n background-color: orange;\n}", + "codeSuffix": "", "previewContainer": "preview-area", "validations": [ + { + "type": "contains", + "value": "span.highlight", + "message": "Use the 'span.highlight' selector (no space between them)" + }, { "type": "regex", - "value": "^span\\.highlight$", - "message": "Use 'span.highlight' to target span elements with the highlight class", + "value": "span\\.highlight\\s*{[^}]*background(-color)?:\\s*orange;?[^}]*}", + "message": "Use 'span.highlight' selector and set background-color: orange", "options": { "caseSensitive": true } @@ -140,19 +190,27 @@ "id": "id-selectors", "title": "ID Selectors: Targeting Unique Elements", "description": "ID selectors target elements with a specific id attribute. They begin with a hash/pound sign (#) followed by the ID name. Unlike classes, IDs must be unique within a document—each ID value should be used only once per page. ID selectors have higher specificity than class or element selectors, meaning they override those selectors when conflicts arise. Because of their uniqueness requirement, IDs are best used for one-of-a-kind elements like page headers, main navigation, or specific unique components that appear only once on a page.", - "task": "Create an ID selector that targets the element with the ID 'main-title'. Set its color to purple and add an underline. Remember that ID selectors begin with a hash/pound (#) symbol and should be used only for truly unique elements on your page.", + "task": "Create a CSS rule with an ID selector that targets the element with the ID 'main-title'. Set its color to purple and add an underline with text-decoration: underline.", "previewHTML": "

Main Page Title

\n

Regular paragraph content.

\n

Secondary Heading

\n

Introduction paragraph (different ID).

", "previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }", "sandboxCSS": "h1, h2, p { padding: 8px; margin-bottom: 10px; border: 1px dashed #ccc; }", "codePrefix": "/* Create an ID selector to target the element with id=\"main-title\" */\n", "initialCode": "", - "codeSuffix": " {\n color: purple;\n text-decoration: underline;\n}", + "codeSuffix": "", "previewContainer": "preview-area", "validations": [ { "type": "regex", - "value": "^#main-title$", - "message": "Use '#main-title' to select the element with id=\"main-title\"", + "value": "#main-title\\s*{[^}]*color:\\s*purple;[^}]*}", + "message": "Use '#main-title' selector and set color: purple", + "options": { + "caseSensitive": true + } + }, + { + "type": "regex", + "value": "#main-title\\s*{[^}]*text-decoration:\\s*underline;[^}]*}", + "message": "Add text-decoration: underline to your #main-title rule", "options": { "caseSensitive": true } @@ -163,19 +221,19 @@ "id": "id-with-type", "title": "Combining Type and ID Selectors", "description": "Similar to how you can combine type and class selectors, you can also combine type selectors with ID selectors. For example, 'h1#title' targets an h1 element with the ID 'title'. While this approach is more specific than using just the ID selector, it's often unnecessary since IDs should already be unique in a document. However, this technique can be useful for improving code readability or when you want to emphasize that a particular ID should only appear on a specific element type.", - "task": "Create a CSS rule that combines a type selector with an ID selector to target specifically a paragraph element with the ID 'special'. Set its font style to italic. While the ID alone would be sufficient to target the element, this combined approach can sometimes improve code readability.", + "task": "Create a CSS rule that combines a type selector with an ID selector to target specifically a paragraph element with the ID 'special'. Set its font style to italic.", "previewHTML": "

Heading with ID \"special\" (should NOT be affected)

\n

Paragraph with ID \"special\" (should become italic)

", "previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }", "sandboxCSS": "h2, p { padding: 8px; margin-bottom: 10px; border: 1px dashed #ccc; }", "codePrefix": "/* Create a combined type+ID selector for a paragraph with id=\"special\" */\n", "initialCode": "", - "codeSuffix": " {\n font-style: italic;\n}", + "codeSuffix": "", "previewContainer": "preview-area", "validations": [ { "type": "regex", - "value": "^p#special$", - "message": "Use 'p#special' to select paragraph elements with id=\"special\"", + "value": "p#special\\s*{[^}]*font-style:\\s*italic;[^}]*}", + "message": "Use 'p#special' selector and set font-style: italic", "options": { "caseSensitive": true } @@ -186,13 +244,13 @@ "id": "selector-lists", "title": "Selector Lists: Applying the Same Rules to Multiple Selectors", "description": "When multiple elements need the same styling, you can group them together using a selector list (also known as grouping selectors). Selector lists are created by separating individual selectors with commas. This approach reduces repetition in your CSS, making it more maintainable and efficient. For example, 'h1, h2, h3 { color: blue; }' applies the same blue color to all three heading levels. Whitespace around commas is optional, and each selector in the list can be any valid selector type—elements, classes, IDs, or even more complex selectors.", - "task": "Create a selector list that applies the same styles to three different elements: paragraphs with class 'note', list items with class 'important', and the element with ID 'summary'. This demonstrates how selector lists can group different selector types (element+class and ID selectors) for efficiency.", + "task": "Create a selector list that applies the same styles to three different elements: paragraphs with class 'note', list items with class 'important', and the element with ID 'summary'. Give them a light yellow background, a gold left border, and some left padding.", "previewHTML": "

This is a note paragraph.

\n\n
Summary section
", "previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }", "sandboxCSS": "p, li, div { padding: 8px; margin-bottom: 8px; border: 1px dashed #ccc; }", "codePrefix": "/* Create a selector list to apply the same styles to multiple different elements */\n", "initialCode": "", - "codeSuffix": " {\n background-color: #ffffcc; /* Light yellow */\n border-left: 3px solid #ffcc00; /* Gold border */\n padding-left: 10px;\n}", + "codeSuffix": "", "previewContainer": "preview-area", "validations": [ { @@ -221,8 +279,24 @@ }, { "type": "regex", - "value": "^(p\\.note|li\\.important|#summary)(\\s*,\\s*(p\\.note|li\\.important|#summary)){2}$", - "message": "Create a comma-separated list of all three selectors", + "value": "(p\\.note|li\\.important|#summary)(\\s*,\\s*(p\\.note|li\\.important|#summary)){2}\\s*{[^}]*background-color:\\s*#ffffcc;[^}]*}", + "message": "Create a comma-separated list with all three selectors and set background-color: #ffffcc", + "options": { + "caseSensitive": true + } + }, + { + "type": "regex", + "value": "(p\\.note|li\\.important|#summary)(\\s*,\\s*(p\\.note|li\\.important|#summary)){2}\\s*{[^}]*border-left:\\s*3px solid #ffcc00;[^}]*}", + "message": "Add border-left: 3px solid #ffcc00 to your rule", + "options": { + "caseSensitive": true + } + }, + { + "type": "regex", + "value": "(p\\.note|li\\.important|#summary)(\\s*,\\s*(p\\.note|li\\.important|#summary)){2}\\s*{[^}]*padding-left:\\s*10px;[^}]*}", + "message": "Add padding-left: 10px to your rule", "options": { "caseSensitive": true } @@ -233,19 +307,19 @@ "id": "universal-selector", "title": "The Universal Selector: Targeting Everything", "description": "The universal selector is denoted by an asterisk (*) and matches any element of any type. It selects everything in the document or, when combined with other selectors, everything within a specific context. For example, '* { margin: 0; }' removes margins from all elements, while 'article *' selects all elements inside article elements. The universal selector is powerful but should be used carefully due to its broad impact. It's commonly used in CSS resets, to override default browser styling, or to target all children of a particular element.", - "task": "Use the universal selector to remove margins from all elements inside the container div. This demonstrates how the universal selector can affect all nested elements within a specific context. Add a rule using 'div.container *' as the selector.", + "task": "Use the universal selector to remove margins from all elements inside the container div. Create a rule using 'div.container *' as the selector and set margin: 0.", "previewHTML": "
\n

Inside Container

\n

This paragraph is inside the container.

\n
    \n
  • List item inside container
  • \n
\n
\n

This paragraph is outside the container and should not be affected.

", "previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } div.container { border: 2px solid #333; padding: 15px; background-color: #f5f5f5; } h2, p, ul, li { margin: 15px 0; }", "sandboxCSS": "", "codePrefix": "/* Use the universal selector to target all elements inside the container */\n", "initialCode": "", - "codeSuffix": " {\n margin: 0;\n}", + "codeSuffix": "", "previewContainer": "preview-area", "validations": [ { "type": "regex", - "value": "^div\\.container\\s+\\*$", - "message": "Use 'div.container *' to select all elements inside the container div", + "value": "div\\.container\\s+\\*\\s*{[^}]*margin:\\s*0;[^}]*}", + "message": "Use 'div.container *' selector and set margin: 0", "options": { "caseSensitive": true } @@ -256,19 +330,19 @@ "id": "specificity-basics", "title": "Understanding Selector Specificity", "description": "CSS specificity determines which styles take precedence when multiple conflicting rules target the same element. Specificity follows a hierarchical system: inline styles have the highest specificity, followed by ID selectors, then class/attribute/pseudo-class selectors, and finally element/pseudo-element selectors. This can be conceptualized as a four-part score (inline, ID, class, element). Understanding specificity is crucial for predictable styling and debugging CSS conflicts. When two selectors have equal specificity, the one that comes last in the stylesheet wins.", - "task": "Examine the existing CSS rules and determine which text color will be applied to the paragraph based on specificity. Add a new rule using '.content p' as the selector and 'green' as the color to demonstrate how combining selectors increases specificity compared to a simple element selector.", + "task": "Examine the existing CSS rules and add a new rule with higher specificity to override the text color of the paragraph. Create a rule using '.content p' as the selector and set color: green.", "previewHTML": "
\n

What color will this paragraph be? Look at the CSS rules and their specificity.

\n
", "previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; }", "sandboxCSS": "p { border: 1px dashed #ccc; padding: 10px; }", "codePrefix": "/* These CSS rules target the same paragraph but have different specificity */\n\n/* Rule 1: Element selector (lowest specificity) */\np {\n color: red;\n}\n\n/* Rule 2: Descendant selector (higher specificity than just 'p') */\n", "initialCode": "", - "codeSuffix": " {\n color: green;\n}", + "codeSuffix": "", "previewContainer": "preview-area", "validations": [ { "type": "regex", - "value": "^\\.content\\s+p$", - "message": "Use '.content p' to select paragraphs within elements with the 'content' class", + "value": "\\.content\\s+p\\s*{[^}]*color:\\s*green;[^}]*}", + "message": "Use '.content p' selector and set color: green", "options": { "caseSensitive": true } @@ -279,35 +353,43 @@ "id": "selector-practice", "title": "Combining Different Selector Types", "description": "In real-world CSS, you'll often combine different selector types to target elements precisely. Combining selectors gives you fine-grained control over which elements receive certain styles. For instance, '.article p' targets paragraphs within elements with the 'article' class, while 'section#main .card h3' targets h3 headings inside elements with class 'card' that are descendants of the section with ID 'main'. By understanding how different selectors work together, you can create more efficient, modular, and maintainable CSS stylesheets.", - "task": "Create CSS rules that demonstrate combining different selector types. Write three rules: one targeting all links inside elements with class 'nav', one for paragraphs inside elements with ID 'sidebar', and one for list items with class 'featured' that are inside elements with class 'products'. This exercise will help you practice creating increasingly specific selectors.", + "task": "Create three CSS rules that demonstrate combining different selector types: one targeting links inside .nav elements (make them blue), one for paragraphs inside #sidebar (make them smaller with font-size: 0.9rem), and one for list items with class 'featured' inside .products elements (make them bold with a yellow background).", "previewHTML": "
\n Home\n About\n Contact\n
\n\n
\n

Sidebar content that should be smaller.

\n

More sidebar text.

\n
\n\n
\n
    \n
  • Regular product
  • \n
  • Featured product
  • \n
  • Another regular product
  • \n
  • Another featured product
  • \n
\n
", "previewBaseCSS": "body { font-family: sans-serif; line-height: 1.5; padding: 20px; } .nav { background-color: #f0f0f0; padding: 10px; } #sidebar { background-color: #f5f5f5; padding: 10px; width: 150px; } .products { padding: 10px; }", "sandboxCSS": "", "codePrefix": "/* Write three combined selectors below */\n\n/* 1. Target all links inside elements with class 'nav' */\n", - "initialCode": "\n\n/* 2. Target paragraphs inside the element with ID 'sidebar' */\n\n\n/* 3. Target list items with class 'featured' inside elements with class 'products' */\n", + "initialCode": "", "codeSuffix": "", "previewContainer": "preview-area", "validations": [ { "type": "regex", - "value": "\\.nav\\s+a\\s*{[^}]*}", - "message": "Create a rule for '.nav a' that styles links in the navigation", + "value": "\\.nav\\s+a\\s*{[^}]*color:\\s*blue;[^}]*}", + "message": "Create a rule for '.nav a' that makes links blue", "options": { "caseSensitive": true } }, { "type": "regex", - "value": "#sidebar\\s+p\\s*{[^}]*}", - "message": "Create a rule for '#sidebar p' that styles paragraphs in the sidebar", + "value": "#sidebar\\s+p\\s*{[^}]*font-size:\\s*0\\.9rem;[^}]*}", + "message": "Create a rule for '#sidebar p' that sets font-size: 0.9rem", "options": { "caseSensitive": true } }, { "type": "regex", - "value": "\\.products\\s+li\\.featured\\s*{[^}]*}", - "message": "Create a rule for '.products li.featured' that styles featured product list items", + "value": "\\.products\\s+li\\.featured\\s*{[^}]*font-weight:\\s*bold;[^}]*}", + "message": "Create a rule for '.products li.featured' with font-weight: bold", + "options": { + "caseSensitive": true + } + }, + { + "type": "regex", + "value": "\\.products\\s+li\\.featured\\s*{[^}]*background(-color)?:\\s*yellow;[^}]*}", + "message": "Add background-color: yellow to your '.products li.featured' rule", "options": { "caseSensitive": true } diff --git a/schemas/code-crispies-module-schema.json b/schemas/code-crispies-module-schema.json index 99a31f3..0e9deca 100644 --- a/schemas/code-crispies-module-schema.json +++ b/schemas/code-crispies-module-schema.json @@ -97,7 +97,7 @@ "properties": { "type": { "type": "string", - "enum": ["contains", "regex", "property_value"], + "enum": ["contains", "not_contains", "regex", "property_value", "syntax", "custom"], "description": "Type of validation to perform" }, "value": { diff --git a/src/app.js b/src/app.js index 10172de..70671f7 100644 --- a/src/app.js +++ b/src/app.js @@ -299,6 +299,38 @@ function runCode() { const validationResult = validateUserCode(userCode, lesson); + // Remove any existing validation indicators before adding new ones + const existingIndicators = elements.codeEditor.querySelectorAll(".validation-success-indicator"); + existingIndicators.forEach((indicator) => indicator.remove()); + + // Add validation indicators based on validCases count if available + if (validationResult.validCases) { + const casesCount = + typeof validationResult.validCases === "number" + ? validationResult.validCases + : Array.isArray(validationResult.validCases) + ? validationResult.validCases.length + : 1; + + // Create a container for indicators if it doesn't exist + let indicatorContainer = elements.codeEditor.querySelector(".validation-indicators-container"); + if (!indicatorContainer) { + indicatorContainer = document.createElement("div"); + indicatorContainer.className = "validation-indicators-container"; + elements.codeEditor.appendChild(indicatorContainer); + } else { + indicatorContainer.innerHTML = ""; + } + + // Add the appropriate number of checkmarks + for (let i = 0; i < casesCount; i++) { + const validationIndicator = document.createElement("div"); + validationIndicator.className = "validation-success-indicator"; + validationIndicator.innerHTML = "✓"; + indicatorContainer.appendChild(validationIndicator); + } + } + if (validationResult.isValid) { // Mark lesson as completed const moduleProgress = state.userProgress[state.currentModule.id]; @@ -308,12 +340,6 @@ function runCode() { updateModuleSelectorButtonProgress(); } - // Add validation indicator to editor - const validationIndicator = document.createElement("div"); - validationIndicator.className = "validation-success-indicator"; - validationIndicator.innerHTML = "✓"; - elements.codeEditor.appendChild(validationIndicator); - // Show success feedback with visual indicators showFeedback(true, validationResult.message || "Great job! Your code works correctly."); diff --git a/src/config/lessons.js b/src/config/lessons.js index 0bde848..69fcb8c 100644 --- a/src/config/lessons.js +++ b/src/config/lessons.js @@ -16,7 +16,7 @@ import responsiveConfig from "../../lessons/08-responsive.json"; // Module store const moduleStore = [ - basicSelectorsConfig + basicSelectorsConfig, // basicsConfig, // boxModelConfig, // selectorsConfig, diff --git a/src/helpers/validator.js b/src/helpers/validator.js index 848e457..72038ad 100644 --- a/src/helpers/validator.js +++ b/src/helpers/validator.js @@ -19,50 +19,82 @@ export function validateUserCode(userCode, lesson) { // Default validation result let result = { isValid: true, + validCases: 0, message: "Your code looks good!" }; // Process each validation rule for (const validation of validations) { const { type, value, message, options } = validation; + let validationPassed = false; switch (type) { case "contains": - if (!containsValidation(userCode, value, options)) { - return { isValid: false, message: message || `Your code should include "${value}".` }; + validationPassed = containsValidation(userCode, value, options); + if (!validationPassed) { + result = { + isValid: false, + validCases: result.validCases, + message: message || `Your code should include "${value}".` + }; } break; case "not_contains": - if (containsValidation(userCode, value, options)) { - return { isValid: false, message: message || `Your code should not include "${value}".` }; + validationPassed = !containsValidation(userCode, value, options); + if (!validationPassed) { + result = { + isValid: false, + validCases: result.validCases, + message: message || `Your code should not include "${value}".` + }; } break; case "regex": - if (!regexValidation(userCode, value, options)) { - return { isValid: false, message: message || "Your code does not match the expected pattern." }; + validationPassed = regexValidation(userCode, value, options); + if (!validationPassed) { + result = { + isValid: false, + validCases: result.validCases, + message: message || "Your code does not match the expected pattern." + }; } break; case "property_value": - if (!propertyValueValidation(userCode, value, options)) { - return { isValid: false, message: message || `The "${value.property}" property should be set to "${value.expected}".` }; + validationPassed = propertyValueValidation(userCode, value, options); + if (!validationPassed) { + result = { + isValid: false, + validCases: result.validCases, + message: message || `The "${value.property}" property should be set to "${value.expected}".` + }; } break; case "syntax": const syntaxResult = syntaxValidation(userCode); - if (!syntaxResult.isValid) { - return { isValid: false, message: message || `CSS syntax error: ${syntaxResult.error}` }; + validationPassed = syntaxResult.isValid; + if (!validationPassed) { + result = { + isValid: false, + validCases: result.validCases, + message: message || `CSS syntax error: ${syntaxResult.error}` + }; } break; case "custom": if (validation.validator && typeof validation.validator === "function") { const customResult = validation.validator(userCode); - if (!customResult.isValid) { - return { isValid: false, message: customResult.message || message || "Your code does not meet the requirements." }; + validationPassed = customResult.isValid; + if (!validationPassed) { + result = { + isValid: false, + validCases: result.validCases, + message: customResult.message || message || "Your code does not meet the requirements." + }; } } break; @@ -71,10 +103,22 @@ export function validateUserCode(userCode, lesson) { default: console.warn(`Unknown validation type: ${type}`); + continue; // Skip counting this validation + } + + // Count valid cases + if (validationPassed) { + result.validCases++; + } + + // Return early if validation failed + if (!validationPassed) { + return result; } } - // If we've passed all validations, return success + // If we've passed all validations, return success with all cases passed + result.validCases = validations.length; return result; } diff --git a/src/index.html b/src/index.html index b76afcc..5bbc7c4 100644 --- a/src/index.html +++ b/src/index.html @@ -51,6 +51,7 @@
CSS Editor +
diff --git a/src/main.css b/src/main.css index 9dd885b..c70a7f3 100644 --- a/src/main.css +++ b/src/main.css @@ -343,15 +343,6 @@ code { transition: background-color 0.2s ease; } -.code-input.inline-input { - display: inline-block; - min-height: 30px; - height: auto; - padding: 2px 4px; - margin: 0 4px; - border-bottom: 1px dashed #666; -} - /* Controls */ .controls { display: flex; @@ -424,10 +415,16 @@ code { border: 1px solid var(--success-color); } -.validation-success-indicator { +.validation-indicators-container { position: absolute; top: 1rem; right: 7rem; + display: flex; + gap: 5px; + z-index: 5; +} + +.validation-success-indicator { background-color: var(--success-color); color: white; width: 20px; @@ -437,7 +434,6 @@ code { align-items: center; justify-content: center; font-size: 12px; - z-index: 5; } /* Module selector button with progress */