From d2fbe0e0850c0da0aa3346ea26b2d1f7b656b81a Mon Sep 17 00:00:00 2001 From: Michael Czechowski Date: Fri, 16 Jan 2026 13:56:29 +0100 Subject: [PATCH] feat: implement milestone-based progress system and activate new lessons Progress System: - Replace percentage-based progress with milestone markers (1, 5, 10, 20, 30, 50, 75, 100) - Add visual milestone indicators with reached/current/next states - Add celebration animation when milestones are reached - Update progress bar to show progress toward next milestone - Add progressTextMilestone i18n key for all 6 languages New Lessons Activated: - HTML Dialog (native modal dialogs) - HTML Progress & Meter (indicator elements) - HTML Fieldset (form grouping) - HTML Datalist (autocomplete inputs) This adds 10 new lessons across all 6 languages, bringing total from ~66 to ~76. --- src/app.js | 43 ++++++++++++++++++++++++++--- src/auth.js | 30 +++++++++++++++++--- src/config/lessons.js | 48 ++++++++++++++++++++++++++++++++ src/i18n.js | 6 ++++ src/impl/LessonEngine.js | 22 +++++++++++++-- src/index.html | 14 ++++++++-- src/main.css | 59 ++++++++++++++++++++++++++++++++++++++++ 7 files changed, 209 insertions(+), 13 deletions(-) diff --git a/src/app.js b/src/app.js index b6438f3..e7baab2 100644 --- a/src/app.js +++ b/src/app.js @@ -165,6 +165,7 @@ const elements = { sectionFooterLessonLinks: document.getElementById("section-footer-lesson-links"), progressFill: document.getElementById("progress-fill"), progressText: document.getElementById("progress-text"), + milestonesContainer: document.getElementById("milestones"), resetBtn: document.getElementById("reset-btn"), disableFeedbackToggle: document.getElementById("disable-feedback-toggle"), @@ -310,14 +311,48 @@ function showSuccessHint(message) { // ================= PROGRESS DISPLAY ================= +// Track last milestone to detect new achievements +let lastMilestoneReached = 0; + function updateProgressDisplay() { const stats = lessonEngine.getProgressStats(); - elements.progressFill.style.width = `${stats.percentComplete}%`; - elements.progressText.textContent = t("progressText", { - percent: stats.percentComplete, + + // Update progress bar (now shows progress to next milestone) + elements.progressFill.style.width = `${stats.progressToNext}%`; + + // Update progress text + elements.progressText.textContent = t("progressTextMilestone", { completed: stats.totalCompleted, - total: stats.totalLessons + next: stats.nextMilestone }); + + // Update milestone indicators + if (elements.milestonesContainer) { + const milestoneEls = elements.milestonesContainer.querySelectorAll(".milestone"); + milestoneEls.forEach((el) => { + const value = parseInt(el.dataset.value, 10); + el.classList.remove("reached", "current", "next", "just-reached"); + + if (stats.milestonesReached.includes(value)) { + el.classList.add("reached"); + // Check if this milestone was just reached + if (value > lastMilestoneReached && value === stats.currentMilestone) { + el.classList.add("just-reached"); + } + } else if (value === stats.nextMilestone) { + el.classList.add("next"); + } + + if (value === stats.currentMilestone) { + el.classList.add("current"); + } + }); + } + + // Update last milestone for celebration detection + if (stats.currentMilestone > lastMilestoneReached) { + lastMilestoneReached = stats.currentMilestone; + } } // ================= USER SETTINGS ================= diff --git a/src/auth.js b/src/auth.js index 3d89887..2268d45 100644 --- a/src/auth.js +++ b/src/auth.js @@ -261,12 +261,18 @@ function setupAuthForms() { }); // OAuth buttons - document.getElementById("google-login")?.addEventListener("click", () => { - authModule?.signInWithGoogle(); + document.getElementById("google-login")?.addEventListener("click", async () => { + const { error } = await authModule?.signInWithGoogle() ?? { error: null }; + if (error) { + showOAuthError(error.message); + } }); - document.getElementById("github-login")?.addEventListener("click", () => { - authModule?.signInWithGitHub(); + document.getElementById("github-login")?.addEventListener("click", async () => { + const { error } = await authModule?.signInWithGitHub() ?? { error: null }; + if (error) { + showOAuthError(error.message); + } }); // Close dialog on backdrop click @@ -364,6 +370,22 @@ async function handleResetSubmit(e) { } } +function showOAuthError(message) { + // Show error in the currently visible form's error element + const loginError = document.getElementById("login-error"); + const signupError = document.getElementById("signup-error"); + + // Use whichever form is visible + const errorEl = !document.getElementById("login-form")?.classList.contains("hidden") + ? loginError + : signupError; + + if (errorEl) { + errorEl.textContent = message; + errorEl.classList.remove("hidden"); + } +} + function switchForm(formName) { const loginForm = document.getElementById("login-form"); const signupForm = document.getElementById("signup-form"); diff --git a/src/config/lessons.js b/src/config/lessons.js index 8eb7144..56dd0e8 100644 --- a/src/config/lessons.js +++ b/src/config/lessons.js @@ -16,6 +16,10 @@ import htmlElementsEN from "../../lessons/20-html-elements.json"; import htmlFormsBasicEN from "../../lessons/21-html-forms-basic.json"; import htmlFormsValidationEN from "../../lessons/22-html-forms-validation.json"; import htmlDetailsSummaryEN from "../../lessons/23-html-details-summary.json"; +import htmlProgressMeterEN from "../../lessons/24-html-progress-meter.json"; +import htmlDatalistEN from "../../lessons/25-html-datalist.json"; +import htmlDialogEN from "../../lessons/27-html-dialog.json"; +import htmlFieldsetEN from "../../lessons/28-html-forms-fieldset.json"; import htmlFigureEN from "../../lessons/29-html-figure.json"; import htmlTablesEN from "../../lessons/30-html-tables.json"; import htmlSvgEN from "../../lessons/32-html-svg.json"; @@ -35,6 +39,10 @@ import htmlElementsDE from "../../lessons/de/20-html-elements.json"; import htmlFormsBasicDE from "../../lessons/de/21-html-forms-basic.json"; import htmlFormsValidationDE from "../../lessons/de/22-html-forms-validation.json"; import htmlDetailsSummaryDE from "../../lessons/de/23-html-details-summary.json"; +import htmlProgressMeterDE from "../../lessons/de/24-html-progress-meter.json"; +import htmlDatalistDE from "../../lessons/de/25-html-datalist.json"; +import htmlDialogDE from "../../lessons/de/27-html-dialog.json"; +import htmlFieldsetDE from "../../lessons/de/28-html-forms-fieldset.json"; import htmlTablesDE from "../../lessons/de/30-html-tables.json"; import htmlSvgDE from "../../lessons/de/32-html-svg.json"; import flexboxDE from "../../lessons/de/flexbox.json"; @@ -50,6 +58,10 @@ import htmlElementsPL from "../../lessons/pl/20-html-elements.json"; import htmlFormsBasicPL from "../../lessons/pl/21-html-forms-basic.json"; import htmlFormsValidationPL from "../../lessons/pl/22-html-forms-validation.json"; import htmlDetailsSummaryPL from "../../lessons/pl/23-html-details-summary.json"; +import htmlProgressMeterPL from "../../lessons/pl/24-html-progress-meter.json"; +import htmlDatalistPL from "../../lessons/pl/25-html-datalist.json"; +import htmlDialogPL from "../../lessons/pl/27-html-dialog.json"; +import htmlFieldsetPL from "../../lessons/pl/28-html-forms-fieldset.json"; import htmlTablesPL from "../../lessons/pl/30-html-tables.json"; import htmlSvgPL from "../../lessons/pl/32-html-svg.json"; import flexboxPL from "../../lessons/pl/flexbox.json"; @@ -65,6 +77,10 @@ import htmlElementsES from "../../lessons/es/20-html-elements.json"; import htmlFormsBasicES from "../../lessons/es/21-html-forms-basic.json"; import htmlFormsValidationES from "../../lessons/es/22-html-forms-validation.json"; import htmlDetailsSummaryES from "../../lessons/es/23-html-details-summary.json"; +import htmlProgressMeterES from "../../lessons/es/24-html-progress-meter.json"; +import htmlDatalistES from "../../lessons/es/25-html-datalist.json"; +import htmlDialogES from "../../lessons/es/27-html-dialog.json"; +import htmlFieldsetES from "../../lessons/es/28-html-forms-fieldset.json"; import htmlTablesES from "../../lessons/es/30-html-tables.json"; import htmlSvgES from "../../lessons/es/32-html-svg.json"; import flexboxES from "../../lessons/es/flexbox.json"; @@ -80,6 +96,10 @@ import htmlElementsAR from "../../lessons/ar/20-html-elements.json"; import htmlFormsBasicAR from "../../lessons/ar/21-html-forms-basic.json"; import htmlFormsValidationAR from "../../lessons/ar/22-html-forms-validation.json"; import htmlDetailsSummaryAR from "../../lessons/ar/23-html-details-summary.json"; +import htmlProgressMeterAR from "../../lessons/ar/24-html-progress-meter.json"; +import htmlDatalistAR from "../../lessons/ar/25-html-datalist.json"; +import htmlDialogAR from "../../lessons/ar/27-html-dialog.json"; +import htmlFieldsetAR from "../../lessons/ar/28-html-forms-fieldset.json"; import htmlTablesAR from "../../lessons/ar/30-html-tables.json"; import htmlSvgAR from "../../lessons/ar/32-html-svg.json"; import flexboxAR from "../../lessons/ar/flexbox.json"; @@ -95,6 +115,10 @@ import htmlElementsUK from "../../lessons/uk/20-html-elements.json"; import htmlFormsBasicUK from "../../lessons/uk/21-html-forms-basic.json"; import htmlFormsValidationUK from "../../lessons/uk/22-html-forms-validation.json"; import htmlDetailsSummaryUK from "../../lessons/uk/23-html-details-summary.json"; +import htmlProgressMeterUK from "../../lessons/uk/24-html-progress-meter.json"; +import htmlDatalistUK from "../../lessons/uk/25-html-datalist.json"; +import htmlDialogUK from "../../lessons/uk/27-html-dialog.json"; +import htmlFieldsetUK from "../../lessons/uk/28-html-forms-fieldset.json"; import htmlTablesUK from "../../lessons/uk/30-html-tables.json"; import htmlSvgUK from "../../lessons/uk/32-html-svg.json"; import flexboxUK from "../../lessons/uk/flexbox.json"; @@ -121,8 +145,12 @@ const moduleStoreEN = [ htmlSvgEN, // HTML Interactive htmlDetailsSummaryEN, + htmlDialogEN, + htmlProgressMeterEN, htmlFormsBasicEN, htmlFormsValidationEN, + htmlFieldsetEN, + htmlDatalistEN, htmlTablesEN, // Outro goodbyeEN, @@ -151,8 +179,12 @@ const moduleStoreDE = [ htmlSvgDE, // HTML Interactive htmlDetailsSummaryDE, + htmlDialogDE, + htmlProgressMeterDE, htmlFormsBasicDE, htmlFormsValidationDE, + htmlFieldsetDE, + htmlDatalistDE, htmlTablesDE, // Outro goodbyeEN, @@ -181,8 +213,12 @@ const moduleStorePL = [ htmlSvgPL, // HTML Interactive htmlDetailsSummaryPL, + htmlDialogPL, + htmlProgressMeterPL, htmlFormsBasicPL, htmlFormsValidationPL, + htmlFieldsetPL, + htmlDatalistPL, htmlTablesPL, // Outro goodbyeEN, @@ -211,8 +247,12 @@ const moduleStoreES = [ htmlSvgES, // HTML Interactive htmlDetailsSummaryES, + htmlDialogES, + htmlProgressMeterES, htmlFormsBasicES, htmlFormsValidationES, + htmlFieldsetES, + htmlDatalistES, htmlTablesES, // Outro goodbyeEN, @@ -241,8 +281,12 @@ const moduleStoreAR = [ htmlSvgAR, // HTML Interactive htmlDetailsSummaryAR, + htmlDialogAR, + htmlProgressMeterAR, htmlFormsBasicAR, htmlFormsValidationAR, + htmlFieldsetAR, + htmlDatalistAR, htmlTablesAR, // Outro goodbyeEN, @@ -271,8 +315,12 @@ const moduleStoreUK = [ htmlSvgUK, // HTML Interactive htmlDetailsSummaryUK, + htmlDialogUK, + htmlProgressMeterUK, htmlFormsBasicUK, htmlFormsValidationUK, + htmlFieldsetUK, + htmlDatalistUK, htmlTablesUK, // Outro goodbyeEN, diff --git a/src/i18n.js b/src/i18n.js index f9e47fa..5a2b621 100644 --- a/src/i18n.js +++ b/src/i18n.js @@ -39,6 +39,7 @@ const translations = { language: "Language", progress: "Progress", progressText: "{percent}% Complete ({completed}/{total})", + progressTextMilestone: "{completed} of {next}", lessons: "Lessons", settings: "Settings", showHints: "Show Hints", @@ -260,6 +261,7 @@ const translations = { language: "Sprache", progress: "Fortschritt", progressText: "{percent}% abgeschlossen ({completed}/{total})", + progressTextMilestone: "{completed} von {next}", lessons: "Lektionen", settings: "Einstellungen", showHints: "Hinweise anzeigen", @@ -481,6 +483,7 @@ const translations = { language: "Język", progress: "Postęp", progressText: "{percent}% ukończone ({completed}/{total})", + progressTextMilestone: "{completed} z {next}", lessons: "Lekcje", settings: "Ustawienia", showHints: "Pokaż podpowiedzi", @@ -701,6 +704,7 @@ const translations = { language: "Idioma", progress: "Progreso", progressText: "{percent}% completado ({completed}/{total})", + progressTextMilestone: "{completed} de {next}", lessons: "Lecciones", settings: "Configuración", showHints: "Mostrar pistas", @@ -923,6 +927,7 @@ const translations = { language: "اللغة", progress: "التقدم", progressText: "{percent}% مكتمل ({completed}/{total})", + progressTextMilestone: "{completed} من {next}", lessons: "الدروس", settings: "الإعدادات", showHints: "إظهار التلميحات", @@ -1140,6 +1145,7 @@ const translations = { language: "Мова", progress: "Прогрес", progressText: "{percent}% завершено ({completed}/{total})", + progressTextMilestone: "{completed} з {next}", lessons: "Уроки", settings: "Налаштування", showHints: "Показувати підказки", diff --git a/src/impl/LessonEngine.js b/src/impl/LessonEngine.js index c0de5ac..795e6b7 100644 --- a/src/impl/LessonEngine.js +++ b/src/impl/LessonEngine.js @@ -472,10 +472,11 @@ export class LessonEngine { } /** - * Get overall progress statistics - * @returns {Object} Progress statistics + * Get overall progress statistics with milestone data + * @returns {Object} Progress statistics including milestone progress */ getProgressStats() { + const MILESTONES = [1, 5, 10, 20, 30, 50, 75, 100]; let totalLessons = 0; let totalCompleted = 0; @@ -490,10 +491,25 @@ export class LessonEngine { } }); + // Calculate milestone progress + const milestonesReached = MILESTONES.filter((m) => totalCompleted >= m); + const currentMilestone = milestonesReached[milestonesReached.length - 1] || 0; + const nextMilestone = MILESTONES.find((m) => m > totalCompleted) || 100; + const progressToNext = + nextMilestone > currentMilestone + ? Math.round(((totalCompleted - currentMilestone) / (nextMilestone - currentMilestone)) * 100) + : 100; + return { totalLessons, totalCompleted, - percentComplete: totalLessons > 0 ? Math.round((totalCompleted / totalLessons) * 100) : 0 + percentComplete: totalLessons > 0 ? Math.round((totalCompleted / totalLessons) * 100) : 0, + // Milestone data + milestones: MILESTONES, + milestonesReached, + currentMilestone, + nextMilestone, + progressToNext }; } diff --git a/src/index.html b/src/index.html index 60a5b3f..8f233ee 100644 --- a/src/index.html +++ b/src/index.html @@ -468,11 +468,21 @@