diff --git a/app/static/JSScript/ContextualIntegrity.js b/app/static/JSScript/ContextualIntegrity.js index c54215a..745bee3 100644 --- a/app/static/JSScript/ContextualIntegrity.js +++ b/app/static/JSScript/ContextualIntegrity.js @@ -118,6 +118,10 @@ document.addEventListener("DOMContentLoaded", function () { progressBar.style.width = "100%"; resultsContainer.classList.add("show"); sessionStorage.setItem("privacy_points", global_score += score); + + if (window.Leveling){ + Leveling.awardForQuiz({ correct: score, total: totalQuestions, difficulty: "normal"}); + } } }; diff --git a/app/static/JSScript/levelingSys.js b/app/static/JSScript/levelingSys.js new file mode 100644 index 0000000..56be5fe --- /dev/null +++ b/app/static/JSScript/levelingSys.js @@ -0,0 +1,190 @@ +// leveling.js +(function () { + const STORAGE_KEY = "mypals_progress_v1"; + + // Level curve: XP needed to reach next level = base + (level-1)*step + const XP_BASE = 50; + const XP_STEP = 25; + + const TITLES = [ + { minLevel: 1, title: "New Explorer" }, + { minLevel: 3, title: "Info Spotter" }, + { minLevel: 5, title: "Privacy Scout" }, + { minLevel: 7, title: "Data Defender" }, + { minLevel: 10, title: "Security Champion" }, + ]; + + const DEFAULT_STATE = { + level: 1, + xp: 0, + totalXp: 0, + streakDays: 0, // calendar-day streak + lastPlayedISO: null, // e.g., "2025-09-15" + quizzesCompleted: 0, + badges: [] // ["Perfect Score!", "3-Day Streak!", ...] + }; + + const state = load(); + + // --- Public-ish API you can call from your quiz code --- + window.Leveling = { + awardForQuiz, // awardForQuiz({ correct, total, difficulty? }) + awardXP, // awardXP(number, reason) + renderStatus, // re-render the text bar + getState, // read-only snapshot + resetProgress, // wipe for testing + }; + + // Will create UI container if needed + ensureMount(); + + // Initial render + renderStatus(); + + // ---------------- Implementation ---------------- + function getRequiredXpFor(level) { + return XP_BASE + (level - 1) * XP_STEP; + } + + function titleFor(level) { + let current = TITLES[0].title; + for (const t of TITLES) if (level >= t.minLevel) current = t.title; + return current; + } + + function awardForQuiz({ correct, total, difficulty = "normal" }) { + // Base XP: 10 per correct answer + let xp = correct * 10; + + // Perfect bonus + if (correct === total && total > 0) { + xp += 15; + grantBadge("Perfect Score!"); + } + + // Small difficulty multiplier (optional) + const mult = difficulty === "hard" ? 1.5 : difficulty === "easy" ? 0.8 : 1; + xp = Math.round(xp * mult); + + trackStreak(); + state.quizzesCompleted += 1; + + awardXP(xp, `Quiz: ${correct}/${total}`); + } + + function awardXP(amount, reason = "") { + if (!Number.isFinite(amount) || amount <= 0) return; + + state.xp += amount; + state.totalXp += amount; + + // Handle level ups (can chain multiple levels if a big grant) + let leveledUp = 0; + while (state.xp >= getRequiredXpFor(state.level)) { + state.xp -= getRequiredXpFor(state.level); + state.level += 1; + leveledUp++; + } + if (leveledUp > 0) { + grantBadge(`Leveled up to ${state.level}!`); + } + + save(); + renderStatus(reason ? `+${amount} XP • ${reason}` : `+${amount} XP`); + } + + function trackStreak() { + const today = new Date(); + const todayISO = today.toISOString().slice(0, 10); // YYYY-MM-DD + + if (!state.lastPlayedISO) { + state.streakDays = 1; + } else { + const last = new Date(state.lastPlayedISO); + const diffDays = Math.floor((today - last) / 86400000); + if (diffDays === 0) { + // same day, do nothing + } else if (diffDays === 1) { + state.streakDays += 1; + if (state.streakDays === 3) grantBadge("3-Day Streak!"); + if (state.streakDays === 7) grantBadge("7-Day Streak!"); + } else { + state.streakDays = 1; // reset + } + } + state.lastPlayedISO = todayISO; + } + + function grantBadge(name) { + if (!state.badges.includes(name)) { + state.badges.push(name); + } + } + + function ensureMount() { + const container = document.querySelector(".game-container") || document.body; + let mount = document.getElementById("level-status"); + if (!mount) { + mount = document.createElement("div"); + mount.id = "level-status"; + container.prepend(mount); + } + // minimal styling; keep it text-first and accessible + mount.style.margin = "0 0 12px 0"; + mount.style.padding = "10px 12px"; + mount.style.border = "1px solid var(--shadow, #ddd)"; + mount.style.borderRadius = "10px"; + mount.style.fontSize = "14px"; + mount.style.lineHeight = "1.35"; + mount.style.background = "rgba(0,0,0,0.02)"; + } + + function renderStatus(announce = "") { + const el = document.getElementById("level-status"); + if (!el) return; + + const req = getRequiredXpFor(state.level); + const progressPct = Math.max(0, Math.min(100, Math.round((state.xp / req) * 100))); + + const rows = [ + `Level ${state.level} · ${titleFor(state.level)}`, + `XP: ${state.xp}/${req} (${progressPct}%)`, + `Total XP: ${state.totalXp} · Streak: ${state.streakDays} day${state.streakDays === 1 ? "" : "s"} · Quizzes: ${state.quizzesCompleted}` + ]; + + if (state.badges.length) { + rows.push(`Badges: ${state.badges.join(", ")}`); + } + + if (announce) { + rows.push(`Update: ${announce}`); + } + + el.textContent = rows.join(" | "); + } + + function getState() { + return JSON.parse(JSON.stringify(state)); + } + + function resetProgress() { + Object.assign(state, DEFAULT_STATE); + save(); + renderStatus("Progress reset"); + } + + function save() { + localStorage.setItem(STORAGE_KEY, JSON.stringify(state)); + } + + function load() { + try { + const raw = localStorage.getItem(STORAGE_KEY); + if (!raw) return { ...DEFAULT_STATE }; + const parsed = JSON.parse(raw); + return { ...DEFAULT_STATE, ...parsed }; + } catch { + return { ...DEFAULT_STATE }; + } + } +})(); diff --git a/app/static/JSScript/myPal-Bots-Quiz.js b/app/static/JSScript/myPal-Bots-Quiz.js index ff96027..74f7a99 100644 --- a/app/static/JSScript/myPal-Bots-Quiz.js +++ b/app/static/JSScript/myPal-Bots-Quiz.js @@ -275,6 +275,10 @@ document.addEventListener("DOMContentLoaded", function () { progressBar.style.width = "100%"; scoreDisplay.textContent = score; sessionStorage.setItem("privacy_points", global_score += score); + + if(window.Leveling){ + Leveling.awardForQuiz({correct: score, total: totalQuestions, difficulty: "normal"}); + } } }); } diff --git a/app/templates/ContextualIntegrity.html b/app/templates/ContextualIntegrity.html index 022b7a1..43b9324 100644 --- a/app/templates/ContextualIntegrity.html +++ b/app/templates/ContextualIntegrity.html @@ -92,5 +92,6 @@

Great Job!

+ \ No newline at end of file diff --git a/app/templates/Quiz.html b/app/templates/Quiz.html index 942ed73..14e3898 100644 --- a/app/templates/Quiz.html +++ b/app/templates/Quiz.html @@ -675,5 +675,6 @@

Great Job!

+