diff --git a/lessons/10-tailwind-basics.json b/lessons/10-tailwind-basics.json
new file mode 100644
index 0000000..1f42b92
--- /dev/null
+++ b/lessons/10-tailwind-basics.json
@@ -0,0 +1,28 @@
+{
+ "$schema": "../schemas/code-crispies-module-schema.json",
+ "id": "tailwind-basics",
+ "title": "Tailwind CSS Basics",
+ "mode": "tailwind",
+ "lessons": [
+ {
+ "id": "bg-colors",
+ "title": "Background Colors",
+ "description": "Learn to apply background colors using Tailwind utilities.",
+ "task": "Add a blue background to the div using Tailwind classes.",
+ "previewHTML": "
Hello Tailwind!
",
+ "previewBaseCSS": "body { padding: 20px; font-family: sans-serif; }",
+ "initialCode": "",
+ "validations": [
+ {
+ "type": "contains_class",
+ "value": "bg-blue-500",
+ "message": "Add the 'bg-blue-500' class for a blue background."
+ }
+ ],
+ "sandboxCSS": "",
+ "previewContainer": ""
+ }
+ ],
+ "description": "",
+ "difficulty": "advanced"
+}
diff --git a/schemas/code-crispies-module-schema.json b/schemas/code-crispies-module-schema.json
index 61ac620..f74cbcc 100644
--- a/schemas/code-crispies-module-schema.json
+++ b/schemas/code-crispies-module-schema.json
@@ -17,6 +17,11 @@
"type": "string",
"description": "Detailed description of the module content and purpose"
},
+ "mode": {
+ "type": "string",
+ "enum": ["css", "tailwind"],
+ "description": "Whether this module teaches CSS or Tailwind"
+ },
"difficulty": {
"type": "string",
"enum": ["beginner", "intermediate", "advanced"],
@@ -53,6 +58,15 @@
"type": "string",
"description": "Detailed description of the lesson content and concepts"
},
+ "mode": {
+ "type": "string",
+ "enum": ["css", "tailwind"],
+ "description": "Override module mode for individual lessons"
+ },
+ "tailwindConfig": {
+ "type": "object",
+ "description": "Custom Tailwind configuration if needed"
+ },
"task": {
"type": "string",
"description": "The specific task instructions for the student to complete"
@@ -91,7 +105,7 @@
"properties": {
"type": {
"type": "string",
- "enum": ["contains", "not_contains", "regex", "property_value", "syntax", "custom"],
+ "enum": ["contains", "contains_class", "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 e1ff4e4..3d4c255 100644
--- a/src/app.js
+++ b/src/app.js
@@ -210,6 +210,19 @@ function resetSuccessIndicators() {
elements.runBtn.classList.remove("re-run");
}
+function updateEditorForMode(mode) {
+ const codeInput = elements.codeInput;
+ const editorLabel = document.querySelector(".editor-label");
+
+ if (mode === "tailwind") {
+ codeInput.placeholder = "Enter Tailwind classes (e.g., bg-blue-500 text-white p-4)";
+ if (editorLabel) editorLabel.textContent = "Tailwind Classes:";
+ } else {
+ codeInput.placeholder = "Enter your CSS code here...";
+ if (editorLabel) editorLabel.textContent = "CSS Code:";
+ }
+}
+
// Configure editor layout based on display type
function resetEditorLayout(lesson) {
elements.validationIndicators.innerHTML = "";
@@ -229,8 +242,12 @@ function loadCurrentLesson() {
}
const lesson = state.currentModule.lessons[state.currentLessonIndex];
+ const mode = lesson.mode || state.currentModule?.mode || "css";
lessonEngine.setLesson(lesson);
+ // Update UI based on mode
+ updateEditorForMode(mode);
+
// Reset any success indicators
resetSuccessIndicators();
diff --git a/src/config/lessons.js b/src/config/lessons.js
index c15d64b..c9f5382 100644
--- a/src/config/lessons.js
+++ b/src/config/lessons.js
@@ -11,22 +11,24 @@ import carouselConfig from "../../lessons/02-css-only-carousel.json";
// import selectorsConfig from "../../lessons/02-selectors.json";
// import colorsConfig from "../../lessons/03-colors.json";
// import typographyConfig from "../../lessons/04-typography.json";
-// import unitVariablesConfig from "../../lessons/05-units-variables.json";
+import unitVariablesConfig from "../../lessons/05-units-variables.json";
// import transitionsAnimationsConfig from "../../lessons/06-transitions-animations.json";
// import layoutConfig from "../../lessons/07-layouts.json";
// import responsiveConfig from "../../lessons/08-responsive.json";
+import tailwindConfig from "../../lessons/10-tailwind-basics.json";
// Module store
const moduleStore = [
// basicsConfig,
basicSelectorsConfig,
- // advancedSelectorsConfig,
+ advancedSelectorsConfig,
+ tailwindConfig
// carouselConfig
// boxModelConfig,
// selectorsConfig,
// colorsConfig
// typographyConfig,
- // unitVariablesConfig,
+ // unitVariablesConfig
// transitionsAnimationsConfig,
// layoutConfig,
// responsiveConfig
@@ -37,8 +39,13 @@ const moduleStore = [
* @returns {Promise} Promise resolving to array of modules
*/
export async function loadModules() {
- // In a real app, we might load these from a server
- return moduleStore;
+ return moduleStore.map((module) => ({
+ ...module,
+ lessons: module.lessons.map((lesson) => ({
+ ...lesson,
+ mode: module.mode || "css"
+ }))
+ }));
}
/**
diff --git a/src/helpers/validator.js b/src/helpers/validator.js
index 63b8140..f191c06 100644
--- a/src/helpers/validator.js
+++ b/src/helpers/validator.js
@@ -2,13 +2,79 @@
* Validator - Functions to validate user CSS code
*/
+export function validateUserCode(userCode, lesson) {
+ const mode = lesson.mode || "css";
+
+ if (mode === "tailwind") {
+ return validateTailwindClasses(userCode, lesson);
+ } else {
+ return validateCssCode(userCode, lesson);
+ }
+}
+
+function validateTailwindClasses(userClasses, lesson) {
+ if (!lesson || !lesson.validations) {
+ return { isValid: true, message: "No validations specified for this lesson." };
+ }
+
+ let result = {
+ isValid: true,
+ validCases: 0,
+ totalCases: lesson.validations.length,
+ message: "Your Tailwind classes look CRISPY!"
+ };
+
+ for (const validation of lesson.validations) {
+ const { type, value, message } = validation;
+ let validationPassed = false;
+
+ switch (type) {
+ case "contains_class":
+ validationPassed = userClasses.split(/\s+/).includes(value);
+ if (!validationPassed) {
+ result = {
+ ...result,
+ isValid: false,
+ message: message || `Your classes should include "${value}".`
+ };
+ }
+ break;
+
+ case "contains_pattern":
+ const regex = new RegExp(value);
+ validationPassed = regex.test(userClasses);
+ if (!validationPassed) {
+ result = {
+ ...result,
+ isValid: false,
+ message: message || "Your classes don't match the expected pattern."
+ };
+ }
+ break;
+
+ default:
+ // Fall back to original CSS validation for other types
+ validationPassed = containsValidation(userClasses, value);
+ }
+
+ if (validationPassed) {
+ result.validCases++;
+ } else {
+ return result;
+ }
+ }
+
+ result.validCases = lesson.validations.length;
+ return result;
+}
+
/**
* Validate user CSS code against the lesson requirements
* @param {string} userCode - User submitted CSS code
* @param {Object} lesson - The current lesson object
* @returns {Object} Validation result with isValid and message properties
*/
-export function validateUserCode(userCode, lesson) {
+export function validateCssCode(userCode, lesson) {
if (!lesson || !lesson.validations) {
return { isValid: true, message: "No validations specified for this lesson." };
}
diff --git a/src/impl/LessonEngine.js b/src/impl/LessonEngine.js
index 93a242d..abfaf8c 100644
--- a/src/impl/LessonEngine.js
+++ b/src/impl/LessonEngine.js
@@ -111,44 +111,64 @@ export class LessonEngine {
renderPreview() {
if (!this.currentLesson) return;
+ const mode = this.currentLesson.mode || this.currentModule?.mode || "css";
const { previewHTML, previewBaseCSS, previewContainer, sandboxCSS } = this.currentLesson;
- // Create an iframe for isolated preview rendering
const iframe = document.createElement("iframe");
iframe.style.width = "100%";
iframe.style.height = "100%";
iframe.style.border = "none";
iframe.title = "Preview";
- // Get the preview container
const container = document.getElementById(previewContainer || "preview-area");
-
- // Clear the container and add the iframe
container.innerHTML = "";
container.appendChild(iframe);
- // Get the complete CSS by combining all parts
- const userCssWithWrapper = this.getCompleteCss();
-
- // Write the content to the iframe
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
iframeDoc.open();
- iframeDoc.write(`
-
-
-
-
-
-
-
-
- ${previewHTML || "No preview available
"}
-
-
- `);
+
+ if (mode === "tailwind") {
+ // For Tailwind mode, user code goes directly in HTML classes
+ const htmlWithClasses = this.injectTailwindClasses(previewHTML, this.userCode);
+ iframeDoc.write(`
+
+
+
+
+
+
+
+
+ ${htmlWithClasses}
+
+
+ `);
+ } else {
+ // Original CSS mode
+ const userCssWithWrapper = this.getCompleteCss();
+ iframeDoc.write(`
+
+
+
+
+
+
+
+
+ ${previewHTML}
+
+
+ `);
+ }
+
iframeDoc.close();
}
+ injectTailwindClasses(html, userClasses) {
+ // Replace placeholder in HTML with user's Tailwind classes
+ return html.replace(/{{USER_CLASSES}}/g, userClasses);
+ }
+
/**
* Validate user code against the current lesson's requirements
* @returns {Object} Validation result