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

@@ -156,6 +156,8 @@ const elements = {
closeSidebar: document.getElementById("close-sidebar"),
moduleList: document.getElementById("module-list"),
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"),
progressText: document.getElementById("progress-text"),
resetBtn: document.getElementById("reset-btn"),
@@ -456,6 +458,13 @@ function selectLesson(moduleId, lessonIndex) {
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
if (window.innerWidth <= 768) {
closeSidebar();
@@ -694,11 +703,15 @@ function nextLesson() {
// Update URL
updateHash(newState.module.id, newState.lessonIndex);
if (newState.module.id !== prevModuleId) {
const moduleChanged = newState.module.id !== prevModuleId;
if (moduleChanged) {
updateModuleHighlight(newState.module.id);
updateSectionColor(getModuleSection(newState.module));
}
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
updateHash(newState.module.id, newState.lessonIndex);
if (newState.module.id !== prevModuleId) {
const moduleChanged = newState.module.id !== prevModuleId;
if (moduleChanged) {
updateModuleHighlight(newState.module.id);
updateSectionColor(getModuleSection(newState.module));
}
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
*/
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 =
"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: {
const sectionNames = { css: "CSS", html: "HTML", tailwind: "Tailwind CSS" };
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.`;
break;
}
@@ -1881,7 +1898,7 @@ function updatePageMeta(route) {
const module = lessonEngine.modules.find((m) => m.id === route.moduleId);
const lesson = module?.lessons[route.lessonIndex];
if (module && lesson) {
title = `${lesson.title} - ${module.title} | Code Crispies`;
title = `${lesson.title} - ${module.title} | CODE CRISPIES`;
const lessonDesc = stripHtml(lesson.description || lesson.task);
description = lessonDesc.length > 155 ? lessonDesc.slice(0, 152) + "..." : lessonDesc || defaultDesc;
}
@@ -1897,7 +1914,7 @@ function updatePageMeta(route) {
html: "HTML Elements"
};
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.`;
break;
}
@@ -1913,13 +1930,13 @@ function updatePageMeta(route) {
// Update Open Graph tags
const ogTitle = document.querySelector('meta[property="og:title"]');
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);
// Update Twitter tags
const twitterTitle = document.querySelector('meta[name="twitter:title"]');
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);
}
@@ -1994,6 +2011,11 @@ function updateSectionColor(sectionId) {
} else {
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
*/
function renderFooterLessonLinks() {
if (!elements.footerLessonLinks) return;
const modules = lessonEngine.modules || [];
const sectionGroups = { css: [], html: [] };
@@ -2042,7 +2062,16 @@ function renderFooterLessonLinks() {
html += "</div>";
});
elements.footerLessonLinks.innerHTML = html;
// Update all footer lesson links
if (elements.footerLessonLinks) {
elements.footerLessonLinks.innerHTML = html;
}
if (elements.refFooterLessonLinks) {
elements.refFooterLessonLinks.innerHTML = html;
}
if (elements.sectionFooterLessonLinks) {
elements.sectionFooterLessonLinks.innerHTML = html;
}
}
/**
@@ -2077,7 +2106,6 @@ function updateLandingProgress() {
function showSectionPage(sectionId) {
hideAllPages();
elements.sectionPage?.classList.remove("hidden");
window.scrollTo(0, 0);
// Update section color
updateSectionColor(sectionId);
@@ -2120,6 +2148,9 @@ function showSectionPage(sectionId) {
const percent = total > 0 ? Math.round((completed / total) * 100) : 0;
if (elements.sectionProgressFill) elements.sectionProgressFill.style.width = `${percent}%`;
if (elements.sectionProgressText) elements.sectionProgressText.textContent = `${completed} of ${total} lessons complete`;
// Scroll to top after content is rendered
requestAnimationFrame(() => window.scrollTo(0, 0));
}
/**