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.
This commit is contained in:
43
src/app.js
43
src/app.js
@@ -165,6 +165,7 @@ const elements = {
|
|||||||
sectionFooterLessonLinks: document.getElementById("section-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"),
|
||||||
|
milestonesContainer: document.getElementById("milestones"),
|
||||||
resetBtn: document.getElementById("reset-btn"),
|
resetBtn: document.getElementById("reset-btn"),
|
||||||
disableFeedbackToggle: document.getElementById("disable-feedback-toggle"),
|
disableFeedbackToggle: document.getElementById("disable-feedback-toggle"),
|
||||||
|
|
||||||
@@ -310,14 +311,48 @@ function showSuccessHint(message) {
|
|||||||
|
|
||||||
// ================= PROGRESS DISPLAY =================
|
// ================= PROGRESS DISPLAY =================
|
||||||
|
|
||||||
|
// Track last milestone to detect new achievements
|
||||||
|
let lastMilestoneReached = 0;
|
||||||
|
|
||||||
function updateProgressDisplay() {
|
function updateProgressDisplay() {
|
||||||
const stats = lessonEngine.getProgressStats();
|
const stats = lessonEngine.getProgressStats();
|
||||||
elements.progressFill.style.width = `${stats.percentComplete}%`;
|
|
||||||
elements.progressText.textContent = t("progressText", {
|
// Update progress bar (now shows progress to next milestone)
|
||||||
percent: stats.percentComplete,
|
elements.progressFill.style.width = `${stats.progressToNext}%`;
|
||||||
|
|
||||||
|
// Update progress text
|
||||||
|
elements.progressText.textContent = t("progressTextMilestone", {
|
||||||
completed: stats.totalCompleted,
|
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 =================
|
// ================= USER SETTINGS =================
|
||||||
|
|||||||
30
src/auth.js
30
src/auth.js
@@ -261,12 +261,18 @@ function setupAuthForms() {
|
|||||||
});
|
});
|
||||||
|
|
||||||
// OAuth buttons
|
// OAuth buttons
|
||||||
document.getElementById("google-login")?.addEventListener("click", () => {
|
document.getElementById("google-login")?.addEventListener("click", async () => {
|
||||||
authModule?.signInWithGoogle();
|
const { error } = await authModule?.signInWithGoogle() ?? { error: null };
|
||||||
|
if (error) {
|
||||||
|
showOAuthError(error.message);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
document.getElementById("github-login")?.addEventListener("click", () => {
|
document.getElementById("github-login")?.addEventListener("click", async () => {
|
||||||
authModule?.signInWithGitHub();
|
const { error } = await authModule?.signInWithGitHub() ?? { error: null };
|
||||||
|
if (error) {
|
||||||
|
showOAuthError(error.message);
|
||||||
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
// Close dialog on backdrop click
|
// 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) {
|
function switchForm(formName) {
|
||||||
const loginForm = document.getElementById("login-form");
|
const loginForm = document.getElementById("login-form");
|
||||||
const signupForm = document.getElementById("signup-form");
|
const signupForm = document.getElementById("signup-form");
|
||||||
|
|||||||
@@ -16,6 +16,10 @@ import htmlElementsEN from "../../lessons/20-html-elements.json";
|
|||||||
import htmlFormsBasicEN from "../../lessons/21-html-forms-basic.json";
|
import htmlFormsBasicEN from "../../lessons/21-html-forms-basic.json";
|
||||||
import htmlFormsValidationEN from "../../lessons/22-html-forms-validation.json";
|
import htmlFormsValidationEN from "../../lessons/22-html-forms-validation.json";
|
||||||
import htmlDetailsSummaryEN from "../../lessons/23-html-details-summary.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 htmlFigureEN from "../../lessons/29-html-figure.json";
|
||||||
import htmlTablesEN from "../../lessons/30-html-tables.json";
|
import htmlTablesEN from "../../lessons/30-html-tables.json";
|
||||||
import htmlSvgEN from "../../lessons/32-html-svg.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 htmlFormsBasicDE from "../../lessons/de/21-html-forms-basic.json";
|
||||||
import htmlFormsValidationDE from "../../lessons/de/22-html-forms-validation.json";
|
import htmlFormsValidationDE from "../../lessons/de/22-html-forms-validation.json";
|
||||||
import htmlDetailsSummaryDE from "../../lessons/de/23-html-details-summary.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 htmlTablesDE from "../../lessons/de/30-html-tables.json";
|
||||||
import htmlSvgDE from "../../lessons/de/32-html-svg.json";
|
import htmlSvgDE from "../../lessons/de/32-html-svg.json";
|
||||||
import flexboxDE from "../../lessons/de/flexbox.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 htmlFormsBasicPL from "../../lessons/pl/21-html-forms-basic.json";
|
||||||
import htmlFormsValidationPL from "../../lessons/pl/22-html-forms-validation.json";
|
import htmlFormsValidationPL from "../../lessons/pl/22-html-forms-validation.json";
|
||||||
import htmlDetailsSummaryPL from "../../lessons/pl/23-html-details-summary.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 htmlTablesPL from "../../lessons/pl/30-html-tables.json";
|
||||||
import htmlSvgPL from "../../lessons/pl/32-html-svg.json";
|
import htmlSvgPL from "../../lessons/pl/32-html-svg.json";
|
||||||
import flexboxPL from "../../lessons/pl/flexbox.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 htmlFormsBasicES from "../../lessons/es/21-html-forms-basic.json";
|
||||||
import htmlFormsValidationES from "../../lessons/es/22-html-forms-validation.json";
|
import htmlFormsValidationES from "../../lessons/es/22-html-forms-validation.json";
|
||||||
import htmlDetailsSummaryES from "../../lessons/es/23-html-details-summary.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 htmlTablesES from "../../lessons/es/30-html-tables.json";
|
||||||
import htmlSvgES from "../../lessons/es/32-html-svg.json";
|
import htmlSvgES from "../../lessons/es/32-html-svg.json";
|
||||||
import flexboxES from "../../lessons/es/flexbox.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 htmlFormsBasicAR from "../../lessons/ar/21-html-forms-basic.json";
|
||||||
import htmlFormsValidationAR from "../../lessons/ar/22-html-forms-validation.json";
|
import htmlFormsValidationAR from "../../lessons/ar/22-html-forms-validation.json";
|
||||||
import htmlDetailsSummaryAR from "../../lessons/ar/23-html-details-summary.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 htmlTablesAR from "../../lessons/ar/30-html-tables.json";
|
||||||
import htmlSvgAR from "../../lessons/ar/32-html-svg.json";
|
import htmlSvgAR from "../../lessons/ar/32-html-svg.json";
|
||||||
import flexboxAR from "../../lessons/ar/flexbox.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 htmlFormsBasicUK from "../../lessons/uk/21-html-forms-basic.json";
|
||||||
import htmlFormsValidationUK from "../../lessons/uk/22-html-forms-validation.json";
|
import htmlFormsValidationUK from "../../lessons/uk/22-html-forms-validation.json";
|
||||||
import htmlDetailsSummaryUK from "../../lessons/uk/23-html-details-summary.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 htmlTablesUK from "../../lessons/uk/30-html-tables.json";
|
||||||
import htmlSvgUK from "../../lessons/uk/32-html-svg.json";
|
import htmlSvgUK from "../../lessons/uk/32-html-svg.json";
|
||||||
import flexboxUK from "../../lessons/uk/flexbox.json";
|
import flexboxUK from "../../lessons/uk/flexbox.json";
|
||||||
@@ -121,8 +145,12 @@ const moduleStoreEN = [
|
|||||||
htmlSvgEN,
|
htmlSvgEN,
|
||||||
// HTML Interactive
|
// HTML Interactive
|
||||||
htmlDetailsSummaryEN,
|
htmlDetailsSummaryEN,
|
||||||
|
htmlDialogEN,
|
||||||
|
htmlProgressMeterEN,
|
||||||
htmlFormsBasicEN,
|
htmlFormsBasicEN,
|
||||||
htmlFormsValidationEN,
|
htmlFormsValidationEN,
|
||||||
|
htmlFieldsetEN,
|
||||||
|
htmlDatalistEN,
|
||||||
htmlTablesEN,
|
htmlTablesEN,
|
||||||
// Outro
|
// Outro
|
||||||
goodbyeEN,
|
goodbyeEN,
|
||||||
@@ -151,8 +179,12 @@ const moduleStoreDE = [
|
|||||||
htmlSvgDE,
|
htmlSvgDE,
|
||||||
// HTML Interactive
|
// HTML Interactive
|
||||||
htmlDetailsSummaryDE,
|
htmlDetailsSummaryDE,
|
||||||
|
htmlDialogDE,
|
||||||
|
htmlProgressMeterDE,
|
||||||
htmlFormsBasicDE,
|
htmlFormsBasicDE,
|
||||||
htmlFormsValidationDE,
|
htmlFormsValidationDE,
|
||||||
|
htmlFieldsetDE,
|
||||||
|
htmlDatalistDE,
|
||||||
htmlTablesDE,
|
htmlTablesDE,
|
||||||
// Outro
|
// Outro
|
||||||
goodbyeEN,
|
goodbyeEN,
|
||||||
@@ -181,8 +213,12 @@ const moduleStorePL = [
|
|||||||
htmlSvgPL,
|
htmlSvgPL,
|
||||||
// HTML Interactive
|
// HTML Interactive
|
||||||
htmlDetailsSummaryPL,
|
htmlDetailsSummaryPL,
|
||||||
|
htmlDialogPL,
|
||||||
|
htmlProgressMeterPL,
|
||||||
htmlFormsBasicPL,
|
htmlFormsBasicPL,
|
||||||
htmlFormsValidationPL,
|
htmlFormsValidationPL,
|
||||||
|
htmlFieldsetPL,
|
||||||
|
htmlDatalistPL,
|
||||||
htmlTablesPL,
|
htmlTablesPL,
|
||||||
// Outro
|
// Outro
|
||||||
goodbyeEN,
|
goodbyeEN,
|
||||||
@@ -211,8 +247,12 @@ const moduleStoreES = [
|
|||||||
htmlSvgES,
|
htmlSvgES,
|
||||||
// HTML Interactive
|
// HTML Interactive
|
||||||
htmlDetailsSummaryES,
|
htmlDetailsSummaryES,
|
||||||
|
htmlDialogES,
|
||||||
|
htmlProgressMeterES,
|
||||||
htmlFormsBasicES,
|
htmlFormsBasicES,
|
||||||
htmlFormsValidationES,
|
htmlFormsValidationES,
|
||||||
|
htmlFieldsetES,
|
||||||
|
htmlDatalistES,
|
||||||
htmlTablesES,
|
htmlTablesES,
|
||||||
// Outro
|
// Outro
|
||||||
goodbyeEN,
|
goodbyeEN,
|
||||||
@@ -241,8 +281,12 @@ const moduleStoreAR = [
|
|||||||
htmlSvgAR,
|
htmlSvgAR,
|
||||||
// HTML Interactive
|
// HTML Interactive
|
||||||
htmlDetailsSummaryAR,
|
htmlDetailsSummaryAR,
|
||||||
|
htmlDialogAR,
|
||||||
|
htmlProgressMeterAR,
|
||||||
htmlFormsBasicAR,
|
htmlFormsBasicAR,
|
||||||
htmlFormsValidationAR,
|
htmlFormsValidationAR,
|
||||||
|
htmlFieldsetAR,
|
||||||
|
htmlDatalistAR,
|
||||||
htmlTablesAR,
|
htmlTablesAR,
|
||||||
// Outro
|
// Outro
|
||||||
goodbyeEN,
|
goodbyeEN,
|
||||||
@@ -271,8 +315,12 @@ const moduleStoreUK = [
|
|||||||
htmlSvgUK,
|
htmlSvgUK,
|
||||||
// HTML Interactive
|
// HTML Interactive
|
||||||
htmlDetailsSummaryUK,
|
htmlDetailsSummaryUK,
|
||||||
|
htmlDialogUK,
|
||||||
|
htmlProgressMeterUK,
|
||||||
htmlFormsBasicUK,
|
htmlFormsBasicUK,
|
||||||
htmlFormsValidationUK,
|
htmlFormsValidationUK,
|
||||||
|
htmlFieldsetUK,
|
||||||
|
htmlDatalistUK,
|
||||||
htmlTablesUK,
|
htmlTablesUK,
|
||||||
// Outro
|
// Outro
|
||||||
goodbyeEN,
|
goodbyeEN,
|
||||||
|
|||||||
@@ -39,6 +39,7 @@ const translations = {
|
|||||||
language: "Language",
|
language: "Language",
|
||||||
progress: "Progress",
|
progress: "Progress",
|
||||||
progressText: "{percent}% Complete ({completed}/{total})",
|
progressText: "{percent}% Complete ({completed}/{total})",
|
||||||
|
progressTextMilestone: "{completed} of {next}",
|
||||||
lessons: "Lessons",
|
lessons: "Lessons",
|
||||||
settings: "Settings",
|
settings: "Settings",
|
||||||
showHints: "Show Hints",
|
showHints: "Show Hints",
|
||||||
@@ -260,6 +261,7 @@ const translations = {
|
|||||||
language: "Sprache",
|
language: "Sprache",
|
||||||
progress: "Fortschritt",
|
progress: "Fortschritt",
|
||||||
progressText: "{percent}% abgeschlossen ({completed}/{total})",
|
progressText: "{percent}% abgeschlossen ({completed}/{total})",
|
||||||
|
progressTextMilestone: "{completed} von {next}",
|
||||||
lessons: "Lektionen",
|
lessons: "Lektionen",
|
||||||
settings: "Einstellungen",
|
settings: "Einstellungen",
|
||||||
showHints: "Hinweise anzeigen",
|
showHints: "Hinweise anzeigen",
|
||||||
@@ -481,6 +483,7 @@ const translations = {
|
|||||||
language: "Język",
|
language: "Język",
|
||||||
progress: "Postęp",
|
progress: "Postęp",
|
||||||
progressText: "{percent}% ukończone ({completed}/{total})",
|
progressText: "{percent}% ukończone ({completed}/{total})",
|
||||||
|
progressTextMilestone: "{completed} z {next}",
|
||||||
lessons: "Lekcje",
|
lessons: "Lekcje",
|
||||||
settings: "Ustawienia",
|
settings: "Ustawienia",
|
||||||
showHints: "Pokaż podpowiedzi",
|
showHints: "Pokaż podpowiedzi",
|
||||||
@@ -701,6 +704,7 @@ const translations = {
|
|||||||
language: "Idioma",
|
language: "Idioma",
|
||||||
progress: "Progreso",
|
progress: "Progreso",
|
||||||
progressText: "{percent}% completado ({completed}/{total})",
|
progressText: "{percent}% completado ({completed}/{total})",
|
||||||
|
progressTextMilestone: "{completed} de {next}",
|
||||||
lessons: "Lecciones",
|
lessons: "Lecciones",
|
||||||
settings: "Configuración",
|
settings: "Configuración",
|
||||||
showHints: "Mostrar pistas",
|
showHints: "Mostrar pistas",
|
||||||
@@ -923,6 +927,7 @@ const translations = {
|
|||||||
language: "اللغة",
|
language: "اللغة",
|
||||||
progress: "التقدم",
|
progress: "التقدم",
|
||||||
progressText: "{percent}% مكتمل ({completed}/{total})",
|
progressText: "{percent}% مكتمل ({completed}/{total})",
|
||||||
|
progressTextMilestone: "{completed} من {next}",
|
||||||
lessons: "الدروس",
|
lessons: "الدروس",
|
||||||
settings: "الإعدادات",
|
settings: "الإعدادات",
|
||||||
showHints: "إظهار التلميحات",
|
showHints: "إظهار التلميحات",
|
||||||
@@ -1140,6 +1145,7 @@ const translations = {
|
|||||||
language: "Мова",
|
language: "Мова",
|
||||||
progress: "Прогрес",
|
progress: "Прогрес",
|
||||||
progressText: "{percent}% завершено ({completed}/{total})",
|
progressText: "{percent}% завершено ({completed}/{total})",
|
||||||
|
progressTextMilestone: "{completed} з {next}",
|
||||||
lessons: "Уроки",
|
lessons: "Уроки",
|
||||||
settings: "Налаштування",
|
settings: "Налаштування",
|
||||||
showHints: "Показувати підказки",
|
showHints: "Показувати підказки",
|
||||||
|
|||||||
@@ -472,10 +472,11 @@ export class LessonEngine {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Get overall progress statistics
|
* Get overall progress statistics with milestone data
|
||||||
* @returns {Object} Progress statistics
|
* @returns {Object} Progress statistics including milestone progress
|
||||||
*/
|
*/
|
||||||
getProgressStats() {
|
getProgressStats() {
|
||||||
|
const MILESTONES = [1, 5, 10, 20, 30, 50, 75, 100];
|
||||||
let totalLessons = 0;
|
let totalLessons = 0;
|
||||||
let totalCompleted = 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 {
|
return {
|
||||||
totalLessons,
|
totalLessons,
|
||||||
totalCompleted,
|
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
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -468,11 +468,21 @@
|
|||||||
|
|
||||||
<div class="sidebar-section">
|
<div class="sidebar-section">
|
||||||
<h4 data-i18n="progress">Progress</h4>
|
<h4 data-i18n="progress">Progress</h4>
|
||||||
<div class="progress-display" id="progress-display">
|
<div class="progress-display milestone-progress" id="progress-display">
|
||||||
|
<div class="milestones" id="milestones">
|
||||||
|
<span class="milestone" data-value="1">1</span>
|
||||||
|
<span class="milestone" data-value="5">5</span>
|
||||||
|
<span class="milestone" data-value="10">10</span>
|
||||||
|
<span class="milestone" data-value="20">20</span>
|
||||||
|
<span class="milestone" data-value="30">30</span>
|
||||||
|
<span class="milestone" data-value="50">50</span>
|
||||||
|
<span class="milestone" data-value="75">75</span>
|
||||||
|
<span class="milestone" data-value="100">100</span>
|
||||||
|
</div>
|
||||||
<div class="progress-bar">
|
<div class="progress-bar">
|
||||||
<div class="progress-fill" id="progress-fill"></div>
|
<div class="progress-fill" id="progress-fill"></div>
|
||||||
</div>
|
</div>
|
||||||
<span class="progress-text" id="progress-text">0% Complete</span>
|
<span class="progress-text" id="progress-text">0 of 100</span>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|||||||
59
src/main.css
59
src/main.css
@@ -1019,6 +1019,65 @@ nav.sidebar-section {
|
|||||||
color: var(--light-text);
|
color: var(--light-text);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/* Milestone Progress */
|
||||||
|
.milestone-progress {
|
||||||
|
gap: var(--spacing-sm);
|
||||||
|
}
|
||||||
|
|
||||||
|
.milestones {
|
||||||
|
display: flex;
|
||||||
|
justify-content: space-between;
|
||||||
|
padding: 0 2px;
|
||||||
|
}
|
||||||
|
|
||||||
|
.milestone {
|
||||||
|
display: flex;
|
||||||
|
align-items: center;
|
||||||
|
justify-content: center;
|
||||||
|
min-width: 24px;
|
||||||
|
height: 24px;
|
||||||
|
font-size: 0.7rem;
|
||||||
|
font-weight: 600;
|
||||||
|
color: var(--light-text);
|
||||||
|
background: var(--border-color);
|
||||||
|
border-radius: 50%;
|
||||||
|
transition: all 0.3s ease;
|
||||||
|
}
|
||||||
|
|
||||||
|
.milestone.reached {
|
||||||
|
background: linear-gradient(135deg, #9163b8, #7c4dff);
|
||||||
|
color: white;
|
||||||
|
}
|
||||||
|
|
||||||
|
.milestone.current {
|
||||||
|
background: linear-gradient(135deg, #d45aa0, #1aafb8);
|
||||||
|
color: white;
|
||||||
|
transform: scale(1.15);
|
||||||
|
box-shadow: 0 2px 8px rgba(212, 90, 160, 0.4);
|
||||||
|
}
|
||||||
|
|
||||||
|
.milestone.next {
|
||||||
|
border: 2px dashed var(--light-text);
|
||||||
|
background: transparent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* Milestone celebration animation */
|
||||||
|
@keyframes milestone-pop {
|
||||||
|
0% {
|
||||||
|
transform: scale(1);
|
||||||
|
}
|
||||||
|
50% {
|
||||||
|
transform: scale(1.4);
|
||||||
|
}
|
||||||
|
100% {
|
||||||
|
transform: scale(1.15);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
.milestone.just-reached {
|
||||||
|
animation: milestone-pop 0.5s ease-out;
|
||||||
|
}
|
||||||
|
|
||||||
/* Module List in Sidebar */
|
/* Module List in Sidebar */
|
||||||
.module-list {
|
.module-list {
|
||||||
/* No max-height - parent nav.sidebar-section handles overflow */
|
/* No max-height - parent nav.sidebar-section handles overflow */
|
||||||
|
|||||||
Reference in New Issue
Block a user