feat: complete section color coding with logo, hints, editor themes, and footers

- Add section-specific CodeMirror syntax highlighting (purple selectors for CSS)
- Logo now uses section colors (CSS purple as default, changes per section)
- Add section color coding for hints
- Add full footer to section and reference pages
- Fix nav highlight updates for sidebar and prev/next navigation
- Change welcome module mode to CSS for purple theme on first lesson
- Rebrand "Code Crispies" to "CODE CRISPIES" across all translations
- Fix scroll to top on section page navigation
- Change HTML section color to raspberry (#c75b7a)
This commit is contained in:
2026-01-16 04:32:55 +01:00
parent 47cda00a81
commit c0db8bba48
12 changed files with 321 additions and 123 deletions

View File

@@ -3,7 +3,7 @@
"id": "welcome", "id": "welcome",
"title": "Welcome", "title": "Welcome",
"description": "Get started with Code Crispies", "description": "Get started with Code Crispies",
"mode": "html", "mode": "css",
"difficulty": "beginner", "difficulty": "beginner",
"excludeFromProgress": true, "excludeFromProgress": true,
"lessons": [ "lessons": [

View File

@@ -3,7 +3,7 @@
"id": "welcome", "id": "welcome",
"title": "مرحباً", "title": "مرحباً",
"description": "ابدأ مع Code Crispies", "description": "ابدأ مع Code Crispies",
"mode": "html", "mode": "css",
"difficulty": "beginner", "difficulty": "beginner",
"excludeFromProgress": true, "excludeFromProgress": true,
"lessons": [ "lessons": [

View File

@@ -3,7 +3,7 @@
"id": "welcome", "id": "welcome",
"title": "Willkommen", "title": "Willkommen",
"description": "Erste Schritte mit Code Crispies", "description": "Erste Schritte mit Code Crispies",
"mode": "html", "mode": "css",
"difficulty": "beginner", "difficulty": "beginner",
"excludeFromProgress": true, "excludeFromProgress": true,
"lessons": [ "lessons": [

View File

@@ -3,7 +3,7 @@
"id": "welcome", "id": "welcome",
"title": "Bienvenido", "title": "Bienvenido",
"description": "Comienza con Code Crispies", "description": "Comienza con Code Crispies",
"mode": "html", "mode": "css",
"difficulty": "beginner", "difficulty": "beginner",
"excludeFromProgress": true, "excludeFromProgress": true,
"lessons": [ "lessons": [

View File

@@ -3,7 +3,7 @@
"id": "welcome", "id": "welcome",
"title": "Witaj", "title": "Witaj",
"description": "Rozpocznij przygodę z Code Crispies", "description": "Rozpocznij przygodę z Code Crispies",
"mode": "html", "mode": "css",
"difficulty": "beginner", "difficulty": "beginner",
"excludeFromProgress": true, "excludeFromProgress": true,
"lessons": [ "lessons": [

View File

@@ -3,7 +3,7 @@
"id": "welcome", "id": "welcome",
"title": "Ласкаво просимо", "title": "Ласкаво просимо",
"description": "Почніть з Code Crispies", "description": "Почніть з Code Crispies",
"mode": "html", "mode": "css",
"difficulty": "beginner", "difficulty": "beginner",
"excludeFromProgress": true, "excludeFromProgress": true,
"lessons": [ "lessons": [

View File

@@ -156,6 +156,8 @@ const elements = {
closeSidebar: document.getElementById("close-sidebar"), closeSidebar: document.getElementById("close-sidebar"),
moduleList: document.getElementById("module-list"), moduleList: document.getElementById("module-list"),
footerLessonLinks: document.getElementById("footer-lesson-links"), footerLessonLinks: document.getElementById("footer-lesson-links"),
refFooterLessonLinks: document.getElementById("ref-footer-lesson-links"),
sectionFooterLessonLinks: document.getElementById("section-footer-lesson-links"),
progressFill: document.getElementById("progress-fill"), progressFill: document.getElementById("progress-fill"),
progressText: document.getElementById("progress-text"), progressText: document.getElementById("progress-text"),
resetBtn: document.getElementById("reset-btn"), resetBtn: document.getElementById("reset-btn"),
@@ -456,6 +458,13 @@ function selectLesson(moduleId, lessonIndex) {
loadCurrentLesson(); loadCurrentLesson();
// Update section color coding (after loadCurrentLesson to ensure content is loaded first)
const newState = lessonEngine.getCurrentState();
updateSectionColor(getModuleSection(newState.module));
// Update nav highlight
updateNavHighlight({ type: RouteType.LESSON, moduleId, lessonIndex });
// Close sidebar after selection on mobile // Close sidebar after selection on mobile
if (window.innerWidth <= 768) { if (window.innerWidth <= 768) {
closeSidebar(); closeSidebar();
@@ -694,11 +703,15 @@ function nextLesson() {
// Update URL // Update URL
updateHash(newState.module.id, newState.lessonIndex); updateHash(newState.module.id, newState.lessonIndex);
if (newState.module.id !== prevModuleId) { const moduleChanged = newState.module.id !== prevModuleId;
if (moduleChanged) {
updateModuleHighlight(newState.module.id); updateModuleHighlight(newState.module.id);
updateSectionColor(getModuleSection(newState.module));
} }
loadCurrentLesson(); loadCurrentLesson();
if (moduleChanged) {
updateSectionColor(getModuleSection(newState.module));
updateNavHighlight({ type: RouteType.LESSON, moduleId: newState.module.id, lessonIndex: newState.lessonIndex });
}
} }
} }
@@ -711,11 +724,15 @@ function prevLesson() {
// Update URL // Update URL
updateHash(newState.module.id, newState.lessonIndex); updateHash(newState.module.id, newState.lessonIndex);
if (newState.module.id !== prevModuleId) { const moduleChanged = newState.module.id !== prevModuleId;
if (moduleChanged) {
updateModuleHighlight(newState.module.id); updateModuleHighlight(newState.module.id);
updateSectionColor(getModuleSection(newState.module));
} }
loadCurrentLesson(); loadCurrentLesson();
if (moduleChanged) {
updateSectionColor(getModuleSection(newState.module));
updateNavHighlight({ type: RouteType.LESSON, moduleId: newState.module.id, lessonIndex: newState.lessonIndex });
}
} }
} }
@@ -1852,7 +1869,7 @@ function stripHtml(html) {
* Update page meta tags based on current route for SEO * Update page meta tags based on current route for SEO
*/ */
function updatePageMeta(route) { function updatePageMeta(route) {
const defaultTitle = "Code Crispies - Learn HTML & CSS Interactively | Free Coding Practice"; const defaultTitle = "CODE CRISPIES - Learn HTML & CSS Interactively | Free Coding Practice";
const defaultDesc = const defaultDesc =
"Master HTML, CSS, and Tailwind through hands-on coding exercises. Free, open-source learning platform with instant feedback. No account required."; "Master HTML, CSS, and Tailwind through hands-on coding exercises. Free, open-source learning platform with instant feedback. No account required.";
@@ -1872,7 +1889,7 @@ function updatePageMeta(route) {
case RouteType.SECTION: { case RouteType.SECTION: {
const sectionNames = { css: "CSS", html: "HTML", tailwind: "Tailwind CSS" }; const sectionNames = { css: "CSS", html: "HTML", tailwind: "Tailwind CSS" };
const sectionName = sectionNames[route.sectionId] || route.sectionId; const sectionName = sectionNames[route.sectionId] || route.sectionId;
title = `${sectionName} Lessons - Code Crispies | Learn ${sectionName}`; title = `${sectionName} Lessons - CODE CRISPIES | Learn ${sectionName}`;
description = `Learn ${sectionName} through interactive coding exercises. Hands-on practice with instant feedback.`; description = `Learn ${sectionName} through interactive coding exercises. Hands-on practice with instant feedback.`;
break; break;
} }
@@ -1881,7 +1898,7 @@ function updatePageMeta(route) {
const module = lessonEngine.modules.find((m) => m.id === route.moduleId); const module = lessonEngine.modules.find((m) => m.id === route.moduleId);
const lesson = module?.lessons[route.lessonIndex]; const lesson = module?.lessons[route.lessonIndex];
if (module && lesson) { if (module && lesson) {
title = `${lesson.title} - ${module.title} | Code Crispies`; title = `${lesson.title} - ${module.title} | CODE CRISPIES`;
const lessonDesc = stripHtml(lesson.description || lesson.task); const lessonDesc = stripHtml(lesson.description || lesson.task);
description = lessonDesc.length > 155 ? lessonDesc.slice(0, 152) + "..." : lessonDesc || defaultDesc; description = lessonDesc.length > 155 ? lessonDesc.slice(0, 152) + "..." : lessonDesc || defaultDesc;
} }
@@ -1897,7 +1914,7 @@ function updatePageMeta(route) {
html: "HTML Elements" html: "HTML Elements"
}; };
const refName = refNames[route.refId] || "Reference"; const refName = refNames[route.refId] || "Reference";
title = `${refName} Reference - Code Crispies`; title = `${refName} Reference - CODE CRISPIES`;
description = `Quick reference guide for ${refName}. Syntax, examples, and common patterns for web development.`; description = `Quick reference guide for ${refName}. Syntax, examples, and common patterns for web development.`;
break; break;
} }
@@ -1913,13 +1930,13 @@ function updatePageMeta(route) {
// Update Open Graph tags // Update Open Graph tags
const ogTitle = document.querySelector('meta[property="og:title"]'); const ogTitle = document.querySelector('meta[property="og:title"]');
const ogDesc = document.querySelector('meta[property="og:description"]'); const ogDesc = document.querySelector('meta[property="og:description"]');
if (ogTitle) ogTitle.setAttribute("content", title.replace(" | Code Crispies", "").replace(" - Code Crispies", "")); if (ogTitle) ogTitle.setAttribute("content", title.replace(" | CODE CRISPIES", "").replace(" - CODE CRISPIES", ""));
if (ogDesc) ogDesc.setAttribute("content", description); if (ogDesc) ogDesc.setAttribute("content", description);
// Update Twitter tags // Update Twitter tags
const twitterTitle = document.querySelector('meta[name="twitter:title"]'); const twitterTitle = document.querySelector('meta[name="twitter:title"]');
const twitterDesc = document.querySelector('meta[name="twitter:description"]'); const twitterDesc = document.querySelector('meta[name="twitter:description"]');
if (twitterTitle) twitterTitle.setAttribute("content", title.replace(" | Code Crispies", "").replace(" - Code Crispies", "")); if (twitterTitle) twitterTitle.setAttribute("content", title.replace(" | CODE CRISPIES", "").replace(" - CODE CRISPIES", ""));
if (twitterDesc) twitterDesc.setAttribute("content", description); if (twitterDesc) twitterDesc.setAttribute("content", description);
} }
@@ -1994,6 +2011,11 @@ function updateSectionColor(sectionId) {
} else { } else {
document.body.removeAttribute("data-section"); document.body.removeAttribute("data-section");
} }
// Update code editor theme for section
if (codeEditor) {
codeEditor.setSection(sectionId);
}
} }
/** /**
@@ -2018,8 +2040,6 @@ function showLandingPage() {
* Render module links in the landing page footer, grouped by section * Render module links in the landing page footer, grouped by section
*/ */
function renderFooterLessonLinks() { function renderFooterLessonLinks() {
if (!elements.footerLessonLinks) return;
const modules = lessonEngine.modules || []; const modules = lessonEngine.modules || [];
const sectionGroups = { css: [], html: [] }; const sectionGroups = { css: [], html: [] };
@@ -2042,8 +2062,17 @@ function renderFooterLessonLinks() {
html += "</div>"; html += "</div>";
}); });
// Update all footer lesson links
if (elements.footerLessonLinks) {
elements.footerLessonLinks.innerHTML = html; elements.footerLessonLinks.innerHTML = html;
} }
if (elements.refFooterLessonLinks) {
elements.refFooterLessonLinks.innerHTML = html;
}
if (elements.sectionFooterLessonLinks) {
elements.sectionFooterLessonLinks.innerHTML = html;
}
}
/** /**
* Update progress indicators on landing page * Update progress indicators on landing page
@@ -2077,7 +2106,6 @@ function updateLandingProgress() {
function showSectionPage(sectionId) { function showSectionPage(sectionId) {
hideAllPages(); hideAllPages();
elements.sectionPage?.classList.remove("hidden"); elements.sectionPage?.classList.remove("hidden");
window.scrollTo(0, 0);
// Update section color // Update section color
updateSectionColor(sectionId); updateSectionColor(sectionId);
@@ -2120,6 +2148,9 @@ function showSectionPage(sectionId) {
const percent = total > 0 ? Math.round((completed / total) * 100) : 0; const percent = total > 0 ? Math.round((completed / total) * 100) : 0;
if (elements.sectionProgressFill) elements.sectionProgressFill.style.width = `${percent}%`; if (elements.sectionProgressFill) elements.sectionProgressFill.style.width = `${percent}%`;
if (elements.sectionProgressText) elements.sectionProgressText.textContent = `${completed} of ${total} lessons complete`; if (elements.sectionProgressText) elements.sectionProgressText.textContent = `${completed} of ${total} lessons complete`;
// Scroll to top after content is rendered
requestAnimationFrame(() => window.scrollTo(0, 0));
} }
/** /**

View File

@@ -15,7 +15,7 @@ export const sections = {
id: "html", id: "html",
title: "HTML", title: "HTML",
description: "Semantic markup and native elements", description: "Semantic markup and native elements",
color: "#d4637b", color: "#c75b7a",
order: 2 order: 2
}, },
tailwind: { tailwind: {

View File

@@ -1,11 +1,11 @@
/** /**
* Internationalization module for Code Crispies * Internationalization module for CODE CRISPIES
*/ */
const translations = { const translations = {
en: { en: {
// Page // Page
pageTitle: "Code Crispies - Learn HTML & CSS Interactively", pageTitle: "CODE CRISPIES - Learn HTML & CSS Interactively",
skipLink: "Skip to main content", skipLink: "Skip to main content",
// Header // Header
@@ -48,9 +48,9 @@ const translations = {
// Help dialog // Help dialog
helpTitle: "Help", helpTitle: "Help",
aboutTitle: "About Code Crispies", aboutTitle: "About CODE CRISPIES",
aboutText: aboutText:
"Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required - just start coding!", "CODE CRISPIES is a free, open-source platform for learning web development through hands-on exercises. No account required - just start coding!",
learningModesTitle: "Learning Modes", learningModesTitle: "Learning Modes",
modeCss: "<strong>CSS</strong> - Write CSS rules to style elements", modeCss: "<strong>CSS</strong> - Write CSS rules to style elements",
modeTailwind: "<strong>Tailwind</strong> - Apply utility classes directly in HTML", modeTailwind: "<strong>Tailwind</strong> - Apply utility classes directly in HTML",
@@ -85,7 +85,7 @@ const translations = {
// Contact // Contact
contactTitle: "Contact & Links", contactTitle: "Contact & Links",
contactText: 'Code Crispies is developed by <a href="https://librete.ch" target="_blank">LibreTECH</a>', contactText: 'CODE CRISPIES is developed by <a href="https://librete.ch" target="_blank">LibreTECH</a>',
// Reset dialog // Reset dialog
resetDialogTitle: "Reset Progress", resetDialogTitle: "Reset Progress",
@@ -121,7 +121,7 @@ const translations = {
landingHeroHighlight: "By Writing Real Code", landingHeroHighlight: "By Writing Real Code",
landingHeroSubtitle: "Master HTML, CSS, and Tailwind through hands-on exercises with instant feedback. Free and open source.", landingHeroSubtitle: "Master HTML, CSS, and Tailwind through hands-on exercises with instant feedback. Free and open source.",
landingCtaStart: "Start Learning NOW", landingCtaStart: "Start Learning NOW",
landingWhyTitle: "Why Code Crispies Works", landingWhyTitle: "Why CODE CRISPIES Works",
landingBenefit1Title: "Learn by Doing", landingBenefit1Title: "Learn by Doing",
landingBenefit1Text: "Write real code from lesson one. No videos to watch—just you, an editor, and instant feedback on every keystroke.", landingBenefit1Text: "Write real code from lesson one. No videos to watch—just you, an editor, and instant feedback on every keystroke.",
landingBenefit2Title: "Practice at Your Pace", landingBenefit2Title: "Practice at Your Pace",
@@ -145,17 +145,17 @@ const translations = {
footerPlayground: "Playground", footerPlayground: "Playground",
footerAbout: "About", footerAbout: "About",
footerSupport: "Support", footerSupport: "Support",
footerSupportText: "Help keep Code Crispies free and open source.", footerSupportText: "Help keep CODE CRISPIES free and open source.",
footerLicense: "Released into the public domain.", footerLicense: "Released into the public domain.",
// Help Dialog Support // Help Dialog Support
supportTitle: "Support the Project", supportTitle: "Support the Project",
supportText: "Help keep Code Crispies free and open source." supportText: "Help keep CODE CRISPIES free and open source."
}, },
de: { de: {
// Page // Page
pageTitle: "Code Crispies - HTML & CSS interaktiv lernen", pageTitle: "CODE CRISPIES - HTML & CSS interaktiv lernen",
skipLink: "Zum Hauptinhalt springen", skipLink: "Zum Hauptinhalt springen",
// Header // Header
@@ -198,9 +198,9 @@ const translations = {
// Help dialog // Help dialog
helpTitle: "Hilfe", helpTitle: "Hilfe",
aboutTitle: "Über Code Crispies", aboutTitle: "Über CODE CRISPIES",
aboutText: aboutText:
"Code Crispies ist eine kostenlose Open-Source-Plattform zum Erlernen von Webentwicklung durch praktische Übungen. Kein Konto erforderlich - einfach loslegen!", "CODE CRISPIES ist eine kostenlose Open-Source-Plattform zum Erlernen von Webentwicklung durch praktische Übungen. Kein Konto erforderlich - einfach loslegen!",
learningModesTitle: "Lernmodi", learningModesTitle: "Lernmodi",
modeCss: "<strong>CSS</strong> - Schreibe CSS-Regeln zum Stylen von Elementen", modeCss: "<strong>CSS</strong> - Schreibe CSS-Regeln zum Stylen von Elementen",
modeTailwind: "<strong>Tailwind</strong> - Wende Utility-Klassen direkt im HTML an", modeTailwind: "<strong>Tailwind</strong> - Wende Utility-Klassen direkt im HTML an",
@@ -236,7 +236,7 @@ const translations = {
// Contact // Contact
contactTitle: "Kontakt & Links", contactTitle: "Kontakt & Links",
contactText: 'Code Crispies wird von <a href="https://librete.ch" target="_blank">LibreTECH</a> entwickelt', contactText: 'CODE CRISPIES wird von <a href="https://librete.ch" target="_blank">LibreTECH</a> entwickelt',
// Reset dialog // Reset dialog
resetDialogTitle: "Fortschritt zurücksetzen", resetDialogTitle: "Fortschritt zurücksetzen",
@@ -272,7 +272,7 @@ const translations = {
landingHeroHighlight: "Selbstständig lernen", landingHeroHighlight: "Selbstständig lernen",
landingHeroSubtitle: "Meistere HTML, CSS und Tailwind durch praktische Übungen mit sofortigem Feedback. Kostenlos und Open Source.", landingHeroSubtitle: "Meistere HTML, CSS und Tailwind durch praktische Übungen mit sofortigem Feedback. Kostenlos und Open Source.",
landingCtaStart: "Jetzt starten", landingCtaStart: "Jetzt starten",
landingWhyTitle: "Warum Code Crispies funktioniert", landingWhyTitle: "Warum CODE CRISPIES funktioniert",
landingBenefit1Title: "Lernen durch Praxis", landingBenefit1Title: "Lernen durch Praxis",
landingBenefit1Text: landingBenefit1Text:
"Schreibe echten Code ab der ersten Lektion. Keine Videos nur du, ein Editor und sofortiges Feedback bei jedem Tastendruck.", "Schreibe echten Code ab der ersten Lektion. Keine Videos nur du, ein Editor und sofortiges Feedback bei jedem Tastendruck.",
@@ -298,18 +298,18 @@ const translations = {
footerPlayground: "Playground", footerPlayground: "Playground",
footerAbout: "Über uns", footerAbout: "Über uns",
footerSupport: "Unterstützen", footerSupport: "Unterstützen",
footerSupportText: "Hilf mit, Code Crispies kostenlos und Open Source zu halten.", footerSupportText: "Hilf mit, CODE CRISPIES kostenlos und Open Source zu halten.",
footerLicense: "Gemeinfrei (Public Domain).", footerLicense: "Gemeinfrei (Public Domain).",
// Help Dialog Support // Help Dialog Support
supportTitle: "Projekt unterstützen", supportTitle: "Projekt unterstützen",
supportText: "Hilf mit, Code Crispies kostenlos und Open Source zu halten." supportText: "Hilf mit, CODE CRISPIES kostenlos und Open Source zu halten."
}, },
// Polish // Polish
pl: { pl: {
// Page // Page
pageTitle: "Code Crispies - Nauka HTML i CSS interaktywnie", pageTitle: "CODE CRISPIES - Nauka HTML i CSS interaktywnie",
skipLink: "Przejdź do głównej treści", skipLink: "Przejdź do głównej treści",
// Header // Header
@@ -352,9 +352,9 @@ const translations = {
// Help dialog // Help dialog
helpTitle: "Pomoc", helpTitle: "Pomoc",
aboutTitle: "O Code Crispies", aboutTitle: "O CODE CRISPIES",
aboutText: aboutText:
"Code Crispies to darmowa platforma open-source do nauki tworzenia stron internetowych poprzez praktyczne ćwiczenia. Nie wymaga konta - po prostu zacznij kodować!", "CODE CRISPIES to darmowa platforma open-source do nauki tworzenia stron internetowych poprzez praktyczne ćwiczenia. Nie wymaga konta - po prostu zacznij kodować!",
learningModesTitle: "Tryby nauki", learningModesTitle: "Tryby nauki",
modeCss: "<strong>CSS</strong> - Pisz reguły CSS do stylizowania elementów", modeCss: "<strong>CSS</strong> - Pisz reguły CSS do stylizowania elementów",
modeTailwind: "<strong>Tailwind</strong> - Stosuj klasy utility bezpośrednio w HTML", modeTailwind: "<strong>Tailwind</strong> - Stosuj klasy utility bezpośrednio w HTML",
@@ -389,7 +389,7 @@ const translations = {
// Contact // Contact
contactTitle: "Kontakt i linki", contactTitle: "Kontakt i linki",
contactText: 'Code Crispies jest rozwijany przez <a href="https://librete.ch" target="_blank">LibreTECH</a>', contactText: 'CODE CRISPIES jest rozwijany przez <a href="https://librete.ch" target="_blank">LibreTECH</a>',
// Reset dialog // Reset dialog
resetDialogTitle: "Resetuj postęp", resetDialogTitle: "Resetuj postęp",
@@ -425,7 +425,7 @@ const translations = {
landingHeroHighlight: "Pisząc prawdziwy kod", landingHeroHighlight: "Pisząc prawdziwy kod",
landingHeroSubtitle: "Opanuj HTML, CSS i Tailwind poprzez praktyczne ćwiczenia z natychmiastową informacją zwrotną. Darmowe i open source.", landingHeroSubtitle: "Opanuj HTML, CSS i Tailwind poprzez praktyczne ćwiczenia z natychmiastową informacją zwrotną. Darmowe i open source.",
landingCtaStart: "Zacznij TERAZ", landingCtaStart: "Zacznij TERAZ",
landingWhyTitle: "Dlaczego Code Crispies działa", landingWhyTitle: "Dlaczego CODE CRISPIES działa",
landingBenefit1Title: "Ucz się przez praktykę", landingBenefit1Title: "Ucz się przez praktykę",
landingBenefit1Text: landingBenefit1Text:
"Pisz prawdziwy kod od pierwszej lekcji. Żadnych filmów tylko ty, edytor i natychmiastowa informacja zwrotna przy każdym naciśnięciu klawisza.", "Pisz prawdziwy kod od pierwszej lekcji. Żadnych filmów tylko ty, edytor i natychmiastowa informacja zwrotna przy każdym naciśnięciu klawisza.",
@@ -451,18 +451,18 @@ const translations = {
footerPlayground: "Plac zabaw", footerPlayground: "Plac zabaw",
footerAbout: "O nas", footerAbout: "O nas",
footerSupport: "Wsparcie", footerSupport: "Wsparcie",
footerSupportText: "Pomóż utrzymać Code Crispies darmowym i open source.", footerSupportText: "Pomóż utrzymać CODE CRISPIES darmowym i open source.",
footerLicense: "Udostępnione jako domena publiczna.", footerLicense: "Udostępnione jako domena publiczna.",
// Help Dialog Support // Help Dialog Support
supportTitle: "Wesprzyj projekt", supportTitle: "Wesprzyj projekt",
supportText: "Pomóż utrzymać Code Crispies darmowym i open source." supportText: "Pomóż utrzymać CODE CRISPIES darmowym i open source."
}, },
// Spanish // Spanish
es: { es: {
// Page // Page
pageTitle: "Code Crispies - Aprende HTML y CSS de forma interactiva", pageTitle: "CODE CRISPIES - Aprende HTML y CSS de forma interactiva",
skipLink: "Saltar al contenido principal", skipLink: "Saltar al contenido principal",
// Header // Header
@@ -505,9 +505,9 @@ const translations = {
// Help dialog // Help dialog
helpTitle: "Ayuda", helpTitle: "Ayuda",
aboutTitle: "Acerca de Code Crispies", aboutTitle: "Acerca de CODE CRISPIES",
aboutText: aboutText:
"Code Crispies es una plataforma gratuita de código abierto para aprender desarrollo web a través de ejercicios prácticos. No se requiere cuenta, ¡solo empieza a programar!", "CODE CRISPIES es una plataforma gratuita de código abierto para aprender desarrollo web a través de ejercicios prácticos. No se requiere cuenta, ¡solo empieza a programar!",
learningModesTitle: "Modos de aprendizaje", learningModesTitle: "Modos de aprendizaje",
modeCss: "<strong>CSS</strong> - Escribe reglas CSS para estilizar elementos", modeCss: "<strong>CSS</strong> - Escribe reglas CSS para estilizar elementos",
modeTailwind: "<strong>Tailwind</strong> - Aplica clases de utilidad directamente en HTML", modeTailwind: "<strong>Tailwind</strong> - Aplica clases de utilidad directamente en HTML",
@@ -543,7 +543,7 @@ const translations = {
// Contact // Contact
contactTitle: "Contacto y enlaces", contactTitle: "Contacto y enlaces",
contactText: 'Code Crispies es desarrollado por <a href="https://librete.ch" target="_blank">LibreTECH</a>', contactText: 'CODE CRISPIES es desarrollado por <a href="https://librete.ch" target="_blank">LibreTECH</a>',
// Reset dialog // Reset dialog
resetDialogTitle: "Reiniciar progreso", resetDialogTitle: "Reiniciar progreso",
@@ -580,7 +580,7 @@ const translations = {
landingHeroSubtitle: landingHeroSubtitle:
"Domina HTML, CSS y Tailwind a través de ejercicios prácticos con retroalimentación instantánea. Gratis y de código abierto.", "Domina HTML, CSS y Tailwind a través de ejercicios prácticos con retroalimentación instantánea. Gratis y de código abierto.",
landingCtaStart: "Empieza AHORA", landingCtaStart: "Empieza AHORA",
landingWhyTitle: "Por qué funciona Code Crispies", landingWhyTitle: "Por qué funciona CODE CRISPIES",
landingBenefit1Title: "Aprende haciendo", landingBenefit1Title: "Aprende haciendo",
landingBenefit1Text: landingBenefit1Text:
"Escribe código real desde la primera lección. Sin videos que ver—solo tú, un editor y retroalimentación instantánea en cada tecla.", "Escribe código real desde la primera lección. Sin videos que ver—solo tú, un editor y retroalimentación instantánea en cada tecla.",
@@ -606,18 +606,18 @@ const translations = {
footerPlayground: "Zona de pruebas", footerPlayground: "Zona de pruebas",
footerAbout: "Acerca de", footerAbout: "Acerca de",
footerSupport: "Apoyar", footerSupport: "Apoyar",
footerSupportText: "Ayuda a mantener Code Crispies gratis y de código abierto.", footerSupportText: "Ayuda a mantener CODE CRISPIES gratis y de código abierto.",
footerLicense: "Liberado al dominio público.", footerLicense: "Liberado al dominio público.",
// Help Dialog Support // Help Dialog Support
supportTitle: "Apoyar el proyecto", supportTitle: "Apoyar el proyecto",
supportText: "Ayuda a mantener Code Crispies gratis y de código abierto." supportText: "Ayuda a mantener CODE CRISPIES gratis y de código abierto."
}, },
// Arabic // Arabic
ar: { ar: {
// Page // Page
pageTitle: "Code Crispies - تعلم HTML و CSS بشكل تفاعلي", pageTitle: "CODE CRISPIES - تعلم HTML و CSS بشكل تفاعلي",
skipLink: "انتقل إلى المحتوى الرئيسي", skipLink: "انتقل إلى المحتوى الرئيسي",
// Header // Header
@@ -660,8 +660,8 @@ const translations = {
// Help dialog // Help dialog
helpTitle: "مساعدة", helpTitle: "مساعدة",
aboutTitle: "عن Code Crispies", aboutTitle: "عن CODE CRISPIES",
aboutText: "Code Crispies هي منصة مجانية مفتوحة المصدر لتعلم تطوير الويب من خلال تمارين عملية. لا يلزم حساب - فقط ابدأ البرمجة!", aboutText: "CODE CRISPIES هي منصة مجانية مفتوحة المصدر لتعلم تطوير الويب من خلال تمارين عملية. لا يلزم حساب - فقط ابدأ البرمجة!",
learningModesTitle: "أوضاع التعلم", learningModesTitle: "أوضاع التعلم",
modeCss: "<strong>CSS</strong> - اكتب قواعد CSS لتنسيق العناصر", modeCss: "<strong>CSS</strong> - اكتب قواعد CSS لتنسيق العناصر",
modeTailwind: "<strong>Tailwind</strong> - طبق فئات الأدوات مباشرة في HTML", modeTailwind: "<strong>Tailwind</strong> - طبق فئات الأدوات مباشرة في HTML",
@@ -696,7 +696,7 @@ const translations = {
// Contact // Contact
contactTitle: "التواصل والروابط", contactTitle: "التواصل والروابط",
contactText: 'Code Crispies تم تطويره بواسطة <a href="https://librete.ch" target="_blank">LibreTECH</a>', contactText: 'CODE CRISPIES تم تطويره بواسطة <a href="https://librete.ch" target="_blank">LibreTECH</a>',
// Reset dialog // Reset dialog
resetDialogTitle: "إعادة تعيين التقدم", resetDialogTitle: "إعادة تعيين التقدم",
@@ -732,7 +732,7 @@ const translations = {
landingHeroHighlight: "بكتابة كود حقيقي", landingHeroHighlight: "بكتابة كود حقيقي",
landingHeroSubtitle: "أتقن HTML و CSS و Tailwind من خلال تمارين عملية مع ملاحظات فورية. مجاني ومفتوح المصدر.", landingHeroSubtitle: "أتقن HTML و CSS و Tailwind من خلال تمارين عملية مع ملاحظات فورية. مجاني ومفتوح المصدر.",
landingCtaStart: "ابدأ الآن", landingCtaStart: "ابدأ الآن",
landingWhyTitle: "لماذا Code Crispies فعال", landingWhyTitle: "لماذا CODE CRISPIES فعال",
landingBenefit1Title: "تعلم بالممارسة", landingBenefit1Title: "تعلم بالممارسة",
landingBenefit1Text: "اكتب كودًا حقيقيًا من الدرس الأول. لا فيديوهات للمشاهدة—فقط أنت ومحرر وملاحظات فورية مع كل ضغطة مفتاح.", landingBenefit1Text: "اكتب كودًا حقيقيًا من الدرس الأول. لا فيديوهات للمشاهدة—فقط أنت ومحرر وملاحظات فورية مع كل ضغطة مفتاح.",
landingBenefit2Title: "بسرعتك الخاصة", landingBenefit2Title: "بسرعتك الخاصة",
@@ -756,18 +756,18 @@ const translations = {
footerPlayground: "ساحة التجربة", footerPlayground: "ساحة التجربة",
footerAbout: "حول", footerAbout: "حول",
footerSupport: "الدعم", footerSupport: "الدعم",
footerSupportText: "ساعد في إبقاء Code Crispies مجانيًا ومفتوح المصدر.", footerSupportText: "ساعد في إبقاء CODE CRISPIES مجانيًا ومفتوح المصدر.",
footerLicense: "مُطلق للملكية العامة.", footerLicense: "مُطلق للملكية العامة.",
// Help Dialog Support // Help Dialog Support
supportTitle: "ادعم المشروع", supportTitle: "ادعم المشروع",
supportText: "ساعد في إبقاء Code Crispies مجانيًا ومفتوح المصدر." supportText: "ساعد في إبقاء CODE CRISPIES مجانيًا ومفتوح المصدر."
}, },
// Ukrainian // Ukrainian
uk: { uk: {
// Page // Page
pageTitle: "Code Crispies - Вивчай HTML та CSS інтерактивно", pageTitle: "CODE CRISPIES - Вивчай HTML та CSS інтерактивно",
skipLink: "Перейти до основного вмісту", skipLink: "Перейти до основного вмісту",
// Header // Header
@@ -810,9 +810,9 @@ const translations = {
// Help dialog // Help dialog
helpTitle: "Допомога", helpTitle: "Допомога",
aboutTitle: "Про Code Crispies", aboutTitle: "Про CODE CRISPIES",
aboutText: aboutText:
"Code Crispies — це безкоштовна платформа з відкритим кодом для вивчення веб-розробки через практичні вправи. Обліковий запис не потрібен — просто починайте кодувати!", "CODE CRISPIES — це безкоштовна платформа з відкритим кодом для вивчення веб-розробки через практичні вправи. Обліковий запис не потрібен — просто починайте кодувати!",
learningModesTitle: "Режими навчання", learningModesTitle: "Режими навчання",
modeCss: "<strong>CSS</strong> - Пишіть правила CSS для стилізації елементів", modeCss: "<strong>CSS</strong> - Пишіть правила CSS для стилізації елементів",
modeTailwind: "<strong>Tailwind</strong> - Застосовуйте утилітарні класи безпосередньо в HTML", modeTailwind: "<strong>Tailwind</strong> - Застосовуйте утилітарні класи безпосередньо в HTML",
@@ -847,7 +847,7 @@ const translations = {
// Contact // Contact
contactTitle: "Контакти та посилання", contactTitle: "Контакти та посилання",
contactText: 'Code Crispies розроблено <a href="https://librete.ch" target="_blank">LibreTECH</a>', contactText: 'CODE CRISPIES розроблено <a href="https://librete.ch" target="_blank">LibreTECH</a>',
// Reset dialog // Reset dialog
resetDialogTitle: "Скинути прогрес", resetDialogTitle: "Скинути прогрес",
@@ -883,7 +883,7 @@ const translations = {
landingHeroHighlight: "Пишучи справжній код", landingHeroHighlight: "Пишучи справжній код",
landingHeroSubtitle: "Опануй HTML, CSS та Tailwind через практичні вправи з миттєвим зворотним зв'язком. Безкоштовно та з відкритим кодом.", landingHeroSubtitle: "Опануй HTML, CSS та Tailwind через практичні вправи з миттєвим зворотним зв'язком. Безкоштовно та з відкритим кодом.",
landingCtaStart: "Почни ЗАРАЗ", landingCtaStart: "Почни ЗАРАЗ",
landingWhyTitle: "Чому Code Crispies працює", landingWhyTitle: "Чому CODE CRISPIES працює",
landingBenefit1Title: "Вчись на практиці", landingBenefit1Title: "Вчись на практиці",
landingBenefit1Text: landingBenefit1Text:
"Пиши справжній код з першого уроку. Жодних відео—тільки ти, редактор і миттєвий зворотний зв'язок при кожному натисканні клавіші.", "Пиши справжній код з першого уроку. Жодних відео—тільки ти, редактор і миттєвий зворотний зв'язок при кожному натисканні клавіші.",
@@ -908,12 +908,12 @@ const translations = {
footerPlayground: "Пісочниця", footerPlayground: "Пісочниця",
footerAbout: "Про нас", footerAbout: "Про нас",
footerSupport: "Підтримка", footerSupport: "Підтримка",
footerSupportText: "Допоможи зберегти Code Crispies безкоштовним та з відкритим кодом.", footerSupportText: "Допоможи зберегти CODE CRISPIES безкоштовним та з відкритим кодом.",
footerLicense: "Передано у суспільне надбання.", footerLicense: "Передано у суспільне надбання.",
// Help Dialog Support // Help Dialog Support
supportTitle: "Підтримати проєкт", supportTitle: "Підтримати проєкт",
supportText: "Допоможи зберегти Code Crispies безкоштовним та з відкритим кодом." supportText: "Допоможи зберегти CODE CRISPIES безкоштовним та з відкритим кодом."
} }
}; };

View File

@@ -60,8 +60,8 @@ const crispyTheme = EditorView.theme(
{ dark: true } { dark: true }
); );
// Syntax highlighting with purple accent // Default syntax highlighting (blue accent)
const crispyHighlight = HighlightStyle.define([ const defaultHighlight = HighlightStyle.define([
{ tag: tags.keyword, color: "#c9a6eb" }, { tag: tags.keyword, color: "#c9a6eb" },
{ tag: tags.operator, color: "#cdd6f4" }, { tag: tags.operator, color: "#cdd6f4" },
{ tag: tags.variableName, color: "#89b4fa" }, { tag: tags.variableName, color: "#89b4fa" },
@@ -83,8 +83,42 @@ const crispyHighlight = HighlightStyle.define([
{ tag: tags.color, color: "#f9e2af" } { tag: tags.color, color: "#f9e2af" }
]); ]);
// Combined theme export // CSS section highlighting (purple selectors)
export const crispyEditorTheme = [crispyTheme, syntaxHighlighting(crispyHighlight)]; const cssHighlight = HighlightStyle.define([
{ tag: tags.keyword, color: "#c9a6eb" },
{ tag: tags.operator, color: "#cdd6f4" },
{ tag: tags.variableName, color: "#c9a6eb" },
{ tag: tags.propertyName, color: "#89b4fa" },
{ tag: tags.attributeName, color: "#89b4fa" },
{ tag: tags.className, color: "#c9a6eb" },
{ tag: tags.tagName, color: "#c9a6eb" },
{ tag: tags.string, color: "#a6e3a1" },
{ tag: tags.number, color: "#fab387" },
{ tag: tags.bool, color: "#fab387" },
{ tag: tags.null, color: "#fab387" },
{ tag: tags.comment, color: "#6c7086", fontStyle: "italic" },
{ tag: tags.bracket, color: "#cdd6f4" },
{ tag: tags.punctuation, color: "#cdd6f4" },
{ tag: tags.definition(tags.variableName), color: "#c9a6eb" },
{ tag: tags.function(tags.variableName), color: "#89b4fa" },
{ tag: tags.atom, color: "#c9a6eb" },
{ tag: tags.unit, color: "#a6e3a1" },
{ tag: tags.color, color: "#f9e2af" }
]);
// Get highlight style based on section
function getHighlightForSection(section) {
if (section === "css") return cssHighlight;
return defaultHighlight;
}
// Get theme with section-specific highlighting
export function getEditorTheme(section) {
return [crispyTheme, syntaxHighlighting(getHighlightForSection(section))];
}
// Default combined theme export (for backwards compatibility)
export const crispyEditorTheme = [crispyTheme, syntaxHighlighting(defaultHighlight)];
// Custom overrides for editor styling // Custom overrides for editor styling
const editorTheme = EditorView.theme( const editorTheme = EditorView.theme(
@@ -110,6 +144,7 @@ export class CodeEditor {
this.options = options; this.options = options;
this.view = null; this.view = null;
this.mode = options.mode || "css"; this.mode = options.mode || "css";
this.section = options.section || null;
this.onChange = options.onChange || (() => {}); this.onChange = options.onChange || (() => {});
} }
@@ -126,7 +161,7 @@ export class CodeEditor {
// Build extensions array // Build extensions array
const extensions = [ const extensions = [
langExtension, langExtension,
crispyEditorTheme, getEditorTheme(this.section),
editorTheme, editorTheme,
// History for undo/redo // History for undo/redo
history(), history(),
@@ -215,6 +250,17 @@ export class CodeEditor {
this.init(currentValue); this.init(currentValue);
} }
/**
* Set section for theme (css, html, tailwind)
*/
setSection(section) {
if (this.section === section) return;
this.section = section;
const currentValue = this.getValue();
this.init(currentValue);
}
/** /**
* Focus the editor * Focus the editor
*/ */

View File

@@ -6,7 +6,7 @@
<meta name="viewport" content="width=device-width, initial-scale=1.0" /> <meta name="viewport" content="width=device-width, initial-scale=1.0" />
<!-- Primary Meta Tags --> <!-- Primary Meta Tags -->
<title>Code Crispies - Learn HTML & CSS Interactively | Free Coding Practice</title> <title>CODE CRISPIES - Learn HTML & CSS Interactively | Free Coding Practice</title>
<meta <meta
name="description" name="description"
content="Master HTML, CSS, and Tailwind through hands-on coding exercises. Free, open-source learning platform with instant feedback. No account required." content="Master HTML, CSS, and Tailwind through hands-on coding exercises. Free, open-source learning platform with instant feedback. No account required."
@@ -19,14 +19,14 @@
<!-- Open Graph / Facebook --> <!-- Open Graph / Facebook -->
<meta property="og:type" content="website" /> <meta property="og:type" content="website" />
<meta property="og:url" content="https://codecrispi.es/" /> <meta property="og:url" content="https://codecrispi.es/" />
<meta property="og:title" content="Code Crispies - Learn HTML & CSS Interactively" /> <meta property="og:title" content="CODE CRISPIES - Learn HTML & CSS Interactively" />
<meta property="og:description" content="Master HTML, CSS, and Tailwind through hands-on coding exercises. Free and open source." /> <meta property="og:description" content="Master HTML, CSS, and Tailwind through hands-on coding exercises. Free and open source." />
<meta property="og:image" content="https://codecrispi.es/og-image.png" /> <meta property="og:image" content="https://codecrispi.es/og-image.png" />
<meta property="og:site_name" content="Code Crispies" /> <meta property="og:site_name" content="CODE CRISPIES" />
<!-- Twitter --> <!-- Twitter -->
<meta name="twitter:card" content="summary_large_image" /> <meta name="twitter:card" content="summary_large_image" />
<meta name="twitter:title" content="Code Crispies - Learn HTML & CSS Interactively" /> <meta name="twitter:title" content="CODE CRISPIES - Learn HTML & CSS Interactively" />
<meta name="twitter:description" content="Master HTML, CSS, and Tailwind through hands-on coding exercises." /> <meta name="twitter:description" content="Master HTML, CSS, and Tailwind through hands-on coding exercises." />
<meta name="twitter:image" content="https://codecrispi.es/og-image.png" /> <meta name="twitter:image" content="https://codecrispi.es/og-image.png" />
@@ -35,7 +35,7 @@
{ {
"@context": "https://schema.org", "@context": "https://schema.org",
"@type": "WebApplication", "@type": "WebApplication",
"name": "Code Crispies", "name": "CODE CRISPIES",
"description": "Interactive platform for learning HTML, CSS, and Tailwind through hands-on coding exercises", "description": "Interactive platform for learning HTML, CSS, and Tailwind through hands-on coding exercises",
"url": "https://codecrispi.es/", "url": "https://codecrispi.es/",
"applicationCategory": "EducationalApplication", "applicationCategory": "EducationalApplication",
@@ -100,7 +100,7 @@
</section> </section>
<section class="why-it-works"> <section class="why-it-works">
<h2 data-i18n="landingWhyTitle">Why Code Crispies Works</h2> <h2 data-i18n="landingWhyTitle">Why CODE CRISPIES Works</h2>
<div class="benefits-grid"> <div class="benefits-grid">
<article class="benefit-card"> <article class="benefit-card">
<svg class="benefit-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"> <svg class="benefit-icon" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2">
@@ -198,7 +198,7 @@
</section> </section>
<section class="footer-section footer-support"> <section class="footer-section footer-support">
<h4 data-i18n="footerSupport">Support</h4> <h4 data-i18n="footerSupport">Support</h4>
<p data-i18n="footerSupportText">Help keep Code Crispies free and open source.</p> <p data-i18n="footerSupportText">Help keep CODE CRISPIES free and open source.</p>
<script src="https://liberapay.com/libretech/widgets/button.js"></script> <script src="https://liberapay.com/libretech/widgets/button.js"></script>
<noscript><a href="https://liberapay.com/libretech/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a></noscript> <noscript><a href="https://liberapay.com/libretech/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a></noscript>
</section> </section>
@@ -227,6 +227,38 @@
<!-- Educational content with integrated module links --> <!-- Educational content with integrated module links -->
<div class="section-intro" id="section-intro"></div> <div class="section-intro" id="section-intro"></div>
</article> </article>
<footer class="section-footer landing-footer">
<div class="footer-grid">
<section class="footer-section footer-modules">
<div id="section-footer-lesson-links" class="footer-links"></div>
</section>
<section class="footer-section">
<h4 data-i18n="footerResources">Resources</h4>
<ul class="footer-links">
<li><a href="#reference/css">CSS Reference</a></li>
<li><a href="#reference/html">HTML Reference</a></li>
<li><a href="#playground/0" data-i18n="footerPlayground">Playground</a></li>
</ul>
</section>
<section class="footer-section">
<h4 data-i18n="footerAbout">About</h4>
<ul class="footer-links">
<li><a href="https://librete.ch" target="_blank">LibreTECH</a></li>
<li><a href="https://git.librete.ch/public/code-crispies" target="_blank">Source Code</a></li>
<li><a href="https://github.com/nextlevelshit/code-crispies" target="_blank">GitHub</a></li>
</ul>
</section>
<section class="footer-section footer-support">
<h4 data-i18n="footerSupport">Support</h4>
<p data-i18n="footerSupportText">Help keep CODE CRISPIES free and open source.</p>
<script src="https://liberapay.com/libretech/widgets/button.js"></script>
<noscript><a href="https://liberapay.com/libretech/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a></noscript>
</section>
</div>
<div class="footer-bottom">
<p>&copy; 2025 <a href="https://librete.ch">LibreTECH</a>. <span data-i18n="footerLicense">Open source under MIT License.</span></p>
</div>
</footer>
</div> </div>
<!-- Reference/Cheatsheet Pages --> <!-- Reference/Cheatsheet Pages -->
@@ -243,8 +275,37 @@
<!-- Reference content injected by JS --> <!-- Reference content injected by JS -->
</div> </div>
</article> </article>
<footer class="reference-footer"> <footer class="reference-footer landing-footer">
<p>&copy; 2025 <a href="https://librete.ch">LibreTECH</a>. <span data-i18n="footerLicense">Released into public domain.</span></p> <div class="footer-grid">
<section class="footer-section footer-modules">
<div id="ref-footer-lesson-links" class="footer-links"></div>
</section>
<section class="footer-section">
<h4 data-i18n="footerResources">Resources</h4>
<ul class="footer-links">
<li><a href="#reference/css">CSS Reference</a></li>
<li><a href="#reference/html">HTML Reference</a></li>
<li><a href="#playground/0" data-i18n="footerPlayground">Playground</a></li>
</ul>
</section>
<section class="footer-section">
<h4 data-i18n="footerAbout">About</h4>
<ul class="footer-links">
<li><a href="https://librete.ch" target="_blank">LibreTECH</a></li>
<li><a href="https://git.librete.ch/public/code-crispies" target="_blank">Source Code</a></li>
<li><a href="https://github.com/nextlevelshit/code-crispies" target="_blank">GitHub</a></li>
</ul>
</section>
<section class="footer-section footer-support">
<h4 data-i18n="footerSupport">Support</h4>
<p data-i18n="footerSupportText">Help keep CODE CRISPIES free and open source.</p>
<script src="https://liberapay.com/libretech/widgets/button.js"></script>
<noscript><a href="https://liberapay.com/libretech/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a></noscript>
</section>
</div>
<div class="footer-bottom">
<p>&copy; 2025 <a href="https://librete.ch">LibreTECH</a>. <span data-i18n="footerLicense">Open source under MIT License.</span></p>
</div>
</footer> </footer>
</div> </div>
@@ -389,9 +450,9 @@
<button id="help-dialog-close" class="dialog-close" aria-label="Close">&times;</button> <button id="help-dialog-close" class="dialog-close" aria-label="Close">&times;</button>
</div> </div>
<div class="dialog-content"> <div class="dialog-content">
<h4 data-i18n="aboutTitle">About Code Crispies</h4> <h4 data-i18n="aboutTitle">About CODE CRISPIES</h4>
<p data-i18n="aboutText"> <p data-i18n="aboutText">
Code Crispies is a free, open-source platform for learning web development through hands-on exercises. No account required - CODE CRISPIES is a free, open-source platform for learning web development through hands-on exercises. No account required -
just start coding! just start coding!
</p> </p>
@@ -455,7 +516,7 @@
</div> </div>
<h4 data-i18n="contactTitle">Contact & Links</h4> <h4 data-i18n="contactTitle">Contact & Links</h4>
<p data-i18n-html="contactText">Code Crispies is developed by <a href="https://librete.ch" target="_blank">LibreTECH</a></p> <p data-i18n-html="contactText">CODE CRISPIES is developed by <a href="https://librete.ch" target="_blank">LibreTECH</a></p>
<ul> <ul>
<li><a href="https://git.librete.ch/public/code-crispies" target="_blank">Gitea</a> Self-hosted source repository</li> <li><a href="https://git.librete.ch/public/code-crispies" target="_blank">Gitea</a> Self-hosted source repository</li>
<li><a href="https://github.com/nextlevelshit/code-crispies" target="_blank">GitHub</a> Public mirror</li> <li><a href="https://github.com/nextlevelshit/code-crispies" target="_blank">GitHub</a> Public mirror</li>
@@ -463,7 +524,7 @@
</ul> </ul>
<h4 data-i18n="supportTitle">Support the Project</h4> <h4 data-i18n="supportTitle">Support the Project</h4>
<p data-i18n="supportText">Help keep Code Crispies free and open source.</p> <p data-i18n="supportText">Help keep CODE CRISPIES free and open source.</p>
<div class="help-support" onclick="typeof umami !== 'undefined' && umami.track('support_click', {location: 'help'})"> <div class="help-support" onclick="typeof umami !== 'undefined' && umami.track('support_click', {location: 'help'})">
<script src="https://liberapay.com/libretech/widgets/button.js"></script> <script src="https://liberapay.com/libretech/widgets/button.js"></script>
<noscript><a href="https://liberapay.com/libretech/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a></noscript> <noscript><a href="https://liberapay.com/libretech/donate"><img alt="Donate using Liberapay" src="https://liberapay.com/assets/widgets/donate.svg"></a></noscript>

View File

@@ -252,11 +252,11 @@ kbd {
} }
.logo h1 .code-text { .logo h1 .code-text {
color: var(--primary-color); color: #8b6bc4;
} }
.logo h1 .crispies-text { .logo h1 .crispies-text {
background: var(--primary-color); background: #8b6bc4;
color: white; color: white;
padding: 0.15rem 0.35rem; padding: 0.15rem 0.35rem;
border-radius: 4px; border-radius: 4px;
@@ -264,6 +264,31 @@ kbd {
box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15); box-shadow: 0 2px 4px rgba(0, 0, 0, 0.15);
} }
/* Logo section color coding */
[data-section="css"] .logo h1 .code-text {
color: #8b6bc4;
}
[data-section="css"] .logo h1 .crispies-text {
background: #8b6bc4;
}
[data-section="html"] .logo h1 .code-text {
color: #c75b7a;
}
[data-section="html"] .logo h1 .crispies-text {
background: #c75b7a;
}
[data-section="tailwind"] .logo h1 .code-text {
color: #26a69a;
}
[data-section="tailwind"] .logo h1 .crispies-text {
background: #26a69a;
}
.help-toggle { .help-toggle {
width: 28px; width: 28px;
height: 28px; height: 28px;
@@ -1954,7 +1979,9 @@ input:checked + .toggle-slider::before {
color: var(--primary-color); color: var(--primary-color);
} }
#footer-lesson-links { #footer-lesson-links,
#ref-footer-lesson-links,
#section-footer-lesson-links {
display: flex; display: flex;
gap: 2rem; gap: 2rem;
} }
@@ -2856,12 +2883,12 @@ input:checked + .toggle-slider::before {
--section-color-rgb: 139, 107, 196; --section-color-rgb: 139, 107, 196;
} }
/* HTML Section - Balanced Rose */ /* HTML Section - Raspberry */
[data-section="html"] { [data-section="html"] {
--section-color: #d4637b; --section-color: #c75b7a;
--section-color-light: #e08899; --section-color-light: #d97a94;
--section-color-dark: #b84d63; --section-color-dark: #a84862;
--section-color-rgb: 212, 99, 123; --section-color-rgb: 199, 91, 122;
} }
/* Tailwind Section - Balanced Teal */ /* Tailwind Section - Balanced Teal */
@@ -2878,7 +2905,7 @@ input:checked + .toggle-slider::before {
} }
.nav-link[data-section="html"] { .nav-link[data-section="html"] {
color: #d4637b; color: #c75b7a;
} }
.nav-link[data-section="tailwind"] { .nav-link[data-section="tailwind"] {
@@ -2893,8 +2920,8 @@ input:checked + .toggle-slider::before {
.nav-link[data-section="html"]:hover, .nav-link[data-section="html"]:hover,
.nav-link[data-section="html"].active { .nav-link[data-section="html"].active {
background: rgba(212, 99, 123, 0.1); background: rgba(199, 91, 122, 0.1);
color: #b84d63; color: #a84862;
} }
.nav-link[data-section="tailwind"]:hover, .nav-link[data-section="tailwind"]:hover,
@@ -2903,17 +2930,45 @@ input:checked + .toggle-slider::before {
color: #00897b; color: #00897b;
} }
/* Logo color coding based on section */ /* Hint section colors */
[data-section="css"] .code-text { body[data-section="css"] .hint {
color: #8b6bc4; background: rgba(139, 107, 196, 0.3);
border-left-color: #a98cd6;
} }
[data-section="html"] .code-text { body[data-section="css"] .hint-progress {
color: #d4637b; background: #8b6bc4;
} }
[data-section="tailwind"] .code-text { body[data-section="html"] .hint {
color: #26a69a; background: rgba(199, 91, 122, 0.3);
border-left-color: #d97a94;
}
body[data-section="html"] .hint-progress {
background: #c75b7a;
}
body[data-section="tailwind"] .hint {
background: rgba(38, 166, 154, 0.3);
border-left-color: #4db6ac;
}
body[data-section="tailwind"] .hint-progress {
background: #26a69a;
}
/* RTL hint border */
[dir="rtl"] body[data-section="css"] .hint {
border-right-color: #a98cd6;
}
[dir="rtl"] body[data-section="html"] .hint {
border-right-color: #d97a94;
}
[dir="rtl"] body[data-section="tailwind"] .hint {
border-right-color: #4db6ac;
} }
/* Reference nav link colors */ /* Reference nav link colors */
@@ -2937,13 +2992,13 @@ input:checked + .toggle-slider::before {
} }
.ref-nav-link[data-ref="html"] { .ref-nav-link[data-ref="html"] {
color: #d4637b; color: #c75b7a;
} }
.ref-nav-link[data-ref="html"]:hover, .ref-nav-link[data-ref="html"]:hover,
.ref-nav-link[data-ref="html"].active { .ref-nav-link[data-ref="html"].active {
background: rgba(212, 99, 123, 0.15); background: rgba(199, 91, 122, 0.15);
color: #b84d63; color: #a84862;
} }
/* CodeMirror section color overrides */ /* CodeMirror section color overrides */
@@ -2966,21 +3021,21 @@ body[data-section="css"] .cm-editor .cm-activeLine {
} }
body[data-section="html"] .cm-editor .cm-content { body[data-section="html"] .cm-editor .cm-content {
caret-color: #d4637b !important; caret-color: #c75b7a !important;
} }
body[data-section="html"] .cm-editor .cm-cursor, body[data-section="html"] .cm-editor .cm-cursor,
body[data-section="html"] .cm-editor .cm-dropCursor { body[data-section="html"] .cm-editor .cm-dropCursor {
border-left-color: #d4637b !important; border-left-color: #c75b7a !important;
} }
body[data-section="html"] .cm-editor .cm-selectionBackground, body[data-section="html"] .cm-editor .cm-selectionBackground,
body[data-section="html"] .cm-editor .cm-content ::selection { body[data-section="html"] .cm-editor .cm-content ::selection {
background-color: rgba(212, 99, 123, 0.25) !important; background-color: rgba(199, 91, 122, 0.25) !important;
} }
body[data-section="html"] .cm-editor .cm-activeLine { body[data-section="html"] .cm-editor .cm-activeLine {
background-color: rgba(212, 99, 123, 0.08) !important; background-color: rgba(199, 91, 122, 0.08) !important;
} }
body[data-section="tailwind"] .cm-editor .cm-content { body[data-section="tailwind"] .cm-editor .cm-content {
@@ -3012,12 +3067,12 @@ body[data-section="css"] .module-pill .level-indicator {
} }
body[data-section="html"] .module-pill { body[data-section="html"] .module-pill {
background: rgba(212, 99, 123, 0.1); background: rgba(199, 91, 122, 0.1);
color: #d4637b; color: #c75b7a;
} }
body[data-section="html"] .module-pill .level-indicator { body[data-section="html"] .module-pill .level-indicator {
color: #b84d63; color: #a84862;
} }
body[data-section="tailwind"] .module-pill { body[data-section="tailwind"] .module-pill {
@@ -3035,7 +3090,7 @@ body[data-section="css"] .code-block {
} }
body[data-section="html"] .code-block { body[data-section="html"] .code-block {
border-color: rgba(212, 99, 123, 0.4); border-color: rgba(199, 91, 122, 0.4);
} }
body[data-section="tailwind"] .code-block { body[data-section="tailwind"] .code-block {
@@ -3061,7 +3116,7 @@ body[data-section="tailwind"] .code-block .cm-editor .cm-line {
} }
[data-section="html"] .task-instruction { [data-section="html"] .task-instruction {
background: rgba(212, 99, 123, 0.92); background: rgba(199, 91, 122, 0.92);
} }
[data-section="tailwind"] .task-instruction { [data-section="tailwind"] .task-instruction {
@@ -3074,7 +3129,7 @@ body[data-section="css"] .section-progress-bar .progress-fill {
} }
body[data-section="html"] .section-progress-bar .progress-fill { body[data-section="html"] .section-progress-bar .progress-fill {
background: #d4637b; background: #c75b7a;
} }
body[data-section="tailwind"] .section-progress-bar .progress-fill { body[data-section="tailwind"] .section-progress-bar .progress-fill {
@@ -3087,23 +3142,28 @@ body[data-section="tailwind"] .section-progress-bar .progress-fill {
} }
[data-section="html"] .section-hero h1 { [data-section="html"] .section-hero h1 {
color: #d4637b; color: #c75b7a;
} }
[data-section="tailwind"] .section-hero h1 { [data-section="tailwind"] .section-hero h1 {
color: #26a69a; color: #26a69a;
} }
/* Reference footer */ /* Section and Reference footer - override landing-footer styles */
.reference-footer { .section-footer.landing-footer,
text-align: center; .reference-footer.landing-footer {
padding: 2rem 1rem; max-width: 900px;
margin-top: 3rem; margin: 6rem auto 0;
border-top: 1px solid var(--border-color); padding: 0 var(--spacing-lg) var(--spacing-lg);
color: var(--light-text); border-top: none;
font-size: 0.9rem;
} }
.reference-footer a { .section-footer .footer-links a,
.reference-footer .footer-links a {
color: var(--light-text);
}
.section-footer .footer-links a:hover,
.reference-footer .footer-links a:hover {
color: var(--section-color, var(--primary-color)); color: var(--section-color, var(--primary-color));
} }