diff --git a/src/quizLogic/advanceTimer.js b/src/quizLogic/advanceTimer.js
new file mode 100644
index 0000000..772d7fa
--- /dev/null
+++ b/src/quizLogic/advanceTimer.js
@@ -0,0 +1,56 @@
+// Shared advance timer for quizzes
+// createAdvanceTimer(onProgress, onComplete) -> { start(duration), cancel() }
+export function createAdvanceTimer(onProgress, onComplete) {
+ let timer = null;
+ let startTime = 0;
+ let duration = 0;
+
+ function _clear() {
+ if (timer) {
+ clearInterval(timer);
+ timer = null;
+ }
+ }
+
+ return {
+ start(d) {
+ duration = d || 0;
+ // reset progress immediately
+ try {
+ onProgress(0);
+ } catch (e) {
+ // ignore
+ }
+ startTime = Date.now();
+ _clear();
+ timer = setInterval(() => {
+ const elapsed = Date.now() - startTime;
+ const progress = Math.min((elapsed / duration) * 100, 100);
+ try {
+ onProgress(progress);
+ } catch (e) {
+ // ignore
+ }
+ if (progress >= 100) {
+ _clear();
+ try {
+ onProgress(0);
+ } catch (e) {}
+ if (typeof onComplete === 'function') {
+ try {
+ onComplete();
+ } catch (e) {
+ console.error('advanceTimer onComplete error', e);
+ }
+ }
+ }
+ }, 50);
+ },
+ cancel() {
+ _clear();
+ try {
+ onProgress(0);
+ } catch (e) {}
+ },
+ };
+}
diff --git a/src/quizLogic/quizSession.js b/src/quizLogic/quizSession.js
index 604bd30..9b984f2 100644
--- a/src/quizLogic/quizSession.js
+++ b/src/quizLogic/quizSession.js
@@ -21,3 +21,22 @@ export function loadSessionState(key, defaultState) {
export function clearSessionState(key) {
localStorage.removeItem(key);
}
+
+// Return a new session state object for a quiz with the provided session length
+export function createNewSessionState(sessionLength = 10) {
+ return {
+ score: { correct: 0, total: 0, skipped: 0 },
+ currentSessionQuestions: 0,
+ sessionStats: {
+ correct: 0,
+ wrong: 0,
+ skipped: 0,
+ total: 0,
+ sessionLength: sessionLength,
+ },
+ sessionInProgress: true,
+ sessionStartTime: Date.now(),
+ showSessionResults: false,
+ sessionRestoredFromReload: false,
+ };
+}
diff --git a/src/utils/theme.js b/src/utils/theme.js
new file mode 100644
index 0000000..1cbd1ea
--- /dev/null
+++ b/src/utils/theme.js
@@ -0,0 +1,29 @@
+export function applyTheme(theme) {
+ let effectiveTheme = theme;
+ if (theme === "system") {
+ effectiveTheme = window.matchMedia("(prefers-color-scheme: dark)").matches
+ ? "dark"
+ : "light";
+ }
+ document.documentElement.setAttribute("data-theme", effectiveTheme);
+ document.documentElement.className = effectiveTheme;
+ console.log("[Theme] Applied theme:", effectiveTheme);
+}
+
+export function setTheme(newTheme) {
+ if (newTheme === "light" || newTheme === "dark" || newTheme === "system") {
+ // Persist choice and apply immediately
+ localStorage.setItem("theme", newTheme);
+ console.log("[Theme] setTheme:", newTheme);
+ setTimeout(() => applyTheme(newTheme), 0);
+ return newTheme;
+ }
+ }
+
+export function getStoredTheme(defaultTheme = "system") {
+ return (typeof localStorage !== "undefined" && localStorage.getItem("theme")) || defaultTheme;
+}
+
+// Svelte store for reactive theme across components
+import { writable } from 'svelte/store';
+export const themeStore = writable(getStoredTheme());