(function () {
  "use strict";

  const LEVELS = [
    { id: 1, title: "Star 1", storyCount: 10 },
    { id: 2, title: "Star 2", storyCount: 30 },
    { id: 3, title: "Star 3", storyCount: 90 },
    { id: 4, title: "Star 4", storyCount: 270 },
    { id: 5, title: "Star 5", storyCount: null }
  ];
  const LITTLE_ANGELS_LABEL = "Little Angels";
  const BABY_COURSE = { id: 0, title: LITTLE_ANGELS_LABEL, storyCount: 10 };
  const POINTS_PER_QUESTION = 10;
  const SECONDS_PER_QUESTION = 30;
  const ELECTIVE_SECONDS_PER_QUESTION = 90;
  const COMPETITION_TIMER_MIN_SECONDS = 10;
  const COMPETITION_TIMER_MAX_SECONDS = 60;
  const COMPETITION_TIMER_DEFAULT_SECONDS = 30;
  const PASS_PERCENT = 80;
  const BABY_PASS_PERCENT = 100;
  const ELECTIVE_PASS_PERCENT = 85;
  const ELECTIVE_OTHERS_LEVEL = "OTHERS";
  const ELECTIVE_OTHERS_SUBJECT = "Others";
  const ELECTIVE_COMPETITION_SCOPE_CLASS = "class";
  const ELECTIVE_COMPETITION_SCOPE_SUBJECT = "subject";
  const ELECTIVE_DEFAULT_SUBJECT_ORDER = ["Math", "SST", "Science", "English"];
  const ELECTIVE_MID_COURSE_AMOUNTS = {
    P1: 10,
    P2: 20,
    P3: 30,
    P4: 40,
    P5: 50,
    P6: 60,
    P7: 70
  };
  const ELECTIVE_FAILURE_MESSAGES = [
    "{name}, every strong learner grows one brave attempt at a time. Keep rising.",
    "{name}, this question is not the end of your wisdom. Move forward with courage.",
    "{name}, learning is winning when you keep trying. You are getting sharper now.",
    "{name}, do not fear a missed answer. Your next attempt can be stronger.",
    "{name}, this is how champions study: try, notice, remember, and continue.",
    "{name}, your mind is growing. Go to the next one with confidence.",
    "{name}, a missed answer can become a strong memory when you stay focused.",
    "{name}, keep calm and keep learning. You are making progress.",
    "{name}, you are still in the race. Your next answer can be stronger.",
    "{name}, good learners do not stop at mistakes. They keep collecting wisdom."
  ];
  const ELECTIVE_CLASS_GROUPS = [
    ["P1", "P2", "P3", "P4", "P5", "P6", "P7"],
    ["S1", "S2", "S3", "S4"],
    ["S5", "S6"],
    ["UNIVERSITY", ELECTIVE_OTHERS_LEVEL]
  ];
  const LECTIVE_LEVELS = ELECTIVE_CLASS_GROUPS.flat();
  const LECTIVE_SUBJECTS_BY_LEVEL = {
    P1: ["Math", "SST", "Science", "English"],
    P2: ["Math", "SST", "Science", "English"],
    P3: ["Math", "SST", "Science", "English"],
    P4: ["Math", "SST", "Science", "English"],
    P5: ["Math", "SST", "Science", "English"],
    P6: ["Math", "SST", "Science", "English"],
    P7: ["Math", "SST", "Science", "English"],
    S1: ["English", "Math", "Geography", "History", "Physics", "Chemistry", "Biology"],
    S2: ["English", "Math", "Geography", "History", "Physics", "Chemistry", "Biology"],
    S3: ["English", "Math", "Geography", "History", "Physics", "Chemistry", "Biology"],
    S4: ["English", "Math", "Geography", "History", "Physics", "Chemistry", "Biology"],
    S5: ["English", "Math", "Geography", "History", "Physics", "Chemistry", "Biology", "Economics", "Literature-English", "GP", "Agriculture"],
    S6: ["English", "Math", "Geography", "History", "Physics", "Chemistry", "Biology", "Economics", "Literature-English", "GP", "Agriculture"],
    UNIVERSITY: ["General"],
    OTHERS: [ELECTIVE_OTHERS_SUBJECT]
  };
  const LECTIVE_SUBJECTS = Array.from(new Set(Object.values(LECTIVE_SUBJECTS_BY_LEVEL).flat()));
  const DEFAULT_LECTIVE_LEVEL = "P1";
  const DEFAULT_LECTIVE_SUBJECT = "Math";
  const ELECTIVE_COMBINATION_LEVELS = ["S5", "S6"];
  const ELECTIVE_COMBINATION_MIN = 4;
  const ELECTIVE_COMBINATION_MAX = 5;
  const CUSTOM_SCHOOL_NAME_DEFAULT = "KGM SCHOOL";
  const CUSTOM_SCHOOL_ADMINS = ["tindiwensi", "arther"];
  const CUSTOM_SCHOOL_ADMIN_PASSWORD = "000000";
  const COMPETITION_SCHOOL_LEVELS = ["P1", "P2", "P3", "P4", "P5", "P6", "P7", "S1", "S2", "S3", "S4", "S5", "S6", "UNIVERSITY"];
  const COMPETITION_LEVEL_MODES = ["same", "different"];
  const COMPETITION_MATERIALS = ["bible", "lective", "both"];
  const COMPETITION_MAX_QUESTION_TARGET = 200;
  const INITIAL_SERVICE_MINUTES = 5;
  const RENEWAL_SERVICE_HOURS = 720;
  const INITIAL_SERVICE_MS = INITIAL_SERVICE_MINUTES * 60 * 1000;
  const RENEWAL_SERVICE_MS = RENEWAL_SERVICE_HOURS * 60 * 60 * 1000;
  const SIX_DIGIT_PERMUTATIONS = buildSixDigitPermutations();
  const RENEWAL_ENDPOINT = window.KNOW_GOD_MORE_RENEWAL_ENDPOINT || "";
  const COMPETITION_ENDPOINT = window.KNOW_GOD_MORE_COMPETITION_ENDPOINT || "";
  const SEARCH_UPLOAD_ENDPOINT = window.KNOW_GOD_MORE_SEARCH_ENDPOINT || "";
  const UPDATE_BASE_URL = window.KNOW_GOD_MORE_UPDATE_BASE_URL || "https://kgm-update-server.arthertindiwensi.workers.dev";
  const UPDATE_APK_NAME = "kgmpc.exe";
  const UPDATE_INSTALL_READY_KEY = "know-god-more-pc-update-install-ready";
  const RENEWAL_PENDING_KEY = "know-god-more-renewal-pending";
  const RENEWAL_LAST_ADDED_KEY = "know-god-more-renewal-last-added";
  const SEARCH_UPLOAD_INTERVAL_MS = 7 * 24 * 60 * 60 * 1000;
  const SEARCH_MAX_RESULTS = 10;
  const SAVED_CERTIFICATE_DB = "know-god-more-saved-certificates";
  const SAVED_CERTIFICATE_STORE = "certificates";
  const SAVED_CERTIFICATE_LIMIT = 100;
  const EDGE_TAP_RADIUS = 12;
  const EDGE_TAP_MAX_MOVE = 18;
  const BOOT_SCREEN_MIN_MS = 3000;
  const APP_VERSION = "1.0.47-pc";
  const APP_BUILD = 162;
  const LECTIVE_DATA_VERSION = "135";
  const LECTIVE_DATA_SCRIPT_URLS = [
    `data/lective-30th-may-2026.js?v=${LECTIVE_DATA_VERSION}`
  ];
  const DEFAULT_LECTIVE_SUBJECT_INDEX = window.KGM_LECTIVE_SUBJECT_INDEX || {};
  const COMPANY_PROFILE = window.KGM_COMPANY_PROFILE || { title: "Know God More Company Profile", subtitle: "Board of Directors and Funders", members: [], funders: [] };
  const AUDIO_ASSETS = {
    click: "assets/audio/click.wav",
    correct: "assets/audio/correct-answer.wav",
    wrong: "assets/audio/wrong-answer.mp3",
    certificate100: "assets/audio/certificate-100.mp3",
    certificateLess: "assets/audio/certificate-less-than-100.mp3"
  };
  const STORAGE_KEY = "know-god-more:v1";
  const PASSWORD_HASH_PREFIX = "kgm-local-v1:";
  const SERVICE_STORAGE_KEY = "know-god-more:service";
  const CRASH_STORAGE_KEY = "know-god-more:crash-log";
  const PAYMENT_NETWORKS = {
    mtn: { label: "MTN Mobile Money", transactionLength: 11 },
    airtel: { label: "Airtel Money", transactionLength: 12 },
    mpesa: { label: "M-Pesa", transactionLength: 10 },
    safaricomMpesa: { label: "Safaricom M-Pesa", transactionLength: 10 },
    vodacomMpesa: { label: "Vodacom M-Pesa", transactionLength: 10 },
    orangeMoney: { label: "Orange Money", transactionLength: 10 },
    moovMoney: { label: "Moov Money", transactionLength: 12 },
    tigoPesa: { label: "Tigo Pesa", transactionLength: 10 },
    haloPesa: { label: "HaloPesa", transactionLength: 10 },
    tkash: { label: "T-Kash", transactionLength: 10 },
    wave: { label: "Wave", transactionLength: 10 },
    freeMoney: { label: "Free Money", transactionLength: 10 },
    ecocash: { label: "EcoCash", transactionLength: 10 },
    telebirr: { label: "Telebirr", transactionLength: 10 },
    afriMoney: { label: "AfriMoney", transactionLength: 10 },
    paypal: { label: "PayPal", transactionLength: 17 },
    westernUnion: { label: "Western Union", transactionLength: 10 },
    moneyGram: { label: "MoneyGram", transactionLength: 8 },
    worldRemit: { label: "WorldRemit", transactionLength: 12 },
    bankTransfer: { label: "Bank Transfer", transactionLength: 12 },
    other: { label: "Other Network", transactionLength: 12 }
  };
  const PAYMENT_NETWORK_SELECT_LABELS = {
    mtn: "MTN",
    airtel: "Airtel",
    mpesa: "M-Pesa",
    safaricomMpesa: "Safaricom M-Pesa",
    vodacomMpesa: "Vodacom M-Pesa",
    orangeMoney: "Orange Money",
    moovMoney: "Moov Money",
    tigoPesa: "Tigo Pesa",
    haloPesa: "HaloPesa",
    tkash: "T-Kash",
    wave: "Wave",
    freeMoney: "Free Money",
    ecocash: "EcoCash",
    telebirr: "Telebirr",
    afriMoney: "AfriMoney",
    paypal: "PayPal",
    westernUnion: "Western Union",
    moneyGram: "MoneyGram",
    worldRemit: "WorldRemit",
    bankTransfer: "Bank Transfer",
    other: "Other"
  };

  const app = document.querySelector("#app");
  const stories = Array.isArray(window.KNOW_GOD_MORE_STORIES) ? window.KNOW_GOD_MORE_STORIES : [];
  let defaultLectiveStories = Array.isArray(window.KNOW_GOD_MORE_DEFAULT_LECTIVE_STORIES) ? window.KNOW_GOD_MORE_DEFAULT_LECTIVE_STORIES : [];
  let defaultLectiveStoryIds = new Set(defaultLectiveStories.map((story) => String(story && story.id || "")).filter(Boolean));
  const storyBank = buildStoryBank(stories);
  const questionBank = storyBank.flatMap((story) => story.questions);
  const questionById = new Map(questionBank.map((item) => [item.id, item]));
  const levelOffsets = buildLevelOffsets();

  let store = readStore();
  let serviceState = readServiceState();
  let selectedLoginLevel = 1;
  let loginError = "";
  let renewalBusy = false;
  let renewalMessage = "";
  let renewalPaidReady = false;
  let renewalPayment = { amountPaid: "", network: "mtn", transactionId: "" };
  let renewalFieldWatcherId = null;
  let renewalSmsPollerId = null;
  let renewalPath = "menu";
  let renewalDial = { amount: "5000", otherCode: "" };
  let updateBusy = false;
  let updateMessage = "";
  let updateInstallReady = localStorage.getItem(UPDATE_INSTALL_READY_KEY) === "1";
  let currentCertificateJpeg = "";
  let readingZoom = 1;
  let route = "login";
  let timerId = null;
  let lectiveTimerId = null;
  let serviceTimerId = null;
  let competitionTimerId = null;
  let competitionCountdownId = null;
  let competitionState = null;
  let competitionView = "setup";
  let competitionAccessKey = "";
  let competitionUnlockMessage = "";
  let competitionDetailsOpen = false;
  let selectedSavedCompetitionId = "";
  let selectedCompetitionLevel = 1;
  let selectedCompetitionSource = "both";
  let selectedCompetitionLevelMode = "same";
  let selectedCompetitionSchoolLevel = "P1";
  let selectedCompetitionQuestionTarget = 1;
  let selectedCompetitionQuestionsPerRound = 1;
  let selectedCompetitionLectiveLevel = DEFAULT_LECTIVE_LEVEL;
  let selectedCompetitionLectiveSubject = DEFAULT_LECTIVE_SUBJECT;
  let selectedCompetitionLectiveScope = ELECTIVE_COMPETITION_SCOPE_SUBJECT;
  let competitionCoordinatorDrafts = {
    online: { name: "", password: "", competitionName: "" },
    offline: { name: "", password: "", competitionName: "" }
  };
  let selectedLectiveLevel = DEFAULT_LECTIVE_LEVEL;
  let selectedLectiveSubject = DEFAULT_LECTIVE_SUBJECT;
  let selectedLectiveStudyLevel = DEFAULT_LECTIVE_LEVEL;
  let selectedLectiveStudySubject = DEFAULT_LECTIVE_SUBJECT;
  let lectiveView = "classes";
  let competitionJoinMessage = "";
  let competitionLectiveMessage = "";
  let competitionCoordinatorMessage = "";
  let searchMessage = "";
  let searchResults = [];
  let pendingSchoolSearch = null;
  let activeVoiceRecognition = null;
  let activeVoiceTarget = "";
  let lastCertificateSoundKey = "";
  let toastMessage = "";
  let savedCertificateList = [];
  let savedCertificateLoading = false;
  let savedCertificateLoaded = false;
  let savedCertificateMessage = "";
  let selectedSavedCertificateId = "";
  let lastCompetitionCertificates = [];
  let onlineJoin = {
    hostUrl: "",
    playerId: "",
    playerName: "",
    password: "",
    state: null,
    message: ""
  };
  let routeStack = [];
  let lastRouteSnapshot = null;
  let handlingBack = false;
  let actionLoaderTimer = null;
  let actionLoaderStartedAt = 0;
  let lastPointerStart = null;
  const appBootStartedAt = Date.now();
  let appReadyScheduled = false;
  let defaultLectiveDataLoaded = false;
  let defaultLectiveDataLoading = null;
  let schoolSearchDataLoaded = false;
  let schoolSearchDataLoading = null;
  let schoolSearchLoadFailures = 0;
  const defaultLectiveSubjectLoading = new Map();
  const defaultLectiveSubjectLoaded = new Set();
  const lectiveFolderStoriesCache = new Map();
  const lectiveStoryBankCache = new Map();
  const lectiveQuestionListCache = new Map();
  let persistableLectiveFoldersCache = null;
  let persistableLectiveStoriesCache = null;
  const autoSavedCertificates = new Set();
  let certificateSaveQueue = Promise.resolve();

  installWindowsShareBridge();
  installCrashLogger();
  render();
  startServiceTicker();
  tryUploadSearchQueries();
  window.setInterval(tryUploadSearchQueries, 60 * 60 * 1000);
  registerOfflineCache();
  window.setTimeout(restorePendingRenewalIfNeeded, 500);

  window.KGMGoBack = handleDeviceBack;
  window.KGMPhoneUpdateResult = handlePhoneUpdateResult;
  window.KGM_RETURN_FROM_DIALER = handleReturnFromDialer;
  window.addEventListener("focus", handleReturnFromDialer);
  document.addEventListener("visibilitychange", () => {
    if (!document.hidden) handleReturnFromDialer();
  });

  document.addEventListener("pointerdown", handlePointerSound, { passive: true });
  document.addEventListener("pointerup", handleForgivingEdgeTap, true);
  document.addEventListener("click", handleButtonClickFeedback, true);
  document.addEventListener("input", handleInputSound);
  document.addEventListener("change", handleInputSound);
  document.addEventListener("input", handleRenewalFieldEvent, true);
  document.addEventListener("change", handleRenewalFieldEvent, true);

  document.addEventListener("click", (event) => {
    const control = event.target.closest("[data-action]");
    if (!control) return;

    const action = control.dataset.action;
    if (!confirmSensitiveAction(action, control)) return;
    showActionLoader(control);
    if (route === "competition") captureVisibleCompetitionCoordinatorDrafts();
    if (action === "app-back") {
      const result = handleDeviceBack();
      if (result === "exit") {
        toastMessage = "You are already at the first screen.";
        render();
      }
      return;
    }

    if (action === "toggle-audio-muted") {
      store.audioMuted = !store.audioMuted;
      saveStore();
      if (route === "login") {
        renderLogin();
      } else {
        render();
      }
      if (!store.audioMuted) playAppSound("click");
      return;
    }
    if (action === "choose-login-level") {
      const levelId = Number(control.dataset.level);
      selectedLoginLevel = Number.isFinite(levelId) ? levelId : 1;
      loginError = "";
      renderLogin();
      return;
    }

    if (action === "logout") {
      stopTimer();
      store.activeUser = null;
      saveStore();
      route = "login";
      selectedLoginLevel = 1;
      loginError = "";
      render();
      return;
    }

    if (action === "renew-service") {
      openRenewalDialog();
      return;
    }

    if (action === "phone-update") {
      startPhoneUpdate();
      return;
    }

    if (action === "phone-install") {
      startPhoneInstall();
      return;
    }

    if (action === "confirm-renewal-paid") {
      confirmRenewalPaid();
      return;
    }

    if (action === "activate-renewal-token") {
      activateRenewalCode();
      return;
    }

    if (action === "search") {
      stopTimer();
      searchMessage = "";
      route = "search";
      render();
      return;
    }

    if (action === "saved-certificates") {
      stopTimer();
      openSavedCertificates();
      return;
    }

    if (action === "edit-text") {
      editVisibleText(control);
      return;
    }

    if (action === "refresh-saved-certificates") {
      loadSavedCertificateList().then(() => {
        if (route === "saved-certificates") renderSavedCertificates();
      });
      return;
    }

    if (action === "share-saved-certificate") {
      shareSavedCertificate(control.dataset.certificateId || "");
      return;
    }

    if (action === "open-saved-certificate") {
      selectedSavedCertificateId = control.dataset.certificateId || "";
      renderSavedCertificates();
      hydrateSavedCertificatePreview();
      return;
    }

    if (action === "save-saved-certificate") {
      saveSavedCertificate(control.dataset.certificateId || "");
      return;
    }

    if (action === "print-saved-certificate") {
      printSavedCertificate(control.dataset.certificateId || "");
      return;
    }

    if (action === "delete-saved-certificate") {
      deleteSavedCertificateFromFolder(control.dataset.certificateId || "");
      return;
    }

    if (action === "search-back-login") {
      pendingSchoolSearch = null;
      route = "login";
      renderLogin();
      return;
    }

    if (action === "search-reset-profile") {
      store.searchProfile = null;
      pendingSchoolSearch = null;
      searchMessage = "Register again to search with another name.";
      saveStore();
      renderSearch();
      return;
    }

    if (action === "search-clear") {
      store.searchLastQuery = "";
      searchResults = [];
      searchMessage = "";
      pendingSchoolSearch = null;
      saveStore();
      renderSearch();
      return;
    }

    if (action === "confirm-school-search") {
      const pending = pendingSchoolSearch;
      pendingSchoolSearch = null;
      if (!pending || !searchTerms(pending.query).length) {
        searchMessage = "Type at least one clear search word.";
        renderSearch();
        return;
      }
      startConfirmedSchoolSearch(pending.query);
      return;
    }

    if (action === "cancel-school-search") {
      pendingSchoolSearch = null;
      searchResults = [];
      searchMessage = "KGM School search cancelled.";
      renderSearch();
      return;
    }

    if (action === "settings") {
      stopTimer();
      route = "settings";
      render();
      return;
    }

    if (action === "help") {
      stopTimer();
      route = "help";
      render();
      return;
    }

    if (action === "about") {
      stopTimer();
      route = "about";
      render();
      return;
    }

    if (action === "company-profile") {
      stopTimer();
      route = "company-profile";
      render();
      return;
    }

    if (action === "export-backup") {
      exportBackup();
      return;
    }

    if (action === "clear-crash-log") {
      localStorage.removeItem(CRASH_STORAGE_KEY);
      toastMessage = "Diagnostic log cleared.";
      render();
      return;
    }

    if (action === "close-renewal-dialog") {
      clearPendingRenewal();
      stopRenewalSmsPolling(true);
      closeRenewalDialog();
      return;
    }

    if (action === "renewal-menu") {
      clearPendingRenewal();
      stopRenewalSmsPolling(true);
      renewalPath = "menu";
      renewalMessage = "";
      refreshRenewalDialog();
      return;
    }

    if (action === "renewal-path") {
      captureRenewalDialFields();
      if (control.dataset.path !== renewalPath) {
        clearPendingRenewal();
        stopRenewalSmsPolling(true);
      }
      renewalPath = control.dataset.path || "menu";
      renewalMessage = "";
      refreshRenewalDialog();
      return;
    }

    if (action === "open-ussd") {
      captureRenewalDialFields();
      openRenewalUssd(control.dataset.ussd || "");
      return;
    }

    if (action === "copy-renewal-value") {
      captureRenewalDialFields();
      updateRenewalReferencePreview();
      const liveReference = document.querySelector("[data-renewal-reference-display]");
      const copyValue = control.dataset.label === "reference" && liveReference
        ? liveReference.textContent
        : (control.dataset.value || "");
      copyRenewalValue(copyValue, control.dataset.label || "value");
      return;
    }

    if (action === "lective-course") {
      if (isServiceExpired()) {
        loginError = "Service time has ended. Renew service before opening KGM School.";
        route = "login";
        renderLogin();
        return;
      }
      openLectiveCourse("classes");
      return;
    }

    if (action === "elective-classes") {
      openLectiveCourse("classes");
      return;
    }

    if (action === "elective-select-class") {
      selectedLectiveStudyLevel = normalizeLectiveLevel(control.dataset.level);
      selectedLectiveStudySubject = normalizeLectiveSubject(selectedLectiveStudySubject, selectedLectiveStudyLevel);
      selectedLectiveLevel = selectedLectiveStudyLevel;
      selectedLectiveSubject = normalizeLectiveSubject(selectedLectiveSubject, selectedLectiveLevel);
      if (selectedLectiveStudyLevel !== ELECTIVE_OTHERS_LEVEL && !availableElectiveSubjectsForClass(selectedLectiveStudyLevel).length) {
        competitionLectiveMessage = `${electiveClassLabel(selectedLectiveStudyLevel)} is coming soon. Add or bring the question database later.`;
        route = "lective";
        lectiveView = "classes";
        renderLectiveCourse();
        return;
      }
      competitionLectiveMessage = "";
      route = "lective";
      lectiveView = selectedLectiveStudyLevel === ELECTIVE_OTHERS_LEVEL ? "others" : "register";
      if (selectedLectiveStudyLevel === ELECTIVE_OTHERS_LEVEL) {
        openLectiveCourse("others");
        return;
      }
      renderLectiveCourse();
      return;
    }

    if (action === "elective-subjects") {
      route = "lective";
      lectiveView = selectedLectiveStudyLevel === ELECTIVE_OTHERS_LEVEL ? "others" : "subjects";
      renderLectiveCourse();
      return;
    }

    if (action === "elective-change-combination") {
      route = "lective";
      lectiveView = "combination";
      renderLectiveCourse();
      return;
    }

    if (action === "elective-start-subject") {
      startElectiveSubject(selectedLectiveStudyLevel, control.dataset.subject || "");
      return;
    }

    if (action === "elective-others-add") {
      selectedLectiveLevel = ELECTIVE_OTHERS_LEVEL;
      selectedLectiveSubject = ELECTIVE_OTHERS_SUBJECT;
      openLectiveCourse("others-add");
      return;
    }

    if (action === "elective-others-start") {
      startElectiveSubject(ELECTIVE_OTHERS_LEVEL, ELECTIVE_OTHERS_SUBJECT);
      return;
    }

    if (action === "school-customize") {
      route = "lective";
      lectiveView = "customize-auth";
      competitionLectiveMessage = "";
      renderLectiveCourse();
      return;
    }

    if (action === "school-customize-form") {
      route = "lective";
      lectiveView = "customize";
      competitionLectiveMessage = "";
      renderLectiveCourse();
      return;
    }

    if (action === "school-customize-reset") {
      store.schoolCustomization = normalizeSchoolCustomization(null);
      saveStore();
      competitionLectiveMessage = "School name and certificate badge returned to KGM SCHOOL.";
      route = "lective";
      lectiveView = "classes";
      renderLectiveCourse();
      return;
    }

    if (action === "lective-delete-story") {
      deleteLectiveStory(control.dataset.lectiveKey || "", control.dataset.lectiveId || "");
      return;
    }

    if (action === "lective-study-begin-quiz") {
      beginLectiveStudyQuiz();
      return;
    }

    if (action === "lective-study-answer") {
      selectLectiveStudyAnswer(Number(control.dataset.answer));
      return;
    }

    if (action === "lective-study-next") {
      nextLectiveStudyStep();
      return;
    }

    if (action === "continue-elective-study") {
      continueElectiveStudyAfterCertificate();
      return;
    }

    if (action === "elective-repeat-class") {
      repeatElectiveClass(control.dataset.level || "");
      return;
    }

    if (action === "elective-repeat-checkpoint") {
      repeatElectiveCheckpoint();
      return;
    }

    if (action === "voice-type") {
      startLectiveVoiceTyping(control.dataset.voiceTarget || "");
      return;
    }

    if (action === "competition") {
      if (isServiceExpired() && !canUseCompetitionWhileExpired()) {
        loginError = "Service time has ended. Competition remains open only when 10 or more competitors are already registered.";
        route = "login";
        renderLogin();
        return;
      }
      startCompetitionSetup();
      return;
    }

    if (action === "competition-open-saved") {
      stopCompetitionPolling();
      selectedSavedCompetitionId = control.dataset.savedCompetitionId || selectedSavedCompetitionId || "";
      competitionView = "saved";
      route = "competition";
      renderCompetition();
      return;
    }

    if (action === "competition-select-saved") {
      selectedSavedCompetitionId = control.dataset.savedCompetitionId || "";
      competitionView = "saved";
      route = "competition";
      renderCompetition();
      return;
    }

    if (action === "competition-toggle-details") {
      competitionDetailsOpen = !competitionDetailsOpen;
      renderCompetition();
      return;
    }

    if (action === "competition-setup") {
      competitionView = "setup";
      route = "competition";
      renderCompetition();
      return;
    }

    if (action === "competition-online") {
      competitionView = "online";
      route = "competition";
      renderCompetition();
      return;
    }

    if (action === "competition-offline") {
      openFreshOfflineCompetition();
      return;
    }

    if (action === "competition-online-host") {
      stopCompetitionPolling();
      window.alert("Online competition has been removed from this version. Use same-level or different-level competition on this device.");
      return;
    }

    if (action === "competition-online-join") {
      stopCompetitionPolling();
      window.alert("Online competition has been removed from this version. Use same-level or different-level competition on this device.");
      renderCompetition();
      return;
    }

    if (action === "choose-competition-level") {
      const levelId = Number(control.dataset.level);
      selectedCompetitionLevel = Number.isFinite(levelId) ? levelId : 1;
      renderCompetition();
      return;
    }

    if (action === "choose-competition-source") {
      const source = control.dataset.source || "both";
      selectedCompetitionSource = COMPETITION_MATERIALS.includes(source) ? source : "both";
      if (competitionUsesKgmSchool(selectedCompetitionSource)) {
        ensureLectiveDataForCompetition(() => {});
        return;
      }
      renderCompetition();
      return;
    }

    if (action === "choose-competition-level-mode") {
      selectedCompetitionLevelMode = control.dataset.mode === "different" ? "different" : "same";
      renderCompetition();
      return;
    }

    if (action === "choose-competition-school-level") {
      selectedCompetitionSchoolLevel = normalizeCompetitionSchoolLevel(control.dataset.schoolLevel || selectedCompetitionSchoolLevel);
      selectedCompetitionLevel = bibleLevelForSchoolLevel(selectedCompetitionSchoolLevel);
      selectedCompetitionLectiveLevel = selectedCompetitionSchoolLevel;
      renderCompetition();
      return;
    }

    if (action === "choose-competition-lective-scope") {
      selectedCompetitionLectiveScope = control.dataset.scope === ELECTIVE_COMPETITION_SCOPE_CLASS ? ELECTIVE_COMPETITION_SCOPE_CLASS : ELECTIVE_COMPETITION_SCOPE_SUBJECT;
      renderCompetition();
      return;
    }

    if (action === "start-competition-host") {
      startCompetitionHost();
      renderCompetition();
      return;
    }

    if (action === "new-online-competition") {
      startNewOnlineCompetition();
      return;
    }

    if (action === "new-offline-competition") {
      startNewOfflineCompetition();
      return;
    }

    if (action === "stop-competition-host") {
      stopCompetitionHost();
      renderCompetition();
      return;
    }

    if (action === "refresh-competition") {
      refreshCompetitionState();
      renderCompetition();
      return;
    }

    if (action === "open-wifi-settings") {
      openCompetitionWifiSettings();
      return;
    }

    if (action === "copy-competition-link") {
      copyCompetitionLink();
      return;
    }

    if (action === "start-online-competition") {
      ensureLectiveDataForCompetition(() => startOnlineCompetition());
      return;
    }

    if (action === "online-host-start-reading") {
      onlineHostSetPhase("reading");
      renderCompetition();
      return;
    }

    if (action === "online-host-begin-questions") {
      onlineHostSetPhase("quiz");
      renderCompetition();
      return;
    }

    if (action === "online-host-next-question") {
      onlineHostNextQuestion();
      renderCompetition();
      return;
    }

    if (action === "online-host-pause") {
      onlineHostPause();
      renderCompetition();
      return;
    }

    if (action === "online-host-resume") {
      onlineHostResume();
      renderCompetition();
      return;
    }

    if (action === "online-host-complete") {
      onlineHostComplete();
      renderCompetition();
      return;
    }

    if (action === "online-join-answer") {
      sendOnlineJoinAnswer(control.dataset.choice);
      return;
    }

    if (action === "add-offline-competitor") {
      addOfflineCompetitorFromPrompt();
      return;
    }

    if (action === "start-offline-competition") {
      ensureLectiveDataForCompetition(() => startOfflineCompetition());
      return;
    }

    if (action === "offline-begin-answers") {
      offlineBeginAnswers();
      return;
    }

    if (action === "offline-answer") {
      offlineAnswer(control.dataset.choice);
      return;
    }

    if (action === "offline-answer-next") {
      offlineAdvanceAfterAnswerFeedback();
      return;
    }

    if (action === "offline-next-competitor") {
      offlineStartNextCompetitor();
      return;
    }

    if (action === "offline-next-reading") {
      offlineNextReading();
      return;
    }

    if (action === "offline-complete-competition") {
      completeOfflineCompetition();
      return;
    }

    if (action === "repeat-offline-competition") {
      repeatOfflineCompetition();
      return;
    }

    if (action === "offline-add-more-questions") {
      addMoreOfflineCompetitionQuestions();
      return;
    }

    if (action === "offline-reset-competition") {
      resetOfflineCompetition();
      return;
    }

    if (action === "competition-delete-saved") {
      deleteSavedCompetition(control.dataset.savedCompetitionId || selectedSavedCompetitionId || "");
      return;
    }

    if (action === "home") {
      stopTimer();
      route = "home";
      render();
      return;
    }

    if (action === "marks") {
      stopTimer();
      route = "marks";
      render();
      return;
    }

    if (action === "users") {
      stopTimer();
      route = "users";
      render();
      return;
    }

    if (action === "timer") {
      const session = currentSession();
      if (session && !session.completed) {
        route = session.phase === "quiz" ? "quiz" : "reading";
      } else {
        route = "home";
      }
      render();
      return;
    }

    if (action === "course-units") {
      stopTimer();
      route = "units";
      render();
      return;
    }

    if (action === "start-level") {
      const levelId = Number(control.dataset.level);
      startLevel(Number.isFinite(levelId) ? levelId : 1, false);
      return;
    }

    if (action === "resume-level") {
      const session = currentSession();
      if (session && !session.completed) {
        route = session.phase === "quiz" ? "quiz" : "reading";
        render();
      }
      return;
    }

    if (action === "show-reading") {
      stopTimer();
      const session = currentSession();
      if (session && !session.completed) {
        route = session.phase === "reading" ? "reading" : "quiz";
        render();
      }
      return;
    }

    if (action === "begin-quiz") {
      const session = currentSession();
      if (!session || session.completed) return;
      session.phase = "quiz";
      saveStore();
      route = "quiz";
      render();
      return;
    }

    if (action === "answer") {
      selectAnswer(Number(control.dataset.answer));
      return;
    }

    if (action === "next-question") {
      nextQuestion();
      return;
    }

    if (action === "retry-level") {
      const requestedLevelId = Number(control.dataset.level);
      const levelId = Number.isFinite(requestedLevelId) ? requestedLevelId : 1;
      startLevel(levelId, true);
      return;
    }

    if (action === "revision") {
      stopTimer();
      route = "revision";
      render();
      return;
    }

    if (action === "print-certificate") {
      printCertificate().catch(() => window.print());
      return;
    }

    if (action === "save-certificate") {
      saveCertificate();
      return;
    }

    if (action === "print-competition-certificates") {
      printCompetitionCertificates();
      return;
    }

    if (action === "share-certificate") {
      shareCertificate();
      return;
    }

    if (action === "save-competition-certificate") {
      saveCompetitionCertificate(Number(control.dataset.certificateIndex || 0));
      return;
    }

    if (action === "share-competition-certificate") {
      shareCompetitionCertificate(Number(control.dataset.certificateIndex || 0));
      return;
    }

    if (action === "print-competition-certificate") {
      printCompetitionCertificate(Number(control.dataset.certificateIndex || 0));
      return;
    }

    if (action === "zoom-reading-in") {
      readingZoom = Math.min(1.8, Math.round((readingZoom + 0.15) * 100) / 100);
      renderReading();
      return;
    }

    if (action === "zoom-reading-out") {
      readingZoom = Math.max(0.85, Math.round((readingZoom - 0.15) * 100) / 100);
      renderReading();
      return;
    }

    if (action === "zoom-reading-reset") {
      readingZoom = 1;
      renderReading();
      return;
    }
  });

  document.addEventListener("keydown", (event) => {
    handleApplicationKeyboard(event);
  });

  document.addEventListener("change", (event) => {
    const timerInput = event.target.closest("[data-competition-timer]");
    if (timerInput) {
      captureVisibleCompetitionCoordinatorDrafts();
      setCompetitionTimerSeconds(timerInput.value);
      renderCompetition();
      return;
    }

    const lectiveLevel = event.target.closest("[data-lective-level]");
    if (lectiveLevel) {
      selectedLectiveLevel = normalizeLectiveLevel(lectiveLevel.value);
      selectedLectiveSubject = normalizeLectiveSubject(selectedLectiveSubject, selectedLectiveLevel);
      renderLectiveCourse();
      return;
    }

    const lectiveSubject = event.target.closest("[data-lective-subject]");
    if (lectiveSubject) {
      selectedLectiveSubject = normalizeLectiveSubject(lectiveSubject.value, selectedLectiveLevel);
      renderLectiveCourse();
      return;
    }

    const lectiveStudyLevel = event.target.closest("[data-lective-study-level]");
    if (lectiveStudyLevel) {
      selectedLectiveStudyLevel = normalizeLectiveLevel(lectiveStudyLevel.value);
      selectedLectiveStudySubject = normalizeLectiveSubject(selectedLectiveStudySubject, selectedLectiveStudyLevel);
      renderLectiveCourse();
      return;
    }

    const lectiveStudySubject = event.target.closest("[data-lective-study-subject]");
    if (lectiveStudySubject) {
      selectedLectiveStudySubject = normalizeLectiveSubject(lectiveStudySubject.value, selectedLectiveStudyLevel);
      renderLectiveCourse();
      return;
    }

    const competitionLectiveLevel = event.target.closest("[data-competition-lective-level]");
    if (competitionLectiveLevel) {
      captureVisibleCompetitionCoordinatorDrafts();
      selectedCompetitionLectiveLevel = normalizeLectiveLevel(competitionLectiveLevel.value);
      selectedCompetitionLectiveSubject = normalizeLectiveSubject(selectedCompetitionLectiveSubject, selectedCompetitionLectiveLevel);
      renderCompetition();
      return;
    }

    const competitionLectiveSubject = event.target.closest("[data-competition-lective-subject]");
    if (competitionLectiveSubject) {
      captureVisibleCompetitionCoordinatorDrafts();
      selectedCompetitionLectiveSubject = normalizeLectiveSubject(competitionLectiveSubject.value, selectedCompetitionLectiveLevel);
      renderCompetition();
      return;
    }

    const competitionSchoolLevel = event.target.closest("[data-competition-school-level]");
    if (competitionSchoolLevel) {
      captureVisibleCompetitionCoordinatorDrafts();
      selectedCompetitionSchoolLevel = normalizeCompetitionSchoolLevel(competitionSchoolLevel.value);
      selectedCompetitionLevel = bibleLevelForSchoolLevel(selectedCompetitionSchoolLevel);
      selectedCompetitionLectiveLevel = selectedCompetitionSchoolLevel;
      selectedCompetitionLectiveSubject = normalizeLectiveSubject(selectedCompetitionLectiveSubject, selectedCompetitionLectiveLevel);
      renderCompetition();
      return;
    }

    const competitionQuestionTargetInput = event.target.closest("[data-competition-question-target]");
    if (competitionQuestionTargetInput) {
      captureVisibleCompetitionCoordinatorDrafts();
      selectedCompetitionQuestionTarget = clampNumber(competitionQuestionTargetInput.value, 1, COMPETITION_MAX_QUESTION_TARGET, 1);
      selectedCompetitionQuestionsPerRound = Math.min(competitionQuestionsPerRound(), competitionQuestionTarget());
      renderCompetition();
      return;
    }

    const competitionQuestionsPerRoundInput = event.target.closest("[data-competition-questions-per-round]");
    if (competitionQuestionsPerRoundInput) {
      captureVisibleCompetitionCoordinatorDrafts();
      selectedCompetitionQuestionsPerRound = clampNumber(competitionQuestionsPerRoundInput.value, 1, competitionQuestionTarget(), 1);
      renderCompetition();
      return;
    }

    const fileInput = event.target.closest("[data-lective-file]");
    if (fileInput && fileInput.files && fileInput.files.length) {
      importLectiveDocument(fileInput.files[0]);
      return;
    }

    const backupInput = event.target.closest("[data-backup-file]");
    if (backupInput && backupInput.files && backupInput.files.length) {
      importBackup(backupInput.files[0]);
    }
  });

  document.addEventListener("submit", (event) => {
    const knownForm = event.target.closest("[data-search-register-form], [data-search-form], [data-elective-register-form], [data-elective-combination-form], [data-school-customize-auth-form], [data-school-customize-form], [data-lective-study-form], [data-lective-form], [data-offline-competitor-form], [data-online-join-form], [data-competition-unlock-form], [data-renewal-form]");
    if (knownForm) showActionLoader(knownForm);

    const searchRegisterForm = event.target.closest("[data-search-register-form]");
    if (searchRegisterForm) {
      event.preventDefault();
      registerSearchProfile(new FormData(searchRegisterForm));
      return;
    }

    const searchForm = event.target.closest("[data-search-form]");
    if (searchForm) {
      event.preventDefault();
      performSearch(new FormData(searchForm));
      return;
    }

    const electiveRegisterForm = event.target.closest("[data-elective-register-form]");
    if (electiveRegisterForm) {
      event.preventDefault();
      signInElectiveClass(new FormData(electiveRegisterForm));
      return;
    }

    const electiveCombinationForm = event.target.closest("[data-elective-combination-form]");
    if (electiveCombinationForm) {
      event.preventDefault();
      saveElectiveCombination(new FormData(electiveCombinationForm));
      return;
    }

    const schoolCustomizeAuthForm = event.target.closest("[data-school-customize-auth-form]");
    if (schoolCustomizeAuthForm) {
      event.preventDefault();
      authorizeSchoolCustomization(new FormData(schoolCustomizeAuthForm));
      return;
    }

    const schoolCustomizeForm = event.target.closest("[data-school-customize-form]");
    if (schoolCustomizeForm) {
      event.preventDefault();
      saveSchoolCustomization(new FormData(schoolCustomizeForm));
      return;
    }

    const lectiveStudyForm = event.target.closest("[data-lective-study-form]");
    if (lectiveStudyForm) {
      event.preventDefault();
      startLectiveStudy(new FormData(lectiveStudyForm));
      return;
    }

    const lectiveForm = event.target.closest("[data-lective-form]");
    if (lectiveForm) {
      event.preventDefault();
      saveLectiveStory(new FormData(lectiveForm));
      return;
    }

    const offlineCompetitorForm = event.target.closest("[data-offline-competitor-form]");
    if (offlineCompetitorForm) {
      event.preventDefault();
      addOfflineCompetitor(new FormData(offlineCompetitorForm));
      return;
    }

    const onlineJoinForm = event.target.closest("[data-online-join-form]");
    if (onlineJoinForm) {
      event.preventDefault();
      joinOnlineCompetition(new FormData(onlineJoinForm));
      return;
    }

    const competitionUnlockForm = event.target.closest("[data-competition-unlock-form]");
    if (competitionUnlockForm) {
      event.preventDefault();
      unlockCompetitionPage(new FormData(competitionUnlockForm));
      return;
    }

    const form = event.target.closest("[data-renewal-form]");
    if (!form) return;
    event.preventDefault();
    activateRenewalCode();
  });

  document.addEventListener("submit", (event) => {
    const form = event.target.closest("[data-login-form]");
    if (!form) return;
    showActionLoader(form);
    event.preventDefault();
    const formData = new FormData(form);
    const name = String(formData.get("name") || "").trim() || "Guest Learner";
    const password = String(formData.get("password") || "").trim();
    if (!String(formData.get("name") || "").trim() || !password || selectedLoginLevel === null || selectedLoginLevel === undefined) {
      loginError = "Enter your name, password, and choose Little Angels or a star level.";
      renderLogin();
      return;
    }
    refreshServiceState();
    if (isServiceExpired()) {
      loginError = "Service expired. Press Renew, pay, then enter the 20 digit token from Admin.";
      renderLogin();
      return;
    }
    signIn(name, password, selectedLoginLevel);
  });

  function showActionLoader(control) {
    if (!control || control.closest("[disabled], [aria-disabled='true']")) return;
    let loader = document.querySelector("[data-action-loader]");
    if (!loader) {
      loader = document.createElement("div");
      loader.className = "action-loader";
      loader.setAttribute("data-action-loader", "");
      loader.setAttribute("aria-live", "polite");
      loader.innerHTML = `
        <div class="action-loader-ring" aria-hidden="true"></div>
        <span data-action-loader-label>Loading...</span>
      `;
      document.body.appendChild(loader);
    }
    const text = loader.querySelector("[data-action-loader-label]");
    if (text) text.textContent = "Loading...";
    actionLoaderStartedAt = Date.now();
    loader.classList.add("is-visible");
    if (actionLoaderTimer) window.clearTimeout(actionLoaderTimer);
    actionLoaderTimer = window.setTimeout(hideActionLoader, 900);
  }

  function hideActionLoader() {
    const loader = document.querySelector("[data-action-loader]");
    if (!loader) return;
    const elapsed = Date.now() - actionLoaderStartedAt;
    if (elapsed < 260) {
      actionLoaderTimer = window.setTimeout(hideActionLoader, 260 - elapsed);
      return;
    }
    loader.classList.remove("is-visible");
  }

  function render() {
    window.requestAnimationFrame(() => window.requestAnimationFrame(hideActionLoader));
    refreshServiceState();
    const expiredCompetitionAllowed = route === "competition" && canUseCompetitionWhileExpired();
    const expiredSearchAllowed = route === "search";
    const expiredSavedCertificatesAllowed = route === "saved-certificates";
    const expiredCompanyAllowed = route === "company-profile";
    if (!store.activeUser && !["login", "competition", "lective", "search", "saved-certificates", "company-profile"].includes(route)) route = "login";
    if (isServiceExpired() && route !== "login" && !expiredCompetitionAllowed && !expiredSearchAllowed && !expiredSavedCertificatesAllowed && !expiredCompanyAllowed) {
      stopTimer();
      store.activeUser = null;
      saveStore();
      loginError = "Service time has ended. Renew service to continue. Competition stays open only for 10 or more competitors.";
      route = "login";
    }
    if (route !== "competition") lockCompetitionAccess();
    if (route !== "quiz") stopTimer();
    if (route !== "lective-study") stopLectiveTimer();
    if (route !== "competition") stopCompetitionCountdownTicker();
    trackRoute();

    if (route === "login") renderLogin();
    if (route === "home") renderHome();
    if (route === "reading") renderReading();
    if (route === "quiz") renderQuiz();
    if (route === "certificate") renderCertificate();
    if (route === "marks") renderMarks();
    if (route === "users") renderUsers();
    if (route === "units") renderUnits();
    if (route === "competition") renderCompetition();
    if (route === "search") renderSearch();
    if (route === "saved-certificates") renderSavedCertificates();
    if (route === "company-profile") renderCompanyProfile();
    if (route === "lective") renderLectiveCourse();
    if (route === "lective-study") renderLectiveStudy();
    if (route === "revision") renderRevision();
    if (route === "settings") renderSettings();
    if (route === "help") renderHelp();
    if (route === "about") renderAbout();
    revealAppWhenBootReady();
  }

  function revealAppWhenBootReady() {
    if (document.body.classList.contains("app-ready") || appReadyScheduled) return;
    appReadyScheduled = true;
    const elapsed = Date.now() - appBootStartedAt;
    const delay = Math.max(0, BOOT_SCREEN_MIN_MS - elapsed);
    window.setTimeout(() => {
      if (window.KGM_BOOT_FAILSAFE) {
        window.clearTimeout(window.KGM_BOOT_FAILSAFE);
        window.KGM_BOOT_FAILSAFE = null;
      }
      document.body.classList.add("app-ready");
      appReadyScheduled = false;
    }, delay);
  }

  function renderCompanyProfile() {
    const profile = COMPANY_PROFILE || {};
    const members = Array.isArray(profile.members) ? profile.members : [];
    const funders = Array.isArray(profile.funders) ? profile.funders.filter((member) => member && member.name) : [];
    const funderSlots = Array.from({ length: 20 }, (_, index) => funders[index] || null);
    const memberCards = members.map((member) => `
      <article class="company-member-card">
        ${member.photo ? `<img src="${escapeHtml(member.photo)}" alt="${escapeHtml(member.name)}">` : `<div class="company-member-initials">${escapeHtml(initials(member.name || "KGM"))}</div>`}
        <div>
          <strong>${escapeHtml(member.name || "Board Member")}</strong>
          <span>${escapeHtml(member.role || "Know God More Leadership")}</span>
        </div>
      </article>
    `).join("");
    const funderCards = funderSlots.map((funder, index) => `
      <article class="company-funder-box ${funder ? "is-filled" : ""}">
        <span>${index + 1}</span>
        <strong>${escapeHtml(funder ? funder.name : "Funder Slot")}</strong>
        <em>${escapeHtml(funder ? funder.role : "Coming soon")}</em>
      </article>
    `).join("");
    app.innerHTML = `
      <section class="screen company-profile-screen">
        <div class="company-profile-panel">
          <div class="section-head">
            <div>
              <p class="kicker">${escapeHtml(profile.subtitle || "Board Members and Funders")}</p>
              <h2>${escapeHtml(profile.title || "Know God More Company Profile")}</h2>
              ${profile.date ? `<span class="small-note">${escapeHtml(profile.date)}</span>` : ""}
            </div>
            <div class="competition-header-actions">
              ${backButtonHtml()}
              <button class="ghost-button" data-action="logout">Login</button>
            </div>
          </div>
          <div class="company-board-grid">
            ${memberCards || `<p class="empty-state">Company profile details are ready for loading.</p>`}
          </div>
          <div class="company-funders-title">
            <span></span>
            <h3>FUNDERS</h3>
            <span></span>
          </div>
          <div class="company-funder-grid">
            ${funderCards}
          </div>
        </div>
      </section>
    `;
    revealAppWhenBootReady();
  }

  function renderLectiveDataLoading(message = "Loading KGM School questions...") {
    revealAppWhenBootReady();
    app.innerHTML = `
      <section class="screen competition-screen">
        <div class="competition-host-panel">
          <div class="section-head">
            <div>
              <p class="kicker">Offline Course Database</p>
              <h2>${escapeHtml(schoolDisplayName())}</h2>
            </div>
            <div class="competition-header-actions">
              ${backButtonHtml()}
              <button class="ghost-button" data-action="${store.activeUser ? "home" : "logout"}">${store.activeUser ? "Home" : "Login"}</button>
            </div>
          </div>
          <div class="competition-card lective-loading-card">
            <div class="action-loader-ring" aria-hidden="true"></div>
            <h3>${escapeHtml(message)}</h3>
            <p class="small-note">The big school-question database is loaded only when needed so the app opens quickly on slower tablets.</p>
          </div>
        </div>
      </section>
    `;
  }

  function refreshDefaultLectiveStories() {
    defaultLectiveStories = Array.isArray(window.KNOW_GOD_MORE_DEFAULT_LECTIVE_STORIES) ? window.KNOW_GOD_MORE_DEFAULT_LECTIVE_STORIES : [];
    defaultLectiveStoryIds = new Set(defaultLectiveStories.map((story) => String(story && story.id || "")).filter(Boolean));
  }

  function loadScriptOnce(src) {
    return new Promise((resolve, reject) => {
      const key = src.split("?")[0];
      const existing = Array.from(document.scripts).find((script) => script.dataset && script.dataset.kgmLazySrc === key);
      if (existing) {
        if (existing.dataset.loaded === "true") {
          resolve();
          return;
        }
        existing.addEventListener("load", () => resolve(), { once: true });
        existing.addEventListener("error", () => reject(new Error(`Could not load ${key}`)), { once: true });
        return;
      }
      const script = document.createElement("script");
      script.src = src;
      script.dataset.kgmLazySrc = key;
      script.onload = () => {
        script.dataset.loaded = "true";
        resolve();
      };
      script.onerror = () => reject(new Error(`Could not load ${key}`));
      document.body.appendChild(script);
    });
  }

  function lectiveSubjectKey(level, subject) {
    return normalizeLectiveFolderKey(lectiveFolderKey(level, subject));
  }

  function defaultLectiveSubjectMeta(level, subject) {
    const key = lectiveSubjectKey(level, subject);
    return DEFAULT_LECTIVE_SUBJECT_INDEX[key] || null;
  }

  function defaultLectiveSubjectAvailable(level, subject) {
    return Boolean(defaultLectiveSubjectMeta(level, subject));
  }

  function defaultLectiveSubjectIsLoaded(level, subject) {
    const key = lectiveSubjectKey(level, subject);
    return defaultLectiveSubjectLoaded.has(key) || defaultLectiveFolderStories(key).length > 0;
  }

  function lectiveSubjectIsAvailable(level, subject) {
    return lectiveStoryBank(level, subject).length > 0 || defaultLectiveSubjectAvailable(level, subject);
  }

  function defaultLectiveSubjectNeedsLoading(level, subject) {
    const normalizedLevel = normalizeLectiveLevel(level);
    const normalizedSubject = normalizeLectiveSubject(subject, normalizedLevel);
    return defaultLectiveSubjectAvailable(normalizedLevel, normalizedSubject)
      && !defaultLectiveSubjectIsLoaded(normalizedLevel, normalizedSubject)
      && !lectiveFolderStories(lectiveSubjectKey(normalizedLevel, normalizedSubject)).length;
  }

  function defaultLectiveSubjectsNeedLoading(level, subjects) {
    const normalizedLevel = normalizeLectiveLevel(level);
    return (Array.isArray(subjects) ? subjects : []).some((subject) => defaultLectiveSubjectNeedsLoading(normalizedLevel, subject));
  }

  function lectiveQuestionCount(level, subject) {
    const list = lectiveQuestionList(level, subject);
    if (list.length) return list.length;
    const meta = defaultLectiveSubjectMeta(level, subject);
    return Number(meta && meta.count || 0);
  }

  function defaultLectiveFolderStories(key) {
    const safeKey = normalizeLectiveFolderKey(key);
    return defaultLectiveStories.filter((story) => {
      if (!story || typeof story !== "object") return false;
      const storyLevel = normalizeLectiveLevel(story.level || DEFAULT_LECTIVE_LEVEL);
      const storySubject = normalizeLectiveSubject(story.subject || DEFAULT_LECTIVE_SUBJECT, storyLevel);
      return lectiveSubjectKey(storyLevel, storySubject) === safeKey;
    });
  }

  function appendDefaultLectiveSubject(level, subject, rows) {
    const normalizedLevel = normalizeLectiveLevel(level);
    const normalizedSubject = normalizeLectiveSubject(subject, normalizedLevel);
    const safeSubject = normalizedSubject.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-+|-+$/g, "") || "subject";
    const list = Array.isArray(rows) ? rows : [];
    window.KNOW_GOD_MORE_DEFAULT_LECTIVE_STORIES = Array.isArray(window.KNOW_GOD_MORE_DEFAULT_LECTIVE_STORIES)
      ? window.KNOW_GOD_MORE_DEFAULT_LECTIVE_STORIES
      : [];
    list.forEach((row, index) => {
      if (!Array.isArray(row) || row.length < 5) return;
      const questionText = cleanText(row[0] || "");
      if (!questionText) return;
      const storyId = `lective-${normalizedLevel.toLowerCase()}-${safeSubject}-q${String(index + 1).padStart(4, "0")}`;
      if (defaultLectiveStoryIds.has(storyId)) return;
      const correctIndex = clampNumber(row[4], 0, 2);
      window.KNOW_GOD_MORE_DEFAULT_LECTIVE_STORIES.push({
        id: storyId,
        course: "KGM School",
        unit: `${normalizedLevel} ${normalizedSubject}`,
        level: normalizedLevel,
        subject: normalizedSubject,
        title: `${normalizedLevel} ${normalizedSubject} - Question ${index + 1}`,
        reference: `${normalizedLevel} ${normalizedSubject}`,
        content: "",
        questions: [{
          id: `${storyId}-q1`,
          question: questionText,
          answers: [0, 1, 2].map((answerIndex) => ({
            text: cleanText(row[answerIndex + 1] || ""),
            correct: answerIndex === correctIndex
          }))
        }]
      });
      defaultLectiveStoryIds.add(storyId);
    });
    refreshDefaultLectiveStories();
    defaultLectiveSubjectLoaded.add(lectiveSubjectKey(normalizedLevel, normalizedSubject));
    invalidateLectiveCaches();
  }

  window.KGM_ADD_LECTIVE_SUBJECT = appendDefaultLectiveSubject;

  function ensureDefaultLectiveSubjectLoaded(level, subject) {
    const normalizedLevel = normalizeLectiveLevel(level);
    const normalizedSubject = normalizeLectiveSubject(subject, normalizedLevel);
    const key = lectiveSubjectKey(normalizedLevel, normalizedSubject);
    if (defaultLectiveSubjectIsLoaded(normalizedLevel, normalizedSubject) || lectiveFolderStories(key).length) {
      defaultLectiveSubjectLoaded.add(key);
      return Promise.resolve(store);
    }
    const meta = defaultLectiveSubjectMeta(normalizedLevel, normalizedSubject);
    if (!meta || !meta.src) return Promise.resolve(store);
    if (defaultLectiveSubjectLoading.has(key)) return defaultLectiveSubjectLoading.get(key);
    const loading = loadScriptOnce(meta.src)
      .then(() => {
        defaultLectiveSubjectLoaded.add(key);
        refreshDefaultLectiveStories();
        invalidateLectiveCaches();
        return store;
      })
      .catch((error) => {
        competitionLectiveMessage = `${electiveClassLabel(normalizedLevel)} ${normalizedSubject} could not load on this device. Restart the app and try again.`;
        saveCrashLog(`${new Date().toISOString()} Elective subject load failed (${key}): ${error && error.message ? error.message : error}`);
        throw error;
      })
      .finally(() => {
        defaultLectiveSubjectLoading.delete(key);
      });
    defaultLectiveSubjectLoading.set(key, loading);
    return loading;
  }

  function ensureDefaultLectiveSubjectsLoaded(level, subjects) {
    const normalizedLevel = normalizeLectiveLevel(level);
    const list = uniqueStrings(Array.isArray(subjects) ? subjects : [])
      .map((subject) => normalizeLectiveSubject(subject, normalizedLevel))
      .filter((subject) => lectiveSubjectIsAvailable(normalizedLevel, subject));
    return list.reduce((chain, subject) => chain.then(() => ensureDefaultLectiveSubjectLoaded(normalizedLevel, subject)), Promise.resolve(store));
  }

  function electiveSubjectsToLoad(level, user = activeUser()) {
    const normalizedLevel = normalizeLectiveLevel(level);
    return electiveSubjectsForUser(normalizedLevel, user).filter((subject) => lectiveSubjectIsAvailable(normalizedLevel, subject));
  }

  function competitionLectiveLevelsToLoad(draft = null) {
    if (!competitionUsesKgmSchool(selectedCompetitionSource)) return [];
    if (selectedCompetitionLevelMode === "different" && draft && Array.isArray(draft.players) && draft.players.length) {
      return uniqueStrings(draft.players.map((player) => competitionPlayerSchoolLevel(player, selectedCompetitionSchoolLevel)));
    }
    return [normalizeCompetitionSchoolLevel(selectedCompetitionSchoolLevel)];
  }

  function ensureCompetitionLectiveSubjectsLoaded(levels, callback) {
    const normalizedLevels = uniqueStrings((Array.isArray(levels) && levels.length ? levels : [selectedCompetitionSchoolLevel]).map((level) => normalizeCompetitionSchoolLevel(level)));
    route = "competition";
    renderLectiveDataLoading("Loading selected KGM School questions...");
    const jobs = normalizedLevels.map((level) => () => ensureDefaultLectiveSubjectsLoaded(level, competitionAvailableSubjectsForLevel(level)));
    jobs.reduce((chain, job) => chain.then(job), Promise.resolve(store))
      .then(() => {
        if (route === "competition") {
          callback();
          renderCompetition();
        }
      })
      .catch(() => {
        if (route === "competition") renderCompetition();
      });
  }

  function ensureDefaultLectiveDataLoaded() {
    refreshDefaultLectiveStories();
    if (defaultLectiveDataLoaded) {
      store = seedDefaultLectiveStories(store);
      return Promise.resolve(store);
    }
    if (defaultLectiveDataLoading) return defaultLectiveDataLoading;
    defaultLectiveDataLoading = LECTIVE_DATA_SCRIPT_URLS
      .reduce((chain, src) => chain.then(() => loadScriptOnce(src)), Promise.resolve())
      .then(() => {
        refreshDefaultLectiveStories();
        store = seedDefaultLectiveStories(store);
        defaultLectiveDataLoaded = true;
        invalidateLectiveCaches();
        saveStore();
        return store;
      })
      .catch((error) => {
        competitionLectiveMessage = "The KGM School database could not load on this device. Restart the app and try again.";
        saveCrashLog(`${new Date().toISOString()} Elective data load failed: ${error && error.message ? error.message : error}`);
        throw error;
      })
      .finally(() => {
        defaultLectiveDataLoading = null;
      });
    return defaultLectiveDataLoading;
  }

  function kgmSchoolSearchSubjectPairs() {
    return Object.keys(DEFAULT_LECTIVE_SUBJECT_INDEX || {}).map((key) => {
      const parts = String(key || "").split("|");
      const level = normalizeLectiveLevel(parts.shift() || DEFAULT_LECTIVE_LEVEL);
      const subject = normalizeLectiveSubject(parts.join("|") || DEFAULT_LECTIVE_SUBJECT, level);
      return { level, subject };
    }).filter((item, index, list) => item.level && item.subject && list.findIndex((other) => other.level === item.level && other.subject === item.subject) === index);
  }

  function kgmSchoolSearchNeedsLoading() {
    if (schoolSearchDataLoaded) return false;
    return kgmSchoolSearchSubjectPairs().some(({ level, subject }) => defaultLectiveSubjectNeedsLoading(level, subject));
  }

  function ensureAllKgmSchoolSearchDataLoaded() {
    refreshDefaultLectiveStories();
    if (schoolSearchDataLoaded) return Promise.resolve(store);
    if (schoolSearchDataLoading) return schoolSearchDataLoading;
    const subjects = kgmSchoolSearchSubjectPairs();
    let failed = 0;
    schoolSearchDataLoading = ensureDefaultLectiveDataLoaded()
      .then(() => subjects.reduce((chain, { level, subject }) => chain.then(() => ensureDefaultLectiveSubjectLoaded(level, subject).catch((error) => {
        failed += 1;
        saveCrashLog(`${new Date().toISOString()} KGM School search subject load failed (${level}|${subject}): ${error && error.message ? error.message : error}`);
        return store;
      })), Promise.resolve(store)))
      .then(() => {
        refreshDefaultLectiveStories();
        invalidateLectiveCaches();
        schoolSearchDataLoaded = true;
        schoolSearchLoadFailures = failed;
        return store;
      })
      .finally(() => {
        schoolSearchDataLoading = null;
      });
    return schoolSearchDataLoading;
  }

  function openLectiveCourse(view = "classes") {
    stopTimer();
    route = "lective";
    lectiveView = view;
    render();
    if (view === "others" || view === "others-add") {
      ensureDefaultLectiveDataLoaded()
        .then(() => {
          if (route === "lective" && (lectiveView === "others" || lectiveView === "others-add")) renderLectiveCourse();
        })
        .catch(() => {
          if (route === "lective") renderLectiveCourse();
        });
    }
  }

  function ensureLectiveDataForCompetition(callback) {
    if (!competitionUsesKgmSchool(selectedCompetitionSource)) {
      callback();
      return;
    }
    ensureCompetitionLectiveSubjectsLoaded(competitionLectiveLevelsToLoad(ensureOfflineCompetitionDraft()), callback);
  }

  function renderLogin() {
    const competitionLocked = isServiceExpired() && !canUseCompetitionWhileExpired();
    app.innerHTML = `
      <section class="screen login-screen">
        <div class="login-template">
          <img class="login-template-image" src="assets/login.jpg" alt="Know God More login screen">
          <form class="login-template-form" data-login-form>
            ${loginError ? `<div class="login-error login-template-error" role="alert">${escapeHtml(loginError)}</div>` : ""}
            <button class="login-lective-button" type="button" data-action="lective-course" style="${serviceToneStyle()}"><span class="login-button-label">${schoolDisplayNameButtonHtml()}</span></button>
            <div class="login-service-badge" data-service-remaining style="${serviceToneStyle()}">${escapeHtml(serviceRemainingText())}</div>
            <button class="login-competition-button" type="button" data-action="competition" style="${serviceToneStyle()}" ${competitionLocked ? "disabled" : ""}><span class="login-button-label">Competition</span></button>
            ${loginUpdateStatusHtml()}
            ${loginTopReaders()}
            <button class="login-baby-course-button ${selectedLoginLevel === BABY_COURSE.id ? "is-selected" : ""}" type="button" data-action="choose-login-level" data-level="0"><span class="login-button-label">Little Angels</span></button>
            <button class="login-search-button" type="button" data-action="search"><span class="login-button-label">Search</span></button>
            <button class="login-renew-button" type="button" data-action="renew-service" style="${renewBreathStyle()}" ${renewalBusy ? "disabled" : ""}><span class="login-button-label">${renewalBusy ? "Checking..." : "Renew"}</span></button>
            <div class="login-star-controls" role="group" aria-label="Choose level">
              ${LEVELS.map((level) => `
                <button class="login-star-hit login-star-${level.id} ${selectedLoginLevel > 0 && level.id <= selectedLoginLevel ? "is-lit" : ""} ${level.id === selectedLoginLevel ? "is-selected" : ""}" type="button" data-action="choose-login-level" data-level="${level.id}" aria-label="${level.title}, ${levelStoryCount(level.id)} stories" aria-pressed="${level.id === selectedLoginLevel}"></button>
              `).join("")}
            </div>
            <label class="sr-only" for="name">Type any Name</label>
            <input class="login-template-input login-name-input" id="name" name="name" autocomplete="username" placeholder="Type any Name" required>
            <label class="sr-only" for="password">Type a Password of your choice</label>
            <input class="login-template-input login-password-input" id="password" name="password" type="password" autocomplete="current-password" placeholder="Type a Password of your choice" required>
            <button class="login-mute-button ${store.audioMuted ? "is-muted" : ""}" type="button" data-action="toggle-audio-muted" aria-label="${store.audioMuted ? "Unmute app sounds" : "Mute app sounds"}" aria-pressed="${store.audioMuted ? "true" : "false"}"><img src="assets/speaker.png" alt="" aria-hidden="true"></button>
            <button class="login-footnote login-profile-link" type="button" data-action="company-profile">
              Contact us at: arthertindiwensi@gmail.com. Or WhatsApp +256782225970
            </button>
            <span class="login-template-submit-reserve login-template-version">v${escapeHtml(APP_VERSION)}</span>
            <button class="login-template-submit" type="submit" aria-label="Log In" ${isServiceExpired() ? "disabled" : ""}></button>
            <button class="login-template-update" type="button" data-action="phone-update" aria-label="Update Know God More" ${updateBusy ? "disabled" : ""}><span class="login-button-label">${updateBusy ? "WAIT" : "UPDATE"}</span></button>
          </form>
        </div>
      </section>
    `;
    return;
    const leaders = leaderBoard().slice(0, 3);
    app.innerHTML = `
      <section class="screen login-screen">
        <div class="login-layout">
          <aside class="login-art" aria-label="Know God More artwork">
            <div class="login-art-copy">
              <div class="brand-lockup">
                <img src="assets/icon.png" alt="Know God More icon">
                <div>
                  <p class="kicker">Offline Bible Course</p>
                  <h1>Know God More</h1>
                </div>
              </div>
            </div>
          </aside>

          <section class="login-panel">
            <div class="section-head login-leaders-head">
              <h2>Top Readers</h2>
              <span class="kicker">${storyBank.length.toLocaleString()} stories</span>
            </div>
            <div class="leader-strip login-leaders">
              ${leaders.map((leader, index) => leaderCard(leader, index)).join("")}
            </div>

            <div>
              <div class="section-head">
                <h2>Bible Knowledge Level</h2>
              </div>
              <p class="small-note">Choose the star level you want to begin with.</p>
              <div class="level-picker" role="group" aria-label="Choose level">
                ${LEVELS.map((level) => `
                  <button class="level-choice ${level.id === selectedLoginLevel ? "is-selected" : ""}" type="button" data-action="choose-login-level" data-level="${level.id}" aria-pressed="${level.id === selectedLoginLevel}">
                    <span class="star">${"★".repeat(level.id)}</span>
                    <strong>${level.title}</strong>
                    <span>${levelStoryCountLabel(level.id)}</span>
                  </button>
                `).join("")}
              </div>
            </div>

            <form class="form-grid" data-login-form>
              ${loginError ? `<div class="login-error" role="alert">${escapeHtml(loginError)}</div>` : ""}
              <div class="input-wrap">
                <label for="name">Type any Name</label>
                <input id="name" name="name" autocomplete="username" placeholder="Type any Name" required>
              </div>
              <div class="input-wrap">
                <label for="password">Type a Password of your choice</label>
                <input id="password" name="password" type="password" autocomplete="current-password" placeholder="Type a Password of your choice" required>
              </div>
              <button class="primary-button" type="submit">Log In</button>
            </form>
          </section>
        </div>
      </section>
    `;
  }

  function renderSearch() {
    trackRoute();
    const profile = normalizeSearchProfile(store.searchProfile);
    if (!profile) {
      app.innerHTML = searchShell(`
        <form class="competition-card form-grid search-register-card" data-search-register-form>
          <div class="section-head">
            <div>
              <p class="kicker">Register To Search</p>
              <h3>Search Know God More</h3>
            </div>
            <button class="ghost-button" type="button" data-action="search-back-login">Login</button>
          </div>
          <label class="input-wrap">
            <span>Name</span>
            <input name="name" autocomplete="username" placeholder="Your name" required>
          </label>
          <label class="input-wrap">
            <span>Password</span>
            <input name="password" type="password" autocomplete="current-password" placeholder="Your password" required>
          </label>
          <button class="primary-button" type="submit">Continue To Search</button>
          ${searchMessage ? `<p class="competition-full-note">${escapeHtml(searchMessage)}</p>` : ""}
        </form>
      `);
      return;
    }

    if (pendingSchoolSearch) {
      app.innerHTML = searchShell(`
        <div class="competition-card form-grid search-confirm-card">
          <div class="section-head">
            <div>
              <p class="kicker">KGM School Search</p>
              <h3>Confirm Giant Library Search</h3>
            </div>
            <button class="ghost-button" type="button" data-action="cancel-school-search">Cancel</button>
          </div>
          <p class="competition-full-note">It is going to search from a giant library of over 60,000 Questions so it may take minutes loading, if your device is strong enough, confirm</p>
          <div class="search-confirm-query">
            <span>Search words</span>
            <strong>${escapeHtml(pendingSchoolSearch.query)}</strong>
          </div>
          <div class="search-actions">
            <button class="primary-button" type="button" data-action="confirm-school-search">I Confirm</button>
            <button class="ghost-button" type="button" data-action="cancel-school-search">Cancel</button>
          </div>
        </div>
      `);
      return;
    }

    const query = cleanText(store.searchLastQuery || "");
    const scope = normalizeSearchScope(store.searchScope || "bible");
    const results = query ? (searchResults.length ? searchResults : searchContent(query, scope)) : [];
    app.innerHTML = searchShell(`
      <form class="competition-card form-grid search-form-card" data-search-form>
        <div class="section-head">
          <div>
            <p class="kicker">Searching as ${escapeHtml(profile.name)}</p>
            <h3>What Are You Searching About?</h3>
          </div>
          <button class="ghost-button" type="button" data-action="search-reset-profile">Change User</button>
        </div>
        <label class="input-wrap search-query-wrap">
          <span>Search words</span>
          <textarea name="query" rows="4" spellcheck="true" autocapitalize="sentences" autocomplete="off" placeholder="Example: faith, Noah, forgiveness, prayer">${escapeHtml(query)}</textarea>
        </label>
        <div class="search-scope-options" role="radiogroup" aria-label="Choose what to search">
          <label class="search-scope-option ${scope === "bible" ? "is-selected" : ""}">
            <input type="radio" name="scope" value="bible" ${scope === "bible" ? "checked" : ""}>
            <span>Search The Bible</span>
          </label>
          <label class="search-scope-option ${scope === "school" ? "is-selected" : ""}">
            <input type="radio" name="scope" value="school" ${scope === "school" ? "checked" : ""}>
            <span>Search KGM School</span>
          </label>
        </div>
        <div class="search-actions">
          <button class="primary-button" type="submit">Search</button>
          <button class="ghost-button" type="button" data-action="search-clear">Clear</button>
          <button class="ghost-button" type="button" data-action="search-back-login">Login</button>
        </div>
        ${searchMessage ? `<p class="competition-full-note">${escapeHtml(searchMessage)}</p>` : ""}
      </form>
      <div class="competition-card search-results-card">
        <div class="section-head">
          <div>
            <h3>Related Topics</h3>
            <span class="kicker">${query ? `${results.length.toLocaleString()} of ${SEARCH_MAX_RESULTS} strongest ${searchScopeLabel(scope)} matches` : `Choose ${searchScopeLabel(scope)} and press Search`}</span>
          </div>
        </div>
        ${results.length ? `<div class="search-result-list">${results.map(searchResultHtml).join("")}</div>` : `<p class="empty-state">No search results yet.</p>`}
      </div>
    `);
  }

  function searchShell(content) {
    return `
      <section class="screen competition-screen search-screen">
        <div class="competition-host-panel">
          <div class="section-head">
            <div>
              <p class="kicker">Offline Search</p>
              <h2>SEARCH</h2>
            </div>
            <div class="competition-header-actions">
              ${backButtonHtml()}
              <button class="ghost-button" data-action="search-back-login">Login</button>
            </div>
          </div>
          ${content}
        </div>
      </section>
    `;
  }

  function searchResultHtml(result) {
    return `
      <article class="search-result-item">
        <div>
          <p class="kicker">${escapeHtml(result.kind)}${result.reference ? ` - ${escapeHtml(result.reference)}` : ""}</p>
          <h3>${escapeHtml(result.title)}</h3>
        </div>
        <p>${escapeHtml(result.snippet)}</p>
        <span>${result.matchedTerms.length.toLocaleString()} matched word${result.matchedTerms.length === 1 ? "" : "s"}</span>
      </article>
    `;
  }

  function renderHome() {
    const user = activeUser();
    const session = currentSession();
    const stats = userStats(user);
    const preferredLevel = resolveStartLevelId(user, user.preferredLevel === BABY_COURSE.id ? BABY_COURSE.id : user.preferredLevel) || 1;
    app.innerHTML = appFrame(`
      <section class="shell home-grid">
        <div class="home-hero">
          <div>
            <p class="kicker">Explore. Learn. Grow.</p>
            <h1>Know God More</h1>
            <div class="reading-actions">
              ${session && !session.completed ? `<button class="primary-button" data-action="resume-level">Continue ${escapeHtml(courseLabel(session.levelId))}</button>` : `<button class="primary-button" data-action="start-level" data-level="${preferredLevel}">Start ${escapeHtml(courseLabel(preferredLevel))}</button>`}
              <button class="ghost-button" data-action="marks">View Marks</button>
              <button class="ghost-button" data-action="settings">Settings</button>
            </div>
            ${session && !session.completed ? `<p class="resume-callout">Continue where you stopped: ${escapeHtml(courseLabel(session.levelId))}, ${session.phase === "quiz" ? "answering questions" : "reading"}, item ${(session.index || 0) + 1}/${session.questions.length}.</p>` : `<p class="resume-callout">Choose a course, read carefully, then answer one question after another.</p>`}
          </div>
        </div>

        <aside class="panel">
          <div class="section-head">
            <h2>${escapeHtml(user.displayName)}</h2>
            <button class="ghost-button" data-action="logout">Exit</button>
          </div>
          ${toastMessage ? `<div class="result-banner good">${escapeHtml(consumeToast())}</div>` : ""}
          <div class="stats-grid">
            <div class="stat"><strong>${stats.completed}</strong><span>Completed</span></div>
            <div class="stat"><strong>${stats.bestPercent}%</strong><span>Best Score</span></div>
            <div class="stat"><strong>${stats.totalPoints.toLocaleString()}</strong><span>Marks</span></div>
          </div>
          ${learningProgressSummary(user)}
          <div class="table-list">
            ${leaderBoard().slice(0, 5).map((leader, index) => `
              <div class="table-row">
                <strong>${index + 1}. ${escapeHtml(leader.name)}</strong>
                <span class="score-line">${leader.points.toLocaleString()}</span>
                <span>marks</span>
              </div>
            `).join("")}
          </div>
          <div class="reading-actions">
            <button class="ghost-button" data-action="help">Help</button>
            <button class="ghost-button" data-action="about">About</button>
            ${user.lastCertificate && Array.isArray(user.lastCertificate.missedQuestions) && user.lastCertificate.missedQuestions.length ? `<button class="primary-button" data-action="revision">Revise Missed</button>` : ""}
          </div>
        </aside>
      </section>

      <section class="shell panel" style="margin-top: 18px;">
        <div class="section-head">
          <h2>Course Unit Levels</h2>
          <span class="kicker">Star Course</span>
        </div>
        <div class="level-grid">
          ${levelTile(BABY_COURSE, user)}
          ${LEVELS.map((level) => levelTile(level, user)).join("")}
        </div>
      </section>
    `);
  }

  function renderReading() {
    const user = activeUser();
    const session = requireSession();
    if (!session) return;
    const item = currentQuestion(session);
    if (!item) return;
    const readingFontStyle = `font-size: clamp(${(1.2 * readingZoom).toFixed(2)}rem, ${(3.7 * readingZoom).toFixed(2)}vw, ${(2.1 * readingZoom).toFixed(2)}rem);`;
    publishCompetitionWaiting(session, item);

    app.innerHTML = appFrame(`
      <section class="stage reading-stage">
        <div class="stage-inner">
          <article class="reading-card" style="${readingFontStyle}">
            <p class="reference">${escapeHtml(item.reference || "Bible Reading")}</p>
            <div class="reading-passage">${formatPassage(item.content)}</div>
          </article>
          <div class="reading-actions reading-actions-bottom" aria-label="Reading actions">
            <button class="primary-button" data-action="begin-quiz">Finished Reading - Test Me Now</button>
            <button class="ghost-button saved-certificates-button" data-action="saved-certificates" aria-label="Saved Certificates">Saved</button>
            ${editTextButtonHtml("reading", item.id)}
            <button class="zoom-button" type="button" data-action="zoom-reading-out" aria-label="Make reading smaller">A-</button>
            <button class="zoom-button" type="button" data-action="zoom-reading-in" aria-label="Make reading bigger">A+</button>
          </div>
          <p class="encouragement-line">${escapeHtml(readingEncouragement(session))}</p>
        </div>
      </section>
    `, user, session);
  }

  function renderQuiz() {
    const user = activeUser();
    const session = requireSession();
    if (!session) return;
    const item = currentQuestion(session);
    if (!item) return;

    const answer = currentAnswer(session);
    const choices = displayAnswersForQuestion(item, session);
    const correctIndex = choices.findIndex((choice) => choice.correct);
    publishCompetitionQuestion(session, item, choices, correctIndex);
    const seconds = Math.max(0, Math.ceil(((session.deadline || Date.now()) - Date.now()) / 1000));
    const safeSeconds = answer ? 0 : Math.min(SECONDS_PER_QUESTION, seconds || SECONDS_PER_QUESTION);
    const angle = Math.max(0, Math.min(360, Math.round((safeSeconds / SECONDS_PER_QUESTION) * 360)));
    const encouragementText = answer
      ? answer.correct
        ? "Good work. Keep building your understanding."
        : "Keep going with courage. You are still growing."
      : "Take your time, choose carefully, and keep growing.";

    app.innerHTML = appFrame(`
      <section class="stage quiz-stage">
        <div class="stage-inner">
          <div class="timer-wrap">
            <div class="timer-ring" style="--timer-angle: ${angle}deg"><strong>${formatClock(safeSeconds)}</strong></div>
            <p class="encouragement-line quiz-timer-encouragement">${escapeHtml(encouragementText)}</p>
          </div>
          <h2 class="quiz-question">${escapeHtml(item.question)}</h2>
          <div class="answers">
            ${choices.map((choice, index) => answerButton(choice, index, answer, correctIndex)).join("")}
          </div>
          ${answer ? resultBanner(answer, choices, correctIndex) : ""}
          <div class="quiz-actions">
            <button class="ghost-button saved-certificates-button" data-action="saved-certificates">SAVED CERTIFICATES</button>
            ${editTextButtonHtml("question", item.id)}
            ${answer ? `<button class="primary-button" data-action="next-question">${session.index + 1 >= session.questions.length ? "Finish Level" : "Next Question"}</button>` : ""}
          </div>
        </div>
      </section>
    `, user, session);

    if (!answer) startTimer();
  }

  function renderCertificate() {
    const user = activeUser();
    const cert = user.lastCertificate;
    if (!cert) {
      route = "home";
      render();
      return;
    }

    const nextLevel = nextCourseAfter(cert.levelId);
    const certLevel = courseById(cert.levelId);
    const certStoryCount = cert.storyCount || (certLevel ? levelStoryCount(certLevel.id) : "");
    const isLectiveCertificate = cert.kind === "lective";
    const requiredPercent = Number.isFinite(Number(cert.passPercent)) ? Number(cert.passPercent) : passPercentForLevel(cert.levelId);
    const passed = isLectiveCertificate ? Boolean(cert.passed) : cert.percent >= requiredPercent;
    const showElectiveContinue = isLectiveCertificate && cert.continueElective;
    const showElectiveCheckpointRepeat = isLectiveCertificate && cert.lectiveLevel && cert.isMidCourse;
    const showElectiveRepeat = isLectiveCertificate && cert.lectiveLevel && !cert.isMidCourse;
    const nextElectiveLevel = isLectiveCertificate && !cert.isMidCourse && passed ? nextElectiveClass(cert.lectiveLevel) : "";
    const showCourseRepeat = !isLectiveCertificate && cert.levelId !== undefined && cert.levelId !== null;
    app.innerHTML = `
      <section class="screen certificate-screen">
        <div class="certificate-template-shell">
          <img id="certificateJpgPreview" class="certificate-template-image" src="assets/certificate.png" alt="Know God More certificate">
        </div>
        <div class="certificate-actions certificate-template-actions">
          ${showElectiveContinue ? `<button class="primary-button" data-action="continue-elective-study">Continue Elective Course</button>` : ""}
          ${passed && nextElectiveLevel ? `<button class="primary-button" data-action="elective-select-class" data-level="${escapeHtml(nextElectiveLevel)}">Start ${escapeHtml(electiveClassLabel(nextElectiveLevel))}</button>` : ""}
          ${showElectiveCheckpointRepeat ? `<button class="ghost-button" data-action="elective-repeat-checkpoint">Repeat These Questions</button>` : ""}
          ${showElectiveRepeat ? `<button class="ghost-button" data-action="elective-repeat-class" data-level="${escapeHtml(cert.lectiveLevel)}">Repeat ${escapeHtml(electiveClassLabel(cert.lectiveLevel))}</button>` : ""}
          ${passed && nextLevel && !isLectiveCertificate ? `<button class="primary-button" data-action="start-level" data-level="${nextLevel.id}">Start ${escapeHtml(courseLabel(nextLevel.id))}</button>` : ""}
          ${showCourseRepeat ? `<button class="ghost-button" data-action="retry-level" data-level="${cert.levelId}">Repeat ${escapeHtml(courseLabel(cert.levelId))}</button>` : ""}
          <button class="primary-button" data-action="save-certificate">Save Again JPG</button>
          <button class="primary-button" data-action="share-certificate">Share JPG</button>
          <button class="primary-button" data-action="print-certificate">Print JPG</button>
          <button class="ghost-button saved-certificates-button" data-action="saved-certificates">SAVED CERTIFICATES</button>
          <button class="ghost-button" data-action="home">Home</button>
        </div>
      </section>
    `;
    playCertificateSoundOnce(cert);
    renderCertificateJpeg(user, cert).then((dataUrl) => {
      currentCertificateJpeg = dataUrl;
      const preview = document.querySelector("#certificateJpgPreview");
      if (preview) preview.src = dataUrl;
      autoSaveCertificate(certificateFileName(cert), dataUrl, cert.certificateId || `${cert.kind || "course"}-${cert.date || ""}`, {
        title: cert.kind === "lective" ? "ELECTIVE Course Certificate" : "Course Certificate",
        name: user.displayName,
        kind: cert.kind || "course",
        cert
      });
    }).catch(() => {});
    return;
    app.innerHTML = `
      <section class="screen certificate-screen">
        <div class="certificate-card">
          <div class="certificate-content">
            <img class="cert-badge" src="assets/icon.png" alt="">
            <p class="kicker">Know God More</p>
            <h2>Certificate of Achievement</h2>
            <p class="cert-line">This is to certify that</p>
            <div class="cert-name">${escapeHtml(user.displayName)}</div>
            <p class="cert-line">has completed Course Unit</p>
            <div class="cert-level">Level ${cert.levelId}</div>
            <div class="cert-stars">${"★".repeat(cert.levelId)}</div>
            <p class="cert-line">${certStoryCount} stories completed</p>
            <p class="cert-line">with a score of</p>
            <div class="cert-score">${cert.score.toLocaleString()}</div>
            <p class="cert-line">marks out of ${cert.maxScore.toLocaleString()}</p>
            <p class="cert-line">${(cert.questionsAsked || Math.round(cert.maxScore / POINTS_PER_QUESTION)).toLocaleString()} questions asked from ${certStoryCount} stories</p>
            <p class="cert-line">${passed && nextLevel ? "PROMOTED TO THE NEXT LEVEL" : passed ? "and has completed the final level" : "repeat for better performance"}</p>
            <p class="cert-line">${escapeHtml(cert.date)}</p>
            <div class="certificate-actions">
              ${passed && nextLevel ? `<button class="primary-button" data-action="start-level" data-level="${nextLevel.id}">Start Level ${nextLevel.id}</button>` : ""}
              <button class="ghost-button" data-action="retry-level" data-level="${cert.levelId}">Repeat Level ${cert.levelId}</button>
              <button class="primary-button" data-action="save-certificate">Save Again JPG</button>
              <button class="primary-button" data-action="share-certificate">Share JPG</button>
              <button class="ghost-button" data-action="print-certificate">Print JPG</button>
              <button class="ghost-button" data-action="home">Home</button>
            </div>
          </div>
        </div>
      </section>
    `;
  }

  function renderMarks() {
    const user = activeUser();
    app.innerHTML = appFrame(`
      <section class="shell panel">
        <div class="section-head">
          <h2>Marks</h2>
          <button class="primary-button" data-action="course-units">Course Units</button>
        </div>
        <div class="table-list">
          ${scoreRow(BABY_COURSE, user)}
          ${LEVELS.map((level) => {
            const best = user.bestScores[String(level.id)];
            const scoreLabel = best ? `${best.score}/${best.maxScore} marks` : "Not tried";
            const questionsLabel = best ? `${best.questionsAsked || Math.round(best.maxScore / POINTS_PER_QUESTION)} questions` : levelStoryCountLabel(level.id);
            return `
              <div class="table-row">
                <strong>Level ${level.id} ${"★".repeat(level.id)}</strong>
                <span>${questionsLabel}</span>
                <span class="score-line">${best ? `${scoreLabel} · ${best.percent}%` : "Not tried"}</span>
              </div>
            `;
          }).join("")}
        </div>
      </section>
    `);
  }

  function renderUnits() {
    const user = activeUser();
    app.innerHTML = appFrame(`
      <section class="shell panel units-panel">
        <div class="section-head">
          <h2>Course Unit Levels</h2>
          <span class="kicker">Star Course</span>
        </div>
        <div class="level-grid">
          ${LEVELS.map((level) => levelTile(level, user)).join("")}
        </div>
      </section>
    `);
  }

  function renderUsers() {
    app.innerHTML = appFrame(`
      <section class="shell panel">
        <div class="section-head">
          <h2>All Users</h2>
          <span class="kicker">Local Readers</span>
        </div>
        <div class="table-list">
          ${leaderBoard().map((leader, index) => `
            <div class="table-row">
              <strong>${index + 1}. ${escapeHtml(leader.name)}</strong>
              <span class="score-line">${leader.points.toLocaleString()}</span>
              <span>marks</span>
            </div>
          `).join("") || `<div class="empty-state">No readers yet.</div>`}
        </div>
      </section>
    `);
  }

  function openSavedCertificates() {
    route = "saved-certificates";
    savedCertificateMessage = "";
    renderSavedCertificates();
    loadSavedCertificateList().then(() => {
      if (route === "saved-certificates") renderSavedCertificates();
    });
  }

  function renderSavedCertificates() {
    if (!savedCertificateLoaded && !savedCertificateLoading) {
      loadSavedCertificateList().then(() => {
        if (route === "saved-certificates") renderSavedCertificates();
      });
    }
    const count = savedCertificateList.length;
    app.innerHTML = appFrame(`
      <section class="shell panel saved-certificates-panel">
        <div class="section-head">
          <div>
            <p class="kicker">In-App Folder</p>
            <h2>SAVED CERTIFICATES</h2>
          </div>
          <button class="ghost-button" data-action="refresh-saved-certificates">Refresh</button>
        </div>
        ${savedCertificateMessage ? `<div class="result-banner good">${escapeHtml(consumeSavedCertificateMessage())}</div>` : ""}
        <p class="small-note">This folder keeps up to ${SAVED_CERTIFICATE_LIMIT} certificates inside the app. When it reaches ${SAVED_CERTIFICATE_LIMIT}, the oldest certificate is removed automatically.</p>
        ${savedCertificateLoading ? `<p class="empty-state">Opening saved certificates...</p>` : ""}
        ${!savedCertificateLoading && !count ? `<p class="empty-state">No saved certificates yet. New course and competition certificates will appear here automatically.</p>` : ""}
        ${count ? savedCertificateFolderHtml() : ""}
      </section>
    `, activeUser(), null);
    hydrateSavedCertificatePreview();
  }

  function savedCertificateFolderHtml() {
    const selected = savedCertificateList.find((record) => record.id === selectedSavedCertificateId) || savedCertificateList[0];
    if (selected && !selectedSavedCertificateId) selectedSavedCertificateId = selected.id;
    return `
      <div class="saved-certificate-folder-layout">
        <div class="saved-certificate-list">
          ${savedCertificateList.map((record, index) => savedCertificateListItemHtml(record, index)).join("")}
        </div>
        ${selected ? savedCertificatePreviewHtml(selected) : ""}
      </div>
    `;
  }

  function savedCertificateListItemHtml(record, index) {
    const savedAt = record && record.savedAt ? new Date(record.savedAt).toLocaleString() : "";
    const selected = record.id === selectedSavedCertificateId;
    return `
      <button class="saved-certificate-list-item ${selected ? "is-selected" : ""}" data-action="open-saved-certificate" data-certificate-id="${escapeHtml(record.id)}">
        <span class="saved-certificate-number">${index + 1}</span>
        <div class="saved-certificate-meta">
          <strong>${escapeHtml(record.title || "Know God More Certificate")}</strong>
          <span>${escapeHtml(record.name || "Reader")}</span>
          <span>${escapeHtml(savedCertificateSummary(record))}</span>
          <span>${escapeHtml(savedAt)}</span>
        </div>
      </button>
    `;
  }

  function savedCertificatePreviewHtml(record) {
    return `
      <article class="saved-certificate-card">
        <div class="saved-certificate-preview-box">
          <img data-saved-certificate-preview="${escapeHtml(record.id)}" src="assets/certificate.png" alt="${escapeHtml(record.title || "Saved certificate")}">
          <span>Preparing certificate preview...</span>
        </div>
        <div class="saved-certificate-meta">
          <strong>${escapeHtml(record.title || "Know God More Certificate")}</strong>
          <span>${escapeHtml(record.name || "Reader")}</span>
          <span>${escapeHtml(savedCertificateSummary(record))}</span>
          <span>${escapeHtml(record.fileName || "certificate.jpg")}</span>
        </div>
        <div class="saved-certificate-actions">
          <button class="primary-button" data-action="share-saved-certificate" data-certificate-id="${escapeHtml(record.id)}">Share</button>
          <button class="primary-button" data-action="save-saved-certificate" data-certificate-id="${escapeHtml(record.id)}">Save To Device</button>
          <button class="ghost-button" data-action="print-saved-certificate" data-certificate-id="${escapeHtml(record.id)}">Print</button>
          <button class="danger-button" data-action="delete-saved-certificate" data-certificate-id="${escapeHtml(record.id)}">Delete</button>
        </div>
      </article>
    `;
  }

  function savedCertificateSummary(record) {
    const details = record && record.details ? record.details : {};
    if (record && record.kind === "competition") {
      const certificate = details.certificate || {};
      return `${Number(certificate.score || 0).toLocaleString()} / ${Number(certificate.maxScore || 0).toLocaleString()} marks`;
    }
    const cert = details.cert || {};
    if (cert.score !== undefined || cert.maxScore !== undefined) {
      return `${Number(cert.score || 0).toLocaleString()} / ${Number(cert.maxScore || 0).toLocaleString()} marks`;
    }
    return record && record.kind ? record.kind : "Certificate record";
  }

  function renderRevision() {
    const user = activeUser();
    const cert = user && user.lastCertificate;
    const missed = cert && Array.isArray(cert.missedQuestions) ? cert.missedQuestions : [];
    app.innerHTML = appFrame(`
      <section class="shell panel">
        <div class="section-head">
          <h2>Revision</h2>
          <span class="kicker">${missed.length ? `${missed.length} to review` : "All clear"}</span>
        </div>
        ${missed.length ? `
          <div class="table-list revision-list">
            ${missed.map((item, index) => `
              <div class="table-row revision-row">
                <strong>${index + 1}. ${escapeHtml(item.question)}</strong>
                <span>Your answer: ${escapeHtml(item.selectedText || "No answer")}</span>
                <span class="score-line">Correct: ${escapeHtml(item.correctText || "")}</span>
              </div>
            `).join("")}
          </div>
          <div class="reading-actions">
            <button class="primary-button" data-action="retry-level" data-level="${cert.levelId}">Repeat ${escapeHtml(courseLabel(cert.levelId))}</button>
            <button class="ghost-button" data-action="home">Home</button>
          </div>
        ` : `
          <p class="empty-state">No missed questions are saved from the latest course. Keep going.</p>
          <button class="primary-button" data-action="home">Home</button>
        `}
      </section>
    `);
  }

  function renderSettings() {
    const crashLog = readCrashLog();
    app.innerHTML = appFrame(`
      <section class="shell settings-grid">
        <div class="panel">
          <div class="section-head">
            <h2>Settings</h2>
            <span class="kicker">Version ${APP_VERSION}</span>
          </div>
          <div class="settings-list">
            <div class="settings-row"><strong>Build</strong><span>${APP_BUILD}</span></div>
            <div class="settings-row"><strong>Service</strong><span>${escapeHtml(serviceRemainingText())}</span></div>
            <div class="settings-row"><strong>Client ID</strong><span>${escapeHtml(serviceState.clientId || "Not ready")}</span></div>
            <div class="settings-row"><strong>Renewal code</strong><span>${escapeHtml(deviceReasonCode())}</span></div>
          </div>
          <div class="reading-actions">
            <button class="primary-button" data-action="export-backup">Backup Progress</button>
            <label class="ghost-button file-button">Restore Backup<input data-backup-file type="file" accept=".json,application/json"></label>
          </div>
        </div>
        <div class="panel">
          <div class="section-head">
            <h2>Support</h2>
            <button class="ghost-button" data-action="help">Help</button>
          </div>
          <p class="small-note">For renewal support, use Tindiwensi Arther on WhatsApp, Instagram, and money collection: +256782225970. Send money there, then press Renew on login.</p>
          <p class="small-note">The app stores names, password verification codes, scores, device/client ID, service time, and competition records on this device so offline learning can continue.</p>
          <div class="reading-actions">
            <button class="ghost-button" data-action="about">About</button>
            <button class="ghost-button" data-action="users">All Users</button>
          </div>
        </div>
        <div class="panel">
          <div class="section-head">
            <h2>Diagnostics</h2>
            <button class="ghost-button" data-action="clear-crash-log">Clear</button>
          </div>
          <pre class="diagnostic-box">${escapeHtml(crashLog || "No crash log saved.")}</pre>
        </div>
      </section>
    `);
  }

  function renderHelp() {
    app.innerHTML = appFrame(`
      <section class="shell panel">
        <div class="section-head">
          <h2>Help</h2>
          <span class="kicker">How to use</span>
        </div>
        <div class="help-grid">
          ${helpCard("Login", "Enter your saved name and password. If a course is unfinished, the app takes you back to the exact reading or question.")}
          ${helpCard("Courses", "Read the story first, then answer every question in order. Passing needs 80% or higher. Little Angels needs 100%.")}
          ${helpCard("Revision", "After a failed course, open Revision to see the questions you missed and the correct answers before repeating.")}
          ${helpCard("Competition", "The host chooses material, starts reading, asks questions, pauses, resumes, and completes. Competitors must answer the same number of questions.")}
          ${helpCard("Renewal", "To renew, click Renew, pay using the six-character code as the reason, then enter the 20 digit token from Admin.")}
          ${helpCard("Backup", "Use Settings to backup progress before changing phones. Restore only a trusted Know God More backup file.")}
        </div>
        <div class="reading-actions">
          <button class="primary-button" data-action="home">Home</button>
          <button class="ghost-button" data-action="settings">Settings</button>
        </div>
      </section>
    `);
  }

  function renderAbout() {
    app.innerHTML = appFrame(`
      <section class="shell panel">
        <div class="section-head">
          <h2>About</h2>
          <span class="kicker">Know God More</span>
        </div>
        <p class="small-note">Know God More is an offline Bible learning app for reading, quizzes, certificates, and guided competition.</p>
        <div class="settings-list">
          <div class="settings-row"><strong>Owner</strong><span>Tindiwensi Arther</span></div>
          <div class="settings-row"><strong>Contact</strong><span>+256782225970</span></div>
          <div class="settings-row"><strong>Version</strong><span>${APP_VERSION} (${APP_BUILD})</span></div>
          <div class="settings-row"><strong>Privacy</strong><span>Learning data is stored locally unless renewal or online competition is used.</span></div>
          <div class="settings-row"><strong>Content note</strong><span>Before public release, confirm every Bible translation and story source is licensed for distribution.</span></div>
          <div class="settings-row"><strong>Release note</strong><span>Use a production signing key for public release builds and test upgrades before distribution.</span></div>
        </div>
        <div class="reading-actions">
          <button class="primary-button" data-action="home">Home</button>
          <button class="ghost-button" data-action="help">Help</button>
        </div>
      </section>
    `);
  }

  function renderCompetition() {
    trackRoute();
    if (competitionView === "online-host") {
      refreshCompetitionState();
      startCompetitionPolling();
    }
    app.innerHTML = `
      <section class="screen competition-screen">
        <div class="competition-host-panel">
          <div class="section-head">
            <div>
              <p class="kicker">Know God More Competition</p>
              <h2>${competitionTitle()}</h2>
            </div>
            <div class="competition-header-actions">
              ${backButtonHtml()}
              <button class="ghost-button detail-button" data-action="competition-toggle-details">${competitionDetailsOpen ? "Hide Detail" : "Detail"}</button>
              <button class="ghost-button" data-action="${store.activeUser ? "home" : "logout"}">${store.activeUser ? "Home" : "Login"}</button>
            </div>
          </div>
          ${competitionBodyHtml()}
          <div class="competition-actions competition-back-actions">
            <button class="ghost-button" data-action="competition-setup">Competition Setup</button>
            ${store.activeUser ? `<button class="ghost-button" data-action="home">Home</button>` : `<button class="ghost-button" data-action="logout">Login</button>`}
          </div>
        </div>
      </section>
    `;
    startCompetitionCountdownTicker();
    hydrateCompetitionCertificateImages();
  }

  function renderCompetitionKeepingPlace(anchorSelector) {
    const anchor = anchorSelector ? document.querySelector(anchorSelector) : null;
    const anchorTop = anchor ? anchor.getBoundingClientRect().top : null;
    const scrollX = window.scrollX || 0;
    const scrollY = window.scrollY || document.documentElement.scrollTop || 0;
    if (document.activeElement && typeof document.activeElement.blur === "function") {
      document.activeElement.blur();
    }
    renderCompetition();
    const restorePlace = () => {
      const nextAnchor = anchorSelector ? document.querySelector(anchorSelector) : null;
      if (nextAnchor && anchorTop !== null) {
        const nextTop = nextAnchor.getBoundingClientRect().top + (window.scrollY || document.documentElement.scrollTop || 0);
        window.scrollTo(scrollX, Math.max(0, Math.round(nextTop - anchorTop)));
        return;
      }
      window.scrollTo(scrollX, scrollY);
    };
    window.requestAnimationFrame(() => window.requestAnimationFrame(restorePlace));
    window.setTimeout(restorePlace, 80);
    window.setTimeout(restorePlace, 220);
  }

  function competitionTitle() {
    if (competitionView === "saved") return "Saved Competitions";
    if (competitionView === "offline") return "Competition";
    return "Competition Setup";
  }

  function competitionBodyHtml() {
    if (competitionView === "saved") return competitionSavedHtml();
    if (competitionView === "offline") return competitionOfflineHtml();
    return competitionSetupHtml();
  }

  function competitionSavedHtml() {
    const records = competitionAccessRecords();
    if (!selectedSavedCompetitionId && records.length) selectedSavedCompetitionId = records[0].id;
    const selected = records.find((record) => record.id === selectedSavedCompetitionId) || records[0] || null;
    return `
      ${competitionUnlockHtml(selected)}
      <div class="competition-card">
        <div class="section-head">
          <div>
            <h3>Saved Competition List</h3>
            <span class="kicker">${records.length.toLocaleString()} saved</span>
          </div>
        </div>
        <div class="competition-player-list">
          ${records.map((record) => `
            <div class="competition-player-row">
              <strong>${escapeHtml(record.competitionName || "Saved Competition")}</strong>
              <span>${escapeHtml(record.label)}</span>
              <span>${escapeHtml(record.coordinator && record.coordinator.name ? record.coordinator.name : "No coordinator")}</span>
              <button class="primary-button small-button" data-action="competition-open-saved" data-saved-competition-id="${escapeHtml(record.id)}">${record.id === (selected && selected.id) ? "Open Selected" : "Open"}</button>
            </div>
          `).join("") || `<p class="empty-state">No saved competitions yet. Start Online Host or Offline One Device to create one.</p>`}
        </div>
      </div>
    `;
  }

  function competitionUnlockHtml(lockRecord) {
    const records = competitionAccessRecords();
    const names = records.map((record) => `${record.competitionName || "Competition"} (${record.label})`).join(" and ");
    return `
      <form class="competition-card form-grid" data-competition-unlock-form>
        <input type="hidden" name="savedCompetitionId" value="${escapeHtml(lockRecord && lockRecord.id ? lockRecord.id : "")}">
        <div class="section-head">
          <div>
            <h3>Open Saved Competition</h3>
            <span class="kicker">${escapeHtml(names || (lockRecord && lockRecord.label) || "Competition")}</span>
          </div>
        </div>
        <p class="small-note">Enter coordinator name, password, and competition name. If this is an older saved competition, these details will become the new saved credentials.</p>
        <label class="input-wrap">
          <span>Coordinator name</span>
          <input name="coordinatorName" placeholder="Coordinator name" autocomplete="username" required>
        </label>
        <label class="input-wrap">
          <span>Coordinator password</span>
          <input name="coordinatorPassword" type="password" placeholder="Coordinator password" autocomplete="current-password" required>
        </label>
        <label class="input-wrap">
          <span>Competition name</span>
          <input name="competitionName" value="${escapeHtml(lockRecord && lockRecord.competitionName ? lockRecord.competitionName : "")}" placeholder="Example: Sunday School Round 1" required>
        </label>
        <button class="primary-button" type="submit">Open Competition</button>
        ${competitionUnlockMessage ? `<p class="competition-full-note">${escapeHtml(competitionUnlockMessage)}</p>` : ""}
      </form>
    `;
  }

  function competitionSetupHtml() {
    return `
      <div class="competition-mode-grid">
        <button class="competition-mode-card ${selectedCompetitionLevelMode === "same" ? "is-selected" : ""}" data-action="choose-competition-level-mode" data-mode="same">
          <strong>Same Level</strong>
          <span>All competitors use one selected school level and answer from the same material plan.</span>
        </button>
        <button class="competition-mode-card ${selectedCompetitionLevelMode === "different" ? "is-selected" : ""}" data-action="choose-competition-level-mode" data-mode="different">
          <strong>Different Levels</strong>
          <span>Each competitor carries a school level from P1 to University, then receives questions for that level.</span>
        </button>
        <button class="competition-mode-card" data-action="competition-offline">
          <strong>Continue Setup</strong>
          <span>Open the coordinator page, choose Bible, ${escapeHtml(schoolDisplayName())}, or both, then register competitors by name.</span>
        </button>
        <button class="competition-mode-card" data-action="competition-open-saved">
          <strong>Open Saved Competition</strong>
          <span>Resume any saved competition by coordinator name, password, and competition name.</span>
        </button>
      </div>
      <div class="competition-status-card">
        <strong>Competition materials</strong>
        <span>Bible levels follow school age: P1-P2 Little Angels, P3-P4 Level 1, P5-P6 Level 2, P7-S1 Level 3, S2-S3 Level 4, and S4 upward Level 5. ${escapeHtml(schoolDisplayName())} can be used alone or together with Bible material.</span>
      </div>
      ${competitionDetailsOpen ? competitionHistoryHtml() : ""}
    `;
  }

  function competitionOnlineChoiceHtml() {
    return `
      <div class="competition-mode-grid">
        <button class="competition-mode-card" data-action="competition-online-host">
          <strong>Host</strong>
          <span>This device becomes the master. It chooses the level, starts, pauses, resumes, stops, and completes.</span>
        </button>
        <button class="competition-mode-card" data-action="competition-online-join">
          <strong>Join</strong>
          <span>Paste the competition link, enter name and password, then answer from this device.</span>
        </button>
      </div>
      ${COMPETITION_ENDPOINT ? "" : `<div class="competition-status-card"><strong>Internet relay</strong><span>No live internet competition server is connected yet. The app link works on the same hotspot or Wi-Fi now; adding a relay endpoint will make it work across distant networks.</span></div>`}
    `;
  }

  function competitionCoordinatorHtml(mode, record) {
    const coordinator = record && record.coordinator ? record.coordinator : null;
    const draft = competitionCoordinatorDraft(mode);
    const coordinatorName = coordinator ? coordinator.name || "" : draft.name || "";
    const coordinatorPassword = coordinator ? coordinator.password || "saved-securely" : draft.password || "";
    const competitionName = coordinator ? coordinator.competitionName || record.competitionName || "Competition" : draft.competitionName || "";
    return `
      <div class="competition-card form-grid competition-coordinator-card" data-coordinator-mode="${escapeHtml(mode)}">
        <div class="section-head">
          <div>
            <h3>Coordinator</h3>
            <span class="kicker">${coordinator ? `Resume key active for ${escapeHtml(coordinator.name || "Coordinator")} - ${escapeHtml(competitionName)}` : "Name, password, and competition name save this competition"}</span>
          </div>
        </div>
        <label class="input-wrap">
          <span>Coordinator name</span>
          <input name="coordinatorName" value="${escapeHtml(coordinatorName)}" placeholder="Coordinator name" ${coordinator ? "readonly" : ""} required>
        </label>
        <label class="input-wrap">
          <span>Coordinator password</span>
          <input name="coordinatorPassword" type="password" value="${escapeHtml(coordinatorPassword)}" placeholder="Different password can control another competition" ${coordinator ? "readonly" : ""} required>
        </label>
        <label class="input-wrap">
          <span>Competition name</span>
          <input name="competitionName" value="${escapeHtml(competitionName)}" placeholder="Example: Sunday School Round 1" ${coordinator ? "readonly" : ""} required>
        </label>
        ${competitionCoordinatorMessage ? `<p class="competition-full-note">${escapeHtml(competitionCoordinatorMessage)}</p>` : ""}
      </div>
    `;
  }

  function competitionOnlineHostHtml() {
    const host = store.onlineCompetition || null;
    const state = competitionState || refreshCompetitionState() || {};
    const running = Boolean(state.running);
    const joinUrl = state.url || "";
    const appLink = joinUrl ? makeCompetitionLink(joinUrl) : "";
    const playerCount = Number(state.playerCount || 0);
    const maxPlayers = Number(state.maxPlayers || 200);
    const material = host ? host.materials[host.materialIndex] : null;
    const question = host && material ? applyQuestionTextEdit(material.questions[host.questionIndex]) : null;
    const hostPromptText = host && host.phase === "quiz" && question
      ? question.question || "Choose the best answer."
      : host && host.phase === "paused"
        ? "Competition paused."
        : host && host.phase === "completed"
          ? "Competition completed."
          : material
            ? material.content
            : "Start host to load reading.";
    if (host && host.phase === "quiz") {
      const beforeDeadline = host.questionDeadlineAt;
      ensureOnlineQuestionTimer(host);
      if (!beforeDeadline && host.questionDeadlineAt) saveStore();
    }
    return `
      ${competitionDetailsOpen || !host ? `
        <div class="competition-detail-panel">
          ${competitionCoordinatorHtml("online", host)}
          ${competitionSourcePickerHtml(host)}
          <div class="competition-status-card ${running ? "is-live" : ""}">
            <strong>${running ? "Host is live" : "Host is stopped"}</strong>
            <span>${running ? `Competitors can join any time. ${playerCount}/${maxPlayers} joined.` : "Choose material and start the host to create a competition link."}</span>
          </div>
          <div class="competition-link-box">
            <span>Competition link to paste in app</span>
            <strong>${appLink ? escapeHtml(appLink) : "Start host to create link"}</strong>
            ${competitionQrHtml(appLink || joinUrl)}
          </div>
          <div class="competition-actions">
            <button class="primary-button" data-action="start-online-competition">${running ? "Restart Host" : "Start Host"}</button>
            <button class="ghost-button" data-action="new-online-competition">New Named Competition</button>
            <button class="ghost-button" data-action="open-wifi-settings">Wi-Fi Settings</button>
            <button class="ghost-button" data-action="copy-competition-link" ${appLink || joinUrl ? "" : "disabled"}>Copy Link</button>
            <button class="danger-button" data-action="stop-competition-host" ${running ? "" : "disabled"}>Stop</button>
          </div>
          ${host ? competitionLiveHtml(state, host) : ""}
        </div>
      ` : ""}
      ${host ? `
        <section class="competition-live-grid competition-live-grid-compact">
          <div class="competition-card competition-live-card ${host.phase === "quiz" ? "is-quiz" : ""}">
            ${competitionDotsHtml(state.players || [], -1)}
            <div class="competition-focus-name">${escapeHtml(host.phase === "quiz" ? "Competitors Answering" : host.phase === "reading" ? "Host Reading" : "Master Control")}</div>
            ${host.phase === "quiz" ? "" : `<p class="competition-reference">${escapeHtml(material ? material.reference : "Ready")}</p>`}
            ${material && host.phase === "reading" ? competitionReadingHtml(material) : `<p class="competition-question ${host.phase === "quiz" ? "competition-question-only" : ""}">${escapeHtml(hostPromptText)}</p>`}
            ${question && host.phase === "quiz" ? competitionCountdownHtml(host) : ""}
            ${question && host.phase === "quiz" ? `<div class="competition-options">${displayAnswersForQuestion(question, host).slice(0, 3).map((option, index) => `<span><strong>${String.fromCharCode(65 + index)}.</strong> ${escapeHtml(option.text)}</span>`).join("")}</div>` : ""}
            <div class="competition-actions compact-actions">
              <button class="ghost-button saved-certificates-button" data-action="saved-certificates">SAVED CERTIFICATES</button>
              ${question && host.phase === "quiz" ? editTextButtonHtml("competition-question", question.id) : ""}
              <button class="primary-button" data-action="online-host-start-reading" ${host.phase === "completed" ? "disabled" : ""}>Start Reading</button>
              <button class="primary-button" data-action="online-host-begin-questions" ${host.phase === "completed" ? "disabled" : ""}>Ask Questions</button>
              <button class="ghost-button" data-action="online-host-next-question" ${host.phase === "completed" ? "disabled" : ""}>Next</button>
              <button class="ghost-button" data-action="${host.phase === "paused" ? "online-host-resume" : "online-host-pause"}">${host.phase === "paused" ? "Resume" : "Pause"}</button>
              <button class="primary-button competition-complete-button" data-action="online-host-complete">Complete</button>
            </div>
          </div>
        </section>
        ${host.phase === "completed" ? competitionOnlineCertificatesHtml(state, host) : ""}
      ` : `<div data-competition-live>${competitionLiveHtml(state, host)}</div>`}
    `;
  }

  function competitionOnlineJoinHtml() {
    const state = onlineJoin.state || {};
    const question = state.question || null;
    const joined = Boolean(onlineJoin.playerId);
    const expired = question && question.phase === "quiz" && competitionQuestionExpired({ ...question, serverNow: state.serverNow, receivedAt: state.receivedAt });
    const joinedPromptText = question
      ? question.phase === "reading"
        ? "The host is reading. Wait for the questions."
        : question.phase === "paused"
          ? "Competition paused by the host."
          : question.phase === "completed"
            ? "Competition completed by the host."
            : question.question || "Choose your answer."
      : "Waiting for the host to start.";
    return `
      <form class="competition-card form-grid" data-online-join-form>
        <h3>Join Competition</h3>
        <label class="input-wrap">
          <span>Competition link</span>
          <input name="link" value="${escapeHtml(onlineJoin.hostUrl || "")}" placeholder="Paste KGM-COMPETITION link here" required>
        </label>
        <label class="input-wrap">
          <span>Name</span>
          <input name="name" value="${escapeHtml(onlineJoin.playerName || "")}" placeholder="Competitor name" required>
        </label>
        <label class="input-wrap">
          <span>Password</span>
          <input name="password" type="password" value="${escapeHtml(onlineJoin.password || "")}" placeholder="Simple password" required>
        </label>
        <button class="primary-button" type="submit">${joined ? "Reconnect" : "Join Competition"}</button>
        ${onlineJoin.message ? `<p class="competition-full-note">${escapeHtml(onlineJoin.message)}</p>` : ""}
      </form>
      ${joined ? `
        <div class="competition-card competition-live-card ${question && question.phase === "quiz" ? "is-quiz" : ""}">
          <div class="competition-focus-name">${escapeHtml(state.playerName || onlineJoin.playerName || "Competitor")}</div>
          ${question && question.phase === "quiz" ? "" : `<p class="competition-reference">${escapeHtml(question ? question.reference || "Question" : "Waiting")}</p>`}
          <p class="competition-question ${question && question.phase === "quiz" ? "competition-question-only" : ""}">${escapeHtml(joinedPromptText)}</p>
          ${question && question.phase === "quiz" ? competitionCountdownHtml({ ...question, serverNow: state.serverNow, receivedAt: state.receivedAt }) : ""}
          ${question && question.phase === "quiz" ? `<div class="competition-options competition-answer-list">${(question.options || []).slice(0, 3).map((option, index) => {
            const letter = option.letter || String.fromCharCode(65 + index);
            const correctLetter = competitionCorrectLetter(question.options || [], question.correctLetter || "");
            return competitionAnswerOptionHtml({
              option,
              index,
              letter,
              action: "online-join-answer",
              selectedChoice: state.answeredChoice || "",
              correctLetter: state.answeredChoice ? correctLetter : "",
              disabled: Boolean(state.answeredChoice || expired)
            });
          }).join("")}</div>` : ""}
          <div class="competition-actions compact-actions">
            <button class="ghost-button saved-certificates-button" data-action="saved-certificates">SAVED CERTIFICATES</button>
          </div>
          <p class="small-note">${state.answeredChoice ? `Answer sent: ${escapeHtml(state.answeredChoice)}` : expired ? "Time finished for this question. Wait for the host to continue." : "Your answer buttons appear when the host asks a question."}</p>
        </div>
      ` : ""}
    `;
  }

  function competitionCorrectLetter(options, fallback = "") {
    const explicit = String(fallback || "").trim().toUpperCase();
    if (explicit) return explicit;
    const correctOption = (options || []).find((option) => option && option.correct);
    return correctOption ? String(correctOption.letter || "").trim().toUpperCase() : "";
  }

  function competitionAnswerOptionHtml({ option, index, letter, action, selectedChoice = "", correctLetter = "", disabled = false }) {
    const currentLetter = String(letter || (option && option.letter) || String.fromCharCode(65 + index)).trim().toUpperCase();
    const selected = String(selectedChoice || "").trim().toUpperCase() === currentLetter;
    const answered = Boolean(selectedChoice || correctLetter);
    const correctOption = String(correctLetter || "").trim().toUpperCase() === currentLetter || Boolean(option && option.correct);
    const correct = answered && correctOption;
    const wrongOption = answered && !correct;
    const wrongSelected = selected && wrongOption;
    const classes = [
      "primary-button",
      "competition-answer-option",
      selected ? "is-selected" : "",
      correct ? "is-correct" : "",
      wrongOption ? "is-wrong-option" : "",
      wrongSelected ? "is-wrong" : ""
    ].filter(Boolean).join(" ");
    const mark = answered ? (correct ? "&#10003;" : "x") : "";
    return `<button class="${classes}" data-action="${escapeHtml(action)}" data-choice="${escapeHtml(currentLetter)}" ${disabled ? "disabled" : ""}><strong>${escapeHtml(currentLetter)}.</strong><span>${escapeHtml(option && option.text || "")}</span><span class="answer-mark">${mark}</span></button>`;
  }

  function competitionOfflineHtml() {
    const offline = store.offlineCompetition || null;
    if (!offline || !offline.active) return competitionOfflineSetupHtml();
    if (offline.status === "completed") return competitionOfflineCertificatesHtml(offline);
    return competitionOfflineRunHtml(offline);
  }

  function competitionOfflineSetupHtml() {
    const players = (store.offlineCompetition && store.offlineCompetition.players) || [];
    return `
      ${competitionCoordinatorHtml("offline", store.offlineCompetition)}
      ${competitionSourcePickerHtml(null)}
      <form class="competition-card form-grid" data-offline-competitor-form>
        <h3>Register Competitors</h3>
        <label class="input-wrap"><span>Name</span><input name="name" placeholder="Competitor name" required></label>
        ${selectedCompetitionLevelMode === "different" ? lectiveSelectHtml("Competitor class", "schoolLevel", selectedCompetitionSchoolLevel, COMPETITION_SCHOOL_LEVELS, "") : ""}
        <button class="primary-button" type="submit">Add Competitor</button>
      </form>
      <div class="competition-card">
        <h3>Competitors (${players.length})</h3>
        <div class="competition-player-list competition-player-list-stable">
          ${players.map((player, index) => {
            const schoolLevel = normalizeCompetitionSchoolLevel(player.schoolLevel || selectedCompetitionSchoolLevel);
            return `<div class="competition-player-row"><strong>${index + 1}. ${escapeHtml(player.name)}</strong><span>${escapeHtml(schoolLevel)}</span><span>${escapeHtml(courseLabel(bibleLevelForSchoolLevel(schoolLevel)))}</span></div>`;
          }).join("") || `<p class="empty-state">No competitors added yet.</p>`}
        </div>
      </div>
      <div class="competition-actions">
        <button class="primary-button" data-action="start-offline-competition" ${players.length ? "" : "disabled"}>Start Competition</button>
        <button class="ghost-button" data-action="new-offline-competition">New Named Competition</button>
        <button class="danger-button" data-action="offline-reset-competition">Clear</button>
      </div>
    `;
  }

  function competitionOfflineRunHtml(offline) {
    const material = offline.materials[offline.materialIndex];
    const player = offline.players[offline.playerIndex];
    const question = offlineCurrentQuestion(offline) ? applyQuestionTextEdit(offlineCurrentQuestion(offline)) : null;
    const isHandoff = offline.status === "handoff";
    const isAnswerFeedback = offline.status === "answer-feedback";
    const previousPlayer = isHandoff ? offline.players[Math.max(0, offline.playerIndex - 1)] : null;
    const feedback = isAnswerFeedback ? offline.lastAnswerFeedback || null : null;
    const feedbackChoices = feedback && question ? displayAnswersForQuestion(question, offline).slice(0, 3) : [];
    return `
      ${competitionDetailsOpen ? `<div class="competition-status-card is-live">
        <strong>Round ${offline.materialIndex + 1} of ${offline.materials.length}</strong>
        <span>${escapeHtml(isHandoff && player ? `${player.name} is next` : player && offline.status === "answering" ? `${player.name} is answering` : "Reading round")} · ${offline.players.length} competitors</span>
      </div>` : ""}
      <div class="competition-card competition-live-card ${offline.status === "answering" || isAnswerFeedback ? "is-quiz" : ""}">
        ${competitionDotsHtml(offline.players, offline.playerIndex)}
        <div class="competition-focus-name">${escapeHtml(player ? player.name : offline.status === "reading" ? "Reading Round" : "Competition")}</div>
        <p class="competition-reference">${escapeHtml(material.reference)}</p>
        ${offline.status === "reading" ? `
          ${competitionReadingHtml(material)}
          <div class="competition-actions">
            <button class="ghost-button saved-certificates-button" data-action="saved-certificates">SAVED CERTIFICATES</button>
            <button class="primary-button" data-action="offline-begin-answers">Start Answers</button>
          </div>
          <p class="small-note">End Competition unlocks after every planned question in this competition is answered.</p>
        ` : isHandoff ? `
          <div class="competition-handoff">
            <strong>${escapeHtml(previousPlayer ? `${previousPlayer.name} has finished this round.` : "Competitor finished.")}</strong>
            <span>${escapeHtml(player ? `${player.name} should get ready before the next questions begin.` : "Prepare the next competitor.")}</span>
          </div>
          <div class="competition-actions">
            <button class="primary-button" data-action="offline-next-competitor">${escapeHtml(player ? `Start ${player.name}` : "Start Next Competitor")}</button>
          </div>
          <p class="small-note">Press the button only when the next competitor is ready. The timer starts after you press it.</p>
        ` : isAnswerFeedback ? `
          <p class="competition-question competition-question-only">${escapeHtml(question ? question.question : "Question answered.")}</p>
          <div class="competition-options competition-answer-list">
            ${feedbackChoices.map((option, index) => competitionAnswerOptionHtml({
              option,
              index,
              letter: String.fromCharCode(65 + index),
              action: "offline-answer",
              selectedChoice: feedback ? feedback.choice : "",
              correctLetter: feedback ? feedback.correctChoice : "",
              disabled: true
            })).join("")}
          </div>
          <div class="result-banner ${feedback && feedback.correct ? "good" : "bad"}">${feedback && feedback.correct ? "Correct answer captured. Keep going." : feedback && feedback.timedOut ? "Time finished. Take courage and continue." : "Answer captured. Keep learning with courage."}</div>
          <div class="competition-actions">
            <button class="primary-button" data-action="offline-answer-next">${escapeHtml(offlineAnswerNextLabel(offline))}</button>
            ${question ? editTextButtonHtml("competition-question", question.id) : ""}
          </div>
        ` : offline.status === "ready-to-complete" ? `
          <div class="competition-handoff">
            <strong>All answers are captured.</strong>
            <span>Press Competition Completed to issue certificates, or add more questions from the same selected material.</span>
          </div>
          <div class="competition-actions">
            <button class="primary-button competition-complete-button" data-action="offline-complete-competition">Competition Completed</button>
            <button class="primary-button" data-action="offline-add-more-questions">Add More Questions</button>
          </div>
        ` : `
          ${competitionCountdownHtml(offline)}
          <p class="competition-question competition-question-only">${escapeHtml(question ? question.question : "Question")}</p>
          <div class="competition-options competition-answer-list">
            ${question ? displayAnswersForQuestion(question, offline).slice(0, 3).map((option, index) => competitionAnswerOptionHtml({
              option,
              index,
              letter: String.fromCharCode(65 + index),
              action: "offline-answer"
            })).join("") : ""}
          </div>
          <div class="competition-actions compact-actions">
            <button class="ghost-button saved-certificates-button" data-action="saved-certificates">SAVED CERTIFICATES</button>
            ${question ? editTextButtonHtml("competition-question", question.id) : ""}
          </div>
          <p class="small-note">Finish this competitor round first. Completion is locked while scores are unbalanced.</p>
        `}
      </div>
      ${competitionDetailsOpen ? `<div class="competition-card competition-detail-panel">
        <h3>Score Dashboard</h3>
        <div class="competition-player-list">
          ${offline.players.map((item, index) => {
            const planned = offlinePlayerPlannedQuestions(offline, item);
            return `<div class="competition-player-row"><strong>${index + 1}. ${escapeHtml(item.name)}</strong><span>${(item.answers || []).length}/${planned}</span><span>${item.score} marks</span></div>`;
          }).join("")}
        </div>
      </div>` : ""}
    `;
  }

  function competitionOfflineCertificatesHtml(offline) {
    const certificates = competitionCertificatesFromOffline(offline);
    rememberVisibleCompetitionCertificates(certificates);
    playCompetitionCertificateSoundOnce(certificates, "offline");
    return `
      <div class="competition-status-card is-live">
        <strong>Competition completed</strong>
        <span>Certificates below show each competitor's marks out of all competition questions.</span>
      </div>
      ${competitionRankingHtml(certificates)}
      ${competitionCertificateGridHtml(certificates)}
      <div class="competition-actions">
        <button class="primary-button" data-action="print-competition-certificates">Print All Certificates</button>
        <button class="primary-button" data-action="repeat-offline-competition">Repeat Competition</button>
        <button class="primary-button" data-action="new-offline-competition">New Competition</button>
      </div>
    `;
  }

  function competitionOnlineCertificatesHtml(state, host) {
    const certificates = competitionCertificatesFromOnline(state, host);
    rememberVisibleCompetitionCertificates(certificates);
    playCompetitionCertificateSoundOnce(certificates, "online");
    return `
      <div class="competition-status-card is-live">
        <strong>Certificates issued</strong>
        <span>Late joiners are marked partial, with marks out of the total competition questions.</span>
      </div>
      ${competitionRankingHtml(certificates)}
      ${competitionCertificateGridHtml(certificates)}
      <div class="competition-actions">
        <button class="primary-button" data-action="print-competition-certificates" ${certificates.length ? "" : "disabled"}>Print All Certificates</button>
      </div>
    `;
  }

  function competitionCertificateGridHtml(certificates) {
    return `
      <div class="competition-certificate-grid">
        ${certificates.map((certificate, index) => competitionCertificateCardHtml(certificate, index)).join("") || `<p class="empty-state">No certificates yet.</p>`}
      </div>
    `;
  }

  function competitionRankingHtml(certificates) {
    const ranked = [...certificates].sort((a, b) => Number(b.score || 0) - Number(a.score || 0) || Number(b.answered || 0) - Number(a.answered || 0));
    return `
      <div class="competition-card">
        <h3>Final Ranking</h3>
        <div class="competition-player-list">
          ${ranked.map((certificate, index) => `
            <div class="competition-player-row">
              <strong>${index + 1}. ${escapeHtml(certificate.name)}</strong>
              <span>${Number(certificate.score || 0).toLocaleString()} marks</span>
              <span>${Number(certificate.answered || 0).toLocaleString()}/${Number(certificate.totalQuestions || 0).toLocaleString()} answered</span>
            </div>
          `).join("") || `<p class="empty-state">No ranking yet.</p>`}
        </div>
      </div>
    `;
  }

  function competitionCertificateCardHtml(certificate, index = 0) {
    return `
      <div class="competition-certificate-card">
        <img class="competition-certificate-template-image" data-competition-certificate-index="${index}" src="assets/certificate.png" alt="Know God More competition certificate for ${escapeHtml(certificate.name)}">
        <strong>${escapeHtml(certificate.name)}</strong>
        <span>${Number(certificate.score || 0).toLocaleString()} / ${Number(certificate.maxScore || 0).toLocaleString()} marks</span>
        <div class="competition-certificate-actions">
          <button class="primary-button" data-action="save-competition-certificate" data-certificate-index="${index}">Save Again JPG</button>
          <button class="primary-button" data-action="share-competition-certificate" data-certificate-index="${index}">Share JPG</button>
          <button class="primary-button" data-action="print-competition-certificate" data-certificate-index="${index}">Print JPG</button>
          <button class="ghost-button saved-certificates-button" data-action="saved-certificates">SAVED CERTIFICATES</button>
        </div>
      </div>
    `;
  }

  function hydrateCompetitionCertificateImages() {
    const images = Array.from(document.querySelectorAll("[data-competition-certificate-index]"));
    if (!images.length) return;
    const certificates = currentCompetitionCertificates();
    rememberVisibleCompetitionCertificates(certificates);
    images.forEach((image) => {
      const index = Number(image.dataset.competitionCertificateIndex || 0);
      const certificate = certificates[index];
      if (!certificate) return;
      renderCompetitionCertificateJpeg(certificate).then((dataUrl) => {
        image.src = dataUrl;
        autoSaveCertificate(competitionCertificateFileName(certificate), dataUrl, certificate.certificateId || `competition-${index}-${certificate.name || ""}`, {
          title: "Competition Certificate",
          name: certificate.name || "Competitor",
          kind: "competition",
          certificate
        });
      }).catch((error) => {
        console.warn("Could not prepare competition certificate preview.", error);
      });
    });
  }

  function competitionLiveHtml(state, host) {
    const question = state && state.question ? state.question : null;
    const players = Array.isArray(state && state.players) ? state.players : [];
    const maxPlayers = Number(state && state.maxPlayers ? state.maxPlayers : 200);
    const totalQuestions = host ? host.totalQuestions : question && question.total ? question.total : 0;
    const answeredNow = players.filter((player) => player.answered).length;
    const waitingNow = Math.max(0, players.length - answeredNow);
    const leadingScore = players.reduce((best, player) => Math.max(best, Number(player.totalCorrect || 0) * POINTS_PER_QUESTION), 0);
    return `
      <div class="competition-card">
        <div class="section-head">
          <h3>Competitors (${players.length}/${maxPlayers})</h3>
          <button class="ghost-button" data-action="refresh-competition">Refresh</button>
        </div>
        <div class="competition-dashboard">
          <span><strong>${answeredNow}</strong> answered now</span>
          <span><strong>${waitingNow}</strong> waiting</span>
          <span><strong>${leadingScore.toLocaleString()}</strong> top marks</span>
        </div>
        ${players.length >= maxPlayers ? `<p class="competition-full-note">Competition full. New devices cannot join.</p>` : ""}
        <div class="competition-player-list">
          ${players.map((player, index) => {
            const answered = Number(player.totalAnswered || 0);
            const partial = totalQuestions && answered < totalQuestions ? "Partial" : "Full";
            return `
              <div class="competition-player-row">
                <strong>${index + 1}. ${escapeHtml(player.name || "Guest")}</strong>
                <span>${player.answered ? `Answered ${escapeHtml(player.currentChoice || "")}` : "Waiting"}</span>
                <span>${Number(player.totalCorrect || 0).toLocaleString()}/${answered.toLocaleString()} correct${totalQuestions ? ` · ${partial}` : ""}</span>
              </div>
            `;
          }).join("") || `<p class="empty-state">No devices joined yet.</p>`}
        </div>
        ${host && host.phase === "completed" ? `<p class="small-note">Joined late users are marked partial when they answered fewer than ${host.totalQuestions} questions.</p>` : ""}
      </div>
    `;
  }

  function competitionReadingHtml(material) {
    const storyBlocks = Array.isArray(material && material.stories) && material.stories.length
      ? material.stories
      : [{ title: "", content: material && material.content ? material.content : "" }];
    return `
      <div class="competition-reading-passages">
        ${storyBlocks.map((story) => `
          <section class="competition-story-paragraph">
            ${story.title ? `<p class="competition-story-title">${escapeHtml(story.title)}</p>` : ""}
            <p>${escapeHtml(story.content)}</p>
          </section>
        `).join("")}
      </div>
    `;
  }

  function competitionDotsHtml(players, activeIndex = -1) {
    const list = Array.isArray(players) ? players : [];
    if (!list.length) return "";
    return `
      <div class="competition-dot-rail" aria-label="Competition members">
        ${list.map((player, index) => `
          <span class="competition-dot ${index === activeIndex ? "is-active" : ""}" style="--dot-color: ${competitionDotColor(index)}" title="${escapeHtml(player.name || player.playerName || `Competitor ${index + 1}`)}"></span>
        `).join("")}
      </div>
    `;
  }

  function competitionDotColor(index) {
    const colors = ["#ffd86b", "#54d59f", "#76b7ff", "#f4a7c9", "#ff8a65", "#a78bfa", "#5eead4", "#facc15", "#fb7185", "#86efac", "#c084fc", "#38bdf8"];
    return colors[Math.abs(index) % colors.length];
  }

  function competitionSourcePickerHtml(host) {
    const locked = Boolean(host && host.phase && host.phase !== "ready");
    const schoolLevel = normalizeCompetitionSchoolLevel(selectedCompetitionSchoolLevel);
    const bibleLevel = bibleLevelForSchoolLevel(schoolLevel);
    const subjects = competitionAvailableSubjectsForLevel(schoolLevel);
    const sourceLabel = competitionMaterialLabel(selectedCompetitionSource, schoolLevel);
    return `
      <div class="competition-card">
        <div class="section-head">
          <h3>Competition Material</h3>
          <span class="kicker">${escapeHtml(sourceLabel)}</span>
        </div>
        <div class="competition-source-row">
          <button class="ghost-button ${selectedCompetitionSource === "bible" || selectedCompetitionSource === "level" ? "is-selected" : ""}" data-action="choose-competition-source" data-source="bible" ${locked ? "disabled" : ""}>Bible</button>
          <button class="ghost-button ${selectedCompetitionSource === "lective" ? "is-selected" : ""}" data-action="choose-competition-source" data-source="lective" ${locked ? "disabled" : ""}>${escapeHtml(schoolDisplayName())}</button>
          <button class="ghost-button ${selectedCompetitionSource === "both" ? "is-selected" : ""}" data-action="choose-competition-source" data-source="both" ${locked ? "disabled" : ""}>Both</button>
        </div>
        <label class="competition-timer-setting">
          <span>Countdown Timer</span>
          <input data-competition-timer type="number" min="${COMPETITION_TIMER_MIN_SECONDS}" max="${COMPETITION_TIMER_MAX_SECONDS}" step="5" value="${competitionTimerDuration()}">
          <strong>${competitionTimerDuration()} seconds</strong>
        </label>
        <div class="competition-source-row">
          <label class="input-wrap">
            <span>Total quiz per subject</span>
            <input data-competition-question-target type="number" min="1" max="${COMPETITION_MAX_QUESTION_TARGET}" step="1" value="${competitionQuestionTarget()}" ${locked ? "disabled" : ""}>
          </label>
          <label class="input-wrap">
            <span>Total quiz per round</span>
            <input data-competition-questions-per-round type="number" min="1" max="${competitionQuestionTarget()}" step="1" value="${competitionQuestionsPerRound()}" ${locked ? "disabled" : ""}>
          </label>
        </div>
        ${selectedCompetitionLevelMode === "same" ? `
          <div class="lective-folder-row">
            ${lectiveSelectHtml("Competition class", "competitionSchoolLevel", schoolLevel, COMPETITION_SCHOOL_LEVELS, "data-competition-school-level")}
          </div>
          <p class="small-note">Bible mapping: ${escapeHtml(electiveClassLabel(schoolLevel))} uses ${escapeHtml(courseLabel(bibleLevel))}. ${escapeHtml(schoolDisplayName())} has ${subjects.length.toLocaleString()} ready subject(s) for this class.</p>
        ` : `<p class="small-note">Different-level mode uses the school level saved on each competitor, then asks Bible and ${escapeHtml(schoolDisplayName())} questions for that competitor's level.</p>`}
        <p class="small-note">${competitionUsesKgmSchool(selectedCompetitionSource) ? `For ${escapeHtml(schoolDisplayName())}, Total quiz per subject controls each subject, while Total quiz per round is the exact number asked before moving to the next round.` : "For Bible-only competitions, the question count is taken from the mapped Bible level."}</p>
      </div>
    `;
  }

  function lectiveVoiceField(label, name, controlHtml) {
    return `
      <label class="input-wrap lective-voice-field">
        <span>${escapeHtml(label)}</span>
        <div class="lective-voice-row">
          ${controlHtml}
          <button class="ghost-button lective-voice-button" type="button" data-action="voice-type" data-voice-target="${escapeHtml(name)}" title="Start microphone typing" aria-label="Start microphone typing for ${escapeHtml(label)}"><img src="assets/mic.jpg" alt="" aria-hidden="true"></button>
        </div>
      </label>
    `;
  }

  function renderLectiveCourse() {
    trackRoute();
    if (lectiveView === "others" || lectiveView === "others-add") {
      renderElectiveOthers();
      return;
    }
    if (lectiveView === "register") {
      renderElectiveRegister();
      return;
    }
    if (lectiveView === "subjects") {
      renderElectiveSubjects();
      return;
    }
    if (lectiveView === "combination") {
      renderElectiveCombination();
      return;
    }
    if (lectiveView === "customize-auth") {
      renderSchoolCustomizeAuth();
      return;
    }
    if (lectiveView === "customize") {
      renderSchoolCustomizeForm();
      return;
    }
    renderElectiveClasses();
  }

  function renderElectiveShell(content, kicker = "School Course Path") {
    app.innerHTML = `
      <section class="screen competition-screen">
        <div class="competition-host-panel">
          <div class="section-head">
            <div>
              <p class="kicker">${escapeHtml(kicker)}</p>
              <h2>${escapeHtml(schoolDisplayName())}</h2>
            </div>
            <div class="competition-header-actions">
              <button class="ghost-button" type="button" data-action="school-customize">CUSTOMIZE</button>
              ${backButtonHtml()}
              <button class="ghost-button" data-action="${store.activeUser ? "home" : "logout"}">${store.activeUser ? "Home" : "Login"}</button>
            </div>
          </div>
          ${content}
        </div>
      </section>
    `;
  }

  function renderSchoolCustomizeAuth() {
    renderElectiveShell(`
      <form class="competition-card form-grid" data-school-customize-auth-form>
        <div class="section-head">
          <div>
            <h3>Customize School</h3>
            <span class="kicker">Authorized owner access only</span>
          </div>
          <button class="ghost-button" type="button" data-action="elective-classes">Classes</button>
        </div>
        <label class="input-wrap">
          <span>Name</span>
          <input name="name" autocomplete="username" placeholder="CONTACT +256782225970" required>
        </label>
        <label class="input-wrap">
          <span>Password</span>
          <input name="password" type="password" autocomplete="current-password" placeholder="CONTACT +256782225970" required>
        </label>
        <button class="primary-button" type="submit">Open Customize</button>
        ${competitionLectiveMessage ? `<p class="competition-full-note">${escapeHtml(competitionLectiveMessage)}</p>` : ""}
      </form>
    `, "Owner Settings");
  }

  function renderSchoolCustomizeForm() {
    const customization = normalizeSchoolCustomization(store.schoolCustomization);
    renderElectiveShell(`
      <form class="competition-card form-grid" data-school-customize-form>
        <div class="section-head">
          <div>
            <h3>School Name And Badge</h3>
            <span class="kicker">Shows inside ${escapeHtml(schoolDisplayName())} and on certificates</span>
          </div>
          <button class="ghost-button" type="button" data-action="elective-classes">Classes</button>
        </div>
        <label class="input-wrap">
          <span>School name</span>
          <input name="schoolName" value="${escapeHtml(customization.name)}" maxlength="48" spellcheck="true" autocomplete="off" required>
        </label>
        <label class="input-wrap">
          <span>Certificate badge image</span>
          <input name="schoolBadge" type="file" accept="image/*">
        </label>
        ${customization.badgeDataUrl ? `<div class="school-badge-preview"><img src="${escapeHtml(customization.badgeDataUrl)}" alt="Current school badge"><span>Current badge is active.</span></div>` : `<p class="small-note">No custom badge yet. Choose any clear image; the app will prepare it for certificates.</p>`}
        <div class="competition-actions">
          <button class="primary-button" type="submit">Save Customize</button>
          <button class="ghost-button" type="button" data-action="school-customize-reset">Reset Default Name</button>
        </div>
        ${competitionLectiveMessage ? `<p class="competition-full-note">${escapeHtml(competitionLectiveMessage)}</p>` : ""}
      </form>
    `, "Owner Settings");
  }

  function authorizeSchoolCustomization(formData) {
    const name = cleanText(formData.get("name") || "").toLowerCase();
    const password = String(formData.get("password") || "");
    if (!CUSTOM_SCHOOL_ADMINS.includes(name) || password !== CUSTOM_SCHOOL_ADMIN_PASSWORD) {
      competitionLectiveMessage = "Customize is for the authorized owner only.";
      lectiveView = "customize-auth";
      renderLectiveCourse();
      return;
    }
    competitionLectiveMessage = "";
    lectiveView = "customize";
    renderLectiveCourse();
  }

  async function saveSchoolCustomization(formData) {
    const current = normalizeSchoolCustomization(store.schoolCustomization);
    const name = cleanText(formData.get("schoolName") || "").slice(0, 48) || CUSTOM_SCHOOL_NAME_DEFAULT;
    const file = formData.get("schoolBadge");
    const next = { name, badgeDataUrl: current.badgeDataUrl || "" };
    try {
      if (file && typeof file === "object" && file.size) {
        if (!/^image\//i.test(file.type || "") && !/\.(jpe?g|png|webp|gif|bmp)$/i.test(file.name || "")) {
          throw new Error("Choose an image file for the certificate badge.");
        }
        next.badgeDataUrl = await prepareSchoolBadge(file);
      }
      store.schoolCustomization = normalizeSchoolCustomization(next);
      saveStore();
      competitionLectiveMessage = `${schoolDisplayName()} is now saved for courses and certificates.`;
      lectiveView = "classes";
      renderLectiveCourse();
    } catch (error) {
      competitionLectiveMessage = error && error.message ? error.message : "Badge could not be prepared. Choose a clear image and try again.";
      lectiveView = "customize";
      renderLectiveCourse();
    }
  }

  function prepareSchoolBadge(file) {
    return new Promise((resolve, reject) => {
      const reader = new FileReader();
      reader.onerror = () => reject(new Error("Badge could not be read."));
      reader.onload = () => {
        const dataUrl = String(reader.result || "");
        const image = new Image();
        image.onerror = () => reject(new Error("Badge image could not be opened."));
        image.onload = () => {
          const width = Number(image.naturalWidth || image.width || 0);
          const height = Number(image.naturalHeight || image.height || 0);
          if (!width || !height) {
            reject(new Error("Badge image has no readable size."));
            return;
          }
          const canvas = document.createElement("canvas");
          canvas.width = 512;
          canvas.height = 512;
          const ctx = canvas.getContext("2d");
          ctx.fillStyle = "#050505";
          ctx.fillRect(0, 0, 512, 512);
          const size = Math.min(width, height);
          const sx = Math.max(0, Math.round((width - size) / 2));
          const sy = Math.max(0, Math.round((height - size) / 2));
          ctx.drawImage(image, sx, sy, size, size, 0, 0, 512, 512);
          resolve(canvas.toDataURL("image/jpeg", 0.9));
        };
        image.src = dataUrl;
      };
      reader.readAsDataURL(file);
    });
  }

  function schoolDisplayName() {
    return normalizeSchoolCustomization(store.schoolCustomization).name;
  }

  function schoolDisplayNameButtonHtml() {
    return escapeHtml(schoolDisplayName()).replace(/\s+/g, "<br>");
  }

  function schoolCertificateLine() {
    const name = schoolDisplayName();
    return name === CUSTOM_SCHOOL_NAME_DEFAULT ? "of our Know God More app" : `of ${name}`;
  }

  function schoolBadgeDataUrl() {
    return normalizeSchoolCustomization(store.schoolCustomization).badgeDataUrl;
  }

  function renderElectiveClasses() {
    const groups = ELECTIVE_CLASS_GROUPS.map((group, groupIndex) => `
      <div class="elective-class-grid">
        ${group.map((level) => `
          <button class="competition-mode-card elective-class-card" data-action="elective-select-class" data-level="${escapeHtml(level)}">
            <strong>${escapeHtml(electiveClassLabel(level))}</strong>
            <span>${escapeHtml(electiveClassSubtitle(level))}</span>
          </button>
        `).join("")}
      </div>
      ${groupIndex + 1 < ELECTIVE_CLASS_GROUPS.length ? `<div class="elective-partition" role="separator"><span>Partition Line</span></div>` : ""}
    `).join("");
    renderElectiveShell(`
      <div class="competition-card">
        <div class="section-head">
          <div>
            <h3>Select Class</h3>
            <span class="kicker">P1 to University, plus Others for your own questions</span>
          </div>
        </div>
        <div class="elective-class-list">${groups}</div>
        ${competitionLectiveMessage ? `<p class="competition-full-note">${escapeHtml(competitionLectiveMessage)}</p>` : ""}
      </div>
    `);
  }

  function renderElectiveRegister() {
    const user = activeUser();
    const level = normalizeLectiveLevel(selectedLectiveStudyLevel);
    renderElectiveShell(`
      <form class="competition-card form-grid" data-elective-register-form>
        <div class="section-head">
          <div>
            <h3>${escapeHtml(electiveClassLabel(level))} Registration</h3>
            <span class="kicker">Saved learners continue exactly where they stopped</span>
          </div>
          <button class="ghost-button" type="button" data-action="elective-classes">Classes</button>
        </div>
        <input type="hidden" name="level" value="${escapeHtml(level)}">
        <label class="input-wrap">
          <span>Name</span>
          <input name="name" value="${escapeHtml(user ? user.displayName || "" : "")}" autocomplete="username" placeholder="Learner name" required>
        </label>
        <label class="input-wrap">
          <span>Password</span>
          <input name="password" type="password" autocomplete="current-password" placeholder="Learner password" required>
        </label>
        <button class="primary-button" type="submit">Continue ${escapeHtml(electiveClassLabel(level))}</button>
        ${competitionLectiveMessage ? `<p class="competition-full-note">${escapeHtml(competitionLectiveMessage)}</p>` : ""}
      </form>
    `, "Learner Registration");
  }

  function renderElectiveSubjects() {
    const user = activeUser();
    if (!user) {
      lectiveView = "register";
      renderElectiveRegister();
      return;
    }
    const level = normalizeLectiveLevel(selectedLectiveStudyLevel);
    if (isElectiveCombinationLevel(level) && !electiveCombinationForUser(user, level).length) {
      lectiveView = "combination";
      renderElectiveCombination();
      return;
    }
    const subjects = electiveSubjectsForUser(level, user);
    const progress = electiveProgressForUser(user, level);
    const availableSubjects = availableElectiveSubjectsForClass(level);
    const checkpointSize = electiveCheckpointSize(level);
    const orderNote = electiveOrderedSubjects(level).join(" -> ");
    const summary = electiveClassCompletionSummary(user, level);
    const combinationNote = isElectiveCombinationLevel(level) ? ` Combination: ${subjects.join(", ")}.` : "";
    renderElectiveShell(`
      <div class="competition-card">
        <div class="section-head">
          <div>
            <h3>${escapeHtml(electiveClassLabel(level))}</h3>
            <span class="kicker">${escapeHtml(user.displayName)} - pass mark ${ELECTIVE_PASS_PERCENT}%</span>
          </div>
          <div class="competition-header-actions">
            ${isElectiveCombinationLevel(level) ? `<button class="ghost-button" type="button" data-action="elective-change-combination">Combination</button>` : ""}
            <button class="ghost-button" type="button" data-action="elective-classes">Classes</button>
          </div>
        </div>
        <div class="course-progress-track"><span class="course-progress-fill" style="width:${electiveClassProgressPercent(user, level)}%"></span></div>
        <p class="small-note">${availableSubjects.length ? `${availableSubjects.length} subject${availableSubjects.length === 1 ? "" : "s"} available now.${escapeHtml(combinationNote)} Mid-course certificates come after about ${checkpointSize} questions per subject. Default order: ${escapeHtml(orderNote)}. Class marks stay separate from other classes.` : "This class is coming soon. Add or bring the question database later."}</p>
        ${summary.maxScore ? `<p class="small-note">Saved ${escapeHtml(electiveClassLabel(level))} total so far: ${summary.score.toLocaleString()} / ${summary.maxScore.toLocaleString()} marks (${summary.percent}%).</p>` : ""}
        <div class="elective-subject-grid">
          ${subjects.map((subject) => {
            const questions = lectiveQuestionCount(level, subject);
            const result = progress.subjects[subject] || null;
            const passed = Boolean(result && result.passed);
            const answered = electiveSubjectAnsweredCount(user, level, subject);
            return `
              <button class="competition-mode-card elective-subject-card ${passed ? "is-complete" : ""}" data-action="elective-start-subject" data-subject="${escapeHtml(subject)}">
                <strong>${escapeHtml(subject)}</strong>
                <span>${questions ? `${answered.toLocaleString()}/${questions.toLocaleString()} questions` : "Coming soon"}</span>
                ${result ? `<em>${result.percent}% ${passed ? "passed" : "repeat"}</em>` : ""}
              </button>
            `;
          }).join("")}
        </div>
        ${competitionLectiveMessage ? `<p class="competition-full-note">${escapeHtml(competitionLectiveMessage)}</p>` : ""}
      </div>
    `, "Class Subjects");
  }

  function renderElectiveCombination() {
    const user = activeUser();
    if (!user) {
      lectiveView = "register";
      renderElectiveRegister();
      return;
    }
    const level = normalizeLectiveLevel(selectedLectiveStudyLevel);
    if (!isElectiveCombinationLevel(level)) {
      lectiveView = "subjects";
      renderElectiveSubjects();
      return;
    }
    const subjects = lectiveSubjectsForLevel(level);
    const available = subjects.filter((subject) => lectiveSubjectIsAvailable(level, subject));
    const saved = electiveCombinationForUser(user, level);
    const started = user.electiveStudy && !user.electiveStudy.completed && normalizeLectiveLevel(user.electiveStudy.level) === level && Array.isArray(user.electiveStudy.answers) && user.electiveStudy.answers.length > 0;
    renderElectiveShell(`
      <form class="competition-card form-grid elective-combination-form" data-elective-combination-form>
        <div class="section-head">
          <div>
            <h3>${escapeHtml(electiveClassLabel(level))} Combination</h3>
            <span class="kicker">Choose 4 or 5 subjects for this learner</span>
          </div>
          <button class="ghost-button" type="button" data-action="elective-subjects">Subjects</button>
        </div>
        <input type="hidden" name="level" value="${escapeHtml(level)}">
        <p class="small-note">This selection becomes the complete ${escapeHtml(electiveClassLabel(level))} class for ${escapeHtml(user.displayName)}. Choose not less than ${ELECTIVE_COMBINATION_MIN} and not more than ${ELECTIVE_COMBINATION_MAX} subjects.</p>
        ${started ? `<p class="competition-full-note">This class has already started. Finish it or repeat it before changing the combination.</p>` : ""}
        <div class="elective-combination-grid">
          ${subjects.map((subject) => {
            const ready = available.includes(subject);
            const checked = saved.includes(subject);
            return `
              <label class="elective-combination-option ${ready ? "" : "is-disabled"}">
                <input type="checkbox" name="subjects" value="${escapeHtml(subject)}" ${checked ? "checked" : ""} ${!ready || started ? "disabled" : ""}>
                <span>${escapeHtml(subject)}</span>
                <em>${ready ? `${lectiveQuestionCount(level, subject).toLocaleString()} questions` : "Coming soon"}</em>
              </label>
            `;
          }).join("")}
        </div>
        <button class="primary-button" type="submit" ${started ? "disabled" : ""}>Save Combination</button>
        ${competitionLectiveMessage ? `<p class="competition-full-note">${escapeHtml(competitionLectiveMessage)}</p>` : ""}
      </form>
    `, "Combination Setup");
  }

  function renderElectiveOthers() {
    const key = lectiveFolderKey(ELECTIVE_OTHERS_LEVEL, ELECTIVE_OTHERS_SUBJECT);
    const saved = lectiveFolderStories(key);
    const showAdd = lectiveView === "others-add" || !saved.length;
    const listHtml = saved.map((story, index) => {
      const question = story.questions[0];
      const answers = question ? question.answers.slice(0, 3).map((answer, answerIndex) => `${String.fromCharCode(65 + answerIndex)}. ${answer.text}`).join(" | ") : "";
      return `<div class="competition-player-row lective-saved-row"><strong>${index + 1}. ${escapeHtml(question ? question.question : story.title)}</strong><span>${escapeHtml(answers)}</span><span>${story.questions.length} question</span><button class="danger-button small-button" data-action="lective-delete-story" data-lective-key="${escapeHtml(key)}" data-lective-id="${escapeHtml(story.id)}">Delete</button></div>`;
    }).join("");
    renderElectiveShell(`
      <div class="competition-card">
        <div class="section-head">
          <div>
            <h3>Others</h3>
            <span class="kicker">${saved.length.toLocaleString()} saved question${saved.length === 1 ? "" : "s"}</span>
          </div>
          <button class="ghost-button" type="button" data-action="elective-classes">Classes</button>
        </div>
        ${saved.length ? `
          <div class="competition-mode-grid">
            <button class="competition-mode-card" data-action="elective-others-add"><strong>Add Question</strong><span>Type, paste, speak, or import a question.</span></button>
            <button class="competition-mode-card" data-action="elective-others-start"><strong>Start Questions</strong><span>Use all saved Others questions as one course.</span></button>
          </div>
        ` : `<p class="empty-state">No Others questions saved yet. Add the first question below.</p>`}
      </div>
      ${showAdd ? electiveOthersFormHtml() : ""}
      <div class="competition-card">
        <h3>Saved Others Questions</h3>
        <div class="competition-player-list">
          ${listHtml || `<p class="empty-state">No saved Others questions yet.</p>`}
        </div>
      </div>
      ${competitionLectiveMessage ? `<p class="competition-full-note">${escapeHtml(competitionLectiveMessage)}</p>` : ""}
    `, "Custom Questions");
  }

  function electiveOthersFormHtml() {
    return `
      <form class="competition-card form-grid lective-form" data-lective-form>
        <div class="section-head">
          <div>
            <h3>Add Question</h3>
            <span class="kicker">Saved into Others</span>
          </div>
          <button class="ghost-button" type="button" data-action="elective-subjects">Back</button>
        </div>
        <input type="hidden" name="level" value="${ELECTIVE_OTHERS_LEVEL}">
        <input type="hidden" name="subject" value="${ELECTIVE_OTHERS_SUBJECT}">
        <div class="lective-plus-row">
          <label class="lective-plus-button">
            <span>+</span>
            <input data-lective-file type="file" accept=".txt,.md,.html,.htm,.csv,.json,.xml,.rtf,.doc,.docx,.pdf,text/*,application/pdf,application/msword,application/vnd.openxmlformats-officedocument.wordprocessingml.document">
          </label>
          <p class="small-note">Add a document, or copy and paste a question below. Spelling correction is enabled where the keyboard supports it.</p>
        </div>
        ${lectiveVoiceField("Question", "question", `<textarea name="question" rows="5" placeholder="Type, paste, or speak the question here" spellcheck="true" autocapitalize="sentences" autocomplete="off" required></textarea>`)}
        <div class="lective-answer-grid">
          ${lectiveVoiceField("A. Correct answer", "rightAnswer", `<input name="rightAnswer" placeholder="Correct answer" spellcheck="true" autocapitalize="sentences" autocomplete="off" required>`)}
          ${lectiveVoiceField("B. Wrong answer 1", "wrongAnswer1", `<input name="wrongAnswer1" placeholder="Wrong option" spellcheck="true" autocapitalize="sentences" autocomplete="off" required>`)}
          ${lectiveVoiceField("C. Wrong answer 2", "wrongAnswer2", `<input name="wrongAnswer2" placeholder="Wrong option" spellcheck="true" autocapitalize="sentences" autocomplete="off" required>`)}
        </div>
        <p class="small-note lective-voice-status" data-lective-voice-status>Tap the microphone beside any field, speak, and the words will be typed there.</p>
        <button class="primary-button" type="submit">Save Question</button>
      </form>
    `;
  }

  function lectiveSelectHtml(label, name, selected, options, dataAttribute) {
    return `
      <label class="input-wrap">
        <span>${escapeHtml(label)}</span>
        <select name="${escapeHtml(name)}" ${dataAttribute || ""}>
          ${options.map((option) => `<option value="${escapeHtml(option)}" ${option === selected ? "selected" : ""}>${escapeHtml(option)}</option>`).join("")}
        </select>
      </label>
    `;
  }

  function electiveClassLabel(level) {
    const normalized = normalizeLectiveLevel(level);
    if (normalized === ELECTIVE_OTHERS_LEVEL) return "OTHERS";
    if (normalized === "UNIVERSITY") return "UNIVERSITY";
    return normalized;
  }

  function electiveClassSubtitle(level) {
    const normalized = normalizeLectiveLevel(level);
    if (normalized === ELECTIVE_OTHERS_LEVEL) return "Add your own questions and answers";
    const subjects = lectiveSubjectsForLevel(normalized);
    const available = availableElectiveSubjectsForClass(normalized).length;
    if (!available) return "Coming soon";
    return `${available}/${subjects.length} subjects ready`;
  }

  function lectiveSubjectsForLevel(level) {
    const normalized = normalizeLectiveLevel(level);
    return LECTIVE_SUBJECTS_BY_LEVEL[normalized] || LECTIVE_SUBJECTS_BY_LEVEL[DEFAULT_LECTIVE_LEVEL];
  }

  function isElectiveCombinationLevel(level) {
    return ELECTIVE_COMBINATION_LEVELS.includes(normalizeLectiveLevel(level));
  }

  function electiveCombinationForUser(user, level) {
    if (!user || !isElectiveCombinationLevel(level)) return [];
    const normalized = normalizeLectiveLevel(level);
    const allowed = lectiveSubjectsForLevel(normalized);
    const combinations = user.electiveCombinations && typeof user.electiveCombinations === "object" ? user.electiveCombinations : {};
    const saved = Array.isArray(combinations[normalized]) ? combinations[normalized] : [];
    return uniqueStrings(saved)
      .map((subject) => normalizeLectiveSubject(subject, normalized))
      .filter((subject) => allowed.includes(subject));
  }

  function electiveSubjectsForUser(level, user = activeUser()) {
    const normalized = normalizeLectiveLevel(level);
    const subjects = lectiveSubjectsForLevel(normalized);
    if (!isElectiveCombinationLevel(normalized)) return subjects;
    const combination = electiveCombinationForUser(user, normalized);
    return combination.length ? combination : subjects;
  }

  function availableElectiveSubjectsForClass(level, user = activeUser()) {
    const normalized = normalizeLectiveLevel(level);
    return electiveSubjectsForUser(normalized, user).filter((subject) => lectiveSubjectIsAvailable(normalized, subject));
  }

  function electiveCheckpointSize(level) {
    const normalized = normalizeLectiveLevel(level);
    return ELECTIVE_MID_COURSE_AMOUNTS[normalized] || 20;
  }

  function electiveOrderedSubjects(level, preferredSubject = "") {
    const normalized = normalizeLectiveLevel(level);
    const available = availableElectiveSubjectsForClass(normalized);
    if (normalized === ELECTIVE_OTHERS_LEVEL) return available.length ? available : [ELECTIVE_OTHERS_SUBJECT];
    const baseOrder = (normalized.startsWith("P") ? ELECTIVE_DEFAULT_SUBJECT_ORDER : electiveSubjectsForUser(normalized))
      .filter((subject) => available.includes(subject));
    available.forEach((subject) => {
      if (!baseOrder.includes(subject)) baseOrder.push(subject);
    });
    const preferred = normalizeLectiveSubject(preferredSubject || baseOrder[0], normalized);
    const index = baseOrder.indexOf(preferred);
    if (index <= 0) return baseOrder;
    return baseOrder.slice(index).concat(baseOrder.slice(0, index));
  }

  function lectiveQuestionList(level, subject) {
    const key = normalizeLectiveFolderKey(lectiveFolderKey(level, subject));
    if (lectiveQuestionListCache.has(key)) return lectiveQuestionListCache.get(key);
    const list = lectiveStoryBank(level, subject).flatMap((story, storyIndex) => {
      const questions = Array.isArray(story.questions) ? story.questions : [];
      return questions.map((question, questionIndex) => ({
        story,
        storyIndex,
        question,
        questionIndex,
        flatIndex: 0
      }));
    }).map((item, index) => ({ ...item, flatIndex: index }));
    lectiveQuestionListCache.set(key, list);
    return list;
  }

  function lectiveClassTotalQuestions(level) {
    return availableElectiveSubjectsForClass(level)
      .reduce((sum, subject) => sum + lectiveQuestionCount(level, subject), 0);
  }

  function electiveSubjectAnsweredCount(user, level, subject) {
    const session = user && user.electiveStudy && !user.electiveStudy.completed && normalizeLectiveLevel(user.electiveStudy.level) === normalizeLectiveLevel(level)
      ? user.electiveStudy
      : null;
    if (session && session.subjectOffsets && Number(session.subjectOffsets[subject] || 0) > 0) {
      return Number(session.subjectOffsets[subject] || 0);
    }
    const progress = user ? electiveProgressForUser(user, level) : null;
    const result = progress && progress.subjects ? progress.subjects[subject] : null;
    return Number(result && result.questionsAsked || 0);
  }

  function electiveProgressForUser(user, level = selectedLectiveStudyLevel) {
    ensureUserDefaults(user);
    const normalized = normalizeLectiveLevel(level);
    user.electiveProgress.classes[normalized] = user.electiveProgress.classes[normalized] || { subjects: {}, midCourseCertificates: [], completed: false };
    user.electiveProgress.classes[normalized].subjects = user.electiveProgress.classes[normalized].subjects || {};
    user.electiveProgress.classes[normalized].midCourseCertificates = Array.isArray(user.electiveProgress.classes[normalized].midCourseCertificates) ? user.electiveProgress.classes[normalized].midCourseCertificates : [];
    return user.electiveProgress.classes[normalized];
  }

  function electiveClassProgressPercent(user, level) {
    const available = availableElectiveSubjectsForClass(level);
    if (!available.length) return 1;
    const session = user && user.electiveStudy && !user.electiveStudy.completed && normalizeLectiveLevel(user.electiveStudy.level) === normalizeLectiveLevel(level)
      ? user.electiveStudy
      : null;
    if (session && session.subjectOffsets) {
      const answered = available.reduce((sum, subject) => sum + Number(session.subjectOffsets[subject] || 0), 0);
      const total = lectiveClassTotalQuestions(level);
      if (total) return Math.max(1, Math.round((answered / total) * 100));
    }
    const progress = electiveProgressForUser(user, level);
    if (progress.completed) return 100;
    const passed = available.filter((subject) => progress.subjects[subject] && progress.subjects[subject].passed).length;
    return Math.max(1, Math.round((passed / available.length) * 100));
  }

  function electiveClassCompletionSummary(user, level, includeActive = true) {
    const activeSession = user && user.electiveStudy && !user.electiveStudy.completed && normalizeLectiveLevel(user.electiveStudy.level) === normalizeLectiveLevel(level)
      ? user.electiveStudy
      : null;
    if (includeActive && activeSession) {
      const totals = electiveSessionTotals(activeSession);
      return {
        score: totals.score,
        maxScore: totals.maxScore,
        storyCount: 0,
        questionsAsked: totals.questionsAsked,
        percent: totals.percent
      };
    }
    const progress = electiveProgressForUser(user, level);
    const available = availableElectiveSubjectsForClass(level);
    const subjects = available.length ? available : Object.keys(progress.subjects || {});
    const totals = subjects.reduce((sum, subject) => {
      const result = progress.subjects[subject] || {};
      sum.score += Number(result.score || 0);
      sum.maxScore += Number(result.maxScore || 0);
      sum.storyCount += Number(result.storyCount || 0);
      sum.questionsAsked += Number(result.questionsAsked || 0);
      return sum;
    }, { score: 0, maxScore: 0, storyCount: 0, questionsAsked: 0 });
    return {
      ...totals,
      percent: totals.maxScore ? Math.round((totals.score / totals.maxScore) * 100) : 0
    };
  }

  function nextElectiveClass(level) {
    const order = ["P1", "P2", "P3", "P4", "P5", "P6", "P7", "S1", "S2", "S3", "S4", "S5", "S6", "UNIVERSITY"];
    const index = order.indexOf(normalizeLectiveLevel(level));
    return index >= 0 && index + 1 < order.length ? order[index + 1] : "";
  }

  function renderLectiveStudy() {
    trackRoute();
    const user = activeUser();
    const session = validLectiveStudySession();
    if (!user || !session) return;
    const current = currentElectiveQuestion(session);
    const question = current && current.question;
    const totalQuestions = lectiveClassTotalQuestions(session.level);
    const subjectQuestions = lectiveQuestionList(session.level, session.subject).length;
    const subjectOffset = Number(session.subjectOffsets[session.subject] || 0);
    const answeredQuestions = (session.answers || []).length;
    const progress = totalQuestions ? Math.round((answeredQuestions / totalQuestions) * 100) : 0;
    const checkpointSize = electiveCheckpointSize(session.level);
    const checkpointAnswered = checkpointSize ? subjectOffset % checkpointSize : subjectOffset;
    const checkpointTarget = Math.min(checkpointSize, Math.max(1, subjectQuestions - subjectOffset));
    const nextSubject = nextElectiveSubjectAfter(session, session.subject);

    if (!question) {
      completeLectiveClass(session);
      return;
    }

    session.phase = "quiz";

    let answer = currentLectiveAnswer(session);
    if (!answer && markExpiredLectiveQuestionIfNeeded(session)) return;
    answer = currentLectiveAnswer(session);
    const remainingSeconds = answer ? 0 : electiveQuestionRemainingSeconds(session);
    const timerAngle = Math.max(0, Math.min(360, Math.round((remainingSeconds / ELECTIVE_SECONDS_PER_QUESTION) * 360)));
    const choices = displayAnswersForQuestion(question, { levelId: -1, startedAt: session.startedAt });
    const correctIndex = choices.findIndex((choice) => choice.correct);
    const progressPanelHtml = `
      <div class="course-progress-panel lective-progress-panel">
        <div class="course-progress-row">
          <span>${escapeHtml(electiveClassLabel(session.level))} Marks <strong>${Number(session.score || 0).toLocaleString()}/${(totalQuestions * POINTS_PER_QUESTION).toLocaleString()}</strong></span>
          <span>Subject <strong>${escapeHtml(session.subject)}</strong></span>
          <span>Answered <strong>${answeredQuestions.toLocaleString()}/${totalQuestions.toLocaleString()}</strong></span>
        </div>
        <div class="course-progress-row lective-mini-progress">
          <span>Checkpoint <strong>${Math.min(checkpointTarget, checkpointAnswered + (answer ? 1 : 0)).toLocaleString()}/${checkpointTarget.toLocaleString()}</strong></span>
          <span>${escapeHtml(session.subject)} <strong>${Math.min(subjectQuestions, subjectOffset + (answer ? 1 : 0)).toLocaleString()}/${subjectQuestions.toLocaleString()}</strong></span>
          <span>Next <strong>${escapeHtml(nextSubject || "Class Certificate")}</strong></span>
        </div>
        <div class="course-progress-track"><span class="course-progress-fill" style="width:${Math.max(1, progress)}%"></span></div>
      </div>
    `;
    app.innerHTML = appFrame(`
      <section class="stage quiz-stage lective-study-stage">
        <div class="stage-inner">
          <div class="lective-main-grid">
            <div class="timer-wrap lective-timer-wrap">
              <div class="timer-ring lective-timer-ring" style="--timer-angle:${timerAngle}deg" aria-label="Elective question timer">
                <strong>${formatClock(remainingSeconds)}</strong>
              </div>
            </div>
            <div class="lective-content-pane ${answer ? "is-answered" : "is-waiting"} ${answer && !answer.correct ? "is-wrong" : ""} ${answer && answer.correct ? "is-correct" : ""}">
              <h2 class="quiz-question">${escapeHtml(question.question)}</h2>
              <div class="answers">
                ${choices.map((choice, index) => lectiveStudyAnswerButton(choice, index, answer, correctIndex)).join("")}
              </div>
              <div class="lective-feedback">
                ${answer ? resultBanner(answer, choices, correctIndex) : `<p class="encouragement-line">Choose the best answer and keep going.</p>`}
                ${answer && !answer.correct ? lectiveCorrectionHtml(answer, question, choices, correctIndex, user) : ""}
                ${answer && answer.correct ? `<p class="encouragement-line">Good work, ${escapeHtml(user.displayName)}. Keep building toward 100%.</p>` : ""}
              </div>
              <div class="lective-flow-actions">
                ${answer ? `<button class="primary-button" data-action="lective-study-next">${electiveNextButtonLabel(session)}</button>` : ""}
              </div>
            </div>
            <div class="quiz-actions lective-side-buttons">
              <button class="ghost-button saved-certificates-button" data-action="saved-certificates">SAVED CERTIFICATES</button>
              ${editTextButtonHtml("elective-question", question.id)}
              ${progressPanelHtml}
              <button class="ghost-button lective-back-button" data-action="elective-subjects">Back To ELECTIVE</button>
            </div>
          </div>
        </div>
      </section>
    `, user, null);
    if (answer) {
      stopLectiveTimer();
    } else {
      startLectiveTimer();
    }
  }

  function lectiveCorrectionHtml(answer, question, choices, correctIndex, user) {
    return `
      <div class="lective-correction-card is-encouragement-only">
        <p>${escapeHtml(answer.inspiration || electiveFailureMessage(user, question, choices[correctIndex]))}</p>
      </div>
    `;
  }

  function electiveFailureMessage(user, question, correctChoice) {
    const name = user && user.displayName ? user.displayName : "Learner";
    const key = `${name}:${question && question.id || question && question.question || ""}:${correctChoice && correctChoice.text || ""}`;
    const index = Math.abs(hash(key)) % ELECTIVE_FAILURE_MESSAGES.length;
    return ELECTIVE_FAILURE_MESSAGES[index].replace("{name}", name);
  }

  function electiveNextButtonLabel(session) {
    const current = currentElectiveQuestion(session);
    if (!current) return "Finish ELECTIVE Course";
    const subject = current.subject;
    const subjectTotal = lectiveQuestionList(session.level, subject).length;
    const nextOffset = Math.min(subjectTotal, Number(session.subjectOffsets[subject] || 0) + 1);
    const subjectDone = subjectTotal > 0 && nextOffset >= subjectTotal;
    const checkpointSize = electiveCheckpointSize(session.level);
    const checkpointDue = subjectDone || (checkpointSize > 0 && nextOffset > 0 && nextOffset % checkpointSize === 0);
    if (!checkpointDue) return "Next Question";
    const nextSubject = nextElectiveSubjectAfter({
      ...session,
      subjectOffsets: { ...(session.subjectOffsets || {}), [subject]: nextOffset },
      subjectCompleted: { ...(session.subjectCompleted || {}), [subject]: subjectDone || Boolean(session.subjectCompleted && session.subjectCompleted[subject]) }
    }, subject);
    if (subjectDone && !nextSubject) return "Finish Class";
    if (subjectDone) return "Subject Certificate";
    return "Mid-Course Certificate";
  }

  function startLectiveVoiceTyping(targetName) {
    const field = findLectiveField(targetName);
    if (!field) {
      updateLectiveVoiceStatus("Select an Elective Course field first.");
      return;
    }

    if (activeVoiceRecognition) {
      try {
        activeVoiceRecognition.stop();
      } catch (error) {}
      activeVoiceRecognition = null;
    }

    clearLectiveVoiceButtons();
    const button = lectiveVoiceButton(targetName);
    activeVoiceTarget = targetName;
    field.focus();
    if (button) button.classList.add("is-listening");
    updateLectiveVoiceStatus(`Microphone is listening for ${fieldLabelFromName(targetName)}. Speak now.`);

    if (window.KnowGodMoreSpeech && typeof window.KnowGodMoreSpeech.start === "function") {
      try {
        window.KnowGodMoreSpeech.start(targetName, navigator.language || "en-US");
        return;
      } catch (error) {
        clearLectiveVoiceButtons();
        activeVoiceTarget = "";
      }
    }

    const SpeechRecognition = window.SpeechRecognition || window.webkitSpeechRecognition;
    if (!SpeechRecognition) {
      clearLectiveVoiceButtons();
      activeVoiceTarget = "";
      updateLectiveVoiceStatus("Microphone typing is not available here. Use Chrome, Edge, or your device keyboard microphone.");
      window.alert("Microphone typing is not available here. Use Chrome, Edge, or your device keyboard microphone.");
      return;
    }

    const recognition = new SpeechRecognition();
    activeVoiceRecognition = recognition;
    recognition.lang = navigator.language || "en-US";
    recognition.continuous = false;
    recognition.interimResults = false;
    recognition.maxAlternatives = 1;

    recognition.onresult = (event) => {
      const transcript = Array.from(event.results || [])
        .map((result) => result && result[0] ? result[0].transcript : "")
        .join(" ")
        .trim();
      if (transcript) {
        insertVoiceText(field, transcript);
        updateLectiveVoiceStatus(`Spoken words added to ${fieldLabelFromName(targetName)}.`);
      }
    };
    recognition.onerror = (event) => {
      const reason = event && event.error ? event.error : "not available";
      updateLectiveVoiceStatus(`Microphone stopped: ${reason}. Check internet and microphone permission.`);
    };
    recognition.onend = () => {
      if (button) button.classList.remove("is-listening");
      if (activeVoiceRecognition === recognition) activeVoiceRecognition = null;
      if (activeVoiceTarget === targetName) activeVoiceTarget = "";
    };

    try {
      recognition.start();
    } catch (error) {
      if (button) button.classList.remove("is-listening");
      activeVoiceRecognition = null;
      activeVoiceTarget = "";
      updateLectiveVoiceStatus("Microphone could not start. Check internet and microphone permission.");
    }
  }

  function findLectiveField(targetName) {
    const form = document.querySelector("[data-lective-form]");
    if (!form) return null;
    return Array.from(form.querySelectorAll("input[name], textarea[name]")).find((field) => field.name === targetName) || null;
  }

  function insertVoiceText(field, transcript) {
    const text = cleanText(transcript);
    if (!text) return;
    const start = typeof field.selectionStart === "number" ? field.selectionStart : field.value.length;
    const end = typeof field.selectionEnd === "number" ? field.selectionEnd : start;
    const before = field.value.slice(0, start);
    const after = field.value.slice(end);
    const prefix = before && !/\s$/.test(before) ? " " : "";
    const suffix = after && !/^\s/.test(after) ? " " : "";
    const insert = `${prefix}${text}${suffix}`;
    if (typeof field.setRangeText === "function") {
      field.setRangeText(insert, start, end, "end");
    } else {
      field.value = `${before}${insert}${after}`;
    }
    field.dispatchEvent(new Event("input", { bubbles: true }));
    field.focus();
  }

  function lectiveVoiceButton(targetName) {
    return Array.from(document.querySelectorAll("[data-action='voice-type']")).find((button) => button.dataset.voiceTarget === targetName) || null;
  }

  function clearLectiveVoiceButtons() {
    document.querySelectorAll("[data-action='voice-type']").forEach((button) => button.classList.remove("is-listening"));
  }

  window.KGMReceiveVoiceText = function (targetName, transcript) {
    const target = targetName || activeVoiceTarget;
    const field = findLectiveField(target);
    clearLectiveVoiceButtons();
    activeVoiceTarget = "";
    if (!field) {
      updateLectiveVoiceStatus("The spoken words were received, but the field is no longer open.");
      return;
    }
    if (!cleanText(transcript || "")) {
      updateLectiveVoiceStatus("No clear words were heard. Tap the microphone and try again.");
      return;
    }
    insertVoiceText(field, transcript);
    updateLectiveVoiceStatus(`Spoken words added to ${fieldLabelFromName(target)}.`);
  };

  window.KGMVoiceError = function (targetName, message) {
    clearLectiveVoiceButtons();
    activeVoiceTarget = "";
    updateLectiveVoiceStatus(message || "Microphone typing stopped. Check internet and microphone permission.");
  };

  function updateLectiveVoiceStatus(message) {
    const status = document.querySelector("[data-lective-voice-status]");
    if (status) status.textContent = message;
  }

  function fieldLabelFromName(name) {
    const labels = {
      topic: "Topic",
      content: "Question",
      question: "Question",
      rightAnswer: "A. Right answer",
      wrongAnswer1: "B. Wrong answer",
      wrongAnswer2: "C. Wrong answer"
    };
    return labels[name] || "this field";
  }

  function competitionQrHtml(joinUrl) {
    if (!joinUrl) {
      return `<div class="competition-qr-empty">QR code appears after Start Host.</div>`;
    }
    try {
      return `
        <div class="competition-qr-wrap">
          ${qrSvg(joinUrl)}
          <span>Scan to join</span>
        </div>
      `;
    } catch (error) {
      return `<div class="competition-qr-empty">QR could not be created. Use the join address.</div>`;
    }
  }

  function qrSvg(text) {
    const matrix = qrMatrix(text);
    const border = 4;
    const size = matrix.length + border * 2;
    const rects = [];
    matrix.forEach((row, y) => {
      row.forEach((dark, x) => {
        if (dark) rects.push(`<rect x="${x + border}" y="${y + border}" width="1" height="1"/>`);
      });
    });
    return `
      <svg class="competition-qr" viewBox="0 0 ${size} ${size}" role="img" aria-label="Competition join QR code">
        <rect width="${size}" height="${size}" fill="#fff"/>
        <g fill="#000">${rects.join("")}</g>
      </svg>
    `;
  }

  function qrMatrix(text) {
    const version = 3;
    const size = 29;
    const dataCodewords = 55;
    const errorCodewords = 15;
    const data = qrDataCodewords(text, dataCodewords);
    const codewords = data.concat(qrErrorCodewords(data, errorCodewords));
    const modules = Array.from({ length: size }, () => Array(size).fill(false));
    const reserved = Array.from({ length: size }, () => Array(size).fill(false));
    const setFunction = (x, y, dark) => {
      if (x < 0 || y < 0 || x >= size || y >= size) return;
      modules[y][x] = Boolean(dark);
      reserved[y][x] = true;
    };
    const finder = (left, top) => {
      for (let y = -1; y <= 7; y += 1) {
        for (let x = -1; x <= 7; x += 1) {
          const xx = left + x;
          const yy = top + y;
          const dark = x >= 0 && x <= 6 && y >= 0 && y <= 6
            && (x === 0 || x === 6 || y === 0 || y === 6 || (x >= 2 && x <= 4 && y >= 2 && y <= 4));
          setFunction(xx, yy, dark);
        }
      }
    };
    finder(0, 0);
    finder(size - 7, 0);
    finder(0, size - 7);
    for (let i = 8; i < size - 8; i += 1) {
      setFunction(i, 6, i % 2 === 0);
      setFunction(6, i, i % 2 === 0);
    }
    const alignment = (cx, cy) => {
      for (let y = -2; y <= 2; y += 1) {
        for (let x = -2; x <= 2; x += 1) {
          setFunction(cx + x, cy + y, Math.max(Math.abs(x), Math.abs(y)) === 2 || (x === 0 && y === 0));
        }
      }
    };
    alignment(22, 22);
    setFunction(8, 4 * version + 9, true);
    qrFormat(1, 0).forEach(([x, y, dark]) => setFunction(x, y, dark));

    const bits = [];
    codewords.forEach((word) => appendBits(bits, word, 8));
    let bitIndex = 0;
    let upward = true;
    for (let right = size - 1; right >= 1; right -= 2) {
      if (right === 6) right = 5;
      for (let vertical = 0; vertical < size; vertical += 1) {
        const y = upward ? size - 1 - vertical : vertical;
        for (let dx = 0; dx < 2; dx += 1) {
          const x = right - dx;
          if (reserved[y][x]) continue;
          let dark = bitIndex < bits.length ? bits[bitIndex] : false;
          bitIndex += 1;
          if ((x + y) % 2 === 0) dark = !dark;
          modules[y][x] = dark;
        }
      }
      upward = !upward;
    }
    return modules;
  }

  function qrDataCodewords(text, capacity) {
    const bytes = utf8Bytes(text);
    const bits = [];
    appendBits(bits, 0x4, 4);
    appendBits(bits, bytes.length, 8);
    bytes.forEach((byte) => appendBits(bits, byte, 8));
    const capacityBits = capacity * 8;
    if (bits.length > capacityBits) throw new Error("QR text too long");
    appendBits(bits, 0, Math.min(4, capacityBits - bits.length));
    while (bits.length % 8) bits.push(false);
    const result = [];
    for (let i = 0; i < bits.length; i += 8) {
      let value = 0;
      for (let j = 0; j < 8; j += 1) value = (value << 1) | (bits[i + j] ? 1 : 0);
      result.push(value);
    }
    for (let pad = 0; result.length < capacity; pad += 1) {
      result.push(pad % 2 === 0 ? 0xec : 0x11);
    }
    return result;
  }

  function utf8Bytes(text) {
    if (window.TextEncoder) return Array.from(new TextEncoder().encode(text));
    return unescape(encodeURIComponent(text)).split("").map((char) => char.charCodeAt(0));
  }

  function appendBits(bits, value, length) {
    for (let i = length - 1; i >= 0; i -= 1) bits.push(((value >>> i) & 1) !== 0);
  }

  function qrErrorCodewords(data, degree) {
    const tables = qrGaloisTables();
    const multiply = (a, b) => (a && b ? tables.exp[tables.log[a] + tables.log[b]] : 0);
    let generator = [1];
    for (let i = 0; i < degree; i += 1) {
      const next = Array(generator.length + 1).fill(0);
      generator.forEach((coef, index) => {
        next[index] ^= multiply(coef, 1);
        next[index + 1] ^= multiply(coef, tables.exp[i]);
      });
      generator = next;
    }
    const result = Array(degree).fill(0);
    data.forEach((word) => {
      const factor = word ^ result.shift();
      result.push(0);
      for (let i = 0; i < degree; i += 1) result[i] ^= multiply(generator[i + 1], factor);
    });
    return result;
  }

  function qrGaloisTables() {
    const exp = Array(512).fill(0);
    const log = Array(256).fill(0);
    let value = 1;
    for (let i = 0; i < 255; i += 1) {
      exp[i] = value;
      log[value] = i;
      value <<= 1;
      if (value & 0x100) value ^= 0x11d;
    }
    for (let i = 255; i < exp.length; i += 1) exp[i] = exp[i - 255];
    return { exp, log };
  }

  function qrFormat(errorLevelBits, mask) {
    const data = (errorLevelBits << 3) | mask;
    let remainder = data << 10;
    for (let i = 14; i >= 10; i -= 1) {
      if (((remainder >>> i) & 1) !== 0) remainder ^= 0x537 << (i - 10);
    }
    const bits = ((data << 10) | remainder) ^ 0x5412;
    const get = (index) => ((bits >>> index) & 1) !== 0;
    const size = 29;
    const coords = [];
    for (let i = 0; i <= 5; i += 1) coords.push([8, i, get(i)]);
    coords.push([8, 7, get(6)], [8, 8, get(7)], [7, 8, get(8)]);
    for (let i = 9; i < 15; i += 1) coords.push([14 - i, 8, get(i)]);
    for (let i = 0; i < 8; i += 1) coords.push([size - 1 - i, 8, get(i)]);
    for (let i = 8; i < 15; i += 1) coords.push([8, size - 15 + i, get(i)]);
    return coords;
  }

  function appFrame(content, user = activeUser(), session = currentSession()) {
    const completed = user ? user.highestCompleted || 0 : 0;
    const levelLabel = session ? courseLabel(session.levelId) : `Level ${Math.max(1, completed + 1)}`;
    const countLabel = session ? `${session.index + 1}/${session.questions.length}` : "Ready";
    const routeClass = `app-route-${String(route || "login").replace(/[^a-z0-9-]/gi, "-").toLowerCase()}`;
    return `
      <div class="app-frame ${routeClass}">
        <header class="topbar">
          ${backButtonHtml("Back", "topbar-back")}
          <div class="user-chip">
            <span class="avatar">${initials(user ? user.displayName : "KGM")}</span>
            <span>${escapeHtml(user ? user.displayName : "Learner")}</span>
          </div>
          <div class="status-pill">Completed Level ${completed}</div>
          <div class="status-pill">${levelLabel} · ${countLabel}</div>
        </header>
        ${toastMessage ? `<div class="app-toast">${escapeHtml(consumeToast())}</div>` : ""}
        ${session ? courseProgress(session) : ""}
        ${content}
        ${bottomNav()}
      </div>
    `;
  }

  function backButtonHtml(label = "Back", extraClass = "") {
    return `<button class="ghost-button app-back-button ${escapeHtml(extraClass)}" type="button" data-action="app-back" aria-label="Go back one step"><span aria-hidden="true">‹</span><span>${escapeHtml(label)}</span></button>`;
  }

  function courseProgress(session) {
    const totalQuestions = session.questions.length || 1;
    const currentQuestionNumber = Math.min(totalQuestions, (session.index || 0) + 1);
    const answeredQuestions = Math.min(totalQuestions, (session.answers || []).length);
    const maxMarks = totalQuestions * POINTS_PER_QUESTION;
    const marks = session.score || 0;
    const reachedPercent = Math.max(0, Math.min(100, Math.round((currentQuestionNumber / totalQuestions) * 100)));
    const answeredPercent = Math.max(0, Math.min(100, Math.round((answeredQuestions / totalQuestions) * 100)));

    return `
      <section class="course-progress-panel" aria-label="Current course progress">
        <div class="course-progress-row">
          <span>Marks Achieved <strong>${marks.toLocaleString()}/${maxMarks.toLocaleString()}</strong></span>
          <span>Reached <strong>${currentQuestionNumber.toLocaleString()}/${totalQuestions.toLocaleString()}</strong></span>
          <span>Answered <strong>${answeredQuestions.toLocaleString()}/${totalQuestions.toLocaleString()}</strong></span>
        </div>
        <div class="course-progress-track" role="progressbar" aria-label="Course unit progress" aria-valuemin="0" aria-valuemax="100" aria-valuenow="${reachedPercent}">
          <span class="course-progress-fill" style="width: ${reachedPercent}%"></span>
          <span class="course-progress-answer-fill" style="width: ${answeredPercent}%"></span>
        </div>
      </section>
    `;
  }

  function bottomNav() {
    const secondAction = route === "lective-study" ? "elective-subjects" : "show-reading";
    const secondLabel = route === "lective-study" ? "Elective" : "Reading";
    return `
      <nav class="bottom-nav" aria-label="Application navigation">
        <button class="nav-button" data-action="home"><span class="nav-icon">⌂</span><span class="nav-label">Home</span></button>
        <button class="nav-button" data-action="${secondAction}"><span class="nav-icon">✦</span><span class="nav-label">${secondLabel}</span></button>
        <button class="nav-button" data-action="course-units"><span class="nav-icon">★</span><span class="nav-label">Course Units</span></button>
        <button class="nav-button" data-action="marks"><span class="nav-icon">▱</span><span class="nav-label">Marks</span></button>
        <button class="nav-button" data-action="users"><span class="nav-icon">◎</span><span class="nav-label">All Users</span></button>
      </nav>
    `;
  }

  function levelTile(level, user) {
    const best = user.bestScores[String(level.id)];
    const completed = level.id === BABY_COURSE.id ? Boolean(user.babyCompleted) : (user.highestCompleted || 0) >= level.id;
    const failedHere = user.lastCertificate && user.lastCertificate.levelId === level.id && !user.lastCertificate.passed;
    return `
      <button class="level-tile ${best ? "is-current" : ""} ${completed ? "is-completed" : ""}" ${completed ? "disabled" : `data-action="start-level" data-level="${level.id}"`}>
        <span class="stars-line">${"★".repeat(level.id)}</span>
        <span>
          <strong>${escapeHtml(courseLabel(level.id))}</strong>
          <small>${levelStoryCountLabel(level.id)}</small>
        </span>
        <span class="score-line">${completed ? "Completed" : failedHere ? "Repeat course" : best ? `Best ${best.percent}%` : "Start course"}</span>
      </button>
    `;
  }

  function scoreRow(level, user) {
    const best = user.bestScores[String(level.id)];
    const scoreLabel = best ? `${best.score}/${best.maxScore} marks` : "Not tried";
    const questionsLabel = best ? `${best.questionsAsked || Math.round(best.maxScore / POINTS_PER_QUESTION)} questions` : levelStoryCountLabel(level.id);
    return `
      <div class="table-row">
        <strong>${escapeHtml(courseLabel(level.id))} ${level.id > 0 ? "★".repeat(level.id) : ""}</strong>
        <span>${questionsLabel}</span>
        <span class="score-line">${best ? `${scoreLabel} - ${best.percent}%` : "Not tried"}</span>
      </div>
    `;
  }

  function helpCard(title, text) {
    return `
      <div class="help-card">
        <strong>${escapeHtml(title)}</strong>
        <span>${escapeHtml(text)}</span>
      </div>
    `;
  }

  function consumeToast() {
    const message = toastMessage;
    toastMessage = "";
    return message;
  }

  function learningProgressSummary(user) {
    const nextLevel = resolveStartLevelId(user, user.preferredLevel || 1);
    const completedCourses = (user.babyCompleted ? 1 : 0) + (user.highestCompleted || 0);
    const totalScores = Object.values(user.bestScores || {});
    const questions = totalScores.reduce((sum, score) => sum + Number(score.questionsAsked || 0), 0);
    return `
      <div class="learning-summary">
        <span><strong>${completedCourses}</strong> courses completed</span>
        <span><strong>${questions.toLocaleString()}</strong> questions answered</span>
        <span><strong>${nextLevel === null ? "Done" : escapeHtml(courseLabel(nextLevel))}</strong> next goal</span>
      </div>
    `;
  }

  function leaderCard(leader, index) {
    return `
      <div class="leader-card">
        <span class="leader-rank">${index + 1}</span>
        <span>
          <span class="leader-name">${escapeHtml(leader.name)}</span>
          <span class="leader-points">★ ${leader.points.toLocaleString()}</span>
        </span>
      </div>
    `;
  }

  function loginTopReaders() {
    const leaders = leaderBoard().filter((leader) => leader.points > 0).slice(0, 3);
    const slots = [0, 1, 2].map((index) => {
      const leader = leaders[index];
      return `
        <div class="login-reader-card ${leader ? "" : "is-empty"}">
          <span class="login-reader-rank">${index + 1}</span>
          <span class="login-reader-avatar">${leader ? escapeHtml(initials(leader.name)) : "-"}</span>
          <span class="login-reader-name">${leader ? escapeHtml(leader.name) : "No score yet"}</span>
          <span class="login-reader-score">&#9733; ${leader ? leader.points.toLocaleString() : "0"}</span>
        </div>
      `;
    }).join("");
    return `<div class="login-top-readers" aria-label="Best scorers">${slots}</div>`;
  }

  function answerButton(choice, index, answer, correctIndex) {
    const selected = answer && answer.selectedIndex === index;
    const isCorrect = answer && index === correctIndex;
    const isWrong = answer && selected && index !== correctIndex;
    const isWrongOption = answer && index !== correctIndex;
    const classes = [
      "answer-button",
      selected ? "is-selected" : "",
      isCorrect ? "is-correct" : "",
      isWrong ? "is-wrong" : "",
      isWrongOption ? "is-wrong-option" : ""
    ].filter(Boolean).join(" ");
    const mark = answer ? (index === correctIndex ? "&#10003;" : "x") : "";
    return `
      <button class="${classes}" ${answer ? "disabled" : ""} data-action="answer" data-answer="${index}">
        <span class="answer-letter">${String.fromCharCode(65 + index)}.</span>
        <span>${escapeHtml(choice.text)}</span>
        <span class="answer-mark">${mark}</span>
      </button>
    `;
  }

  function lectiveStudyAnswerButton(choice, index, answer, correctIndex) {
    const selected = answer && answer.selectedIndex === index;
    const isCorrect = answer && index === correctIndex;
    const isWrong = answer && selected && index !== correctIndex;
    const isWrongOption = answer && index !== correctIndex;
    const classes = [
      "answer-button",
      selected ? "is-selected" : "",
      isCorrect ? "is-correct" : "",
      isWrong ? "is-wrong" : "",
      isWrongOption ? "is-wrong-option" : ""
    ].filter(Boolean).join(" ");
    const mark = answer ? (index === correctIndex ? "&#10003;" : "x") : "";
    return `
      <button class="${classes}" ${answer ? "disabled" : ""} data-action="lective-study-answer" data-answer="${index}">
        <span class="answer-letter">${String.fromCharCode(65 + index)}.</span>
        <span>${escapeHtml(choice.text)}</span>
        <span class="answer-mark">${mark}</span>
      </button>
    `;
  }

  function resultBanner(answer, choices, correctIndex) {
    if (answer.correct) {
      return `<div class="result-banner good">Correct. You earned ${POINTS_PER_QUESTION} marks. Keep going.</div>`;
    }
    if (answer.timedOut) {
      return `<div class="result-banner bad">Time finished. Keep calm and continue with courage.</div>`;
    }
    return `<div class="result-banner bad">Keep going. Every attempt is helping you grow.</div>`;
  }

  function confirmSensitiveAction(action, control) {
    const message = confirmationMessageForAction(action, control);
    if (!message) return true;
    return window.confirm(displayBrandText(message));
  }

  function confirmationMessageForAction(action, control) {
    switch (action) {
      case "logout":
        return "Exit this learner account and return to login?";
      case "delete-saved-certificate":
        return "Delete this saved certificate from the app folder?";
      case "lective-delete-story":
        return "Delete this KGM School question from this device?";
      case "elective-repeat-class":
        return "Repeat this elective class from the beginning for a better score?";
      case "elective-repeat-checkpoint":
        return "Repeat these checkpoint questions for a better score?";
      case "retry-level":
        return "Repeat this course level from the beginning?";
      case "search-reset-profile":
        return "Change the search user and remove the current saved search profile?";
      case "search-clear":
        return "Clear this search text and the current search results?";
      case "clear-crash-log":
        return "Clear the diagnostic log from this device?";
      case "new-online-competition":
        return "Start a new named online competition? The current online competition will move to saved competitions.";
      case "new-offline-competition":
        return "Start a new named offline competition? The current offline competition will move to saved competitions.";
      case "stop-competition-host":
        return "Stop the online competition host now?";
      case "online-host-complete":
        return "Complete this online competition now and issue certificates?";
      case "offline-complete-competition":
        return "Complete this offline competition now and issue certificates?";
      case "repeat-offline-competition":
        return "Repeat this competition with the same competitors and selected material?";
      case "offline-reset-competition":
        return "Clear the offline competition setup and competitors from this draft?";
      case "start-online-competition": {
        const host = store.onlineCompetition;
        const state = refreshCompetitionState();
        const running = state && state.running;
        if (host && (running || host.phase === "quiz" || host.phase === "reading" || host.phase === "paused")) {
          return "Restart the online competition host now?";
        }
        return "";
      }
      case "competition-online-host":
        if (store.onlineCompetition && store.onlineCompetition.coordinator) {
          return "Open a fresh online host setup? Your current online competition remains in saved competitions.";
        }
        return "";
      case "competition-offline":
        if (store.offlineCompetition && store.offlineCompetition.active) {
          return "Open a fresh offline competition setup? Your current offline competition remains in saved competitions.";
        }
        return "";
      default:
        return "";
    }
  }

  function handlePointerSound(event) {
    rememberPointerStart(event);
    if (!event || !event.target || !event.target.closest) return;
    const control = event.target.closest("button, [data-action], input, textarea, select, label, a, [role='button']");
    if (!control) return;
    if (isDisabledInteractive(control)) return;
    playAppSound("click");
  }

  function rememberPointerStart(event) {
    if (!event) return;
    lastPointerStart = {
      id: event.pointerId,
      x: Number(event.clientX || 0),
      y: Number(event.clientY || 0),
      time: Date.now()
    };
  }

  function handleButtonClickFeedback(event) {
    if (!event || !event.target || !event.target.closest) return;
    const control = event.target.closest("button, [data-action], [role='button']");
    if (!control || isDisabledInteractive(control)) return;
    triggerButtonPressShake(control);
  }

  function handleForgivingEdgeTap(event) {
    if (!event || !event.target || !event.target.closest) return;
    if (event.button && event.button !== 0) return;
    if (event.target.closest("button, [data-action], input, textarea, select, label, a, [role='button']")) return;
    if (isTypingTarget(event.target)) return;
    const x = Number(event.clientX || 0);
    const y = Number(event.clientY || 0);
    if (!x && !y) return;
    if (lastPointerStart && lastPointerStart.id === event.pointerId) {
      const move = Math.hypot(x - lastPointerStart.x, y - lastPointerStart.y);
      if (move > EDGE_TAP_MAX_MOVE) return;
    }
    const control = nearestInteractiveControl(x, y);
    if (!control || isDisabledInteractive(control)) return;
    event.preventDefault();
    event.stopPropagation();
    triggerButtonPressShake(control);
    playAppSound("click");
    control.click();
  }

  function nearestInteractiveControl(x, y) {
    let best = null;
    document.querySelectorAll("button, [data-action], a[href], label, [role='button']").forEach((control) => {
      if (!control || isDisabledInteractive(control) || control.matches("input, textarea, select")) return;
      const rect = control.getBoundingClientRect();
      if (!rect || rect.width <= 0 || rect.height <= 0) return;
      const style = window.getComputedStyle ? window.getComputedStyle(control) : null;
      if (style && (style.visibility === "hidden" || style.display === "none" || style.pointerEvents === "none")) return;
      const withinX = x >= rect.left - EDGE_TAP_RADIUS && x <= rect.right + EDGE_TAP_RADIUS;
      const withinY = y >= rect.top - EDGE_TAP_RADIUS && y <= rect.bottom + EDGE_TAP_RADIUS;
      if (!withinX || !withinY) return;
      const dx = x < rect.left ? rect.left - x : x > rect.right ? x - rect.right : 0;
      const dy = y < rect.top ? rect.top - y : y > rect.bottom ? y - rect.bottom : 0;
      const distance = Math.hypot(dx, dy);
      const area = rect.width * rect.height;
      const score = distance * 100000 + area;
      if (!best || score < best.score) best = { control, score };
    });
    return best ? best.control : null;
  }

  function isDisabledInteractive(control) {
    if (!control || !control.closest) return true;
    if (control.matches && (control.matches(":disabled") || control.matches("[aria-disabled='true']"))) return true;
    return Boolean(control.closest("[disabled], [aria-disabled='true']"));
  }

  function triggerButtonPressShake(control) {
    if (!control || !control.closest) return;
    const button = control.closest("button, [data-action], [role='button']");
    if (!button || !button.classList || button.matches("input, textarea, select")) return;
    button.classList.remove("button-press-shake");
    void button.offsetWidth;
    button.classList.add("button-press-shake");
    window.setTimeout(() => button.classList.remove("button-press-shake"), 360);
  }

  function handleInputSound(event) {
    if (!event || !event.target) return;
    if (isTypingTarget(event.target) || event.target.matches && event.target.matches("select")) {
      playAppSound("click");
    }
  }

  function playAppSound(name) {
    if (store && store.audioMuted) return;
    const src = AUDIO_ASSETS[name];
    if (!src || typeof Audio === "undefined") return;
    try {
      const audio = new Audio(src);
      audio.preload = "auto";
      audio.volume = name === "click" ? 0.45 : 0.95;
      const result = audio.play();
      if (result && typeof result.catch === "function") result.catch(() => {});
    } catch (error) {}
  }

  function playCertificateSoundOnce(cert) {
    if (!cert) return;
    const perfect = isPerfectCertificate(cert);
    const key = `certificate:${cert.certificateId || cert.levelId || "latest"}:${perfect ? "100" : "less"}`;
    if (lastCertificateSoundKey === key) return;
    lastCertificateSoundKey = key;
    window.setTimeout(() => playAppSound(perfect ? "certificate100" : "certificateLess"), 80);
  }

  function playCompetitionCertificateSoundOnce(certificates, context) {
    if (!Array.isArray(certificates) || !certificates.length) return;
    const allPerfect = certificates.every((certificate) => isPerfectCertificate(certificate));
    const ids = certificates.map((certificate) => [
      certificate.name || "",
      certificate.score || 0,
      certificate.maxScore || 0,
      certificate.answered || 0,
      certificate.totalQuestions || 0
    ].join(":")).join("|");
    const key = `competition:${context}:${allPerfect ? "100" : "less"}:${ids}`;
    if (lastCertificateSoundKey === key) return;
    lastCertificateSoundKey = key;
    window.setTimeout(() => playAppSound(allPerfect ? "certificate100" : "certificateLess"), 80);
  }

  function isPerfectCertificate(certificate) {
    const score = Number(certificate && certificate.score);
    const maxScore = Number(certificate && certificate.maxScore);
    const percent = Number(certificate && certificate.percent);
    if (Number.isFinite(percent)) return percent >= 100;
    return Number.isFinite(score) && Number.isFinite(maxScore) && maxScore > 0 && score >= maxScore;
  }

  function handleApplicationKeyboard(event) {
    if (handleAnswerKeyboard(event)) {
      playAppSound("click");
      return true;
    }
    if (handleNavigationKeyboard(event)) {
      playAppSound("click");
      return true;
    }
    return false;
  }

  function handleAnswerKeyboard(event) {
    if (!event || event.repeat || isTypingTarget(event.target)) return false;
    const letter = String(event.key || "").trim().toUpperCase();
    const index = ["A", "B", "C"].indexOf(letter);
    if (index < 0) return false;

    const button = firstUsableAnswerButton([
      `.answer-button[data-action="answer"][data-answer="${index}"]`,
      `.answer-button[data-action="lective-study-answer"][data-answer="${index}"]`,
      `[data-action="offline-answer"][data-choice="${letter}"]`,
      `[data-action="online-join-answer"][data-choice="${letter}"]`,
      `.competition-answer-option[data-choice="${letter}"]`
    ]);

    if (!button) return false;
    event.preventDefault();
    button.click();
    return true;
  }

  function handleNavigationKeyboard(event) {
    if (!event || event.repeat || isTypingTarget(event.target)) return false;
    const key = String(event.key || "");
    if (key === "Home") {
      event.preventDefault();
      keyboardGoHome();
      return true;
    }
    if (key === "End") {
      event.preventDefault();
      keyboardNextAction();
      return true;
    }
    if (key === "PageUp") {
      event.preventDefault();
      keyboardPageScroll(-1);
      return true;
    }
    if (key === "PageDown") {
      event.preventDefault();
      keyboardPageScroll(1);
      return true;
    }
    if (key === "Enter") {
      const button = keyboardPrimaryButton();
      if (!button) return false;
      event.preventDefault();
      button.click();
      return true;
    }
    if (key === "ArrowLeft") {
      event.preventDefault();
      handleDeviceBack();
      return true;
    }
    if (key === "ArrowRight") {
      event.preventDefault();
      keyboardNextAction();
      return true;
    }
    if (key === "ArrowUp") {
      event.preventDefault();
      keyboardPageScroll(-0.35);
      return true;
    }
    if (key === "ArrowDown") {
      event.preventDefault();
      keyboardPageScroll(0.35);
      return true;
    }
    return false;
  }

  function keyboardGoHome() {
    const homeButton = firstUsableAnswerButton([
      `[data-action="home"]`
    ]);
    if (homeButton) {
      homeButton.click();
      return;
    }
    stopTimer();
    route = store.activeUser ? "home" : "login";
    competitionView = "setup";
    render();
  }

  function keyboardNextAction() {
    const button = keyboardPrimaryButton();
    if (button) button.click();
  }

  function keyboardPrimaryButton() {
    return firstUsableAnswerButton([
      `[data-action="next-question"]`,
      `[data-action="lective-study-next"]`,
      `[data-action="lective-study-begin-quiz"]`,
      `[data-action="begin-quiz"]`,
      `[data-action="offline-answer-next"]`,
      `[data-action="offline-next-competitor"]`,
      `[data-action="offline-begin-answers"]`,
      `[data-action="offline-next-reading"]`,
      `[data-action="online-host-next-question"]`,
      `[data-action="online-host-begin-questions"]`,
      `[data-action="online-host-start-reading"]`,
      `[data-action="online-host-resume"]`,
      `[data-action="resume-level"]`,
      `[data-action="start-level"]`,
      `[data-action="start-offline-competition"]`,
      `[data-action="start-online-competition"]`,
      `button.primary-button:not([data-action="answer"]):not([data-action="offline-answer"]):not([data-action="online-join-answer"])`
    ]);
  }

  function keyboardPageScroll(direction) {
    const distance = Math.round(window.innerHeight * 0.78 * direction);
    const target = document.scrollingElement || document.documentElement || document.body;
    if (target && typeof target.scrollBy === "function") {
      target.scrollBy({ top: distance, behavior: "smooth" });
    } else {
      window.scrollBy(0, distance);
    }
  }

  function firstUsableAnswerButton(selectors) {
    for (const selector of selectors) {
      const buttons = Array.from(document.querySelectorAll(selector));
      const button = buttons.find((item) => !item.disabled && item.getAttribute("aria-disabled") !== "true" && item.getClientRects().length > 0);
      if (button) return button;
    }
    return null;
  }

  function isTypingTarget(target) {
    if (!target || !target.closest) return false;
    return Boolean(target.closest("input, textarea, select, [contenteditable='true'], [contenteditable='']"));
  }

  function readingEncouragement(session) {
    const total = session && session.questions ? session.questions.length : 1;
    const current = session ? (session.index || 0) + 1 : 1;
    if (current <= 1) return "Read with attention. Every story is building your faith and memory.";
    if (current >= total) return "You are close to finishing this course unit. Finish strong.";
    return "You are making progress. Read, understand, and answer with confidence.";
  }

  function signIn(name, password, levelId) {
    name = cleanText(name);
    const id = normalizeUserId(name);
    if (!store.users[id]) {
      store.users[id] = {
        id,
        displayName: name,
        preferredLevel: levelId,
        createdAt: new Date().toISOString(),
        highestCompleted: 0,
        bestScores: {},
        history: [],
        activeSession: null,
        lastCertificate: null,
        readStoryIds: [],
        babyCompleted: false
      };
      setLocalPassword(store.users[id], id, password);
    } else {
      const user = ensureUserDefaults(store.users[id]);
      if (!verifyLocalPassword(user, id, password)) {
        loginError = "This name is saved already. Enter the correct password to continue.";
        route = "login";
        renderLogin();
        return;
      }
      user.displayName = name;
      user.preferredLevel = levelId;
      if (password) setLocalPassword(user, id, password);
    }
    store.activeUser = id;
    const user = ensureUserDefaults(store.users[id]);
    levelId = levelId === BABY_COURSE.id || LEVELS.some((level) => level.id === levelId) ? levelId : 1;
    selectedLoginLevel = 1;
    loginError = "";
    saveStore();
    toastMessage = user.activeSession && !user.activeSession.completed ? "Welcome back. Continuing where you stopped." : "Welcome. Your course is ready.";
    if (resumeSavedSession(user)) return;
    startLevel(levelId);
  }

  function signInElectiveClass(formData) {
    const level = normalizeLectiveLevel(formData.get("level") || selectedLectiveStudyLevel);
    const name = cleanText(formData.get("name") || "");
    const password = cleanText(formData.get("password") || "");
    if (!name || !password) {
      competitionLectiveMessage = "Enter the learner name and password to continue this Elective Course.";
      lectiveView = "register";
      renderLectiveCourse();
      return;
    }
    const id = normalizeUserId(name);
    if (!store.users[id]) {
      store.users[id] = {
        id,
        displayName: name,
        preferredLevel: 1,
        createdAt: new Date().toISOString(),
        highestCompleted: 0,
        bestScores: {},
        history: [],
        activeSession: null,
        lastCertificate: null,
        readStoryIds: [],
        babyCompleted: false
      };
      setLocalPassword(store.users[id], id, password);
    } else {
      const user = ensureUserDefaults(store.users[id]);
      if (!verifyLocalPassword(user, id, password)) {
        competitionLectiveMessage = "This learner name is saved already. Enter the correct password to continue where that learner stopped.";
        lectiveView = "register";
        renderLectiveCourse();
        return;
      }
      user.displayName = name;
      if (password) setLocalPassword(user, id, password);
    }
    store.activeUser = id;
    const user = ensureUserDefaults(store.users[id]);
    selectedLectiveStudyLevel = level;
    selectedLectiveStudySubject = normalizeLectiveSubject(selectedLectiveStudySubject, level);
    user.electiveProgress.currentClass = level;
    const savedSession = user.electiveStudy && !user.electiveStudy.completed && normalizeLectiveLevel(user.electiveStudy.level) === level ? user.electiveStudy : null;
    competitionLectiveMessage = savedSession ? "Welcome back. Continuing where you stopped." : `Welcome. ${electiveClassLabel(level)} is ready.`;
    saveStore();
    if (savedSession) {
      store.lectiveStudy = savedSession;
      const resumeSubjects = Array.isArray(savedSession.subjectOrder) && savedSession.subjectOrder.length
        ? savedSession.subjectOrder
        : electiveSubjectsToLoad(level, user);
      if (defaultLectiveSubjectsNeedLoading(level, resumeSubjects)) {
        route = "lective";
        renderLectiveDataLoading(`Loading ${electiveClassLabel(level)} where you stopped...`);
        ensureDefaultLectiveSubjectsLoaded(level, resumeSubjects)
          .then(() => {
            route = "lective-study";
            render();
          })
          .catch(() => {
            route = "lective";
            lectiveView = "subjects";
            renderLectiveCourse();
          });
        return;
      }
      route = "lective-study";
      render();
      return;
    }
    route = "lective";
    lectiveView = level === ELECTIVE_OTHERS_LEVEL ? "others" : "subjects";
    renderLectiveCourse();
  }

  function saveElectiveCombination(formData) {
    const user = activeUser();
    if (!user) {
      lectiveView = "register";
      renderElectiveRegister();
      return;
    }
    const level = normalizeLectiveLevel(formData.get("level") || selectedLectiveStudyLevel);
    if (!isElectiveCombinationLevel(level)) {
      lectiveView = "subjects";
      renderElectiveSubjects();
      return;
    }
    const activeSessionStarted = user.electiveStudy && !user.electiveStudy.completed && normalizeLectiveLevel(user.electiveStudy.level) === level && Array.isArray(user.electiveStudy.answers) && user.electiveStudy.answers.length > 0;
    if (activeSessionStarted) {
      competitionLectiveMessage = "This class has already started. Finish it or repeat it before changing the combination.";
      lectiveView = "combination";
      renderElectiveCombination();
      return;
    }
    const allowed = lectiveSubjectsForLevel(level);
    const selected = uniqueStrings(formData.getAll("subjects"))
      .map((subject) => normalizeLectiveSubject(subject, level))
      .filter((subject) => allowed.includes(subject) && lectiveSubjectIsAvailable(level, subject));
    if (selected.length < ELECTIVE_COMBINATION_MIN || selected.length > ELECTIVE_COMBINATION_MAX) {
      competitionLectiveMessage = `Choose ${ELECTIVE_COMBINATION_MIN} or ${ELECTIVE_COMBINATION_MAX} available subjects for ${electiveClassLabel(level)}.`;
      lectiveView = "combination";
      renderElectiveCombination();
      return;
    }
    ensureUserDefaults(user);
    user.electiveCombinations[level] = selected;
    if (user.electiveStudy && normalizeLectiveLevel(user.electiveStudy.level) === level && !user.electiveStudy.completed) {
      user.electiveStudy.subjectOrder = selected.slice();
      user.electiveStudy.subject = selected[0];
      user.electiveStudy.key = lectiveFolderKey(level, selected[0]);
      user.electiveStudy.subjectOffsets = user.electiveStudy.subjectOffsets && typeof user.electiveStudy.subjectOffsets === "object" ? user.electiveStudy.subjectOffsets : {};
      user.electiveStudy.subjectScores = user.electiveStudy.subjectScores && typeof user.electiveStudy.subjectScores === "object" ? user.electiveStudy.subjectScores : {};
      user.electiveStudy.subjectMaxScores = user.electiveStudy.subjectMaxScores && typeof user.electiveStudy.subjectMaxScores === "object" ? user.electiveStudy.subjectMaxScores : {};
      selected.forEach((subject) => {
        user.electiveStudy.subjectOffsets[subject] = Number(user.electiveStudy.subjectOffsets[subject] || 0);
        user.electiveStudy.subjectScores[subject] = Number(user.electiveStudy.subjectScores[subject] || 0);
        user.electiveStudy.subjectMaxScores[subject] = Number(user.electiveStudy.subjectMaxScores[subject] || 0);
      });
      store.lectiveStudy = user.electiveStudy;
    }
    selectedLectiveStudyLevel = level;
    selectedLectiveStudySubject = selected[0];
    selectedLectiveLevel = level;
    selectedLectiveSubject = selected[0];
    competitionLectiveMessage = `${electiveClassLabel(level)} combination saved: ${selected.join(", ")}.`;
    saveStore();
    lectiveView = "subjects";
    renderElectiveSubjects();
  }

  function registerSearchProfile(formData) {
    const name = cleanText(formData.get("name") || "");
    const password = cleanText(formData.get("password") || "");
    if (!name || !password) {
      searchMessage = "Enter your name and password before searching.";
      renderSearch();
      return;
    }
    store.searchProfile = {
      id: normalizeUserId(name),
      name,
      passwordHash: localPasswordHash(normalizeUserId(name), password),
      registeredAt: new Date().toISOString()
    };
    searchMessage = "Registration saved. Type what you are searching about.";
    saveStore();
    renderSearch();
  }

  function performSearch(formData) {
    const profile = normalizeSearchProfile(store.searchProfile);
    if (!profile) {
      searchMessage = "Register with name and password first.";
      renderSearch();
      return;
    }
    const query = cleanText(formData.get("query") || "");
    const scope = normalizeSearchScope(formData.get("scope") || store.searchScope || "bible");
    const terms = searchTerms(query);
    store.searchLastQuery = query;
    store.searchScope = scope;
    if (!terms.length) {
      searchResults = [];
      searchMessage = "Type at least one clear search word.";
      saveStore();
      renderSearch();
      return;
    }
    if (scope === "school") {
      searchResults = [];
      searchMessage = "";
      pendingSchoolSearch = { query, scope: "school" };
      saveStore();
      renderSearch();
      return;
    }
    finishSearch(query, scope);
  }

  function startConfirmedSchoolSearch(query) {
    store.searchLastQuery = query;
    store.searchScope = "school";
    if (kgmSchoolSearchNeedsLoading()) {
      searchResults = [];
      searchMessage = "Loading all KGM School subjects for search...";
      saveStore();
      renderLectiveDataLoading("Loading all KGM School subjects for search...");
      ensureAllKgmSchoolSearchDataLoaded()
        .then(() => {
          if (route === "search") finishSearch(query, "school");
        })
        .catch(() => {
          if (route !== "search") return;
          searchResults = [];
          searchMessage = "KGM School search could not load every subject on this device. Restart the app and try again.";
          saveStore();
          renderSearch();
        });
      return;
    }
    finishSearch(query, "school");
  }

  function finishSearch(query, scope) {
    searchResults = searchContent(query, scope);
    rememberSearchQuery(query, searchResults.length, scope);
    const partialNote = normalizeSearchScope(scope) === "school" && schoolSearchLoadFailures
      ? ` ${schoolSearchLoadFailures.toLocaleString()} subject file${schoolSearchLoadFailures === 1 ? "" : "s"} could not load.`
      : "";
    searchMessage = searchResults.length
      ? `Showing the ${Math.min(SEARCH_MAX_RESULTS, searchResults.length)} most connected ${searchScopeLabel(scope)} readings.${partialNote}`
      : `No related ${searchScopeLabel(scope)} topic was found yet.${partialNote}`;
    saveStore();
    tryUploadSearchQueries();
    renderSearch();
  }

  function searchContent(query, scope) {
    const terms = searchTerms(query);
    if (!terms.length) return [];
    return searchSourceItems(scope)
      .map((item) => scoreSearchItem(item, terms, query))
      .filter((item) => item.score > 0)
      .sort((a, b) => b.score - a.score || a.title.localeCompare(b.title))
      .slice(0, SEARCH_MAX_RESULTS);
  }

  function normalizeSearchScope(scope) {
    const clean = cleanText(scope || "").toLowerCase();
    return clean === "school" || clean === "kgm school" ? "school" : "bible";
  }

  function searchScopeLabel(scope) {
    return normalizeSearchScope(scope) === "school" ? "KGM School" : "Bible";
  }

  function searchSourceItems(scope) {
    const normalizedScope = normalizeSearchScope(scope);
    const bibleItems = storyBank.map(applyStoryTextEdit).map((story) => ({
      id: `story-${story.id}`,
      kind: "Bible Course",
      title: story.title || story.reference || "Know God More Story",
      reference: story.reference || "",
      content: story.content || "",
      questionsText: story.questions.map((question) => `${question.question} ${question.answers.map((answer) => answer.text).join(" ")}`).join(" ")
    }));
    const schoolStoryMap = new Map();
    flattenLectiveFolders(store.lectiveFolders).concat(defaultLectiveStories).forEach((story, index) => {
      if (!story || typeof story !== "object") return;
      const key = String(story.id || `school-search-${index}`);
      if (!schoolStoryMap.has(key)) schoolStoryMap.set(key, story);
    });
    const electiveItems = buildStoryBank(Array.from(schoolStoryMap.values())).map(applyStoryTextEdit).map((story) => ({
      id: `elective-${story.id}`,
      kind: "KGM School",
      title: story.title || story.reference || "KGM School Question",
      reference: story.reference || "",
      content: story.content || "",
      questionsText: story.questions.map(schoolSearchQuestionText).join(" ")
    }));
    return normalizedScope === "school" ? electiveItems : bibleItems;
  }

  function schoolSearchQuestionText(question) {
    if (!question || typeof question !== "object") return "";
    const correctAnswers = (Array.isArray(question.answers) ? question.answers : [])
      .filter((answer) => answer && answer.correct)
      .map((answer) => cleanText(answer.text || ""))
      .filter(Boolean);
    return cleanText(`${question.question || ""} ${correctAnswers.join(" ")}`);
  }

  function scoreSearchItem(item, terms, query) {
    const titleText = `${item.title} ${item.reference}`.toLowerCase();
    const contentText = String(item.content || "").toLowerCase();
    const questionText = String(item.questionsText || "").toLowerCase();
    const matchedTerms = [];
    let score = 0;
    terms.forEach((term) => {
      const titleCount = countTermOccurrences(titleText, term);
      const contentCount = countTermOccurrences(contentText, term);
      const questionCount = countTermOccurrences(questionText, term);
      if (titleCount || contentCount || questionCount) {
        matchedTerms.push(term);
        score += 10 + titleCount * 8 + questionCount * 4 + contentCount * 2;
      }
    });
    const cleanQuery = cleanText(query).toLowerCase();
    if (cleanQuery && (`${titleText} ${contentText}`).includes(cleanQuery)) score += 18;
    return {
      ...item,
      score,
      matchedTerms,
      snippet: searchSnippet(item.content || item.questionsText, matchedTerms)
    };
  }

  function searchTerms(query) {
    const stopWords = new Set(["a", "an", "and", "are", "as", "at", "be", "for", "from", "in", "is", "it", "of", "on", "or", "the", "to", "was", "were", "what", "when", "where", "who", "why", "with"]);
    return uniqueStrings(cleanText(query).toLowerCase().match(/[a-z0-9]+/g) || [])
      .filter((term) => term.length >= 2 && !stopWords.has(term));
  }

  function countTermOccurrences(text, term) {
    if (!text || !term) return 0;
    const matches = text.match(new RegExp(`\\b${escapeRegExp(term)}\\b`, "g"));
    return matches ? matches.length : 0;
  }

  function searchSnippet(content, terms) {
    const text = cleanText(content);
    if (!text) return "";
    const lower = text.toLowerCase();
    const firstIndex = Math.max(0, terms.reduce((best, term) => {
      const index = lower.indexOf(term);
      return index >= 0 && (best < 0 || index < best) ? index : best;
    }, -1));
    const start = Math.max(0, firstIndex - 80);
    const snippet = text.slice(start, start + 300);
    return `${start > 0 ? "... " : ""}${snippet}${start + 300 < text.length ? " ..." : ""}`;
  }

  function rememberSearchQuery(query, resultCount, scope) {
    const profile = normalizeSearchProfile(store.searchProfile);
    store.searchQueries = normalizeSearchQueries(store.searchQueries);
    store.searchQueries.push({
      id: `search-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`,
      query,
      scope: normalizeSearchScope(scope),
      terms: searchTerms(query),
      resultCount,
      userId: profile ? profile.id : "",
      userName: profile ? profile.name : "",
      createdAt: new Date().toISOString(),
      uploadedAt: ""
    });
    store.searchQueries = store.searchQueries.slice(-500);
  }

  async function tryUploadSearchQueries() {
    if (!SEARCH_UPLOAD_ENDPOINT || typeof fetch !== "function") return;
    if (typeof navigator !== "undefined" && navigator.onLine === false) return;
    const upload = normalizeSearchUpload(store.searchUpload);
    const now = Date.now();
    if (upload.lastAttemptAt && now - upload.lastAttemptAt < SEARCH_UPLOAD_INTERVAL_MS) return;
    const pending = normalizeSearchQueries(store.searchQueries).filter((item) => !item.uploadedAt);
    if (!pending.length) return;
    store.searchUpload = { ...upload, lastAttemptAt: now };
    saveStore();
    try {
      const response = await fetch(SEARCH_UPLOAD_ENDPOINT, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          clientId: serviceClientId(),
          appVersion: APP_VERSION,
          appBuild: APP_BUILD,
          sentAt: new Date().toISOString(),
          searches: pending.slice(0, 200)
        })
      });
      if (!response.ok) return;
      const uploadedAt = new Date().toISOString();
      const uploadedIds = new Set(pending.slice(0, 200).map((item) => item.id));
      store.searchQueries = normalizeSearchQueries(store.searchQueries).map((item) => uploadedIds.has(item.id) ? { ...item, uploadedAt } : item);
      store.searchUpload = { lastAttemptAt: now, lastSuccessAt: now };
      saveStore();
    } catch (error) {}
  }

  function normalizeSearchProfile(profile) {
    if (!profile || typeof profile !== "object") return null;
    const name = cleanText(profile.name || "");
    if (!name) return null;
    const id = cleanText(profile.id || normalizeUserId(name));
    const normalized = {
      id,
      name,
      passwordHash: cleanText(profile.passwordHash || ""),
      registeredAt: profile.registeredAt || new Date().toISOString()
    };
    if (!hasLocalPasswordHash(normalized) && profile.password) {
      setLocalPassword(normalized, id, profile.password);
    }
    if (!hasLocalPasswordHash(normalized)) return null;
    return {
      id: normalized.id,
      name: normalized.name,
      passwordHash: normalized.passwordHash,
      registeredAt: normalized.registeredAt
    };
  }

  function normalizeSearchQueries(queries) {
    return (Array.isArray(queries) ? queries : [])
      .filter((item) => item && typeof item === "object" && cleanText(item.query || ""))
      .map((item) => ({
        id: cleanText(item.id || `search-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`),
        query: cleanText(item.query || ""),
        scope: normalizeSearchScope(item.scope || "bible"),
        terms: Array.isArray(item.terms) ? uniqueStrings(item.terms) : searchTerms(item.query || ""),
        resultCount: Math.max(0, Number(item.resultCount || 0)),
        userId: cleanText(item.userId || ""),
        userName: cleanText(item.userName || ""),
        createdAt: item.createdAt || new Date().toISOString(),
        uploadedAt: item.uploadedAt || ""
      }));
  }

  function normalizeSearchUpload(upload) {
    return {
      lastAttemptAt: Number(upload && upload.lastAttemptAt || 0),
      lastSuccessAt: Number(upload && upload.lastSuccessAt || 0)
    };
  }

  function normalizeSchoolCustomization(customization) {
    const name = cleanText(customization && customization.name || CUSTOM_SCHOOL_NAME_DEFAULT).slice(0, 48) || CUSTOM_SCHOOL_NAME_DEFAULT;
    const badgeDataUrl = String(customization && customization.badgeDataUrl || "");
    return {
      name,
      badgeDataUrl: /^data:image\/jpe?g;base64,/i.test(badgeDataUrl) ? badgeDataUrl : ""
    };
  }

  function startLevel(levelId, forceExactLevel = false) {
    const user = activeUser();
    ensureUserDefaults(user);
    const exactLevelId = Number(levelId);
    const resolvedLevelId = forceExactLevel && courseById(exactLevelId) ? exactLevelId : resolveStartLevelId(user, levelId);
    if (resolvedLevelId === null || resolvedLevelId === undefined) {
      user.activeSession = null;
      saveStore();
      route = "home";
      render();
      window.setTimeout(() => {
        window.alert("This learner has completed all course units.");
      }, 0);
      return;
    }
    const level = courseById(resolvedLevelId) || LEVELS[0];
    const questions = questionsForLevel(level.id, user);
    if (!questions.length) {
      user.activeSession = null;
      saveStore();
      route = "home";
      render();
      window.setTimeout(() => {
        window.alert("This course unit does not have stories yet.");
      }, 0);
      return;
    }
    const storyIds = uniqueStrings(questions.map((item) => item.storyId));
    user.activeSession = {
      levelId: level.id,
      phase: "reading",
      index: 0,
      score: 0,
      questions: questions.map((item) => item.id),
      storyIds,
      storyCount: storyIds.length,
      answers: [],
      startedAt: new Date().toISOString(),
      completed: false,
      deadline: null
    };
    saveStore();
    route = "reading";
    render();
  }

  function startTimer() {
    const session = currentSession();
    if (!session || session.completed) return;
    if (!session.deadline || session.deadline < Date.now()) {
      session.deadline = Date.now() + SECONDS_PER_QUESTION * 1000;
      saveStore();
    }
    stopTimer(false);
    timerId = window.setInterval(() => {
      const active = currentSession();
      if (!active || active.phase !== "quiz" || currentAnswer(active)) {
        stopTimer();
        return;
      }
      const remaining = Math.ceil((active.deadline - Date.now()) / 1000);
      if (remaining <= 0) {
        selectAnswer(null, true);
        return;
      }
      const ring = document.querySelector(".timer-ring");
      if (ring) {
        ring.style.setProperty("--timer-angle", `${Math.round((remaining / SECONDS_PER_QUESTION) * 360)}deg`);
        const label = ring.querySelector("strong");
        if (label) label.textContent = formatClock(remaining);
      }
    }, 250);
  }

  function stopTimer(clearDeadline = false) {
    if (timerId) {
      window.clearInterval(timerId);
      timerId = null;
    }
    if (clearDeadline) {
      const session = currentSession();
      if (session) session.deadline = null;
    }
  }

  function selectAnswer(selectedIndex, timedOut = false) {
    const session = currentSession();
    if (!session || session.completed || currentAnswer(session)) return;
    const item = currentQuestion(session);
    if (!item) return;
    const choices = displayAnswersForQuestion(item, session);
    const selectedChoice = choices[selectedIndex] || null;
    const correctIndex = choices.findIndex((choice) => choice.correct);
    const correct = selectedIndex === correctIndex;
    session.answers.push({
      questionId: item.id,
      index: session.index,
      selectedIndex,
      selectedOriginalIndex: selectedChoice ? selectedChoice.originalIndex : selectedIndex,
      correctIndex,
      correctOriginalIndex: choices[correctIndex] ? choices[correctIndex].originalIndex : correctIndex,
      correct,
      timedOut,
      marks: correct ? POINTS_PER_QUESTION : 0,
      answeredAt: new Date().toISOString()
    });
    if (correct) session.score += POINTS_PER_QUESTION;
    playAppSound(correct ? "correct" : "wrong");
    session.deadline = null;
    stopTimer();
    saveStore();
    renderQuiz();
  }

  function nextQuestion() {
    const session = currentSession();
    if (!session || session.completed) return;
    if (!currentAnswer(session)) return;
    session.deadline = null;
    if (session.index + 1 >= session.questions.length) {
      completeLevel(session);
      return;
    }
    const currentItem = currentQuestion(session);
    session.index += 1;
    const nextItem = currentQuestion(session);
    const sameStory = currentItem && nextItem && String(currentItem.storyId) === String(nextItem.storyId);
    session.phase = sameStory ? "quiz" : "reading";
    saveStore();
    route = session.phase;
    render();
  }

  function completeLevel(session) {
    const user = activeUser();
    ensureUserDefaults(user);
    const level = courseById(session.levelId) || LEVELS[0];
    const maxScore = session.questions.length * POINTS_PER_QUESTION;
    const percent = Math.round((session.score / maxScore) * 100);
    const requiredPercent = passPercentForLevel(level.id);
    const passed = percent >= requiredPercent;
    const sessionStoryIds = session.storyIds && session.storyIds.length ? session.storyIds : storyIdsFromQuestionIds(session.questions);
    const storyCount = uniqueStrings(sessionStoryIds).length || levelStoryCount(level.id);
    const missedQuestions = missedQuestionsForSession(session);
    if (passed) markStoriesRead(user, sessionStoryIds);
    const certificate = {
      certificateId: makeCertificateId("KGM"),
      levelId: level.id,
      score: session.score,
      maxScore,
      percent,
      passed,
      passPercent: requiredPercent,
      courseTitle: courseLabel(level.id),
      storyCount,
      questionsAsked: session.questions.length,
      missedQuestions,
      date: new Date().toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric" })
    };

    const best = user.bestScores[String(level.id)];
    if (!best || session.score > best.score) {
      user.bestScores[String(level.id)] = certificate;
    }
    if (passed) {
      if (level.id === BABY_COURSE.id) {
        user.babyCompleted = true;
      } else {
        user.highestCompleted = Math.max(user.highestCompleted || 0, level.id);
      }
    }
    user.history.unshift({
      ...certificate,
      completedAt: new Date().toISOString()
    });
    user.history = user.history.slice(0, 20);
    user.lastCertificate = certificate;
    user.activeSession = null;
    saveStore();
    route = "certificate";
    render();
  }

  async function renderCertificateJpeg(user, cert) {
    if (cert && cert.kind === "lective") {
      const storyCount = Number(cert.storyCount || 0);
      const questionsAsked = Number(cert.questionsAsked || Math.round((cert.maxScore || 0) / POINTS_PER_QUESTION));
      const folderLabel = `${cert.lectiveLevel || DEFAULT_LECTIVE_LEVEL} ${cert.lectiveSubject || DEFAULT_LECTIVE_SUBJECT}`;
      return renderAchievementCertificateJpeg({
        name: user.displayName,
        completedLabel: cert.completedLabel || "KGM School",
        starLine: cert.starLine || folderLabel,
        storyLine: cert.storyLine || `${storyCount.toLocaleString()} questions completed`,
        score: cert.score,
        maxScore: cert.maxScore,
        questionsLine: cert.questionsLine || `${questionsAsked.toLocaleString()} questions answered`,
        promotionText: cert.promotionText || "ELECTIVE COURSE COMPLETED",
        promotionLevel: cert.promotionLevel || folderLabel,
        adviceText: cert.adviceText || "",
        certificateId: cert.certificateId || makeCertificateId("KGM-LECT"),
        date: cert.date
      });
    }
    const level = courseById(cert.levelId);
    const storyCount = cert.storyCount || (level ? levelStoryCount(level.id) : "");
    const questionsAsked = cert.questionsAsked || Math.round(cert.maxScore / POINTS_PER_QUESTION);
    const nextLevel = nextCourseAfter(cert.levelId);
    const promotionText = cert.passed && nextLevel ? "PROMOTED TO THE NEXT LEVEL" : cert.passed ? "COMPLETED THE FINAL LEVEL" : "REPEAT FOR BETTER PERFORMANCE";
    const promotionLevel = cert.passed && nextLevel ? courseLabel(nextLevel.id) : cert.passed ? "Final Level" : courseLabel(cert.levelId);
    const completedLabel = courseLabel(cert.levelId);
    const starLine = cert.levelId === BABY_COURSE.id ? "\u2605" : "\u2605".repeat(cert.levelId);
    return renderAchievementCertificateJpeg({
      name: user.displayName,
      completedLabel,
      starLine,
      storyLine: `${storyCount} stories completed`,
      score: cert.score,
      maxScore: cert.maxScore,
      questionsLine: `${questionsAsked} questions asked`,
      promotionText,
      promotionLevel,
      certificateId: cert.certificateId || makeCertificateId("KGM"),
      date: cert.date
    });
  }

  async function renderCompetitionCertificateJpeg(certificate) {
    const answered = Number(certificate.answered || 0);
    const totalQuestions = Number(certificate.totalQuestions || 0);
    const partial = totalQuestions > 0 && answered < totalQuestions;
    const courseName = certificateBaseCourseLabel(competitionCertificateCourseLabel(certificate.courseLabel || "Competition"));
    const levelLine = competitionCertificateLevelLabel(certificate);
    return renderAchievementCertificateJpeg({
      compactCompetition: true,
      name: certificate.name || "Competitor",
      completedLabel: courseName,
      starLine: partial ? "PARTIAL COMPETITION" : "FULL COMPETITION",
      storyLine: levelLine ? `Level: ${levelLine}` : "",
      score: Number(certificate.score || 0),
      maxScore: Number(certificate.maxScore || 0),
      questionsLine: `${answered.toLocaleString()} / ${totalQuestions.toLocaleString()} questions answered`,
      promotionText: partial ? "PARTIAL COMPETITION COMPLETED" : "FULL COMPETITION COMPLETED",
      promotionLevel: "",
      certificateId: certificate.certificateId || makeCertificateId("KGM-COMP"),
      date: certificate.date || new Date().toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric" })
    });
  }

  function certificateBaseCourseLabel(label) {
    const text = String(label || "").replace(/\s+/g, " ").trim();
    const withoutCompetition = text.replace(/\s+Competition$/i, "").trim();
    return withoutCompetition || text || "Competition";
  }

  function competitionCertificateCourseLabel(label) {
    let text = displayBrandText(label || "Competition");
    const schoolName = schoolDisplayName();
    if (/\bLittle Angels\b\s*\+\s*/i.test(text) && text.toLowerCase().includes(schoolName.toLowerCase())) {
      text = text.replace(/\bLittle Angels\b\s*\+\s*/i, "").trim();
    }
    const levelPattern = "\\b(?:P[1-7]|S[1-6]|UNIVERSITY|OTHERS)\\b";
    text = text.replace(new RegExp(`\\s+${levelPattern}$`, "i"), "").trim();
    text = text.replace(new RegExp(`\\s+-\\s+${levelPattern}$`, "i"), "").trim();
    return text || "Competition";
  }

  function competitionCertificateLevelLabel(certificate) {
    const level = cleanText(certificate && (certificate.schoolLevel || certificate.lectiveLevel || certificate.levelLabel || ""));
    if (level) return electiveClassLabel(level);
    const levelId = Number(certificate && certificate.levelId);
    if (Number.isFinite(levelId) && levelId > 0) return courseLabel(levelId);
    return "";
  }

  async function renderAchievementCertificateJpeg(details) {
    const image = await loadImage("assets/certificate.png");
    const canvas = document.createElement("canvas");
    canvas.width = image.naturalWidth || 863;
    canvas.height = image.naturalHeight || 1608;
    const ctx = canvas.getContext("2d");
    ctx.drawImage(image, 0, 0, canvas.width, canvas.height);
    const compactCompetition = Boolean(details.compactCompetition);

    drawCover(ctx, 92, 384, 680, 642, 0.96);
    drawCover(ctx, 196, 1302, 472, 156, 0.94);
    drawCover(ctx, 256, 1462, 352, 104, 0.96);
    drawCover(ctx, 270, 390, 325, 42, 0.98);
    drawCover(ctx, 118, 418, 625, 100, 0.98);
    drawCover(ctx, 300, 522, 265, 50, 0.98);
    drawCover(ctx, 268, 570, 330, 172, 0.98);
    drawCover(ctx, 264, 742, 338, 174, 0.98);
    drawCover(ctx, 218, 914, 430, 108, 0.98);

    await drawCustomSchoolBadge(ctx, compactCompetition ? { size: 266, y: 1008 } : { size: 221, y: 1040 });
    drawCentered(ctx, "This is to certify that", 432, 409, "26px Arial", "#ffffff", 360);
    drawCentered(ctx, details.name, 432, 471, "italic 58px Georgia", "#ffd86b", 590);
    drawCentered(ctx, "has completed", 432, 548, "26px Arial", "#ffffff", 300);
    drawCentered(ctx, details.completedLabel, 432, 609, compactCompetition ? "bold 42px Georgia" : "bold 54px Georgia", "#ffd86b", compactCompetition ? 620 : 420);
    drawCentered(ctx, details.starLine, 432, 663, "bold 42px Georgia", "#ffd86b", 520);
    if (!compactCompetition) {
      drawCentered(ctx, schoolCertificateLine(), 432, 704, "bold 28px Arial", "#fff4cf", 520);
    }
    drawCentered(ctx, details.storyLine, 432, compactCompetition ? 711 : 731, "bold 25px Arial", "#ffffff", 520);
    drawCentered(ctx, "with a score of", 432, 767, "26px Arial", "#ffffff", 330);
    drawCentered(ctx, String(details.score || 0), 432, 815, "bold 74px Arial", "#ffd86b", 260);
    drawCentered(ctx, `marks out of ${details.maxScore || 0}`, 432, 867, "30px Arial", "#ffffff", 360);
    drawCentered(ctx, details.questionsLine, 432, 900, "24px Arial", "#fff4cf", 480);
    drawCentered(ctx, details.promotionText, 432, 939, "bold 25px Arial", "#ffffff", 560);
    if (!compactCompetition && details.promotionLevel) {
      drawCentered(ctx, details.promotionLevel, 432, 984, "bold 50px Georgia", "#ffd86b", 430);
    }
    const advice = cleanText(details.adviceText || "");
    drawCentered(ctx, advice || "Your passion for spiritual growth", 432, 1352, "italic 24px Georgia", "#ffffff", 560);
    drawCentered(ctx, advice ? "Keep learning, correcting, and growing." : "and dedication to learning more about God inspires greatness.", 432, 1384, "italic 22px Georgia", "#ffffff", 590);
    drawCentered(ctx, "KEEP GROWING. KEEP SHINING.", 432, 1424, "bold 25px Georgia", "#ffd86b", 460);
    drawCentered(ctx, details.date, 432, 1533, "28px Arial", "#ffffff", 320);

    return canvas.toDataURL("image/jpeg", 0.92);
  }

  async function drawCustomSchoolBadge(ctx, options = {}) {
    const badge = schoolBadgeDataUrl();
    if (!badge) return;
    try {
      const image = await loadImage(badge);
      const size = Number(options.size || 184);
      const x = 432 - (size / 2);
      const y = Number(options.y || 1060);
      ctx.save();
      ctx.globalAlpha = 0.94;
      ctx.beginPath();
      ctx.arc(x + size / 2, y + size / 2, size / 2, 0, Math.PI * 2);
      ctx.closePath();
      ctx.clip();
      ctx.drawImage(image, x, y, size, size);
      ctx.restore();
      ctx.save();
      ctx.strokeStyle = "#ffd86b";
      ctx.lineWidth = 4;
      ctx.beginPath();
      ctx.arc(x + size / 2, y + size / 2, size / 2 + 2, 0, Math.PI * 2);
      ctx.stroke();
      ctx.restore();
    } catch (error) {
      console.warn("Could not draw custom school badge.", error);
    }
  }

  const canvasImageSourceCache = new Map();

  function loadImage(src) {
    return new Promise((resolve, reject) => {
      const image = new Image();
      image.onload = () => resolve(image);
      image.onerror = reject;
      image.src = canvasSafeImageSource(src);
    });
  }

  function canvasSafeImageSource(src) {
    if (!src) return src;
    if (canvasImageSourceCache.has(src)) return canvasImageSourceCache.get(src);
    if (src === "assets/certificate.png" && window.KnowGodMoreShare && typeof window.KnowGodMoreShare.certificateTemplateDataUrl === "function") {
      try {
        const dataUrl = window.KnowGodMoreShare.certificateTemplateDataUrl();
        if (typeof dataUrl === "string" && dataUrl.startsWith("data:image/")) {
          canvasImageSourceCache.set(src, dataUrl);
          return dataUrl;
        }
      } catch (error) {
        console.warn("Could not load native certificate template.", error);
      }
    }
    canvasImageSourceCache.set(src, src);
    return src;
  }

  function drawCover(ctx, x, y, width, height, alpha = 0.9) {
    ctx.save();
    ctx.fillStyle = `rgba(3, 3, 3, ${alpha})`;
    roundRect(ctx, x, y, width, height, 18);
    ctx.fill();
    ctx.restore();
  }

  function drawCentered(ctx, text, x, y, font, color, maxWidth) {
    ctx.save();
    ctx.font = font;
    ctx.fillStyle = color;
    ctx.textAlign = "center";
    ctx.textBaseline = "middle";
    ctx.shadowColor = "rgba(0, 0, 0, 0.9)";
    ctx.shadowBlur = 8;
    ctx.lineWidth = 4;
    ctx.strokeStyle = "rgba(0, 0, 0, 0.94)";
    const label = displayBrandText(text);
    ctx.strokeText(label, x, y, maxWidth);
    ctx.fillText(label, x, y, maxWidth);
    ctx.restore();
  }

  function roundRect(ctx, x, y, width, height, radius) {
    ctx.beginPath();
    ctx.moveTo(x + radius, y);
    ctx.lineTo(x + width - radius, y);
    ctx.quadraticCurveTo(x + width, y, x + width, y + radius);
    ctx.lineTo(x + width, y + height - radius);
    ctx.quadraticCurveTo(x + width, y + height, x + width - radius, y + height);
    ctx.lineTo(x + radius, y + height);
    ctx.quadraticCurveTo(x, y + height, x, y + height - radius);
    ctx.lineTo(x, y + radius);
    ctx.quadraticCurveTo(x, y, x + radius, y);
    ctx.closePath();
  }

  async function shareCertificate() {
    const user = activeUser();
    if (!user || !user.lastCertificate) return;
    const cert = user.lastCertificate;
    const level = courseById(cert.levelId);
    const nextLevel = nextCourseAfter(cert.levelId);
    const storyCount = cert.storyCount || (level ? levelStoryCount(level.id) : "");
    const questionsAsked = cert.questionsAsked || Math.round(cert.maxScore / POINTS_PER_QUESTION);
    const completedCourse = cert.kind === "lective" ? (cert.courseTitle || lectiveFolderLabel(cert.lectiveLevel, cert.lectiveSubject)) : courseLabel(cert.levelId);
    const text = [
      "Know God More Certificate of Achievement",
      "",
      `This certifies that ${user.displayName} has completed ${completedCourse}.`,
      `Course unit: ${storyCount} ${cert.kind === "lective" ? "questions" : "stories"}`,
      `Score: ${cert.score} marks out of ${cert.maxScore} (${cert.percent}%)`,
      `Questions asked: ${questionsAsked}`,
      cert.kind === "lective" ? `Certificate: ${cert.isMidCourse ? "MID-COURSE CHECKPOINT" : (cert.passed ? "ELECTIVE COURSE COMPLETED" : "REPEAT FOR BETTER PERFORMANCE")}` : cert.passed && nextLevel ? "Promotion: PROMOTED TO THE NEXT LEVEL" : cert.passed ? "Promotion: final level completed" : "Promotion: repeat for better performance",
      `Date: ${cert.date}`,
      "",
      "Keep growing. Keep shining."
    ].join("\n");
    const title = "Know God More Certificate";
    const dataUrl = currentCertificateJpeg || await renderCertificateJpeg(user, cert);
    currentCertificateJpeg = dataUrl;

    if (window.KnowGodMoreShare && typeof window.KnowGodMoreShare.shareJpeg === "function") {
      try {
        await window.KnowGodMoreShare.shareJpeg(title, text, certificateFileName(cert), dataUrl);
        return;
      } catch (error) {
        toastMessage = "Share could not open, so the certificate will be saved as a JPG instead.";
      }
    }

    if (window.KnowGodMoreShare && typeof window.KnowGodMoreShare.share === "function") {
      window.KnowGodMoreShare.share(title, text);
      return;
    }

    if (navigator.share) {
      try {
        const file = dataUrlToFile(dataUrl, certificateFileName(cert), "image/jpeg");
        if (!navigator.canShare || navigator.canShare({ files: [file] })) {
          await navigator.share({ title, text, files: [file] });
          return;
        }
      } catch (error) {
        // Fall back to sharing the certificate text below.
      }
      navigator.share({ title, text }).catch(() => {});
      return;
    }

    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(text).then(() => {
        downloadDataUrl(certificateFileName(cert), dataUrl);
        window.alert("Certificate text copied and JPG saved. You can attach the saved JPG to share.");
      }).catch(() => {
        downloadDataUrl(certificateFileName(cert), dataUrl);
        window.alert("Certificate JPG saved. Attach it where you want to share.");
      });
      return;
    }

    downloadDataUrl(certificateFileName(cert), dataUrl);
    window.alert("Certificate JPG saved. Attach it where you want to share.");
  }

  async function saveCertificate() {
    const user = activeUser();
    if (!user || !user.lastCertificate) return;
    const cert = user.lastCertificate;
    try {
      const dataUrl = currentCertificateJpeg || await renderCertificateJpeg(user, cert);
      currentCertificateJpeg = dataUrl;
      await saveCertificateImage(certificateFileName(cert), dataUrl, "Certificate saved.", {
        identity: cert.certificateId || `${cert.kind || "course"}-${cert.date || ""}`,
        title: cert.kind === "lective" ? "ELECTIVE Course Certificate" : "Course Certificate",
        name: user.displayName,
        kind: cert.kind || "course",
        cert
      });
    } catch (error) {
      toastMessage = "Certificate could not be prepared. Open the certificate again and try Save.";
      render();
    }
  }

  async function printCertificate() {
    const user = activeUser();
    if (!user || !user.lastCertificate) {
      window.print();
      return;
    }
    const cert = user.lastCertificate;
    const dataUrl = currentCertificateJpeg || await renderCertificateJpeg(user, cert);
    currentCertificateJpeg = dataUrl;

    if (window.KnowGodMoreShare && typeof window.KnowGodMoreShare.printJpeg === "function") {
      try {
        await window.KnowGodMoreShare.printJpeg(certificateFileName(cert), dataUrl);
        return;
      } catch (error) {
        toastMessage = "Print could not open directly, so the certificate preview is opening instead.";
      }
    }

    const win = window.open("", "_blank");
    if (win) {
      win.document.open();
      win.document.write(singleCertificatePrintHtml("Know God More Certificate", dataUrl));
      win.document.close();
      win.focus();
      window.setTimeout(() => win.print(), 250);
      return;
    }
    window.print();
  }

  async function saveCompetitionCertificate(index) {
    const certificate = competitionCertificateAt(index);
    if (!certificate) {
      window.alert("This competition certificate is not ready yet.");
      return;
    }
    try {
      const dataUrl = await renderCompetitionCertificateJpeg(certificate);
      await saveCertificateImage(competitionCertificateFileName(certificate), dataUrl, "Competition certificate saved.", {
        identity: certificate.certificateId || `competition-${index}-${certificate.name || ""}`,
        title: "Competition Certificate",
        name: certificate.name || "Competitor",
        kind: "competition",
        certificate
      });
    } catch (error) {
      toastMessage = "Competition certificate could not be prepared. Stay on this page and try again.";
      renderCompetition();
    }
  }

  async function shareCompetitionCertificate(index) {
    const certificate = competitionCertificateAt(index);
    if (!certificate) {
      window.alert("This competition certificate is not ready yet.");
      return;
    }
    const dataUrl = await renderCompetitionCertificateJpeg(certificate);
    const title = "Know God More Competition Certificate";
    const text = competitionCertificateShareText(certificate);
    const fileName = competitionCertificateFileName(certificate);
    await keepCompetitionCertificateInApp(certificate, index, fileName);

    if (window.KnowGodMoreShare && typeof window.KnowGodMoreShare.shareJpeg === "function") {
      try {
        await window.KnowGodMoreShare.shareJpeg(title, text, fileName, dataUrl);
        return;
      } catch (error) {
        toastMessage = "Share could not open, so the competition certificate will be saved as a JPG instead.";
        await saveCertificateImage(fileName, dataUrl, "Competition certificate saved.", {
          identity: certificate.certificateId || `competition-${index}-${certificate.name || ""}`,
          title: "Competition Certificate",
          name: certificate.name || "Competitor",
          kind: "competition",
          certificate
        });
        return;
      }
    }

    if (navigator.share) {
      try {
        const file = dataUrlToFile(dataUrl, fileName, "image/jpeg");
        if (!navigator.canShare || navigator.canShare({ files: [file] })) {
          await navigator.share({ title, text, files: [file] });
          return;
        }
      } catch (error) {
        navigator.share({ title, text }).catch(() => {});
        return;
      }
    }

    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(text).then(() => {
        downloadDataUrl(fileName, dataUrl);
        window.alert("Certificate text copied and JPG saved. You can attach the saved JPG to share.");
      }).catch(() => {
        downloadDataUrl(fileName, dataUrl);
        window.alert("Competition certificate JPG saved. Attach it where you want to share.");
      });
      return;
    }

    downloadDataUrl(fileName, dataUrl);
    window.alert("Competition certificate JPG saved. Attach it where you want to share.");
  }

  async function printCompetitionCertificate(index) {
    const certificate = competitionCertificateAt(index);
    if (!certificate) {
      window.alert("This competition certificate is not ready yet.");
      return;
    }
    const dataUrl = await renderCompetitionCertificateJpeg(certificate);
    const fileName = competitionCertificateFileName(certificate);
    await keepCompetitionCertificateInApp(certificate, index, fileName);
    if (window.KnowGodMoreShare && typeof window.KnowGodMoreShare.printJpeg === "function") {
      try {
        await window.KnowGodMoreShare.printJpeg(fileName, dataUrl);
        return;
      } catch (error) {
        toastMessage = "Print could not open directly, so the certificate preview is opening instead.";
      }
    }
    const win = window.open("", "_blank");
    if (win) {
      win.document.write(singleCertificatePrintHtml(fileName, dataUrl));
      win.document.close();
      win.focus();
      win.print();
      return;
    }
    window.print();
  }

  async function printCompetitionCertificates() {
    const certificates = currentCompetitionCertificates();
    if (!certificates.length) {
      window.alert("No competition certificates are ready yet.");
      return;
    }
    const images = await Promise.all(certificates.map((certificate) => renderCompetitionCertificateJpeg(certificate)));
    await Promise.all(certificates.map((certificate, index) => keepCompetitionCertificateInApp(certificate, index, competitionCertificateFileName(certificate))));
    const html = competitionCertificatesPrintHtml(certificates, images);
    if (window.KnowGodMoreShare && typeof window.KnowGodMoreShare.printHtml === "function") {
      try {
        await window.KnowGodMoreShare.printHtml("know-god-more-competition-certificates", html);
        return;
      } catch (error) {
        toastMessage = "Print all could not open directly, so the certificate pages are opening instead.";
      }
    }
    const win = window.open("", "_blank");
    if (win) {
      win.document.write(html);
      win.document.close();
      win.focus();
      win.print();
      return;
    }
    window.print();
  }

  async function keepCompetitionCertificateInApp(certificate, index, fileName) {
    try {
      await saveCertificateToAppFolder({
        fileName,
        identity: certificate.certificateId || `competition-${index}-${certificate.name || ""}`,
        title: "Competition Certificate",
        name: certificate.name || "Competitor",
        kind: "competition",
        certificate
      });
    } catch (error) {
      console.warn("Could not keep competition certificate in app folder.", error);
    }
  }

  function currentCompetitionCertificates() {
    const offline = store.offlineCompetition || null;
    if (route === "competition" && competitionView === "offline" && offline && offline.status === "completed") {
      const certificates = competitionCertificatesFromOffline(offline);
      rememberVisibleCompetitionCertificates(certificates);
      return certificates;
    }
    const host = store.onlineCompetition || null;
    if (route === "competition" && competitionView === "online-host" && host && host.phase === "completed") {
      const state = refreshCompetitionState() || {};
      const certificates = competitionCertificatesFromOnline(state, host);
      rememberVisibleCompetitionCertificates(certificates);
      return certificates;
    }
    return Array.isArray(lastCompetitionCertificates) ? lastCompetitionCertificates : [];
  }

  function rememberVisibleCompetitionCertificates(certificates) {
    if (!Array.isArray(certificates) || !certificates.length) return;
    lastCompetitionCertificates = certificates.map((certificate) => cloneData(certificate));
  }

  function competitionCertificateAt(index) {
    const certificates = currentCompetitionCertificates();
    const safeIndex = Number.isFinite(index) ? Math.max(0, Math.floor(index)) : 0;
    return certificates[safeIndex] || null;
  }

  function competitionCertificatesFromOffline(offline) {
    if (!offline || !Array.isArray(offline.players)) return [];
    return offline.players.map((player) => {
      const answered = Array.isArray(player.answers) ? player.answers.length : 0;
      const totalQuestions = offlinePlayerPlannedQuestions(offline, player);
      const maxScore = totalQuestions * POINTS_PER_QUESTION;
      return {
        certificateId: stableCompetitionCertificateId("offline", offline, player, answered),
        name: player.name || "Competitor",
        score: Number(player.score || 0),
        maxScore,
        answered,
        totalQuestions,
        completionLabel: answered < totalQuestions ? "a partial competition" : "the full competition",
        courseLabel: competitionCourseLabel(offline),
        schoolLevel: player.schoolLevel || offline.schoolLevel || offline.lectiveLevel || "",
        levelId: offline.levelId || "",
        date: offline.completedAt || new Date().toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric" })
      };
    });
  }

  function competitionCertificatesFromOnline(state, host) {
    const players = Array.isArray(state && state.players) ? state.players : [];
    const totalQuestions = Number(host && host.totalQuestions ? host.totalQuestions : 0);
    const maxScore = totalQuestions * POINTS_PER_QUESTION;
    return players.map((player) => {
      const answered = Number(player.totalAnswered || 0);
      const correct = Number(player.totalCorrect || 0);
      return {
        certificateId: stableCompetitionCertificateId("online", host, player, answered),
        name: player.name || "Competitor",
        score: correct * POINTS_PER_QUESTION,
        maxScore,
        answered,
        totalQuestions,
        completionLabel: answered < totalQuestions ? "a partial competition" : "the full competition",
        courseLabel: competitionCourseLabel(host),
        schoolLevel: player.schoolLevel || host.schoolLevel || host.lectiveLevel || "",
        levelId: host.levelId || "",
        date: host.completedAt || new Date().toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric" })
      };
    });
  }

  function competitionCourseLabel(record) {
    if (!record) return "Competition";
    const source = record.source === "level" ? "bible" : record.source || "bible";
    const schoolLevel = normalizeCompetitionSchoolLevel(record.schoolLevel || record.lectiveLevel || selectedCompetitionSchoolLevel);
    const bibleLevel = Number(record.levelId || bibleLevelForSchoolLevel(schoolLevel));
    const schoolLabel = schoolDisplayName();
    const materialLabel = source === "lective"
      ? schoolLabel
      : source === "both"
        ? bibleLevel === BABY_COURSE.id ? schoolLabel : `${courseLabel(bibleLevel)} + ${schoolLabel}`
        : courseLabel(bibleLevel);
    const name = competitionRecordName(record);
    return name && name !== "Competition" ? `${name} - ${materialLabel}` : materialLabel;
  }

  function stableCompetitionCertificateId(mode, record, player, answered) {
    const source = [
      mode || "competition",
      record && (record.startedAt || record.completedAt || record.competitionName || record.levelId || record.lectiveLevel || ""),
      player && (player.id || player.playerId || player.name || ""),
      player && player.name ? player.name : "",
      Number(answered || 0)
    ].join("|");
    return `KGM-COMP-${Math.abs(hash(source)).toString(36).toUpperCase()}`;
  }

  function rememberCompetitionHistory(mode, certificates, record) {
    store.competitionHistory = Array.isArray(store.competitionHistory) ? store.competitionHistory : [];
    const startedAt = record && record.startedAt ? record.startedAt : new Date().toISOString();
    if (store.competitionHistory.some((item) => item.startedAt === startedAt && item.mode === mode)) return;
    const ranked = [...(certificates || [])].sort((a, b) => Number(b.score || 0) - Number(a.score || 0));
    store.competitionHistory.unshift({
      id: makeCertificateId("KGM-RUN"),
      mode,
      source: record && record.source ? record.source : "level",
      courseLabel: competitionCourseLabel(record),
      startedAt,
      completedAt: new Date().toISOString(),
      totalQuestions: Number(record && record.totalQuestions ? record.totalQuestions : 0),
      players: ranked.map((certificate, index) => ({
        rank: index + 1,
        name: certificate.name,
        score: certificate.score,
        maxScore: certificate.maxScore,
        answered: certificate.answered,
        totalQuestions: certificate.totalQuestions
      }))
    });
    store.competitionHistory = store.competitionHistory.slice(0, 25);
    saveStore();
  }

  function competitionHistoryHtml() {
    const history = Array.isArray(store.competitionHistory) ? store.competitionHistory.slice(0, 5) : [];
    return `
      <div class="competition-card">
        <h3>Past Competitions</h3>
        <div class="competition-player-list">
          ${history.map((item) => {
            const winner = item.players && item.players[0] ? item.players[0] : null;
            return `
              <div class="competition-player-row">
                <strong>${escapeHtml(item.courseLabel || "Competition")}</strong>
                <span>${winner ? `Winner: ${escapeHtml(winner.name)}` : "No winner saved"}</span>
                <span>${winner ? `${Number(winner.score || 0).toLocaleString()} marks` : ""}</span>
              </div>
            `;
          }).join("") || `<p class="empty-state">No past competition yet.</p>`}
        </div>
      </div>
    `;
  }

  function competitionCertificatesPrintHtml(certificates, images = []) {
    return `<!doctype html>
      <html>
        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width,initial-scale=1">
          <title>Know God More Competition Certificates</title>
          <style>
            @page { size: A4 portrait; margin: 12mm; }
            * { box-sizing: border-box; }
            body {
              margin: 0;
              background: #fff;
            }
            .certificate-page {
              page-break-after: always;
              break-after: page;
              display: flex;
              justify-content: center;
              align-items: center;
            }
            .certificate-page:last-child {
              page-break-after: auto;
              break-after: auto;
            }
            img {
              display: block;
              width: 100%;
              max-width: 186mm;
              height: auto;
            }
          </style>
        </head>
        <body>
          ${certificates.map((certificate, index) => `<section class="certificate-page"><img src="${images[index] || "assets/certificate.png"}" alt="Know God More competition certificate for ${escapeHtml(certificate.name)}"></section>`).join("")}
        </body>
      </html>`;
  }

  function singleCertificatePrintHtml(title, dataUrl) {
    return `<!doctype html>
      <html>
        <head>
          <meta charset="utf-8">
          <meta name="viewport" content="width=device-width,initial-scale=1">
          <title>${escapeHtml(title || "Know God More Certificate")}</title>
          <style>
            @page { margin: 0; }
            html, body { margin: 0; padding: 0; background: #fff; }
            body { display: grid; place-items: start center; }
            img { display: block; width: 100%; max-width: 100%; height: auto; page-break-inside: avoid; }
          </style>
        </head>
        <body><img src="${dataUrl}" alt="Know God More certificate"></body>
      </html>`;
  }

  async function saveCertificateImage(fileName, dataUrl, successMessage = "Certificate saved.", meta = {}) {
    const details = {
      fileName,
      dataUrl,
      identity: meta.identity || fileName,
      title: meta.title || successMessage,
      name: meta.name || (activeUser() ? activeUser().displayName : "Reader"),
      kind: meta.kind || "certificate",
      cert: meta.cert || null,
      certificate: meta.certificate || null
    };
    let keptInApp = false;
    try {
      await saveCertificateToAppFolder(details);
      keptInApp = true;
    } catch (error) {
      console.warn("Could not keep certificate in app folder.", error);
    }
    if (window.KnowGodMoreShare && (typeof window.KnowGodMoreShare.saveJpegToPictures === "function" || typeof window.KnowGodMoreShare.saveJpeg === "function")) {
      queueCertificateSave(() => saveJpegThroughBridge(fileName, dataUrl, false, "pictures"), {
        onSuccess: (result) => {
          const bridgeMessage = result && result.message ? result.message : successMessage;
          refreshAfterCertificateAction(`${bridgeMessage} ${keptInApp ? "Also kept in SAVED CERTIFICATES." : "The device copy was created, but the in-app certificate list could not be updated."}`);
        },
        onFailure: () => {
          downloadDataUrl(fileName, dataUrl);
          refreshAfterCertificateAction(keptInApp ? "Certificate kept in SAVED CERTIFICATES and opened as a device download fallback." : "Certificate opened as a device download fallback.");
        }
      });
      return;
    }
    downloadDataUrl(fileName, dataUrl);
    refreshAfterCertificateAction(`${successMessage} ${keptInApp ? "Also kept in SAVED CERTIFICATES." : "Opened as a device download fallback."}`);
  }

  function refreshAfterCertificateAction(message) {
    if (route === "saved-certificates") {
      savedCertificateMessage = message || "";
      renderSavedCertificates();
      return;
    }
    toastMessage = message || "";
    if (route === "competition") {
      renderCompetition();
    } else {
      render();
    }
  }

  function autoSaveCertificate(fileName, dataUrl, identity, meta = {}) {
    if (!dataUrl) return;
    const key = `know-god-more:auto-certificate:${slugify(identity || fileName || "certificate")}`;
    if (autoSavedCertificates.has(key)) return;
    autoSavedCertificates.add(key);
    saveCertificateToAppFolder({
      fileName,
      dataUrl,
      identity: identity || fileName,
      title: meta.title || "Know God More Certificate",
      name: meta.name || (activeUser() ? activeUser().displayName : "Reader"),
      kind: meta.kind || "certificate",
      cert: meta.cert || null,
      certificate: meta.certificate || null
    }).catch(() => {
      autoSavedCertificates.delete(key);
    });
    try {
      if (window.KnowGodMoreShare && (typeof window.KnowGodMoreShare.autoSaveJpeg === "function" || typeof window.KnowGodMoreShare.saveJpeg === "function")) {
        queueCertificateSave(() => saveJpegThroughBridge(fileName, dataUrl, true, "downloads"), {
          onFailure: () => {
            // The in-app folder is the reliable save path; device saving can be retried from there.
          }
        });
        return;
      }
    } catch (error) {}
  }

  function queueCertificateSave(saveTask, handlers = {}) {
    const run = () => {
      let result = null;
      try {
        result = typeof saveTask === "function" ? saveTask() : null;
      } catch (error) {
        if (typeof handlers.onFailure === "function") handlers.onFailure(error);
        return Promise.resolve();
      }
      if (result && typeof result.then === "function") {
        return result
          .then((value) => {
            const checked = requireBridgeSaveSuccess(value);
            if (typeof handlers.onSuccess === "function") handlers.onSuccess(checked);
            return checked;
          })
          .catch((error) => {
            if (typeof handlers.onFailure === "function") handlers.onFailure(error);
          });
      }
      try {
        const checked = requireBridgeSaveSuccess(result);
        if (typeof handlers.onSuccess === "function") handlers.onSuccess(checked);
        return Promise.resolve(checked);
      } catch (error) {
        if (typeof handlers.onFailure === "function") handlers.onFailure(error);
        return Promise.resolve();
      }
    };
    certificateSaveQueue = certificateSaveQueue.catch(() => {}).then(run);
    return certificateSaveQueue;
  }

  function requireBridgeSaveSuccess(result) {
    if (!result || typeof result !== "string") {
      return result;
    }
    const trimmed = result.trim();
    if (!trimmed || (trimmed[0] !== "{" && trimmed[0] !== "[")) {
      return result;
    }
    try {
      const parsed = JSON.parse(trimmed);
      if (parsed && parsed.ok === false) {
        const bridgeError = new Error(parsed.message || "Certificate could not be saved.");
        bridgeError.bridgeSaveFailure = true;
        throw bridgeError;
      }
      return parsed || result;
    } catch (error) {
      if (error && error.bridgeSaveFailure) {
        throw error;
      }
      return result;
    }
  }

  function saveJpegThroughBridge(fileName, dataUrl, quietOrMessage, destination = "") {
    const bridge = window.KnowGodMoreShare;
    if (!bridge) return null;
    const quiet = quietOrMessage === true;
    let windowsBridge = false;
    try {
      windowsBridge = bridge.isLocalWindowsBridge === true;
    } catch (error) {}
    if (destination === "downloads" && typeof bridge.autoSaveJpeg === "function") {
      return bridge.autoSaveJpeg(fileName, dataUrl);
    }
    if (destination === "pictures" && typeof bridge.saveJpegToPictures === "function") {
      if (windowsBridge) {
        return bridge.saveJpegToPictures(fileName, dataUrl, quiet);
      }
      return bridge.saveJpegToPictures(fileName, dataUrl);
    }
    if (typeof bridge.saveJpeg !== "function") return null;
    if (windowsBridge) {
      return bridge.saveJpeg(fileName, dataUrl, quietOrMessage, destination);
    }
    return bridge.saveJpeg(fileName, dataUrl);
  }

  function downloadDataUrl(fileName, dataUrl) {
    const link = document.createElement("a");
    link.href = dataUrl;
    link.download = fileName || "know-god-more-certificate.jpg";
    document.body.appendChild(link);
    link.click();
    link.remove();
  }

  function openSavedCertificateDb() {
    return new Promise((resolve, reject) => {
      if (!window.indexedDB) {
        reject(new Error("Saved certificate storage is not available."));
        return;
      }
      const request = window.indexedDB.open(SAVED_CERTIFICATE_DB, 1);
      request.onupgradeneeded = () => {
        const db = request.result;
        if (!db.objectStoreNames.contains(SAVED_CERTIFICATE_STORE)) {
          const storeObject = db.createObjectStore(SAVED_CERTIFICATE_STORE, { keyPath: "id" });
          storeObject.createIndex("savedAt", "savedAt", { unique: false });
        }
      };
      request.onsuccess = () => resolve(request.result);
      request.onerror = () => reject(request.error || new Error("Could not open saved certificates."));
    });
  }

  function savedCertificateFallbackKey() {
    return `${STORAGE_KEY}:saved-certificates`;
  }

  async function readSavedCertificatesFromFolder() {
    const memoryRecords = normalizeSavedCertificateRecords(store && Array.isArray(store.savedCertificates) ? store.savedCertificates : []);
    let dbRecords = [];
    let fallbackRecords = [];
    try {
      const fallback = JSON.parse(localStorage.getItem(savedCertificateFallbackKey()) || "[]");
      fallbackRecords = Array.isArray(fallback) ? fallback : [];
    } catch (fallbackError) {
      fallbackRecords = [];
    }
    try {
      const db = await openSavedCertificateDb();
      dbRecords = await new Promise((resolve, reject) => {
        const transaction = db.transaction(SAVED_CERTIFICATE_STORE, "readonly");
        const request = transaction.objectStore(SAVED_CERTIFICATE_STORE).getAll();
        request.onsuccess = () => resolve(Array.isArray(request.result) ? request.result : []);
        request.onerror = () => reject(request.error || new Error("Could not read saved certificates."));
        transaction.oncomplete = () => db.close();
        transaction.onabort = () => db.close();
      });
    } catch (error) {
      dbRecords = [];
    }
    const merged = normalizeSavedCertificateRecords([...memoryRecords, ...fallbackRecords, ...dbRecords]);
    if (store) store.savedCertificates = merged;
    return merged;
  }

  async function putSavedCertificateRecord(record) {
    upsertSavedCertificateInMemory(record);
    saveStore();
    try {
      const db = await openSavedCertificateDb();
      await new Promise((resolve, reject) => {
        const transaction = db.transaction(SAVED_CERTIFICATE_STORE, "readwrite");
        transaction.objectStore(SAVED_CERTIFICATE_STORE).put(record);
        transaction.oncomplete = () => {
          db.close();
          resolve();
        };
        transaction.onerror = () => {
          db.close();
          reject(transaction.error || new Error("Could not save certificate."));
        };
        transaction.onabort = () => {
          db.close();
          reject(transaction.error || new Error("Could not save certificate."));
        };
      });
      return;
    } catch (error) {
      const records = await readSavedCertificatesFromFolder();
      const next = [record, ...records.filter((item) => item.id !== record.id)]
        .sort((a, b) => Number(b.savedAt || 0) - Number(a.savedAt || 0))
        .slice(0, SAVED_CERTIFICATE_LIMIT);
      try {
        localStorage.setItem(savedCertificateFallbackKey(), JSON.stringify(next));
      } catch (fallbackError) {}
    }
  }

  async function deleteSavedCertificateRecord(id) {
    removeSavedCertificateFromMemory(id);
    saveStore();
    try {
      const db = await openSavedCertificateDb();
      await new Promise((resolve, reject) => {
        const transaction = db.transaction(SAVED_CERTIFICATE_STORE, "readwrite");
        transaction.objectStore(SAVED_CERTIFICATE_STORE).delete(id);
        transaction.oncomplete = () => {
          db.close();
          resolve();
        };
        transaction.onerror = () => {
          db.close();
          reject(transaction.error || new Error("Could not delete certificate."));
        };
        transaction.onabort = () => {
          db.close();
          reject(transaction.error || new Error("Could not delete certificate."));
        };
      });
      return;
    } catch (error) {
      const records = await readSavedCertificatesFromFolder();
      try {
        localStorage.setItem(savedCertificateFallbackKey(), JSON.stringify(records.filter((item) => item.id !== id)));
      } catch (fallbackError) {}
    }
  }

  async function trimSavedCertificateFolder() {
    const records = (await readSavedCertificatesFromFolder())
      .sort((a, b) => Number(b.savedAt || 0) - Number(a.savedAt || 0));
    const extra = records.slice(SAVED_CERTIFICATE_LIMIT);
    for (const record of extra) {
      await deleteSavedCertificateRecord(record.id);
    }
    const kept = normalizeSavedCertificateRecords(records.slice(0, SAVED_CERTIFICATE_LIMIT));
    store.savedCertificates = kept;
    saveStore();
    return kept;
  }

  function normalizeSavedCertificateRecords(records) {
    const map = new Map();
    (Array.isArray(records) ? records : []).forEach((record) => {
      if (!record || !record.id || !record.details) return;
      const normalized = {
        id: String(record.id),
        fileName: cleanText(record.fileName || "know-god-more-certificate.jpg"),
        title: cleanText(record.title || "Know God More Certificate"),
        name: cleanText(record.name || "Reader"),
        kind: cleanText(record.kind || "certificate"),
        details: clonePlain(record.details),
        savedAt: Number(record.savedAt || Date.now())
      };
      const existing = map.get(normalized.id);
      if (!existing || Number(existing.savedAt || 0) <= normalized.savedAt) {
        map.set(normalized.id, normalized);
      }
    });
    return Array.from(map.values())
      .sort((a, b) => Number(b.savedAt || 0) - Number(a.savedAt || 0))
      .slice(0, SAVED_CERTIFICATE_LIMIT);
  }

  function upsertSavedCertificateInMemory(record) {
    if (!store) return;
    store.savedCertificates = normalizeSavedCertificateRecords([record, ...(Array.isArray(store.savedCertificates) ? store.savedCertificates : [])]);
  }

  function removeSavedCertificateFromMemory(id) {
    if (!store) return;
    store.savedCertificates = normalizeSavedCertificateRecords(Array.isArray(store.savedCertificates) ? store.savedCertificates.filter((record) => record.id !== id) : []);
  }

  function savedCertificateId(identity, fileName, details) {
    const base = slugify(identity || fileName || "certificate") || "certificate";
    let hash = 0;
    const sample = `${fileName || ""}:${JSON.stringify(details || {}).slice(0, 600)}`;
    for (let index = 0; index < sample.length; index += 1) {
      hash = ((hash << 5) - hash + sample.charCodeAt(index)) | 0;
    }
    return `cert-${base}-${Math.abs(hash).toString(36)}`;
  }

  async function saveCertificateToAppFolder(details) {
    if (!details) throw new Error("Certificate details are empty.");
    const now = Date.now();
    const fileName = details.fileName || "know-god-more-certificate.jpg";
    const certificateDetails = savedCertificateDetails(details);
    if (!certificateDetails) throw new Error("Certificate achievement details are required.");
    const record = {
      id: savedCertificateId(details.identity || fileName, fileName, certificateDetails || details.dataUrl || now),
      fileName,
      title: details.title || "Know God More Certificate",
      name: details.name || "Reader",
      kind: details.kind || "certificate",
      details: certificateDetails,
      savedAt: now
    };
    await putSavedCertificateRecord(record);
    savedCertificateList = await trimSavedCertificateFolder();
    savedCertificateLoaded = true;
    selectedSavedCertificateId = record.id;
    if (route === "saved-certificates") renderSavedCertificates();
    return record;
  }

  function savedCertificateDetails(details) {
    if (details && details.certificate) {
      return {
        renderer: "competition",
        certificate: slimCompetitionCertificate(details.certificate)
      };
    }
    if (details && details.cert) {
      return {
        renderer: "course",
        userName: details.name || "Reader",
        cert: slimCourseCertificate(details.cert)
      };
    }
    return null;
  }

  function slimCourseCertificate(cert) {
    const source = clonePlain(cert);
    return {
      certificateId: source.certificateId,
      kind: source.kind,
      levelId: source.levelId,
      score: source.score,
      maxScore: source.maxScore,
      percent: source.percent,
      passed: source.passed,
      passPercent: source.passPercent,
      courseTitle: source.courseTitle,
      storyCount: source.storyCount,
      questionsAsked: source.questionsAsked,
      date: source.date,
      lectiveLevel: source.lectiveLevel,
      lectiveSubject: source.lectiveSubject,
      completedLabel: source.completedLabel,
      starLine: source.starLine,
      promotionText: source.promotionText,
      promotionLevel: source.promotionLevel
    };
  }

  function slimCompetitionCertificate(certificate) {
    const source = clonePlain(certificate);
    return {
      certificateId: source.certificateId,
      name: source.name,
      courseLabel: source.courseLabel,
      score: source.score,
      maxScore: source.maxScore,
      percent: source.percent,
      answered: source.answered,
      totalQuestions: source.totalQuestions,
      completionLabel: source.completionLabel,
      date: source.date
    };
  }

  function clonePlain(value) {
    try {
      return JSON.parse(JSON.stringify(value || {}));
    } catch (error) {
      return {};
    }
  }

  async function loadSavedCertificateList() {
    savedCertificateLoading = true;
    try {
      savedCertificateList = (await readSavedCertificatesFromFolder())
        .sort((a, b) => Number(b.savedAt || 0) - Number(a.savedAt || 0))
        .slice(0, SAVED_CERTIFICATE_LIMIT);
      if (savedCertificateList.length && !savedCertificateList.some((record) => record.id === selectedSavedCertificateId)) {
        selectedSavedCertificateId = savedCertificateList[0].id;
      }
      if (!savedCertificateList.length) selectedSavedCertificateId = "";
      savedCertificateLoaded = true;
    } catch (error) {
      savedCertificateMessage = "Saved certificates could not be opened on this device.";
    } finally {
      savedCertificateLoading = false;
    }
    return savedCertificateList;
  }

  async function savedCertificateById(id) {
    let record = savedCertificateList.find((item) => item.id === id);
    if (record) return record;
    const records = await readSavedCertificatesFromFolder();
    record = records.find((item) => item.id === id);
    return record || null;
  }

  function consumeSavedCertificateMessage() {
    const message = savedCertificateMessage;
    savedCertificateMessage = "";
    return message;
  }

  async function shareSavedCertificate(id) {
    const record = await savedCertificateById(id);
    if (!record) {
      savedCertificateMessage = "That saved certificate could not be found.";
      renderSavedCertificates();
      return;
    }
    const title = record.title || "Know God More Certificate";
    const text = savedCertificateShareText(record);
    const dataUrl = await renderSavedCertificateDataUrl(record);
    if (window.KnowGodMoreShare && typeof window.KnowGodMoreShare.shareJpeg === "function") {
      try {
        await window.KnowGodMoreShare.shareJpeg(title, text, record.fileName, dataUrl);
        return;
      } catch (error) {
        savedCertificateMessage = "Share could not open, so the certificate was opened as a device download fallback.";
      }
    }
    if (navigator.share) {
      try {
        const file = dataUrlToFile(dataUrl, record.fileName, "image/jpeg");
        if (!navigator.canShare || navigator.canShare({ files: [file] })) {
          await navigator.share({ title, text, files: [file] });
          return;
        }
      } catch (error) {}
      navigator.share({ title, text }).catch(() => {});
      return;
    }
    downloadDataUrl(record.fileName, dataUrl);
    window.alert("Certificate JPG opened for saving. You can attach it to share.");
  }

  async function saveSavedCertificate(id) {
    const record = await savedCertificateById(id);
    if (!record) {
      savedCertificateMessage = "That saved certificate could not be found.";
      renderSavedCertificates();
      return;
    }
    const dataUrl = await renderSavedCertificateDataUrl(record);
    if (window.KnowGodMoreShare && (typeof window.KnowGodMoreShare.saveJpegToPictures === "function" || typeof window.KnowGodMoreShare.saveJpeg === "function")) {
      queueCertificateSave(() => saveJpegThroughBridge(record.fileName, dataUrl, false, "pictures"), {
        onSuccess: () => {
          savedCertificateMessage = "A copy was sent to the device Pictures folder.";
          if (route === "saved-certificates") renderSavedCertificates();
        },
        onFailure: () => {
          downloadDataUrl(record.fileName, dataUrl);
          savedCertificateMessage = "The app kept the certificate here and opened a device download fallback.";
          if (route === "saved-certificates") renderSavedCertificates();
        }
      });
      return;
    }
    downloadDataUrl(record.fileName, dataUrl);
    savedCertificateMessage = "The certificate was opened as a device download.";
    renderSavedCertificates();
  }

  async function printSavedCertificate(id) {
    const record = await savedCertificateById(id);
    if (!record) {
      savedCertificateMessage = "That saved certificate could not be found.";
      renderSavedCertificates();
      return;
    }
    const dataUrl = await renderSavedCertificateDataUrl(record);
    if (window.KnowGodMoreShare && typeof window.KnowGodMoreShare.printJpeg === "function") {
      try {
        await window.KnowGodMoreShare.printJpeg(record.fileName, dataUrl);
        return;
      } catch (error) {
        savedCertificateMessage = "Print could not open directly, so the certificate preview is opening instead.";
      }
    }
    const win = window.open("", "_blank");
    if (win) {
      win.document.open();
      win.document.write(singleCertificatePrintHtml(record.title || "Know God More Certificate", dataUrl));
      win.document.close();
      win.focus();
      window.setTimeout(() => win.print(), 250);
      return;
    }
    window.print();
  }

  async function deleteSavedCertificateFromFolder(id) {
    if (!id) return;
    await deleteSavedCertificateRecord(id);
    await loadSavedCertificateList();
    savedCertificateMessage = "Certificate deleted from SAVED CERTIFICATES.";
    renderSavedCertificates();
  }

  async function renderSavedCertificateDataUrl(record) {
    if (!record) throw new Error("Saved certificate was not found.");
    const details = record.details || {};
    if (details.renderer === "competition" && details.certificate) {
      return renderCompetitionCertificateJpeg(details.certificate);
    }
    if (details.renderer === "course" && details.cert) {
      return renderCertificateJpeg({ displayName: details.userName || record.name || "Reader" }, details.cert);
    }
    if (record.dataUrl) return record.dataUrl;
    throw new Error("Saved certificate details are incomplete.");
  }

  function hydrateSavedCertificatePreview() {
    const image = document.querySelector("[data-saved-certificate-preview]");
    if (!image) return;
    const id = image.getAttribute("data-saved-certificate-preview") || "";
    savedCertificateById(id).then((record) => {
      if (!record) return;
      return renderSavedCertificateDataUrl(record);
    }).then((dataUrl) => {
      if (!dataUrl) return;
      const current = document.querySelector(`[data-saved-certificate-preview="${cssEscape(id)}"]`);
      if (current) {
        current.src = dataUrl;
        const label = current.parentElement ? current.parentElement.querySelector("span") : null;
        if (label) label.textContent = "Certificate ready.";
      }
    }).catch(() => {
      const current = document.querySelector(`[data-saved-certificate-preview="${cssEscape(id)}"]`);
      const label = current && current.parentElement ? current.parentElement.querySelector("span") : null;
      if (label) label.textContent = "Certificate preview could not be prepared.";
    });
  }

  function savedCertificateShareText(record) {
    const details = record && record.details ? record.details : {};
    const cert = details.certificate || details.cert || {};
    const score = cert.score !== undefined ? `${Number(cert.score || 0).toLocaleString()} marks` : "";
    const maxScore = cert.maxScore !== undefined ? ` out of ${Number(cert.maxScore || 0).toLocaleString()}` : "";
    return [
      record.title || "Know God More Certificate",
      record.name || "Reader",
      savedCertificateSummary(record),
      score ? `Score: ${score}${maxScore}` : "",
      cert.date ? `Date: ${cert.date}` : "",
      "Saved from Know God More."
    ].filter(Boolean).join("\n");
  }

  function cssEscape(value) {
    if (window.CSS && typeof window.CSS.escape === "function") return window.CSS.escape(value);
    return String(value || "").replace(/"/g, "\\\"");
  }

  function dataUrlToFile(dataUrl, fileName, mimeType) {
    const parts = String(dataUrl || "").split(",");
    const binary = atob(parts[1] || "");
    const bytes = new Uint8Array(binary.length);
    for (let index = 0; index < binary.length; index += 1) {
      bytes[index] = binary.charCodeAt(index);
    }
    return new File([bytes], fileName, { type: mimeType });
  }

  function certificateFileName(cert) {
    if (cert && cert.kind === "lective") {
      const id = cert.certificateId ? `-${slugify(cert.certificateId)}` : "";
      const type = cert.isMidCourse ? "mid-course" : "class";
      return `know-god-more-lective-${type}-${slugify(cert.lectiveLevel || DEFAULT_LECTIVE_LEVEL)}-${slugify(cert.lectiveSubject || DEFAULT_LECTIVE_SUBJECT)}${id}.jpg`;
    }
    const unit = cert.levelId === BABY_COURSE.id ? "little-angeles" : `level-${cert.levelId}`;
    return `know-god-more-${unit}.jpg`;
  }

  function competitionCertificateFileName(certificate) {
    const name = slugify(certificate && certificate.name ? certificate.name : "competitor");
    const id = certificate && certificate.certificateId ? slugify(certificate.certificateId) : "competition";
    return `know-god-more-${name}-${id}.jpg`;
  }

  function competitionCertificateShareText(certificate) {
    const answered = Number(certificate.answered || 0).toLocaleString();
    const total = Number(certificate.totalQuestions || 0).toLocaleString();
    const score = Number(certificate.score || 0).toLocaleString();
    const maxScore = Number(certificate.maxScore || 0).toLocaleString();
    return [
      "Know God More Competition Certificate",
      "",
      `${certificate.name || "Competitor"} completed ${competitionCertificateCourseLabel(certificate.courseLabel || "Competition")}.`,
      `Score: ${score} marks out of ${maxScore}`,
      `Answered: ${answered} of ${total} questions`,
      `Date: ${certificate.date || ""}`,
      "",
      "Keep growing. Keep shining."
    ].join("\n");
  }

  function normalizeTextEdits(value) {
    const source = value && typeof value === "object" ? value : {};
    return {
      questions: source.questions && typeof source.questions === "object" ? source.questions : {},
      stories: source.stories && typeof source.stories === "object" ? source.stories : {}
    };
  }

  function ensureTextEditStore() {
    store.textEdits = normalizeTextEdits(store.textEdits);
    return store.textEdits;
  }

  function textEditKey(value) {
    return String(value ?? "");
  }

  function applyStoryTextEdit(story) {
    if (!story) return story;
    const edits = normalizeTextEdits(store && store.textEdits);
    const storyEdit = edits.stories[textEditKey(story.id)] || null;
    const next = storyEdit ? {
      ...story,
      title: storyEdit.title !== undefined ? cleanText(storyEdit.title) : story.title,
      reference: storyEdit.reference !== undefined ? cleanText(storyEdit.reference) : story.reference,
      content: storyEdit.content !== undefined ? cleanText(storyEdit.content) : story.content
    } : story;
    return {
      ...next,
      questions: (Array.isArray(next.questions) ? next.questions : []).map(applyQuestionTextEdit)
    };
  }

  function applyQuestionTextEdit(question) {
    if (!question) return question;
    const edits = normalizeTextEdits(store && store.textEdits);
    const questionEdit = edits.questions[textEditKey(question.id)] || null;
    const storyEdit = edits.stories[textEditKey(question.storyId)] || null;
    const next = { ...question };
    if (storyEdit) {
      if (storyEdit.title !== undefined) next.title = cleanText(storyEdit.title);
      if (storyEdit.reference !== undefined) next.reference = cleanText(storyEdit.reference);
      if (storyEdit.content !== undefined) next.content = cleanText(storyEdit.content);
    }
    if (questionEdit) {
      if (questionEdit.question !== undefined) next.question = cleanText(questionEdit.question);
      if (Array.isArray(questionEdit.answers)) {
        next.answers = (Array.isArray(question.answers) ? question.answers : []).map((answer, index) => {
          const editAnswer = questionEdit.answers[index] || {};
          return {
            text: editAnswer.text !== undefined ? cleanText(editAnswer.text) : cleanText(answer && answer.text || ""),
            correct: editAnswer.correct !== undefined ? Boolean(editAnswer.correct) : Boolean(answer && answer.correct)
          };
        });
      }
    }
    return next;
  }

  function canEditText() {
    return true;
  }

  function authorizeTextEdit() {
    const name = cleanText(window.prompt("Admin name", "") || "").toLowerCase();
    if (name !== "admin") {
      window.alert("Text editing is for the authorised admin only.");
      return false;
    }
    const password = cleanText(window.prompt("Admin password", "") || "");
    if (password !== "0000") {
      window.alert("Wrong admin password. Text editing was not opened.");
      return false;
    }
    return true;
  }

  function editTextButtonHtml(kind, questionId) {
    if (questionId === undefined || questionId === null) return "";
    return `<button class="ghost-button edit-text-button" type="button" data-action="edit-text" data-edit-kind="${escapeHtml(kind)}" data-question-id="${escapeHtml(questionId)}" title="Edit this text with admin password">EDIT TEXT</button>`;
  }

  function findEditableQuestionById(questionId) {
    const id = textEditKey(questionId);
    if (!id) return null;
    const direct = questionById.get(id);
    if (direct) return applyQuestionTextEdit(direct);
    const lectiveBank = buildStoryBank(Object.values(store.lectiveFolders || {}).flat());
    for (const story of lectiveBank) {
      const found = (story.questions || []).find((question) => textEditKey(question.id) === id);
      if (found) return applyQuestionTextEdit(found);
    }
    const pools = [
      store.onlineCompetition && store.onlineCompetition.materials,
      store.offlineCompetition && store.offlineCompetition.materials
    ].filter(Array.isArray);
    for (const materials of pools) {
      for (const material of materials) {
        const found = (material.questions || []).find((question) => textEditKey(question.id) === id);
        if (found) return applyQuestionTextEdit(found);
      }
    }
    return null;
  }

  function editVisibleText(control) {
    if (!authorizeTextEdit()) return;
    const question = findEditableQuestionById(control.dataset.questionId || "");
    if (!question) {
      window.alert("This text cannot be edited from this screen yet.");
      return;
    }
    const kind = control.dataset.editKind || "question";
    const edits = ensureTextEditStore();
    const questionText = promptForText("Question", question.question);
    if (questionText === null) return;
    const originalAnswers = Array.isArray(question.answers) ? question.answers : [];
    const currentAnswers = [0, 1, 2].map((index) => originalAnswers[index] || { text: "", correct: index === 0 });
    const answers = Array.from({ length: Math.max(3, originalAnswers.length) }, (_, index) => ({
      text: cleanText(originalAnswers[index] && originalAnswers[index].text || ""),
      correct: false
    }));
    for (let index = 0; index < currentAnswers.length; index += 1) {
      const text = promptForText(`Answer ${String.fromCharCode(65 + index)}`, currentAnswers[index].text);
      if (text === null) return;
      answers[index] = { text, correct: false };
    }
    const currentCorrectIndex = Math.max(0, currentAnswers.findIndex((answer) => answer.correct));
    const correctLetter = cleanText(window.prompt("Correct answer letter: A, B, or C", String.fromCharCode(65 + currentCorrectIndex)) || "").toUpperCase();
    const correctIndex = ["A", "B", "C"].indexOf(correctLetter);
    if (correctIndex < 0) {
      window.alert("Correction was not saved. The correct answer must be A, B, or C.");
      return;
    }
    answers.forEach((answer, index) => {
      answer.correct = index === correctIndex;
    });
    edits.questions[textEditKey(question.id)] = {
      question: questionText,
      answers
    };
    if (kind === "reading") {
      const reference = promptForText("Scripture/reference/title", question.reference || question.title || "");
      if (reference === null) return;
      const content = promptForText("Reading text", question.content || "");
      if (content === null) return;
      edits.stories[textEditKey(question.storyId)] = {
        title: question.title || reference || "Reading",
        reference,
        content
      };
    }
    invalidateLectiveCaches();
    saveStore();
    toastMessage = "Text correction saved on this device.";
    render();
  }

  function promptForText(label, currentValue) {
    const value = window.prompt(label, cleanText(currentValue || ""));
    if (value === null) return null;
    return cleanText(value);
  }

  function currentQuestion(session) {
    return applyQuestionTextEdit(questionById.get(session.questions[session.index]));
  }

  function currentAnswer(session) {
    return session.answers.find((answer) => answer.index === session.index);
  }

  function requireSession() {
    const session = currentSession();
    if (!session || session.completed) {
      route = "home";
      render();
      return null;
    }
    return session;
  }

  function currentSession() {
    const user = activeUser();
    return user ? user.activeSession : null;
  }

  function activeUser() {
    return store.activeUser ? store.users[store.activeUser] : null;
  }

  function levelStoryCount(levelId) {
    const level = courseById(levelId) || LEVELS[0];
    if (typeof level.storyCount === "number") return level.storyCount;
    const offset = levelOffsets[level.id] || 0;
    return Math.max(0, storyBank.length - offset);
  }

  function levelStoryCountLabel(levelId) {
    return `${levelStoryCount(levelId).toLocaleString()} stories`;
  }

  function resumeSavedSession(user) {
    const session = user && user.activeSession;
    if (!session || session.completed || !Array.isArray(session.questions) || !session.questions.length) {
      return false;
    }
    if (session.index < 0 || session.index >= session.questions.length || !currentQuestion(session)) {
      user.activeSession = null;
      saveStore();
      return false;
    }
    session.phase = session.phase === "quiz" ? "quiz" : "reading";
    session.deadline = null;
    saveStore();
    route = session.phase;
    render();
    return true;
  }

  function missedQuestionsForSession(session) {
    return (session.answers || [])
      .filter((answer) => !answer.correct)
      .map((answer) => {
        const question = applyQuestionTextEdit(questionById.get(answer.questionId));
        if (!question) return null;
        const choices = displayAnswersForQuestion(question, session);
        const selected = choices[answer.selectedIndex] || null;
        const correct = choices[answer.correctIndex] || choices.find((choice) => choice.correct) || null;
        return {
          questionId: question.id,
          question: question.question,
          reference: question.reference,
          selectedText: answer.timedOut ? "Time finished" : selected ? selected.text : "No answer",
          correctText: correct ? correct.text : ""
        };
      })
      .filter(Boolean);
  }

  function makeCertificateId(prefix) {
    const date = new Date();
    const stamp = `${date.getFullYear()}${String(date.getMonth() + 1).padStart(2, "0")}${String(date.getDate()).padStart(2, "0")}`;
    const random = Math.random().toString(36).slice(2, 7).toUpperCase();
    return `${prefix}-${stamp}-${random}`;
  }

  function questionsForLevel(levelId, user) {
    const level = courseById(levelId) || LEVELS[0];
    ensureUserDefaults(user);
    const selectedStories = courseStoriesForLevel(level.id);
    return selectedStories.flatMap((story) => story.questions.map(applyQuestionTextEdit));
  }

  function resolveStartLevelId(user, requestedLevelId) {
    const learner = ensureUserDefaults(user);
    const requested = requestedLevelId === BABY_COURSE.id || LEVELS.some((level) => level.id === requestedLevelId) ? requestedLevelId : 1;
    const failedCertificate = learner.lastCertificate && !learner.lastCertificate.passed ? learner.lastCertificate : null;
    if (failedCertificate) return failedCertificate.levelId;
    if (requested === BABY_COURSE.id) return learner.babyCompleted ? 1 : BABY_COURSE.id;

    const highestCompleted = learner.highestCompleted || 0;
    if (highestCompleted >= requested) {
      const nextLevel = LEVELS.find((level) => level.id > highestCompleted);
      return nextLevel ? nextLevel.id : null;
    }
    return requested;
  }

  function courseStoriesForLevel(levelId) {
    const level = courseById(levelId) || LEVELS[0];
    const offset = levelOffsets[level.id] || 0;
    return storyBank.slice(offset, offset + levelStoryCount(level.id)).map(applyStoryTextEdit);
  }

  function markStoriesRead(user, storyIds) {
    const learner = ensureUserDefaults(user);
    const known = new Set((learner.readStoryIds || []).map(String));
    storyIds.filter((id) => id !== undefined && id !== null).forEach((id) => known.add(String(id)));
    learner.readStoryIds = Array.from(known);
  }

  function storyIdsFromQuestionIds(questionIds) {
    return (questionIds || [])
      .map((id) => questionById.get(id))
      .filter(Boolean)
      .map((question) => question.storyId);
  }

  function ensureUserDefaults(user) {
    if (!user) return user;
    user.id = cleanText(user.id || normalizeUserId(user.displayName || "guest"));
    migrateLocalPassword(user, user.id);
    user.bestScores = user.bestScores && typeof user.bestScores === "object" ? user.bestScores : {};
    user.history = Array.isArray(user.history) ? user.history : [];
    user.activeSession = user.activeSession || null;
    user.lastCertificate = user.lastCertificate || null;
    user.readStoryIds = uniqueStrings(user.readStoryIds || []);
    user.babyCompleted = Boolean(user.babyCompleted);
    user.electiveProgress = user.electiveProgress && typeof user.electiveProgress === "object" ? user.electiveProgress : {};
    user.electiveProgress.classes = user.electiveProgress.classes && typeof user.electiveProgress.classes === "object" ? user.electiveProgress.classes : {};
    user.electiveProgress.completedClasses = user.electiveProgress.completedClasses && typeof user.electiveProgress.completedClasses === "object" ? user.electiveProgress.completedClasses : {};
    user.electiveProgress.currentClass = normalizeLectiveLevel(user.electiveProgress.currentClass || DEFAULT_LECTIVE_LEVEL);
    user.electiveCombinations = user.electiveCombinations && typeof user.electiveCombinations === "object" ? user.electiveCombinations : {};
    ELECTIVE_COMBINATION_LEVELS.forEach((level) => {
      user.electiveCombinations[level] = electiveCombinationForUser(user, level);
    });
    user.electiveStudy = user.electiveStudy || null;
    return user;
  }

  function uniqueStrings(values) {
    return Array.from(new Set((values || [])
      .filter((value) => value !== undefined && value !== null && value !== "")
      .map((value) => String(value))));
  }

  function buildStoryBank(sourceStories) {
    return sourceStories
      .map((story, storyIndex) => {
        const storyId = story.id || storyIndex + 1;
        const title = cleanText(story.title || `Story ${storyIndex + 1}`);
        let reference = cleanText(story.reference || "");
        const courseName = cleanText(story.course || "").toLowerCase();
        if (courseName === "baby course" || reference.toLowerCase() === "baby course") {
          reference = LITTLE_ANGELS_LABEL;
        }
        const content = cleanText(story.content || "");
        const wordCount = countWords(`${title} ${reference} ${content}`);
        const questions = (Array.isArray(story.questions) ? story.questions : [])
        .filter((question) => question && Array.isArray(question.answers) && question.answers.length)
        .map((question, questionIndex) => ({
          id: `${storyId}:${questionIndex}`,
          storyId,
          title,
          reference,
          content,
          question: cleanText(question.question || "Question"),
          answers: question.answers.map((answer) => ({
            text: cleanText(answer.text || ""),
            correct: Boolean(answer.correct)
          }))
        }));
        return { id: storyId, title, reference, content, questions, wordCount, originalIndex: storyIndex };
      })
      .filter((story) => story.questions.length);
  }

  function countWords(text) {
    return cleanText(text).split(/\s+/).filter(Boolean).length;
  }

  function buildLevelOffsets() {
    const offsets = {};
    offsets[BABY_COURSE.id] = 0;
    let cursor = BABY_COURSE.storyCount;
    LEVELS.forEach((level) => {
      offsets[level.id] = cursor;
      cursor += typeof level.storyCount === "number" ? level.storyCount : 0;
    });
    return offsets;
  }

  function courseById(levelId) {
    if (Number(levelId) === BABY_COURSE.id) return BABY_COURSE;
    return LEVELS.find((level) => level.id === Number(levelId)) || null;
  }

  function courseLabel(levelId) {
    return Number(levelId) === BABY_COURSE.id ? LITTLE_ANGELS_LABEL : `Level ${Number(levelId) || 1}`;
  }

  function nextCourseAfter(levelId) {
    if (Number(levelId) === BABY_COURSE.id) return LEVELS[0];
    return LEVELS.find((level) => level.id === Number(levelId) + 1) || null;
  }

  function passPercentForLevel(levelId) {
    return Number(levelId) === BABY_COURSE.id ? BABY_PASS_PERCENT : PASS_PERCENT;
  }

  function displayAnswersForQuestion(item, session) {
    const safeItem = applyQuestionTextEdit(item);
    const answers = (Array.isArray(safeItem && safeItem.answers) ? safeItem.answers : []).map((choice, index) => ({ ...choice, originalIndex: index }));
    if (!session || Number(session.levelId) !== BABY_COURSE.id) return answers;
    return seededShuffle(answers, `${session.startedAt || ""}:${safeItem.id}:little-angeles`);
  }

  function seededShuffle(items, seedText) {
    const result = [...items];
    let seed = hash(seedText);
    for (let index = result.length - 1; index > 0; index -= 1) {
      seed = (seed * 1664525 + 1013904223) >>> 0;
      const swapIndex = seed % (index + 1);
      const temp = result[index];
      result[index] = result[swapIndex];
      result[swapIndex] = temp;
    }
    return result;
  }

  function hash(text) {
    let value = 2166136261;
    for (let index = 0; index < text.length; index += 1) {
      value ^= text.charCodeAt(index);
      value = Math.imul(value, 16777619);
    }
    return value >>> 0;
  }

  function localPasswordHash(id, password) {
    const normalizedId = cleanText(id || "").toLowerCase();
    const value = String(password || "");
    const first = hash(`${normalizedId}|${value}|know-god-more-password-a`).toString(36);
    const second = hash(`${value}|${normalizedId}|know-god-more-password-b`).toString(36);
    return `${PASSWORD_HASH_PREFIX}${first}.${second}`;
  }

  function hasLocalPasswordHash(record) {
    return Boolean(record && typeof record.passwordHash === "string" && record.passwordHash.startsWith(PASSWORD_HASH_PREFIX));
  }

  function setLocalPassword(record, id, password) {
    if (!record) return record;
    record.passwordHash = localPasswordHash(id, password);
    delete record.password;
    return record;
  }

  function migrateLocalPassword(record, id) {
    if (!record || typeof record !== "object") return record;
    const legacyPassword = typeof record.password === "string" ? record.password : "";
    if (!hasLocalPasswordHash(record) && legacyPassword) {
      setLocalPassword(record, id, legacyPassword);
      return record;
    }
    if (hasLocalPasswordHash(record)) delete record.password;
    return record;
  }

  function verifyLocalPassword(record, id, password) {
    if (!record) return false;
    migrateLocalPassword(record, id);
    if (!hasLocalPasswordHash(record)) {
      setLocalPassword(record, id, password);
      return true;
    }
    return record.passwordHash === localPasswordHash(id, password);
  }

  function sanitizeCoordinatorForStorage(coordinator) {
    if (!coordinator || typeof coordinator !== "object") return coordinator;
    const next = { ...coordinator };
    if (next.password || next.passwordHash) next.passwordSet = true;
    delete next.password;
    delete next.passwordHash;
    return next;
  }

  function sanitizeCompetitionRecordForStorage(record) {
    if (!record || typeof record !== "object") return record;
    const next = cloneData(record);
    if (next.coordinator) next.coordinator = sanitizeCoordinatorForStorage(next.coordinator);
    if (Array.isArray(next.players)) {
      next.players = next.players.map((player) => {
        if (!player || typeof player !== "object") return player;
        const cleanPlayer = { ...player };
        if (cleanPlayer.password || cleanPlayer.passwordHash) cleanPlayer.passwordSet = true;
        delete cleanPlayer.password;
        delete cleanPlayer.passwordHash;
        return cleanPlayer;
      });
    }
    return next;
  }

  function sanitizeCompetitionSessionsForStorage(sessions) {
    const normalized = sessions && typeof sessions === "object" ? sessions : {};
    return Object.fromEntries(Object.entries(normalized).map(([key, saved]) => {
      if (!saved || typeof saved !== "object") return [key, saved];
      return [key, {
        ...saved,
        record: sanitizeCompetitionRecordForStorage(saved.record)
      }];
    }));
  }

  function userStats(user) {
    const scores = Object.values(user.bestScores || {});
    const totalPoints = scores.reduce((sum, score) => sum + (score.score || 0), 0);
    const bestPercent = scores.reduce((best, score) => Math.max(best, score.percent || 0), 0);
    return {
      completed: user.highestCompleted || 0,
      totalPoints,
      bestPercent
    };
  }

  function leaderBoard() {
    const local = Object.values(store.users || {}).map((user) => ({
      name: user.displayName,
      points: userStats(user).totalPoints
    }));
    return local
      .sort((a, b) => b.points - a.points)
      .slice(0, 25);
  }

  function startServiceTicker() {
    if (serviceTimerId) window.clearInterval(serviceTimerId);
    serviceTimerId = window.setInterval(() => {
      refreshServiceState();
      updateServiceRemainingPreview();
      if (isServiceExpired() && route !== "login") render();
    }, 60000);
  }

  function refreshServiceState() {
    serviceState = readServiceState();
    return serviceState;
  }

  function readServiceState() {
    if (window.KnowGodMoreService && typeof window.KnowGodMoreService.getState === "function") {
      try {
        const nativeState = JSON.parse(window.KnowGodMoreService.getState());
        if (nativeState && typeof nativeState.remainingMs === "number") return nativeState;
      } catch (error) {}
    }

    try {
      const now = performance.now();
      const saved = JSON.parse(localStorage.getItem(SERVICE_STORAGE_KEY) || "{}");
      const startedAt = typeof saved.startedAt === "number" ? saved.startedAt : now;
      const addedMs = typeof saved.addedMs === "number" ? saved.addedMs : 0;
      const allowedMs = INITIAL_SERVICE_MS + addedMs;
      const remainingMs = Math.max(0, allowedMs - (now - startedAt));
      const clientId = saved.clientId || makeClientId();
      const state = {
        totalMs: allowedMs,
        allowedMs,
        remainingMs,
        usedMs: allowedMs - remainingMs,
        expired: remainingMs <= 0,
        clientId,
        source: "browser-performance"
      };
      localStorage.setItem(SERVICE_STORAGE_KEY, JSON.stringify({ ...saved, startedAt, addedMs, clientId }));
      return state;
    } catch (error) {
      return { totalMs: INITIAL_SERVICE_MS, allowedMs: INITIAL_SERVICE_MS, remainingMs: INITIAL_SERVICE_MS, usedMs: 0, expired: false, clientId: "KGM-FALLBACK", source: "fallback" };
    }
  }

  function isServiceExpired() {
    return Boolean(serviceState && serviceState.expired);
  }

  function competitionMemberCount() {
    const offline = store.offlineCompetition;
    const offlineCount = offline && Array.isArray(offline.players) ? offline.players.length : 0;
    const onlineStateCount = competitionState && Number.isFinite(Number(competitionState.playerCount)) ? Number(competitionState.playerCount) : 0;
    const onlinePlayersCount = competitionState && Array.isArray(competitionState.players) ? competitionState.players.length : 0;
    return Math.max(offlineCount, onlineStateCount, onlinePlayersCount);
  }

  function canUseCompetitionWhileExpired() {
    return competitionMemberCount() >= 10;
  }

  function serviceRemainingText() {
    const remainingMs = Math.max(0, serviceState && typeof serviceState.remainingMs === "number" ? serviceState.remainingMs : INITIAL_SERVICE_MS);
    if (remainingMs <= 0) return "EXPIRED";
    const remainingMinutes = Math.ceil(remainingMs / (60 * 1000));
    if (remainingMinutes < 60) return `${remainingMinutes.toLocaleString()} MINUTES`;
    const remainingHours = Math.ceil(remainingMinutes / 60);
    return `${remainingHours.toLocaleString()} HOURS`;
  }

  function servicePercent() {
    const remainingMs = Math.max(0, serviceState && typeof serviceState.remainingMs === "number" ? serviceState.remainingMs : INITIAL_SERVICE_MS);
    const allowedMs = Math.max(1, serviceState && typeof serviceState.allowedMs === "number" ? serviceState.allowedMs : INITIAL_SERVICE_MS);
    return Math.max(0, Math.min(100, Math.round((remainingMs / allowedMs) * 100)));
  }

  function serviceToneStyle() {
    const hue = Math.round((servicePercent() / 100) * 120);
    return `--service-tone: hsl(${hue}, 84%, 54%); --service-tone-soft: hsla(${hue}, 84%, 54%, 0.22);`;
  }

  function renewBreathStyle() {
    const remainingMs = Math.max(0, serviceState && typeof serviceState.remainingMs === "number" ? serviceState.remainingMs : INITIAL_SERVICE_MS);
    const remainingHours = remainingMs / (60 * 60 * 1000);
    if (remainingHours >= 72) return "animation: none;";
    const hoursForSpeed = Math.max(1, Math.min(71, remainingHours));
    const durationSeconds = 1 + ((hoursForSpeed - 1) / 70) * 4;
    return `--renew-breath-duration: ${durationSeconds.toFixed(2)}s;`;
  }

  function updateServiceRemainingPreview() {
    const node = document.querySelector("[data-service-remaining]");
    if (node) {
      node.textContent = serviceRemainingText();
      node.setAttribute("style", serviceToneStyle());
    }
    const renewButton = document.querySelector(".login-renew-button");
    if (renewButton) renewButton.setAttribute("style", renewBreathStyle());
    document.querySelectorAll(".login-lective-button,.login-competition-button").forEach((button) => {
      button.setAttribute("style", serviceToneStyle());
    });
  }

  function startPhoneUpdate() {
    if (updateBusy) return;
    if (isWindowsPcBuild()) {
      updateInstallReady = false;
      localStorage.removeItem(UPDATE_INSTALL_READY_KEY);
      updateBusy = true;
      updateMessage = `Opening ${UPDATE_APK_NAME} download...`;
      renderLogin();
      window.setTimeout(() => {
        try {
          const downloadUrl = `${UPDATE_BASE_URL.replace(/\/+$/, "")}/${UPDATE_APK_NAME}`;
          window.open(downloadUrl, "_blank", "noopener");
          handlePhoneUpdateResult("pc-download-opened");
        } catch (error) {
          handlePhoneUpdateResult("failed");
        }
      }, 80);
      return;
    }
    updateInstallReady = false;
    localStorage.removeItem(UPDATE_INSTALL_READY_KEY);
    updateBusy = true;
    updateMessage = `Downloading ${UPDATE_APK_NAME}...`;
    loginError = "";
    renderLogin();
    window.setTimeout(() => {
      try {
        if (window.KnowGodMoreUpdater && typeof window.KnowGodMoreUpdater.update === "function") {
          window.KnowGodMoreUpdater.update(UPDATE_BASE_URL);
        } else {
          handlePhoneUpdateResult("native-missing");
        }
      } catch (error) {
        handlePhoneUpdateResult("failed");
      }
    }, 80);
  }

  function startPhoneInstall() {
    if (isWindowsPcBuild()) {
      updateMessage = "Open Downloads and run kgmpc.exe to install.";
      renderLogin();
      try {
        handlePhoneUpdateResult("pc-open-downloads");
      } catch (error) {
      }
      return;
    }
    updateMessage = "Opening Downloads. Tap kgmphone.apk to install.";
    renderLogin();
    window.setTimeout(() => {
      try {
        if (window.KnowGodMoreUpdater && typeof window.KnowGodMoreUpdater.openDownloads === "function") {
          window.KnowGodMoreUpdater.openDownloads();
        } else if (window.KnowGodMoreUpdater && typeof window.KnowGodMoreUpdater.install === "function") {
          window.KnowGodMoreUpdater.install();
        } else {
          handlePhoneUpdateResult("native-missing");
        }
      } catch (error) {
        handlePhoneUpdateResult("installer-unavailable");
      }
    }, 80);
  }

  function handlePhoneUpdateResult(status) {
    const value = String(status || "");
    updateBusy = value === "downloading";
    updateInstallReady = ["download-ready", "downloads", "install-opened", "install-permission", "installer-unavailable"].includes(value);
    if (updateInstallReady) {
      localStorage.setItem(UPDATE_INSTALL_READY_KEY, "1");
    } else if (value !== "downloading") {
      localStorage.removeItem(UPDATE_INSTALL_READY_KEY);
    }
    const messages = {
      "checking-install": "Checking downloaded update...",
      downloading: `Downloading ${UPDATE_APK_NAME}...`,
      "download-ready": isWindowsPcBuild() ? `Update downloaded and checked. Open Downloads and run ${UPDATE_APK_NAME}.` : "Update downloaded and checked. Tap the button below to open Downloads.",
      downloads: isWindowsPcBuild() ? `Downloads opened. Run ${UPDATE_APK_NAME}, then follow Windows setup.` : "Downloads opened. Tap kgmphone.apk, then choose UPDATE.",
      "storage-permission": isWindowsPcBuild() ? `Allow the browser to download ${UPDATE_APK_NAME}.` : "Allow storage permission so kgmphone.apk can be saved.",
      "storage-denied": "Storage permission was denied. Allow storage, then press UPDATE again.",
      "download-save-failed": isWindowsPcBuild() ? "The update downloaded, but Windows refused to save it. Free space and try again." : "The update downloaded, but this phone refused to save it. Free space and try again.",
      "install-opened": isWindowsPcBuild() ? `${UPDATE_APK_NAME} opened from Downloads. Continue with Windows setup.` : "kgmphone.apk opened from Downloads. Choose UPDATE or INSTALL when Android asks.",
      "install-permission": isWindowsPcBuild() ? "Allow the installer to run, then continue setup." : "Allow installs from Know God More, then return and press INSTALL.",
      "installer-unavailable": isWindowsPcBuild() ? `Update downloaded. Open Downloads and run ${UPDATE_APK_NAME}.` : "Update downloaded. Open Downloads and tap kgmphone.apk.",
      "download-missing": "The downloaded update could not be confirmed. Press UPDATE to download it again.",
      "not-found": isWindowsPcBuild() ? `${UPDATE_APK_NAME} was not found on the update server.` : "kgmphone.apk was not found on the update server.",
      "not-apk": isWindowsPcBuild() ? "The downloaded file is not a valid Windows installer." : "The downloaded file is not a valid APK.",
      "wrong-package": isWindowsPcBuild() ? "The downloaded file is not the Know God More PC app." : "The downloaded APK is not the main Know God More phone app.",
      "wrong-signature": isWindowsPcBuild() ? "The downloaded installer was not signed with the Know God More release key." : "The downloaded APK was not signed with the Know God More release key.",
      "same-version": "THE APPLICATION IS UPTODATE",
      "native-missing": isWindowsPcBuild() ? "Open Downloads and run the latest Know God More PC installer manually once." : "This build cannot update itself. Install the latest APK manually once.",
      "pc-download-opened": "The PC installer download opened. Run kgmpc.exe from Downloads to update.",
      "pc-open-downloads": "Open Downloads and run kgmpc.exe to complete the update.",
      failed: "Update failed. Check internet and try again."
    };
    updateMessage = messages[value] || messages.failed;
    if (route === "login") renderLogin();
  }

  function loginUpdateStatusHtml() {
    if (updateInstallReady) {
      if (isWindowsPcBuild()) {
        return `<button class="login-renew-message login-update-install-button" type="button" data-action="phone-install">OPEN DOWNLOADS AND RUN kgmpc.exe</button>`;
      }
      return `<button class="login-renew-message login-update-install-button" type="button" data-action="phone-install">CLOSE, INSTALL kgmphone.apk IN DOWNLOADS</button>`;
    }
    if (updateMessage || renewalMessage) {
      return `<div class="login-renew-message">${escapeHtml(updateMessage || renewalMessage)}</div>`;
    }
    return "";
  }

  function isWindowsPcBuild() {
    return !!(window.KnowGodMoreShare && window.KnowGodMoreShare.isLocalWindowsBridge === true);
  }

  function openRenewalDialog() {
    if (document.querySelector("[data-renewal-dialog]")) return;
    renewalPath = "menu";
    renewalPaidReady = true;
    renewalMessage = "";
    document.body.insertAdjacentHTML("beforeend", renewalDialogHtml());
    fitRenewalActivationInput();
  }

  function closeRenewalDialog() {
    const dialog = document.querySelector("[data-renewal-dialog]");
    if (dialog) dialog.remove();
    renewalPath = "menu";
    renewalPaidReady = false;
    stopRenewalFieldWatcher();
    stopRenewalSmsPolling(false);
  }

  function renewalDialogHtml() {
    const reasonCode = deviceReasonCode();
    const lock = activationLockState();
    const visibleMessage = renewalMessage || lock.message || "Token works only on this device.";
    const content = renewalPath === "menu"
      ? renewalMenuHtml(reasonCode, lock, visibleMessage)
      : (renewalPath === "free-token"
        ? renewalFreeTokenHtml()
        : renewalGuideHtml(renewalPath, reasonCode, lock, visibleMessage));
    return `
      <div class="renewal-dialog-backdrop" data-renewal-dialog role="dialog" aria-modal="true" aria-label="Renew service">
        <form class="renewal-dialog" data-renewal-form>
          <div class="section-head">
            <div>
              <p class="kicker">Renew Page</p>
              <h2>BUY AIRTIME TOKEN</h2>
            </div>
            <button class="ghost-button renewal-close-button" type="button" data-action="close-renewal-dialog">Close</button>
          </div>
          ${content}
        </form>
      </div>
    `;
  }

  function renewalMenuHtml(reasonCode, lock, visibleMessage) {
    return `
      <div class="renewal-token-rate">500 UGX = 12 HOURS</div>
      <div class="renewal-menu-grid">
        <button class="renewal-path-button renewal-path-mtn" type="button" data-action="renewal-path" data-path="mtn-self">BUYING AIRTIME TOKEN FOR THIS DEVICE WITH MTN</button>
        <button class="renewal-path-button renewal-path-airtel" type="button" data-action="renewal-path" data-path="airtel-self">BUY AIRTIME TOKEN FOR THIS DEVICE USING AIRTEL</button>
      </div>
      <div class="renewal-device-code-wrap">
        <div class="renewal-device-code" aria-label="Device code">${bouncingCodeHtml(reasonCode)}</div>
      </div>
      <div class="renewal-token-panel renewal-menu-token-panel">
        <label class="renewal-field">
          <span>20 digit token</span>
          <input name="activationCode" data-renewal-token inputmode="numeric" autocomplete="off" spellcheck="false" maxlength="24" value="" placeholder="0000 0000 0000 0000 0000" ${lock.locked ? "disabled" : ""}>
        </label>
        <p class="renewal-dialog-message" data-renewal-dialog-message>${escapeHtml(visibleMessage)}</p>
        <button class="primary-button" type="submit" ${lock.locked ? "disabled" : ""}>ACTIVATE</button>
      </div>
      <div class="renewal-menu-grid">
        <button class="renewal-path-button renewal-path-airtel" type="button" data-action="renewal-path" data-path="airtel-other">BUYING AIRTIME TOKEN FOR A DIFFERENT DEVICE WITH AIRTEL</button>
        <button class="renewal-path-button renewal-path-mtn" type="button" data-action="renewal-path" data-path="mtn-other">BUYING AIRTIME TOKEN FOR A DIFFERENT DEVICE WITH MTN</button>
      </div>
      <button class="renewal-path-button renewal-free-button" type="button" data-action="renewal-path" data-path="free-token">FREE AIRTIME TOKEN</button>
      <p class="renewal-footnote">TO BECOME A DISTRIBUTOR AND EARN 50% OF ALL PAYMENTS TO THIS APP JOIN WHATSAPP 0782225970 OR VISIT: wwwknowGodmore.com</p>
    `;
  }

  function renewalFreeTokenHtml() {
    return `
      <button class="ghost-button renewal-back-button" type="button" data-action="renewal-menu">&lsaquo; Back</button>
      <div class="renewal-guide renewal-free-guide">
        <h3>FREE AIRTIME TOKEN</h3>
        <div class="renewal-step-list">
          <div><strong>1.</strong><span>Get a person, explain to him how Know God More app can help him.</span></div>
          <div><strong>2.</strong><span>Assist him install the app from any source like wwwknowGodmore.com. In the paying process, at Reference or Reason, when you put the new customer's device ID, add your device ID too.</span></div>
          <div><strong>3.</strong><span>The new customer will receive his full amount token and another token for you in the same SMS.</span></div>
          <div><strong>4.</strong><span>The more time he buys, the more time he gets, and the same time you too will get.</span></div>
        </div>
      </div>
      <p class="renewal-footnote">TO BECOME A DISTRIBUTOR AND EARN 50% OF ALL PAYMENTS TO THIS APP JOIN WHATSAPP 0782225970 OR VISIT: wwwknowGodmore.com</p>
    `;
  }

  function renewalGuideHtml(path, ownReasonCode, lock, visibleMessage) {
    const isMtn = path === "mtn-self" || path === "mtn-other";
    const isAirtel = path === "airtel-self" || path === "airtel-other";
    const isOther = path === "mtn-other" || path === "airtel-other";
    const shouldCaptureSms = path === "mtn-self" || path === "airtel-self";
    const title = renewalPathTitle(path);
    const ussd = isMtn ? "*185*1*1#" : "*185*9#";
    const amount = renewalDial.amount || "5000";
    const reasonCode = isOther ? normalizeRenewalReasonCode(renewalDial.otherCode) : ownReasonCode;
    const reference = renewalReferenceText(reasonCode);
    const referenceDisplay = isOther
      ? "PUT THE APP DEVICE ID OF THE DEVICE THAT YOU'RE PAYING FOR"
      : (reference || "ADD DEVICE CODE");
    const reasonLabel = isMtn ? "Reason" : "Reference";
    const requiredSteps = isMtn
      ? [
          `Dial *185*1*1# and press CALL button.`,
          `At, Enter number....Put 0782225970. and press SEND.`,
          `At, Enter Amount, put eg. ${amount}. and press SEND.`,
          isOther
            ? `At, Enter Reason put ${referenceDisplay} and press SEND.`
            : `At, Enter Reason put ${reference || ownReasonCode} and press SEND.`,
          `At, Enter pin_ _ _ _ _ and press SEND.`
        ]
      : [
          `Dial *185*9# and press CALL.`,
          `At, Enter Merchant ID put 6174231 and press SEND.`,
          `At, Enter amount, put eg ${amount} press SEND.`,
          isOther
            ? `At Reference put ${referenceDisplay} and press SEND.`
            : `At Reference put ${reference || ownReasonCode} and press SEND.`,
          `At, Enter Pin_ _ _ _ and press SEND.`
        ];
    const stepRows = requiredSteps.map((step, index) => `<div><strong>${index + 1}.</strong><span>${escapeHtml(step)}</span></div>`).join("");
    const referenceBox = isOther
      ? ""
      : `
          <div class="renewal-reference-box">
            <span>${escapeHtml(reasonLabel)}</span>
            <strong data-renewal-reference-display>${escapeHtml(referenceDisplay)}</strong>
            <button class="ghost-button renewal-copy-code-button" type="button" data-action="copy-renewal-value" data-renewal-reference-copy data-label="${escapeHtml(reasonLabel)}" data-value="${escapeHtml(referenceDisplay)}">COPY</button>
          </div>
        `;
    const tokenPanel = isOther
      ? ""
      : `
        <div class="renewal-token-panel">
          <label class="renewal-field">
            <span>20 digit token</span>
            <input name="activationCode" data-renewal-token inputmode="numeric" autocomplete="off" spellcheck="false" maxlength="24" value="" placeholder="0000 0000 0000 0000 0000" ${lock.locked ? "disabled" : ""}>
          </label>
          <p class="renewal-dialog-message" data-renewal-dialog-message>${escapeHtml(visibleMessage)}</p>
          <button class="primary-button" type="submit" ${lock.locked ? "disabled" : ""}>ACTIVATE</button>
        </div>
      `;
    return `
      <button class="ghost-button renewal-back-button" type="button" data-action="renewal-menu">&lsaquo; Back</button>
      <div class="renewal-guide ${isAirtel ? "renewal-guide-airtel" : "renewal-guide-mtn"}">
        <h3>${escapeHtml(title)}</h3>
        <div class="renewal-token-rate">500 UGX = 12 HOURS</div>
        <div class="renewal-step-list">
          ${stepRows}
          ${referenceBox}
        </div>
        <button class="primary-button renewal-start-button" type="button" data-action="open-ussd" data-ussd="${escapeHtml(ussd)}">START ${escapeHtml(ussd)}</button>
      </div>
      ${tokenPanel}
      <p class="renewal-footnote">TO BECOME A DISTRIBUTOR AND EARN 50% OF ALL PAYMENTS TO THIS APP JOIN WHATSAPP 0782225970 OR VISIT: wwwknowGodmore.com</p>
    `;
  }

  function renewalPathTitle(path) {
    if (path === "mtn-self") return "BUYING AIRTIME TOKEN FOR THIS DEVICE WITH MTN";
    if (path === "airtel-self") return "BUY AIRTIME TOKEN FOR THIS DEVICE USING AIRTEL";
    if (path === "airtel-other") return "BUYING AIRTIME TOKEN FOR A DIFFERENT DEVICE WITH AIRTEL";
    if (path === "mtn-other") return "BUYING AIRTIME TOKEN FOR A DIFFERENT DEVICE WITH MTN";
    if (path === "free-token") return "FREE AIRTIME TOKEN";
    return "BUY AIRTIME TOKEN";
  }

  function normalizeRenewalReasonCode(value) {
    return String(value || "").replace(/[^a-z0-9]/gi, "").toUpperCase().slice(0, 6);
  }

  function renewalReferenceText(reasonCode) {
    const cleanCode = normalizeRenewalReasonCode(reasonCode);
    if (cleanCode.length !== 6) return "";
    return cleanCode;
  }

  function captureRenewalDialFields() {
    const amountNode = document.querySelector("[data-renewal-amount]");
    const otherCodeNode = document.querySelector("[data-renewal-other-code]");
    if (amountNode) renewalDial.amount = String(amountNode.value || "").replace(/[^\d]/g, "").slice(0, 8) || "5000";
    if (otherCodeNode) renewalDial.otherCode = normalizeRenewalReasonCode(otherCodeNode.value);
  }

  function updateRenewalReferencePreview() {
    const isOther = renewalPath === "mtn-other" || renewalPath === "airtel-other";
    const baseCode = isOther ? renewalDial.otherCode : deviceReasonCode();
    const reference = renewalReferenceText(baseCode) || "ADD DEVICE CODE";
    const referenceNode = document.querySelector("[data-renewal-reference-display]");
    if (referenceNode) referenceNode.textContent = reference;
    const sendButton = document.querySelector("[data-renewal-reference-copy]");
    if (sendButton) {
      sendButton.textContent = "COPY";
      sendButton.dataset.value = reference;
    }
  }

  function renewalNetworkLabel(path) {
    return path === "airtel-self" || path === "airtel-other" ? "Airtel" : "MTN";
  }

  function currentRenewalBaseCode() {
    return renewalPath === "mtn-other" || renewalPath === "airtel-other"
      ? normalizeRenewalReasonCode(renewalDial.otherCode)
      : deviceReasonCode();
  }

  function renewalShouldCaptureSms(path = renewalPath) {
    return path === "mtn-self" || path === "airtel-self";
  }

  function renewalPendingPayload(ussd, smsCapture = false) {
    const baseCode = currentRenewalBaseCode();
    return {
      path: renewalPath,
      amount: renewalDial.amount || "5000",
      otherCode: renewalDial.otherCode || "",
      reference: renewalReferenceText(baseCode),
      ussd: String(ussd || "").trim(),
      network: renewalNetworkLabel(renewalPath),
      smsCapture: !!smsCapture,
      savedAt: Date.now()
    };
  }

  function savePendingRenewal(ussd, smsCapture = false) {
    try {
      localStorage.setItem(RENEWAL_PENDING_KEY, JSON.stringify(renewalPendingPayload(ussd, smsCapture)));
    } catch (error) {}
  }

  function readPendingRenewal() {
    try {
      const raw = localStorage.getItem(RENEWAL_PENDING_KEY);
      return raw ? JSON.parse(raw) : null;
    } catch (error) {
      return null;
    }
  }

  function clearPendingRenewal() {
    try {
      localStorage.removeItem(RENEWAL_PENDING_KEY);
    } catch (error) {}
  }

  function restorePendingRenewalIfNeeded() {
    const pending = readPendingRenewal();
    if (!pending || !pending.path || pending.path === "menu") return false;
    if (pending.path === "out") {
      clearPendingRenewal();
      return false;
    }
    const ageMs = Date.now() - Number(pending.savedAt || 0);
    if (Number.isFinite(ageMs) && ageMs > 24 * 60 * 60 * 1000) {
      clearPendingRenewal();
      return false;
    }
    renewalPath = pending.path;
    renewalDial = {
      amount: String(pending.amount || "5000").replace(/[^\d]/g, "").slice(0, 8) || "5000",
      otherCode: normalizeRenewalReasonCode(pending.otherCode || "")
    };
    renewalPaidReady = true;
    renewalMessage = pending.smsCapture
      ? `Welcome back. ${pending.network || "Network"} payment details are still saved.`
      : `Welcome back. ${pending.network || "Network"} payment details are still saved. Enter the token on the other device.`;
    if (document.querySelector("[data-renewal-dialog]")) {
      refreshRenewalDialog();
    } else {
      document.body.insertAdjacentHTML("beforeend", renewalDialogHtml());
      fitRenewalActivationInput();
    }
    if (pending.smsCapture) startRenewalSmsPolling();
    return true;
  }

  function handleReturnFromDialer() {
    const restored = restorePendingRenewalIfNeeded();
    const pending = readPendingRenewal();
    if (pending && pending.smsCapture) startRenewalSmsPolling();
    showRecentRenewalAddedMessage();
    return restored;
  }

  function openRenewalUssd(ussd) {
    const code = String(ussd || "").trim();
    if (!code) return;
    captureRenewalDialFields();
    updateRenewalReferencePreview();
    const shouldCaptureSms = renewalShouldCaptureSms();
    let captureSms = shouldCaptureSms;
    const reference = shouldCaptureSms ? renewalReferenceText(currentRenewalBaseCode()) : "";
    if (shouldCaptureSms && !reference) {
      renewalMessage = "Add the six-character device code first.";
      updateRenewalDialogMessage(renewalMessage);
      return;
    }
    if (shouldCaptureSms) {
      const prepared = prepareRenewalSmsCapture(currentRenewalBaseCode());
      if (prepared && prepared.permission === "requested") {
        renewalMessage = "After payment, type or paste the 20-digit token from SMS.";
        updateRenewalDialogMessage(renewalMessage);
        return;
      }
      if (prepared && prepared.permission === "disabled") {
        captureSms = false;
        stopRenewalSmsPolling(true);
        renewalMessage = "After payment, type or paste the 20-digit token from SMS.";
      }
    } else {
      stopRenewalSmsPolling(true);
    }
    savePendingRenewal(code, captureSms);
    if (reference && navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(reference).catch(() => {});
    }
    renewalMessage = `Opening ${renewalNetworkLabel(renewalPath)} payment menu.`;
    updateRenewalDialogMessage(renewalMessage);
    window.setTimeout(() => {
      if (captureSms) startRenewalSmsPolling();
      window.location.href = `tel:${encodeURIComponent(code)}`;
    }, 300);
  }

  function prepareRenewalSmsCapture(reasonCode) {
    const bridge = window.KnowGodMoreRenewalSms;
    if (!bridge || typeof bridge.prepare !== "function") {
      return { ok: false, permission: "missing" };
    }
    try {
      return JSON.parse(bridge.prepare(reasonCode) || "{}");
    } catch (error) {
      return { ok: false, permission: "error" };
    }
  }

  function startRenewalSmsPolling() {
    if (renewalSmsPollerId) return;
    pollRenewalSmsTokens();
    renewalSmsPollerId = window.setInterval(pollRenewalSmsTokens, 2500);
  }

  function stopRenewalSmsPolling(nativeStop = false) {
    if (renewalSmsPollerId) {
      window.clearInterval(renewalSmsPollerId);
      renewalSmsPollerId = null;
    }
    if (nativeStop && window.KnowGodMoreRenewalSms && typeof window.KnowGodMoreRenewalSms.stop === "function") {
      try {
        window.KnowGodMoreRenewalSms.stop();
      } catch (error) {}
    }
  }

  function pollRenewalSmsTokens() {
    const bridge = window.KnowGodMoreRenewalSms;
    if (!bridge || typeof bridge.poll !== "function") {
      stopRenewalSmsPolling(false);
      return;
    }
    let result = null;
    try {
      result = JSON.parse(bridge.poll() || "{}");
    } catch (error) {
      return;
    }
    if (!result || !result.ok) return;
    if (result.permission === "missing") {
      renewalMessage = "Type or paste the 20-digit token from SMS.";
      updateRenewalDialogMessage(renewalMessage);
      return;
    }
    const tokens = Array.isArray(result.tokens) ? result.tokens : [];
    if (tokens.length && handleRenewalSmsTokens(tokens)) return;
    if (!result.active && Number(result.activeUntil || 0) > 0 && Date.now() > Number(result.activeUntil || 0)) {
      stopRenewalSmsPolling(false);
      renewalMessage = "Token waiting time has ended. You can still type the token manually.";
      updateRenewalDialogMessage(renewalMessage);
    }
  }

  function handleRenewalSmsTokens(tokens) {
    const serial = getDeviceSerial();
    let firstFailure = "";
    for (const token of tokens) {
      const cleanToken = normalizeToken(token);
      const validation = validateActivationToken(cleanToken, serial);
      if (validation.ok) {
        const field = document.querySelector("[data-renewal-token]");
        if (field) {
          field.value = cleanToken;
          formatRenewalTokenField(field);
        }
        renewalMessage = "Token SMS received. Activating service...";
        updateRenewalDialogMessage(renewalMessage);
        window.setTimeout(activateRenewalCode, 150);
        return true;
      }
      if (!firstFailure) firstFailure = validation.message || "The token was not accepted.";
    }
    renewalMessage = `A token SMS arrived, but it was not accepted for this device. ${firstFailure}`;
    updateRenewalDialogMessage(renewalMessage);
    return false;
  }

  async function copyRenewalValue(value, label) {
    const text = String(value || "").trim();
    if (!text || text === "ADD DEVICE CODE") {
      renewalMessage = "Add the device code first.";
      updateRenewalDialogMessage(renewalMessage);
      return;
    }
    try {
      if (navigator.clipboard && navigator.clipboard.writeText) {
        await navigator.clipboard.writeText(text);
        renewalMessage = `${label || "Value"} ready: ${text}`;
      } else {
        renewalMessage = `${label || "Value"}: ${text}`;
      }
    } catch (error) {
      renewalMessage = `${label || "Value"}: ${text}`;
    }
    updateRenewalDialogMessage(renewalMessage);
  }

  function updateRenewalTransactionHint() {
    const networkNode = document.querySelector("[data-renewal-network]");
    const transactionNode = document.querySelector("[data-renewal-transaction]");
    if (!networkNode || !transactionNode) return;
    const key = resolvePaymentNetworkKey(networkNode.value || "mtn") || "mtn";
    const network = PAYMENT_NETWORKS[key] || PAYMENT_NETWORKS.other;
    transactionNode.removeAttribute("minlength");
    transactionNode.removeAttribute("maxlength");
    transactionNode.dataset.requiredLength = String(network.transactionLength);
    transactionNode.placeholder = `${network.transactionLength} characters required`;
    normalizeRenewalTransactionInput();
    updateRenewalDialogMessage(transactionHint(key));
  }

  function handleRenewalFieldEvent(event) {
    const target = event && event.target;
    if (!target || !target.matches) return;
    if (target.matches("[data-renewal-token]")) {
      formatRenewalActivationCode();
      return;
    }
    if (target.matches("[data-renewal-token-display]")) {
      formatRenewalTokenField(target);
      return;
    }
    if (target.matches("[data-renewal-amount]")) {
      target.value = String(target.value || "").replace(/[^\d]/g, "").slice(0, 8);
      captureRenewalDialFields();
      return;
    }
    if (target.matches("[data-renewal-other-code]")) {
      const cleanCode = normalizeRenewalReasonCode(target.value);
      if (target.value !== cleanCode) target.value = cleanCode;
      captureRenewalDialFields();
      updateRenewalReferencePreview();
      return;
    }
    if (target.matches("[data-renewal-network]")) {
      updateRenewalTransactionHint();
      return;
    }
    if (target.matches("[data-renewal-transaction]")) {
      normalizeRenewalTransactionInput();
    }
  }

  function startRenewalFieldWatcher() {
    stopRenewalFieldWatcher();
    let lastNetwork = "";
    renewalFieldWatcherId = window.setInterval(() => {
      if (!document.querySelector("[data-renewal-dialog]")) {
        stopRenewalFieldWatcher();
        return;
      }
      const networkNode = document.querySelector("[data-renewal-network]");
      if (!networkNode || networkNode.value === lastNetwork) return;
      lastNetwork = networkNode.value;
      updateRenewalTransactionHint();
    }, 180);
  }

  function stopRenewalFieldWatcher() {
    if (!renewalFieldWatcherId) return;
    window.clearInterval(renewalFieldWatcherId);
    renewalFieldWatcherId = null;
  }

  function paymentNetworkSelectLabel(key, network) {
    return PAYMENT_NETWORK_SELECT_LABELS[key] || (network && network.label) || key || "Network";
  }

  function normalizeRenewalTransactionInput() {
    const networkNode = document.querySelector("[data-renewal-network]");
    const transactionNode = document.querySelector("[data-renewal-transaction]");
    if (!networkNode || !transactionNode) return;
    const key = resolvePaymentNetworkKey(networkNode.value || "mtn") || "mtn";
    const network = PAYMENT_NETWORKS[key] || PAYMENT_NETWORKS.other;
    const normalized = normalizeTransactionId(transactionNode.value).slice(0, network.transactionLength);
    if (transactionNode.value === normalized) return;
    transactionNode.value = normalized;
    try {
      transactionNode.setSelectionRange(normalized.length, normalized.length);
    } catch (error) {}
  }

  function updateRenewalDialogMessage(message) {
    const node = document.querySelector("[data-renewal-dialog-message]");
    if (node) node.textContent = message || "";
  }

  function transactionHint(networkKey) {
    const network = PAYMENT_NETWORKS[networkKey] || PAYMENT_NETWORKS.other;
    return `${network.label} transaction ID must be exactly ${network.transactionLength} characters, no spaces.`;
  }

  function resolvePaymentNetworkKey(value) {
    const raw = String(value || "").trim();
    if (PAYMENT_NETWORKS[raw]) return raw;
    const lower = raw.toLowerCase();
    return Object.keys(PAYMENT_NETWORKS).find((key) => key.toLowerCase() === lower) || "";
  }

  function normalizeTransactionId(value) {
    return String(value || "").replace(/\s+/g, "").trim().toUpperCase();
  }

  function validateRenewalPayment(payment) {
    const amount = Number(String(payment.amountPaid || "").replace(/[^\d.]/g, ""));
    if (!Number.isFinite(amount) || amount <= 0) {
      return { ok: false, message: "Enter the amount paid before renewal can be checked." };
    }
    const networkKey = resolvePaymentNetworkKey(payment.network);
    const network = PAYMENT_NETWORKS[networkKey];
    if (!network) {
      return { ok: false, message: "Choose the network used to pay." };
    }
    const transactionId = normalizeTransactionId(payment.transactionId);
    if (transactionId.length !== network.transactionLength) {
      return { ok: false, message: `${network.label} transaction ID must be exactly ${network.transactionLength} characters, no spaces.` };
    }
    return { ok: true, amountPaid: amount, networkKey, transactionId };
  }

  async function requestServiceRenewal(payment) {
    if (renewalBusy) return;
    refreshServiceState();
    const validation = validateRenewalPayment(payment || renewalPayment);
    if (!validation.ok) {
      renewalMessage = validation.message;
      updateRenewalDialogMessage(validation.message);
      updateRenewalUi();
      return;
    }
    renewalBusy = true;
    renewalMessage = "Checking renewal...";
    updateRenewalUi();
    updateRenewalDialogMessage(renewalMessage);

    try {
      if (!RENEWAL_ENDPOINT) {
        throw new Error("renewal endpoint not connected");
      }

      const clientId = serviceClientId();
      const nameInput = document.querySelector("#name");
      const displayName = cleanText((nameInput && nameInput.value) || (activeUser() && activeUser().displayName) || "");
      const response = await fetch(RENEWAL_ENDPOINT, {
        method: "POST",
        cache: "no-store",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          app: "Know God More",
          clientId,
          displayName,
          amountPaid: validation.amountPaid,
          paymentNetwork: validation.networkKey,
          paymentNetworkName: PAYMENT_NETWORKS[validation.networkKey].label,
          transactionId: validation.transactionId,
          requestedHours: RENEWAL_SERVICE_HOURS,
          remainingMs: serviceState.remainingMs || 0,
          source: serviceState.source || "unknown"
        })
      });
      if (!response.ok) throw new Error("renewal request failed");
      const result = await response.json();
      const status = String(result.status || "").toLowerCase();
      if (["approved", "renewed", "success", "added"].includes(status)) {
        const hours = Math.max(1, Math.floor(Number(result.hours || result.addHours || RENEWAL_SERVICE_HOURS)));
        addServiceHours(hours);
        refreshServiceState();
        renewalMessage = `${hours.toLocaleString()} hours added successfully.`;
        loginError = "";
        clearPendingRenewal();
        stopRenewalSmsPolling(true);
        closeRenewalDialog();
        saveStore();
        renderLogin();
        return;
      }
      renewalMessage = result.message || "Try again later.";
    } catch (error) {
      renewalMessage = "Try again later.";
    } finally {
      renewalBusy = false;
      updateRenewalUi();
      updateRenewalDialogMessage(renewalMessage);
    }
  }

  function confirmRenewalPaid() {
    renewalPaidReady = true;
    renewalMessage = "Enter the 20 digit token from Admin.";
    const dialog = document.querySelector("[data-renewal-dialog]");
    if (dialog) {
      dialog.outerHTML = renewalDialogHtml();
      const field = document.querySelector("[data-renewal-token]");
      if (field && field.focus) field.focus();
      fitRenewalActivationInput();
    } else {
      openRenewalDialog();
    }
  }

  function activateRenewalCode() {
    if (renewalBusy) return;
    const lock = activationLockState();
    if (lock.locked) {
      renewalMessage = lock.message;
      refreshRenewalDialog();
      return;
    }
    formatRenewalActivationCode();
    const field = document.querySelector("[data-renewal-token]");
    const validation = validateActivationToken(field ? field.value : "", getDeviceSerial());
    if (!validation.ok) {
      renewalMessage = registerActivationFailure(getDeviceSerial(), validation.message);
      refreshRenewalDialog();
      return;
    }
    const used = store.usedActivationCodes && typeof store.usedActivationCodes === "object" ? store.usedActivationCodes : {};
    used[validation.token] = {
      hours: validation.hours,
      permutation: validation.permutationIndex,
      timePosition: validation.timePosition,
      serial: getDeviceSerial(),
      usedAt: new Date().toISOString()
    };
    store.usedActivationCodes = used;
    store.activationGuard = { stage: 0, attempts: [] };
    addServiceHours(validation.hours);
    refreshServiceState();
    renewalPaidReady = false;
    renewalMessage = rememberRenewalAddedMessage(validation.hours);
    loginError = "";
    saveStore();
    clearPendingRenewal();
    stopRenewalSmsPolling(true);
    closeRenewalDialog();
    renderLogin();
  }

  function rememberRenewalAddedMessage(hours) {
    const addedHours = Math.max(1, Math.floor(Number(hours) || 0));
    const message = `${addedHours.toLocaleString()} hours added successfully.`;
    try {
      localStorage.setItem(RENEWAL_LAST_ADDED_KEY, JSON.stringify({ message, hours: addedHours, at: Date.now() }));
    } catch (error) {}
    return message;
  }

  function readRecentRenewalAddedMessage() {
    try {
      const payload = JSON.parse(localStorage.getItem(RENEWAL_LAST_ADDED_KEY) || "null");
      if (!payload || !payload.message) return "";
      const ageMs = Date.now() - Number(payload.at || 0);
      if (!Number.isFinite(ageMs) || ageMs > 30 * 60 * 1000) return "";
      return String(payload.message);
    } catch (error) {
      return "";
    }
  }

  function showRecentRenewalAddedMessage() {
    const message = readRecentRenewalAddedMessage();
    if (!message) return false;
    renewalMessage = message;
    updateRenewalDialogMessage(message);
    return true;
  }

  function refreshRenewalDialog() {
    const dialog = document.querySelector("[data-renewal-dialog]");
    if (!dialog) return;
    dialog.outerHTML = renewalDialogHtml();
    if (renewalPaidReady) {
      const field = document.querySelector("[data-renewal-token]");
      if (field && field.focus) field.focus();
    }
    fitRenewalActivationInput();
  }

  function validateActivationToken(rawCode, serial) {
    const token = normalizeToken(rawCode);
    if (!token) return { ok: false, message: "Enter the 20 digit token first." };
    const used = store.usedActivationCodes || {};
    if (used[token]) return { ok: false, message: "This token has already been used on this device." };
    if (token.length !== 20) return { ok: false, message: "wrong token try again later" };
    const permutationIndex = Number(token.substring(0, 3));
    if (!permutationIndex || permutationIndex < 1 || permutationIndex > SIX_DIGIT_PERMUTATIONS.length) {
      return { ok: false, message: "wrong token try again later" };
    }
    const timePosition = Number(token.charAt(3));
    const payload = token.substring(4);
    const hoursText = payload.substring(timePosition, timePosition + 4);
    const devicePart = payload.substring(0, timePosition) + payload.substring(timePosition + 4);
    const hours = Number(hoursText);
    const expectedDevicePart = encodedDevicePartForOrder(serial, SIX_DIGIT_PERMUTATIONS[permutationIndex - 1]);
    if (!hours || hours < 1 || hours > 8760) return { ok: false, message: "wrong token try again later" };
    if (!expectedDevicePart || devicePart !== expectedDevicePart) return { ok: false, message: "wrong token try again later" };
    return { ok: true, token, hours, permutationIndex, timePosition };
  }

  function addServiceHours(hours) {
    const addMs = Math.max(1, Math.floor(Number(hours) || RENEWAL_SERVICE_HOURS)) * 60 * 60 * 1000;
    if (window.KnowGodMoreService && typeof window.KnowGodMoreService.addHours === "function") {
      try {
        const nativeState = JSON.parse(window.KnowGodMoreService.addHours(String(hours)));
        if (nativeState && typeof nativeState.remainingMs === "number") {
          serviceState = nativeState;
          return;
        }
      } catch (error) {}
    }

    const saved = JSON.parse(localStorage.getItem(SERVICE_STORAGE_KEY) || "{}");
    const clientId = saved.clientId || makeClientId();
    const startedAt = typeof saved.startedAt === "number" ? saved.startedAt : performance.now();
    const addedMs = (typeof saved.addedMs === "number" ? saved.addedMs : 0) + addMs;
    localStorage.setItem(SERVICE_STORAGE_KEY, JSON.stringify({ ...saved, startedAt, addedMs, clientId }));
    serviceState = readServiceState();
  }

  function startCompetitionSetup() {
    competitionView = "setup";
    route = "competition";
    render();
  }



  function normalizeCompetitionSchoolLevel(level) {
    const text = String(level || "").trim().toUpperCase();
    return COMPETITION_SCHOOL_LEVELS.includes(text) ? text : DEFAULT_LECTIVE_LEVEL;
  }

  function bibleLevelForSchoolLevel(level) {
    const normalized = normalizeCompetitionSchoolLevel(level);
    if (["P1", "P2"].includes(normalized)) return BABY_COURSE.id;
    if (["P3", "P4"].includes(normalized)) return 1;
    if (["P5", "P6"].includes(normalized)) return 2;
    if (["P7", "S1"].includes(normalized)) return 3;
    if (["S2", "S3"].includes(normalized)) return 4;
    return 5;
  }

  function competitionUsesBible(source = selectedCompetitionSource) {
    return source === "bible" || source === "level" || source === "both";
  }

  function competitionUsesKgmSchool(source = selectedCompetitionSource) {
    return source === "lective" || source === "both";
  }

  function competitionQuestionTarget() {
    const value = Number(selectedCompetitionQuestionTarget);
    if (!Number.isFinite(value)) return 1;
    return Math.max(1, Math.min(COMPETITION_MAX_QUESTION_TARGET, Math.round(value)));
  }

  function competitionQuestionsPerRound() {
    const total = competitionQuestionTarget();
    const value = Number(selectedCompetitionQuestionsPerRound);
    if (!Number.isFinite(value)) return 1;
    return Math.max(1, Math.min(total, Math.round(value)));
  }

  function competitionAvailableSubjectsForLevel(level) {
    const normalized = normalizeCompetitionSchoolLevel(level);
    return lectiveSubjectsForLevel(normalized).filter((subject) => lectiveSubjectIsAvailable(normalized, subject));
  }

  function competitionMaterialLabel(source = selectedCompetitionSource, schoolLevel = selectedCompetitionSchoolLevel) {
    const normalizedSource = source === "level" ? "bible" : source;
    const normalizedLevel = normalizeCompetitionSchoolLevel(schoolLevel);
    const bibleLevel = bibleLevelForSchoolLevel(normalizedLevel);
    const schoolLabel = `${schoolDisplayName()} ${electiveClassLabel(normalizedLevel)}`;
    if (normalizedSource === "lective") return schoolLabel;
    if (normalizedSource === "both") return bibleLevel === BABY_COURSE.id ? schoolLabel : `${courseLabel(bibleLevel)} + ${schoolLabel}`;
    return courseLabel(bibleLevelForSchoolLevel(normalizedLevel));
  }

  function competitionPlayerSchoolLevel(player, fallback = selectedCompetitionSchoolLevel) {
    if (selectedCompetitionLevelMode === "same") return normalizeCompetitionSchoolLevel(fallback);
    return normalizeCompetitionSchoolLevel(player && player.schoolLevel || fallback);
  }

  function competitionQuestionClone(question, suffix, extras = {}) {
    const source = applyQuestionTextEdit(question);
    return {
      ...source,
      ...extras,
      id: `${source.id || "question"}:${suffix}`,
      originalQuestionId: source.id || "",
      answers: Array.isArray(source.answers) ? source.answers.map((answer) => ({ ...answer })) : []
    };
  }

  function takeRotatingQuestions(pool, count, offset, suffixBase, extras = {}) {
    if (!Array.isArray(pool) || !pool.length || count <= 0) return [];
    const result = [];
    for (let index = 0; index < count; index += 1) {
      const sourceIndex = Math.abs(offset + index) % pool.length;
      result.push(competitionQuestionClone(pool[sourceIndex], `${suffixBase}-${index}-${sourceIndex}`, extras));
    }
    return result;
  }

  function bibleCompetitionPoolForLevel(levelId) {
    return courseStoriesForLevel(levelId).flatMap((story) => {
      const questions = Array.isArray(story.questions) ? story.questions : [];
      return questions.map((question) => ({
        ...question,
        title: story.title || question.title || "",
        reference: story.reference || question.reference || courseLabel(levelId),
        content: story.content || question.content || ""
      }));
    });
  }

  function kgmSchoolCompetitionPoolsForLevel(level) {
    const normalized = normalizeCompetitionSchoolLevel(level);
    return competitionAvailableSubjectsForLevel(normalized).map((subject) => {
      const pool = lectiveQuestionList(normalized, subject).map((entry) => ({
        ...entry.question,
        title: entry.story && entry.story.title ? entry.story.title : `${subject} Question`,
        reference: `${electiveClassLabel(normalized)} ${subject}`,
        content: entry.story && entry.story.content ? entry.story.content : "",
        competitionSubject: subject
      }));
      return { subject, pool };
    }).filter((item) => item.pool.length);
  }

  function competitionQuestionsForPlayer(player, playerIndex, options = {}) {
    const source = options.source || selectedCompetitionSource;
    const schoolLevel = competitionPlayerSchoolLevel(player, options.schoolLevel || selectedCompetitionSchoolLevel);
    const target = Math.max(1, Number(options.questionTarget || competitionQuestionTarget()));
    const startOffsets = options.startOffsets && typeof options.startOffsets === "object" ? options.startOffsets : {};
    const baseOffset = Number(startOffsets[player && player.id] || 0);
    const queues = [];
    if (competitionUsesBible(source)) {
      const levelId = bibleLevelForSchoolLevel(schoolLevel);
      const pool = bibleCompetitionPoolForLevel(levelId);
      queues.push(...takeRotatingQuestions(pool, target, baseOffset + playerIndex * target, `${player.id || playerIndex}-bible`, {
        competitionSource: "bible",
        schoolLevel,
        levelId
      }));
    }
    if (competitionUsesKgmSchool(source)) {
      kgmSchoolCompetitionPoolsForLevel(schoolLevel).forEach(({ subject, pool }) => {
        queues.push(...takeRotatingQuestions(pool, target, baseOffset + playerIndex * target, `${player.id || playerIndex}-${subject}`, {
          competitionSource: "lective",
          schoolLevel,
          competitionSubject: subject
        }));
      });
    }
    return queues;
  }

  function competitionPlannedMaterials(players, options = {}) {
    const list = Array.isArray(players) ? players : [];
    const perRound = Math.max(1, Number(options.questionsPerRound || competitionQuestionsPerRound()));
    const basePlanned = options.basePlanned && typeof options.basePlanned === "object" ? options.basePlanned : {};
    const playerQueues = {};
    let maxRounds = 0;
    list.forEach((player, index) => {
      const queue = competitionQuestionsForPlayer(player, index, options);
      playerQueues[player.id] = queue;
      player.plannedQuestions = Number(basePlanned[player.id] || 0) + queue.length;
      player.schoolLevel = competitionPlayerSchoolLevel(player, options.schoolLevel || selectedCompetitionSchoolLevel);
      maxRounds = Math.max(maxRounds, Math.ceil(queue.length / perRound));
    });
    const materials = [];
    for (let roundIndex = 0; roundIndex < maxRounds; roundIndex += 1) {
      const playerQuestions = {};
      const stories = [];
      list.forEach((player) => {
        const slice = (playerQueues[player.id] || []).slice(roundIndex * perRound, (roundIndex + 1) * perRound);
        if (!slice.length) return;
        playerQuestions[player.id] = slice;
        slice.filter((question) => question.competitionSource === "bible" && question.content).forEach((question) => {
          stories.push({
            title: `${player.name} - ${question.reference || courseLabel(question.levelId)}`,
            content: question.content
          });
        });
      });
      const firstQuestions = Object.values(playerQuestions).find((questions) => questions.length) || [];
      materials.push({
        id: `offline-round-${Date.now()}-${roundIndex}`,
        reference: `Competition Round ${roundIndex + 1}`,
        content: stories.length ? stories.map((story) => `${story.title}\n\n${story.content}`).join("\n\n\n") : "KGM School question round. Press Start Answers when competitors are ready.",
        stories: stories.length ? stories : [{ title: "KGM School Round", content: "This round has question-and-answer material only." }],
        questions: firstQuestions,
        playerQuestions
      });
    }
    return materials;
  }

  function offlinePlayerQuestionsForMaterial(material, player) {
    if (material && material.playerQuestions && player && Array.isArray(material.playerQuestions[player.id])) {
      return material.playerQuestions[player.id];
    }
    return Array.isArray(material && material.questions) ? material.questions : [];
  }

  function offlineCurrentQuestion(offline) {
    const material = offline && offline.materials ? offline.materials[offline.materialIndex] : null;
    const player = offline && offline.players ? offline.players[offline.playerIndex] : null;
    const questions = offlinePlayerQuestionsForMaterial(material, player);
    return questions[offline && Number.isFinite(Number(offline.questionIndex)) ? offline.questionIndex : 0] || null;
  }

  function offlineMaterialQuestionCountForPlayer(material, player) {
    return offlinePlayerQuestionsForMaterial(material, player).length;
  }

  function offlinePlayerPlannedQuestions(offline, player) {
    if (!player) return Number(offline && offline.totalQuestions || 0);
    return Number(player.plannedQuestions || 0) || (Array.isArray(offline && offline.materials) ? offline.materials.reduce((sum, material) => sum + offlineMaterialQuestionCountForPlayer(material, player), 0) : 0);
  }

  function offlineFirstPlayerIndexForMaterial(offline, materialIndex = offline && offline.materialIndex || 0, startIndex = 0) {
    if (!offline || !Array.isArray(offline.players) || !Array.isArray(offline.materials)) return -1;
    const material = offline.materials[materialIndex];
    if (!material) return -1;
    for (let index = Math.max(0, startIndex); index < offline.players.length; index += 1) {
      if (offlineMaterialQuestionCountForPlayer(material, offline.players[index]) > 0) return index;
    }
    return -1;
  }

  function offlinePrepareMaterialForAnswers(offline) {
    const first = offlineFirstPlayerIndexForMaterial(offline, offline.materialIndex, 0);
    if (first < 0) return false;
    offline.playerIndex = first;
    offline.questionIndex = 0;
    return true;
  }

  function clampCompetitionSeconds(value) {
    const number = Number(value);
    if (!Number.isFinite(number)) return COMPETITION_TIMER_DEFAULT_SECONDS;
    return Math.max(COMPETITION_TIMER_MIN_SECONDS, Math.min(COMPETITION_TIMER_MAX_SECONDS, Math.round(number)));
  }

  function competitionTimerDuration() {
    return clampCompetitionSeconds(store.competitionTimerSeconds);
  }

  function setCompetitionTimerSeconds(value) {
    const seconds = clampCompetitionSeconds(value);
    store.competitionTimerSeconds = seconds;
    if (store.onlineCompetition && store.onlineCompetition.phase !== "completed") {
      store.onlineCompetition.timerSeconds = seconds;
    }
    if (store.offlineCompetition && store.offlineCompetition.active && store.offlineCompetition.status !== "completed") {
      store.offlineCompetition.timerSeconds = seconds;
    }
    saveStore();
  }

  function competitionTimerNow(target) {
    const serverNow = Number(target && target.serverNow ? target.serverNow : 0);
    const receivedAt = Number(target && target.receivedAt ? target.receivedAt : 0);
    if (serverNow && receivedAt) return serverNow + Math.max(0, Date.now() - receivedAt);
    return Date.now();
  }

  function competitionTimerData(target) {
    const duration = clampCompetitionSeconds(target && (target.durationSeconds || target.questionDurationSeconds || target.timerSeconds));
    const deadline = Number(target && (target.deadlineAt || target.questionDeadlineAt || 0));
    const now = competitionTimerNow(target || {});
    const remaining = deadline ? Math.max(0, Math.ceil((deadline - now) / 1000)) : duration;
    const angle = Math.max(0, Math.min(360, Math.round((Math.min(duration, remaining) / duration) * 360)));
    return { duration, deadline, remaining, angle };
  }

  function competitionQuestionExpired(target) {
    const data = competitionTimerData(target || {});
    return Boolean(data.deadline && data.remaining <= 0);
  }

  function formatCompetitionClock(seconds) {
    const safe = Math.max(0, Number(seconds) || 0);
    const minutes = Math.floor(safe / 60);
    const remainder = safe % 60;
    return `${String(minutes).padStart(2, "0")}:${String(remainder).padStart(2, "0")}`;
  }

  function competitionCountdownHtml(target) {
    const data = competitionTimerData(target || {});
    return `
      <div class="competition-countdown ${data.deadline && data.remaining <= 10 ? "is-warning" : ""}" style="--timer-angle: ${data.angle}deg">
        <div class="competition-countdown-ring"><strong>${formatCompetitionClock(data.remaining)}</strong></div>
        <span>${data.deadline && data.remaining <= 10 ? "10 seconds remaining" : `${data.duration} second timer`}</span>
      </div>
    `;
  }

  function competitionQuestionTimerKey(state) {
    return `${Number(state && state.materialIndex || 0)}:${Number(state && state.questionIndex || 0)}`;
  }

  function startOnlineQuestionTimer(host, remainingMs = null) {
    if (!host) return;
    const seconds = clampCompetitionSeconds(host.timerSeconds || competitionTimerDuration());
    const now = Date.now();
    host.timerSeconds = seconds;
    host.questionDurationSeconds = seconds;
    host.questionStartedAt = now;
    host.questionDeadlineAt = now + (Number.isFinite(remainingMs) ? Math.max(0, remainingMs) : seconds * 1000);
    host.questionTimerKey = competitionQuestionTimerKey(host);
    delete host.pausedRemainingMs;
  }

  function ensureOnlineQuestionTimer(host) {
    if (!host || host.phase !== "quiz") return;
    const key = competitionQuestionTimerKey(host);
    if (host.questionTimerKey !== key || !host.questionDeadlineAt) startOnlineQuestionTimer(host);
  }

  function clearOnlineQuestionTimer(host) {
    if (!host) return;
    host.questionStartedAt = null;
    host.questionDeadlineAt = null;
    host.questionTimerKey = "";
    delete host.pausedRemainingMs;
  }

  function pauseOnlineQuestionTimer(host) {
    if (!host || host.phase !== "quiz" || !host.questionDeadlineAt) return;
    host.pausedRemainingMs = Math.max(0, Number(host.questionDeadlineAt) - Date.now());
  }

  function resumeOnlineQuestionTimer(host) {
    if (!host || host.phase !== "quiz") return;
    if (Number.isFinite(Number(host.pausedRemainingMs))) {
      startOnlineQuestionTimer(host, Number(host.pausedRemainingMs));
    } else {
      ensureOnlineQuestionTimer(host);
    }
  }

  function startOfflineQuestionTimer(offline) {
    if (!offline) return;
    const seconds = clampCompetitionSeconds(offline.timerSeconds || competitionTimerDuration());
    const now = Date.now();
    offline.timerSeconds = seconds;
    offline.questionDurationSeconds = seconds;
    offline.questionStartedAt = now;
    offline.questionDeadlineAt = now + seconds * 1000;
    offline.questionTimerKey = competitionQuestionTimerKey(offline);
  }

  function clearOfflineQuestionTimer(offline) {
    if (!offline) return;
    offline.questionStartedAt = null;
    offline.questionDeadlineAt = null;
    offline.questionTimerKey = "";
  }

  function hasActiveCompetitionCountdown() {
    if (route !== "competition") return false;
    if (competitionView === "online-host") {
      const host = store.onlineCompetition;
      return Boolean(host && host.phase === "quiz" && host.questionDeadlineAt);
    }
    if (competitionView === "online-join") {
      const question = onlineJoin.state && onlineJoin.state.question;
      return Boolean(question && question.phase === "quiz" && question.deadlineAt);
    }
    if (competitionView === "offline") {
      const offline = store.offlineCompetition;
      return Boolean(offline && offline.status === "answering" && offline.questionDeadlineAt);
    }
    return false;
  }

  function startCompetitionCountdownTicker() {
    if (!hasActiveCompetitionCountdown()) {
      stopCompetitionCountdownTicker();
      return;
    }
    if (competitionCountdownId) return;
    competitionCountdownId = window.setInterval(() => {
      if (!hasActiveCompetitionCountdown()) {
        stopCompetitionCountdownTicker();
        return;
      }
      const offline = store.offlineCompetition;
      if (competitionView === "offline" && offline && offline.status === "answering" && competitionQuestionExpired(offline)) {
        offlineAnswer("", true);
        return;
      }
      if (route === "competition") renderCompetition();
    }, 1000);
  }

  function stopCompetitionCountdownTicker() {
    if (competitionCountdownId) {
      window.clearInterval(competitionCountdownId);
      competitionCountdownId = null;
    }
  }

  function competitionBridge() {
    return window.KnowGodMoreCompetition || null;
  }

  function startCompetitionHost() {
    const bridge = competitionBridge();
    if (!bridge || typeof bridge.startHost !== "function") {
      competitionState = { running: false, error: "Competition host works inside the Android APK." };
      return competitionState;
    }
    const user = activeUser();
    const hostName = user ? user.displayName : "Know God More";
    competitionState = parseCompetitionState(bridge.startHost(hostName));
    startCompetitionPolling();
    const session = currentSession();
    const item = session ? currentQuestion(session) : null;
    if (session && item) {
      if (session.phase === "quiz") {
        const choices = displayAnswersForQuestion(item, session);
        publishCompetitionQuestion(session, item, choices, choices.findIndex((choice) => choice.correct));
      } else {
        publishCompetitionWaiting(session, item);
      }
    }
    return competitionState;
  }

  function stopCompetitionHost() {
    const bridge = competitionBridge();
    if (bridge && typeof bridge.stopHost === "function") {
      competitionState = parseCompetitionState(bridge.stopHost());
    } else {
      competitionState = { running: false };
    }
    stopCompetitionPolling();
    return competitionState;
  }

  function startNewOnlineCompetition() {
    openFreshOnlineCompetitionHost();
  }

  function openFreshOnlineCompetitionHost() {
    archiveActiveCompetitionSession("online");
    stopCompetitionHost();
    store.onlineCompetition = null;
    competitionCoordinatorDrafts.online = emptyCompetitionCoordinatorDraft();
    competitionCoordinatorMessage = "New online competition ready. Enter coordinator name, password, and competition name.";
    competitionAccessKey = "";
    saveStore();
    competitionView = "online-host";
    renderCompetition();
  }

  function refreshCompetitionState() {
    const bridge = competitionBridge();
    if (bridge && typeof bridge.getState === "function") {
      competitionState = parseCompetitionState(bridge.getState());
    }
    return competitionState;
  }

  function parseCompetitionState(raw) {
    if (!raw) return { running: false };
    try {
      return typeof raw === "string" ? JSON.parse(raw) : raw;
    } catch (error) {
      return { running: false, error: "Competition state could not be read." };
    }
  }

  function startCompetitionPolling() {
    if (competitionTimerId) return;
    competitionTimerId = window.setInterval(() => {
      const state = refreshCompetitionState();
      if (route === "competition") updateCompetitionPanel(state);
      if (!state || !state.running) stopCompetitionPolling();
    }, 1400);
  }

  function stopCompetitionPolling() {
    if (competitionTimerId) {
      window.clearInterval(competitionTimerId);
      competitionTimerId = null;
    }
  }

  function updateCompetitionPanel(state) {
    const panel = document.querySelector("[data-competition-live]");
    if (panel) panel.innerHTML = competitionLiveHtml(state || {}, store.onlineCompetition || null);
    const link = document.querySelector(".competition-link-box strong");
    if (link && state) link.textContent = state.url ? makeCompetitionLink(state.url) : "Start host to create address";
  }

  function openCompetitionWifiSettings() {
    const bridge = competitionBridge();
    if (bridge && typeof bridge.openWifiSettings === "function") {
      bridge.openWifiSettings();
    }
  }

  function copyCompetitionLink() {
    const state = refreshCompetitionState();
    const link = state && state.url ? makeCompetitionLink(state.url) : "";
    if (!link) return;
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(link).catch(() => {});
    }
    window.alert(`Competition link: ${link}`);
  }

  function emptyCompetitionCoordinatorDraft() {
    return { name: "", password: "", competitionName: "" };
  }

  function competitionCoordinatorDraft(mode) {
    const key = mode === "offline" ? "offline" : "online";
    if (!competitionCoordinatorDrafts[key]) competitionCoordinatorDrafts[key] = emptyCompetitionCoordinatorDraft();
    return competitionCoordinatorDrafts[key];
  }

  function readCompetitionDraftField(panel, name) {
    const field = panel ? panel.querySelector(`[name='${name}']`) : null;
    return field && typeof field.value === "string" ? field.value : "";
  }

  function rememberCompetitionCoordinator(mode, coordinator) {
    if (!coordinator) return;
    const draft = competitionCoordinatorDraft(mode);
    draft.name = coordinator.name || "";
    draft.password = coordinator.password || "";
    draft.competitionName = coordinator.competitionName || "";
  }

  function captureCompetitionCoordinatorDraft(mode, panel = null) {
    const key = mode === "offline" ? "offline" : "online";
    const targetPanel = panel || document.querySelector(`[data-coordinator-mode="${key}"]`);
    const draft = competitionCoordinatorDraft(key);
    if (!targetPanel) return draft;
    draft.name = readCompetitionDraftField(targetPanel, "coordinatorName");
    draft.password = readCompetitionDraftField(targetPanel, "coordinatorPassword");
    draft.competitionName = readCompetitionDraftField(targetPanel, "competitionName");
    return draft;
  }

  function captureVisibleCompetitionCoordinatorDrafts() {
    document.querySelectorAll("[data-coordinator-mode]").forEach((panel) => {
      captureCompetitionCoordinatorDraft(panel.getAttribute("data-coordinator-mode"), panel);
    });
  }

  function readCompetitionCoordinator(mode) {
    const activeRecord = mode === "online" ? store.onlineCompetition : store.offlineCompetition;
    if (activeRecord && activeRecord.coordinator && activeRecord.coordinator.sessionKey) {
      return activeRecord.coordinator;
    }
    const draft = captureCompetitionCoordinatorDraft(mode);
    const name = cleanText(draft.name);
    const password = cleanText(draft.password);
    const rawCompetitionName = cleanText(draft.competitionName);
    const competitionName = normalizeCompetitionName(rawCompetitionName);
    if (!name || !password || !rawCompetitionName) {
      window.alert("Enter the coordinator name, password, and competition name first. Those three details keep this competition resumable.");
      return null;
    }
    const coordinator = {
      name,
      password,
      competitionName,
      sessionKey: competitionSessionKey(mode, name, password, competitionName)
    };
    rememberCompetitionCoordinator(mode, coordinator);
    return coordinator;
  }

  function lockCompetitionAccess() {
    competitionAccessKey = "";
    competitionUnlockMessage = "";
  }

  function hasCompetitionAccess(lockRecord) {
    return Boolean(lockRecord && lockRecord.coordinator && lockRecord.coordinator.sessionKey && competitionAccessKey === lockRecord.coordinator.sessionKey);
  }

  function competitionLockRecord() {
    if (competitionView === "online-join") return null;
    const records = competitionAccessRecords();
    if (!records.length) return null;
    if (competitionView === "online-host" || competitionView === "online") {
      return records.find((record) => record.mode === "online") || null;
    }
    if (competitionView === "offline") {
      return records.find((record) => record.mode === "offline") || null;
    }
    if (records.some((record) => hasCompetitionAccess(record))) return null;
    return records[0];
  }

  function competitionAccessRecords() {
    const records = [];
    const seen = new Set();
    const online = store.onlineCompetition;
    if (online && (online.coordinator || online.startedAt || online.materials || online.phase)) {
      const key = online.coordinator && online.coordinator.sessionKey ? online.coordinator.sessionKey : `active-online-${online.startedAt || ""}`;
      seen.add(`online:${key}`);
      records.push({
        id: `online:${key}`,
        mode: "online",
        view: "online-host",
        label: "Online Host",
        competitionName: competitionRecordName(online),
        record: online,
        coordinator: online.coordinator || null
      });
    }
    const offline = store.offlineCompetition;
    if (offline && (offline.active || offline.status === "completed" || offline.coordinator)) {
      const key = offline.coordinator && offline.coordinator.sessionKey ? offline.coordinator.sessionKey : `active-offline-${offline.startedAt || ""}`;
      seen.add(`offline:${key}`);
      records.push({
        id: `offline:${key}`,
        mode: "offline",
        view: "offline",
        label: "Offline One Device",
        competitionName: competitionRecordName(offline),
        record: offline,
        coordinator: offline.coordinator || null
      });
    }
    const sessions = store.competitionSessions && typeof store.competitionSessions === "object" ? store.competitionSessions : {};
    Object.entries(sessions).forEach(([sessionKey, saved]) => {
      if (!saved || !saved.mode || !saved.record) return;
      const mode = saved.mode === "online" ? "online" : saved.mode === "offline" ? "offline" : "";
      if (!mode) return;
      const coordinator = saved.record.coordinator || null;
      const key = coordinator && coordinator.sessionKey ? coordinator.sessionKey : sessionKey || saved.record.startedAt || saved.savedAt || JSON.stringify(saved.record).slice(0, 40);
      const seenKey = `${mode}:${key}`;
      if (seen.has(seenKey)) return;
      seen.add(seenKey);
      records.push({
        id: `${mode}:${key}`,
        mode,
        view: mode === "online" ? "online-host" : "offline",
        label: mode === "online" ? "Saved Online Host" : "Saved Offline One Device",
        competitionName: competitionRecordName(saved.record),
        record: saved.record,
        coordinator
      });
    });
    return records;
  }

  function unlockCompetitionPage(formData) {
    const name = cleanText(formData.get("coordinatorName") || "");
    const password = cleanText(formData.get("coordinatorPassword") || "");
    const rawCompetitionName = cleanText(formData.get("competitionName") || "");
    const competitionName = normalizeCompetitionName(rawCompetitionName);
    const selectedId = String(formData.get("savedCompetitionId") || selectedSavedCompetitionId || "");
    if (!name || !password || !rawCompetitionName) {
      competitionUnlockMessage = "Enter the coordinator name, password, and competition name first.";
      renderCompetition();
      return;
    }
    const records = competitionAccessRecords();
    const exactKeys = records.flatMap((record) => competitionSessionKeysForCredentials(record.mode, name, password, competitionName));
    const selectedRecord = records.find((record) => record.id === selectedId) || null;
    const match = records.find((record) => record.coordinator && exactKeys.includes(record.coordinator.sessionKey));
    const nameMatch = records.find((record) => record.competitionName && normalizeCompetitionName(record.competitionName).toLowerCase() === competitionName.toLowerCase());
    const target = selectedRecord || match || nameMatch || records[0];
    if (!target) {
      competitionUnlockMessage = "No saved competition was found on this device.";
      renderCompetition();
      return;
    }
    const targetMatchesCredentials = Boolean(target && target.coordinator && exactKeys.includes(target.coordinator.sessionKey));
    const coordinator = targetMatchesCredentials
      ? upgradeCompetitionCoordinator(target, name, password, competitionName)
      : claimCompetitionWithCoordinator(target, name, password, competitionName);
    if (!coordinator) {
      competitionUnlockMessage = "Those coordinator details could not open the saved competition. Try again.";
      renderCompetition();
      return;
    }
    activateCompetitionRecord(target, coordinator);
    competitionAccessKey = coordinator.sessionKey;
    competitionUnlockMessage = "";
    if (!targetMatchesCredentials) {
      competitionCoordinatorMessage = "Saved competition opened with the new coordinator credentials.";
    }
    saveStore();
    competitionView = target.view;
    route = "competition";
    renderCompetition();
  }

  function claimCompetitionWithCoordinator(record, name, password, competitionName) {
    if (!record || !record.mode) return null;
    const coordinator = {
      name,
      password,
      competitionName: normalizeCompetitionName(competitionName || record.competitionName || ""),
      sessionKey: competitionSessionKey(record.mode, name, password, competitionName || record.competitionName)
    };
    const previousKey = record.coordinator && record.coordinator.sessionKey ? record.coordinator.sessionKey : "";
    store.competitionSessions = store.competitionSessions && typeof store.competitionSessions === "object" ? store.competitionSessions : {};
    if (previousKey && previousKey !== coordinator.sessionKey) delete store.competitionSessions[previousKey];
    return activateCompetitionRecord(record, coordinator) ? coordinator : null;
  }

  function upgradeCompetitionCoordinator(record, name, password, competitionName) {
    if (!record || !record.mode) return null;
    const current = record.coordinator || {};
    const normalizedName = normalizeCompetitionName(competitionName || current.competitionName || record.competitionName || "");
    const expectedKey = competitionSessionKey(record.mode, name || current.name, password || current.password, normalizedName);
    if (current.sessionKey === expectedKey && current.competitionName === normalizedName) return current;
    return claimCompetitionWithCoordinator(record, name || current.name, password || current.password, normalizedName);
  }

  function activateCompetitionRecord(record, coordinator) {
    if (!record || !record.mode || !coordinator) return false;
    const nextRecord = record.record ? cloneData(record.record) : {};
    nextRecord.coordinator = coordinator;
    nextRecord.competitionName = normalizeCompetitionName(coordinator.competitionName || nextRecord.competitionName || record.competitionName);
    if (record.mode === "online") {
      store.onlineCompetition = { ...nextRecord, coordinator };
      return true;
    }
    if (record.mode === "offline") {
      store.offlineCompetition = { ...nextRecord, coordinator };
      return true;
    }
    return false;
  }

  function competitionSessionKey(mode, name, password, competitionName = "") {
    const normalizedName = normalizeCompetitionName(competitionName);
    return `${mode}:${hash(`${cleanText(name).toLowerCase()}|${cleanText(password)}|${normalizedName.toLowerCase()}`).toString(36)}`;
  }

  function legacyCompetitionSessionKey(mode, name, password) {
    return `${mode}:${hash(`${cleanText(name).toLowerCase()}|${cleanText(password)}`).toString(36)}`;
  }

  function competitionSessionKeysForCredentials(mode, name, password, competitionName) {
    return [
      competitionSessionKey(mode, name, password, competitionName),
      legacyCompetitionSessionKey(mode, name, password)
    ];
  }

  function normalizeCompetitionName(value) {
    return cleanText(value || "Competition");
  }

  function competitionRecordName(record) {
    if (!record) return "Competition";
    return normalizeCompetitionName(record.competitionName || (record.coordinator && record.coordinator.competitionName) || "Competition");
  }

  function resumeCompetitionSession(mode, coordinator) {
    store.competitionSessions = store.competitionSessions && typeof store.competitionSessions === "object" ? store.competitionSessions : {};
    const keys = [
      coordinator.sessionKey,
      legacyCompetitionSessionKey(mode, coordinator.name, coordinator.password)
    ].filter(Boolean);
    const savedKey = keys.find((key) => store.competitionSessions[key]);
    const saved = savedKey ? store.competitionSessions[savedKey] : null;
    if (!saved || saved.mode !== mode || !saved.record) return false;
    const record = cloneData(saved.record);
    record.competitionName = normalizeCompetitionName(coordinator.competitionName || record.competitionName || saved.record.competitionName);
    record.coordinator = coordinator;
    if (savedKey && savedKey !== coordinator.sessionKey) delete store.competitionSessions[savedKey];
    if (mode === "online") {
      if (record.phase === "completed") return false;
      store.onlineCompetition = { ...record, coordinator };
      return true;
    }
    if (record.status === "completed") return false;
    store.offlineCompetition = { ...record, coordinator };
    return true;
  }

  function syncActiveCompetitionSessions() {
    store.competitionSessions = store.competitionSessions && typeof store.competitionSessions === "object" ? store.competitionSessions : {};
    if (store.onlineCompetition && store.onlineCompetition.coordinator && store.onlineCompetition.coordinator.sessionKey) {
      store.onlineCompetition.competitionName = competitionRecordName(store.onlineCompetition);
      store.onlineCompetition.coordinator.competitionName = competitionRecordName(store.onlineCompetition);
      store.competitionSessions[store.onlineCompetition.coordinator.sessionKey] = {
        mode: "online",
        savedAt: new Date().toISOString(),
        record: cloneData(store.onlineCompetition)
      };
    }
    if (store.offlineCompetition && store.offlineCompetition.coordinator && store.offlineCompetition.coordinator.sessionKey) {
      store.offlineCompetition.competitionName = competitionRecordName(store.offlineCompetition);
      store.offlineCompetition.coordinator.competitionName = competitionRecordName(store.offlineCompetition);
      store.competitionSessions[store.offlineCompetition.coordinator.sessionKey] = {
        mode: "offline",
        savedAt: new Date().toISOString(),
        record: cloneData(store.offlineCompetition)
      };
    }
  }

  function cloneData(value) {
    try {
      return JSON.parse(JSON.stringify(value));
    } catch (error) {
      return value;
    }
  }

  function publishCompetitionWaiting(session, item) {
    const bridge = competitionBridge();
    if (!bridge || typeof bridge.setQuestion !== "function") return;
    const state = competitionState || refreshCompetitionState();
    if (!state || !state.running) return;
    bridge.setQuestion(JSON.stringify({
      phase: "reading",
      questionId: `reading-${session.levelId}-${session.index}`,
      reference: item.reference || "Bible Reading",
      question: "",
      options: []
    }));
    refreshCompetitionState();
  }

  function publishCompetitionQuestion(session, item, choices, correctIndex) {
    const bridge = competitionBridge();
    if (!bridge || typeof bridge.setQuestion !== "function") return;
    const state = competitionState || refreshCompetitionState();
    if (!state || !state.running) return;
    const options = choices.slice(0, 3).map((choice, index) => ({
      letter: String.fromCharCode(65 + index),
      text: choice.text,
      correct: index === correctIndex
    }));
    const durationSeconds = clampCompetitionSeconds(session.timerSeconds || session.questionDurationSeconds || COMPETITION_TIMER_DEFAULT_SECONDS);
    const deadlineAt = Number(session.questionDeadlineAt || session.deadlineAt || session.deadline || 0);
    bridge.setQuestion(JSON.stringify({
      phase: "quiz",
      questionId: String(item.id),
      reference: item.reference || `Question ${session.index + 1}`,
      question: item.question,
      index: session.index + 1,
      total: session.questions.length,
      durationSeconds,
      deadlineAt,
      correctLetter: options[correctIndex] ? options[correctIndex].letter : "",
      options
    }));
    refreshCompetitionState();
  }

  function startOnlineCompetition() {
    const coordinator = readCompetitionCoordinator("online");
    if (!coordinator) return;
    const materials = competitionMaterials(selectedCompetitionSource, selectedCompetitionLevel);
    if (!materials.length) {
      window.alert(selectedCompetitionSource === "lective" ? `Add ${schoolDisplayName()} questions first.` : "No competition material found for this level.");
      return;
    }
    startCompetitionHost();
    store.onlineCompetition = {
      source: selectedCompetitionSource,
      levelId: selectedCompetitionLevel,
      lectiveLevel: selectedCompetitionSource === "lective" ? selectedCompetitionLectiveLevel : "",
      lectiveSubject: selectedCompetitionSource === "lective" ? selectedCompetitionLectiveSubject : "",
      lectiveScope: selectedCompetitionSource === "lective" ? selectedCompetitionLectiveScope : "",
      coordinator,
      competitionName: coordinator.competitionName,
      materials,
      materialIndex: 0,
      questionIndex: 0,
      phase: "ready",
      previousPhase: "ready",
      timerSeconds: competitionTimerDuration(),
      questionStartedAt: null,
      questionDeadlineAt: null,
      questionTimerKey: "",
      totalQuestions: materials.reduce((sum, material) => sum + material.questions.length, 0),
      startedAt: new Date().toISOString(),
      completedAt: null
    };
    competitionCoordinatorMessage = "Online competition saved. Resume it with the same coordinator name, password, and competition name.";
    competitionAccessKey = coordinator.sessionKey;
    saveStore();
    publishOnlineHostState();
  }

  function onlineHostSetPhase(phase) {
    const host = store.onlineCompetition;
    if (!host || host.phase === "completed") return;
    host.phase = phase;
    if (phase === "quiz") {
      host.questionIndex = Math.max(0, host.questionIndex || 0);
      startOnlineQuestionTimer(host);
    } else {
      clearOnlineQuestionTimer(host);
    }
    saveStore();
    publishOnlineHostState();
  }

  function onlineHostPause() {
    const host = store.onlineCompetition;
    if (!host || host.phase === "completed") return;
    pauseOnlineQuestionTimer(host);
    host.previousPhase = host.phase === "paused" ? host.previousPhase || "reading" : host.phase;
    host.phase = "paused";
    saveStore();
    publishOnlineHostState();
  }

  function onlineHostResume() {
    const host = store.onlineCompetition;
    if (!host || host.phase !== "paused") return;
    host.phase = host.previousPhase || "reading";
    resumeOnlineQuestionTimer(host);
    saveStore();
    publishOnlineHostState();
  }

  function onlineHostNextQuestion() {
    const host = store.onlineCompetition;
    if (!host || host.phase === "completed") return;
    const material = host.materials[host.materialIndex];
    if (!material) return;
    if (host.phase !== "quiz") {
      host.phase = "quiz";
      host.questionIndex = 0;
      startOnlineQuestionTimer(host);
    } else if (host.questionIndex + 1 < material.questions.length) {
      host.questionIndex += 1;
      startOnlineQuestionTimer(host);
    } else if (host.materialIndex + 1 < host.materials.length) {
      host.materialIndex += 1;
      host.questionIndex = 0;
      host.phase = "reading";
      clearOnlineQuestionTimer(host);
    } else {
      host.phase = "completed";
      host.completedAt = new Date().toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric" });
      clearOnlineQuestionTimer(host);
    }
    saveStore();
    publishOnlineHostState();
    if (host.phase === "completed") rememberCompetitionHistory("online", competitionCertificatesFromOnline(refreshCompetitionState() || {}, host), host);
  }

  function onlineHostComplete() {
    const host = store.onlineCompetition;
    if (!host) return;
    const state = refreshCompetitionState() || {};
    const blocker = onlineCompletionBlocker(state);
    if (blocker) {
      window.alert(blocker);
      return;
    }
    host.phase = "completed";
    host.completedAt = new Date().toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric" });
    clearOnlineQuestionTimer(host);
    saveStore();
    publishOnlineHostState();
    rememberCompetitionHistory("online", competitionCertificatesFromOnline(refreshCompetitionState() || {}, host), host);
  }

  function canCompleteOnlineCompetition(state) {
    return !onlineCompletionBlocker(state);
  }

  function onlineCompletionBlocker(state) {
    const players = Array.isArray(state && state.players) ? state.players : [];
    if (!players.length) {
      return "Competition cannot be completed because no competitors have joined yet. Ask competitors to join with the competition link, then complete after they answer.";
    }
    const counts = players.map((player) => Number(player.totalAnswered || 0));
    const highest = Math.max(...counts);
    if (highest <= 0) {
      return "Competition cannot be completed because no competitor has answered any question yet. Start the questions and let competitors answer first.";
    }
    const behind = players
      .filter((player) => Number(player.totalAnswered || 0) < highest)
      .map((player) => `${player.name || "Guest"} (${Number(player.totalAnswered || 0)}/${highest})`);
    if (behind.length) {
      return `Competition cannot be completed because answer counts are not balanced. These competitors still need to answer: ${behind.join(", ")}. Keep the competition open until they answer the missing question(s), then press Competition Completed again.`;
    }
    return "";
  }

  function publishOnlineHostState() {
    const host = store.onlineCompetition;
    if (!host) return;
    const material = host.materials[host.materialIndex];
    if (!material) return;
    if (host.phase === "quiz") {
      ensureOnlineQuestionTimer(host);
      saveStore();
      const item = applyQuestionTextEdit(material.questions[host.questionIndex]);
      if (!item) return;
      const choices = displayAnswersForQuestion(item, host).slice(0, 3);
      const correctIndex = choices.findIndex((choice) => choice.correct);
      publishCompetitionQuestion({
        levelId: host.levelId,
        index: host.questionIndex,
        timerSeconds: host.timerSeconds,
        questionDurationSeconds: host.questionDurationSeconds,
        questionDeadlineAt: host.questionDeadlineAt,
        questions: material.questions.map((question) => question.id)
      }, item, choices, correctIndex);
      return;
    }
    const phaseLabel = host.phase === "paused" ? "Competition paused by the host." : host.phase === "completed" ? "Competition completed by the host." : "Reading is in progress.";
    publishCompetitionWaiting({
      levelId: host.levelId,
      index: host.materialIndex
    }, {
      reference: material.reference || phaseLabel
    });
    const bridge = competitionBridge();
    const state = competitionState || refreshCompetitionState();
    if (bridge && typeof bridge.setQuestion === "function" && state && state.running) {
      bridge.setQuestion(JSON.stringify({
        phase: host.phase === "completed" ? "completed" : host.phase === "paused" ? "paused" : "reading",
        questionId: `competition-${host.phase}-${host.materialIndex}`,
        reference: material.reference || "Competition",
        question: phaseLabel,
        options: []
      }));
    }
    refreshCompetitionState();
  }

  async function joinOnlineCompetition(formData) {
    const rawLink = String(formData.get("link") || "").trim();
    const hostUrl = parseCompetitionLink(rawLink);
    const name = cleanText(formData.get("name") || "");
    const password = cleanText(formData.get("password") || "");
    onlineJoin = { ...onlineJoin, hostUrl, playerName: name, password, message: "Joining..." };
    renderCompetition();
    if (!hostUrl || !name || !password) {
      onlineJoin.message = "Paste the link, enter name, and enter password.";
      renderCompetition();
      return;
    }
    try {
      const response = await fetch(`${hostUrl.replace(/\/+$/, "")}/join`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({ name, password })
      });
      const result = await response.json();
      if (!result.ok) throw new Error(result.error || "Could not join");
      onlineJoin.playerId = result.playerId;
      onlineJoin.playerName = result.name || name;
      onlineJoin.message = "Joined. Waiting for the host.";
      startOnlineJoinPolling();
      await refreshOnlineJoinState();
    } catch (error) {
      onlineJoin.message = "Could not join. Check that the link is correct and the host is reachable.";
    }
    renderCompetition();
  }

  function startOnlineJoinPolling() {
    if (competitionTimerId) return;
    competitionTimerId = window.setInterval(() => {
      if (route !== "competition" || competitionView !== "online-join" || !onlineJoin.playerId) return;
      refreshOnlineJoinState().then(() => {
        if (route === "competition" && competitionView === "online-join") renderCompetition();
      });
    }, 1400);
  }

  async function refreshOnlineJoinState() {
    if (!onlineJoin.hostUrl || !onlineJoin.playerId) return;
    try {
      const response = await fetch(`${onlineJoin.hostUrl.replace(/\/+$/, "")}/state?playerId=${encodeURIComponent(onlineJoin.playerId)}`, { cache: "no-store" });
      onlineJoin.state = await response.json();
      onlineJoin.state.receivedAt = Date.now();
      onlineJoin.message = onlineJoin.state && onlineJoin.state.ok ? "" : "Waiting for host.";
    } catch (error) {
      onlineJoin.message = "Waiting for host connection...";
    }
  }

  async function sendOnlineJoinAnswer(choice) {
    if (!onlineJoin.hostUrl || !onlineJoin.playerId || !onlineJoin.state || !onlineJoin.state.question) return;
    if (onlineJoin.state.answeredChoice) {
      onlineJoin.message = "Your answer is already sent for this question.";
      renderCompetition();
      return;
    }
    if (competitionQuestionExpired({ ...onlineJoin.state.question, serverNow: onlineJoin.state.serverNow, receivedAt: onlineJoin.state.receivedAt })) {
      onlineJoin.message = "Time finished for this question. Wait for the host to continue.";
      renderCompetition();
      return;
    }
    try {
      const response = await fetch(`${onlineJoin.hostUrl.replace(/\/+$/, "")}/answer`, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify({
          playerId: onlineJoin.playerId,
          questionId: onlineJoin.state.question.id,
          choice
        })
      });
      const result = await response.json();
      onlineJoin.message = result.ok ? `Answer sent: ${choice}` : result.error || "Could not send answer.";
      if (result.ok) playAppSound(result.correct ? "correct" : "wrong");
      await refreshOnlineJoinState();
    } catch (error) {
      onlineJoin.message = "Could not send answer. Check host connection.";
    }
    renderCompetition();
  }

  function addOfflineCompetitor(formData) {
    captureCompetitionCoordinatorDraft("offline");
    const name = cleanText(formData.get("name") || "");
    if (!name) return;
    const schoolLevel = selectedCompetitionLevelMode === "different"
      ? normalizeCompetitionSchoolLevel(formData.get("schoolLevel") || selectedCompetitionSchoolLevel)
      : normalizeCompetitionSchoolLevel(selectedCompetitionSchoolLevel);
    const offline = ensureOfflineCompetitionDraft();
    offline.players.push({
      id: `offline-${Date.now()}-${Math.random().toString(36).slice(2, 6)}`,
      name,
      schoolLevel,
      score: 0,
      answers: [],
      joinedAt: new Date().toISOString()
    });
    saveStore();
    renderCompetitionKeepingPlace("[data-offline-competitor-form]");
  }

  function addOfflineCompetitorFromPrompt() {
    const name = window.prompt("Competitor name");
    if (!name) return;
    addOfflineCompetitor(new Map([["name", name], ["schoolLevel", selectedCompetitionSchoolLevel]]));
  }

  function startOfflineCompetition() {
    const draft = ensureOfflineCompetitionDraft();
    const coordinator = readCompetitionCoordinator("offline");
    if (!coordinator) return;
    if (!draft.players.length) {
      window.alert("Add competitors first.");
      return;
    }
    if (competitionUsesKgmSchool(selectedCompetitionSource)) {
      const levelsToLoad = competitionLectiveLevelsToLoad(draft);
      const needsLoading = levelsToLoad.some((level) => defaultLectiveSubjectsNeedLoading(level, competitionAvailableSubjectsForLevel(level)));
      if (needsLoading) {
        ensureCompetitionLectiveSubjectsLoaded(levelsToLoad, startOfflineCompetition);
        return;
      }
    }
    const players = draft.players.map((player, index) => ({
      ...player,
      schoolLevel: selectedCompetitionLevelMode === "same" ? normalizeCompetitionSchoolLevel(selectedCompetitionSchoolLevel) : competitionPlayerSchoolLevel(player),
      score: 0,
      answers: [],
      playerIndex: index
    }));
    const materials = competitionPlannedMaterials(players, {
      source: selectedCompetitionSource,
      schoolLevel: selectedCompetitionSchoolLevel,
      questionTarget: competitionQuestionTarget(),
      questionsPerRound: competitionQuestionsPerRound()
    });
    if (!materials.length) {
      window.alert(competitionUsesKgmSchool(selectedCompetitionSource) && !competitionUsesBible(selectedCompetitionSource) ? `Add ${schoolDisplayName()} questions first for the selected class.` : "No competition material found.");
      return;
    }
    const plannedQuestions = players.map((player) => offlinePlayerPlannedQuestions({ materials }, player));
    const maxPlannedQuestions = plannedQuestions.length ? Math.max(...plannedQuestions) : 0;
    store.offlineCompetition = {
      active: true,
      status: "reading",
      source: selectedCompetitionSource,
      levelMode: selectedCompetitionLevelMode,
      schoolLevel: normalizeCompetitionSchoolLevel(selectedCompetitionSchoolLevel),
      levelId: bibleLevelForSchoolLevel(selectedCompetitionSchoolLevel),
      lectiveLevel: selectedCompetitionSource === "lective" || selectedCompetitionSource === "both" ? normalizeCompetitionSchoolLevel(selectedCompetitionSchoolLevel) : "",
      lectiveSubject: "",
      lectiveScope: ELECTIVE_COMPETITION_SCOPE_CLASS,
      questionTarget: competitionQuestionTarget(),
      questionsPerRound: competitionQuestionsPerRound(),
      coordinator,
      competitionName: coordinator.competitionName,
      materials,
      materialIndex: 0,
      playerIndex: 0,
      questionIndex: 0,
      timerSeconds: competitionTimerDuration(),
      questionStartedAt: null,
      questionDeadlineAt: null,
      questionTimerKey: "",
      totalQuestions: maxPlannedQuestions,
      maxScore: maxPlannedQuestions * POINTS_PER_QUESTION,
      players,
      startedAt: new Date().toISOString()
    };
    competitionCoordinatorMessage = "Offline competition saved. Resume it with the same coordinator name, password, and competition name.";
    competitionAccessKey = coordinator.sessionKey;
    saveStore();
    renderCompetition();
  }

  function offlineBeginAnswers() {
    const offline = store.offlineCompetition;
    if (!offline || offline.status === "completed") return;
    if (!offlinePrepareMaterialForAnswers(offline)) {
      if (offline.materialIndex + 1 < offline.materials.length) {
        offline.materialIndex += 1;
        offline.status = "reading";
        saveStore();
        renderCompetition();
        return;
      }
      offline.status = "ready-to-complete";
      saveStore();
      renderCompetition();
      return;
    }
    offline.status = "answering";
    startOfflineQuestionTimer(offline);
    saveStore();
    renderCompetition();
  }

  function offlineAnswer(choice, timedOut = false) {
    const offline = store.offlineCompetition;
    if (!offline || offline.status !== "answering") return;
    const material = offline.materials[offline.materialIndex];
    const player = offline.players[offline.playerIndex];
    const question = offlineCurrentQuestion(offline);
    if (!material || !player || !question) return;
    const choices = displayAnswersForQuestion(question, offline).slice(0, 3);
    const answerTimedOut = timedOut || competitionQuestionExpired(offline);
    const submittedChoice = answerTimedOut ? "TIME" : String(choice || "").toUpperCase();
    const selectedIndex = ["A", "B", "C"].indexOf(submittedChoice);
    const correctIndex = choices.findIndex((item) => item.correct);
    const correct = !answerTimedOut && selectedIndex === correctIndex;
    player.answers.push({
      materialId: material.id,
      questionId: question.id,
      choice: submittedChoice,
      correct,
      marks: correct ? POINTS_PER_QUESTION : 0,
      timedOut: answerTimedOut,
      answeredAt: new Date().toISOString()
    });
    if (correct) player.score += POINTS_PER_QUESTION;
    playAppSound(correct ? "correct" : "wrong");
    offline.lastAnswerFeedback = {
      materialIndex: offline.materialIndex,
      playerIndex: offline.playerIndex,
      questionIndex: offline.questionIndex,
      choice: submittedChoice,
      selectedIndex,
      correctIndex,
      correctChoice: correctIndex >= 0 ? String.fromCharCode(65 + correctIndex) : "",
      correct,
      timedOut: answerTimedOut
    };
    offline.status = "answer-feedback";
    clearOfflineQuestionTimer(offline);
    saveStore();
    renderCompetition();
  }

  function offlineAnswerNextLabel(offline) {
    if (!offline) return "Next";
    const material = offline.materials && offline.materials[offline.materialIndex];
    const player = offline.players && offline.players[offline.playerIndex];
    if (!material) return "Next";
    if (offline.questionIndex + 1 < offlineMaterialQuestionCountForPlayer(material, player)) return "Next Question";
    const nextPlayerIndex = offlineFirstPlayerIndexForMaterial(offline, offline.materialIndex, offline.playerIndex + 1);
    if (nextPlayerIndex >= 0) {
      const nextPlayer = offline.players[nextPlayerIndex];
      return `Start ${nextPlayer && nextPlayer.name ? nextPlayer.name : "Next Competitor"}`;
    }
    if (offline.materialIndex + 1 < offline.materials.length) return "Next Reading";
    return "Finish Competition";
  }

  function offlineAdvanceAfterAnswerFeedback() {
    const offline = store.offlineCompetition;
    if (!offline || offline.status !== "answer-feedback") return;
    const material = offline.materials && offline.materials[offline.materialIndex];
    const player = offline.players && offline.players[offline.playerIndex];
    if (!material) return;
    delete offline.lastAnswerFeedback;
    if (offline.questionIndex + 1 < offlineMaterialQuestionCountForPlayer(material, player)) {
      offline.questionIndex += 1;
      offline.status = "answering";
      startOfflineQuestionTimer(offline);
    } else {
      const nextPlayerIndex = offlineFirstPlayerIndexForMaterial(offline, offline.materialIndex, offline.playerIndex + 1);
      if (nextPlayerIndex >= 0) {
        offline.playerIndex = nextPlayerIndex;
        offline.questionIndex = 0;
        offline.status = "handoff";
        clearOfflineQuestionTimer(offline);
      } else if (offline.materialIndex + 1 < offline.materials.length) {
        offline.materialIndex += 1;
        offlinePrepareMaterialForAnswers(offline);
        offline.status = "reading";
        clearOfflineQuestionTimer(offline);
      } else {
        offline.status = "ready-to-complete";
        clearOfflineQuestionTimer(offline);
      }
    }
    saveStore();
    renderCompetition();
  }

  function offlineStartNextCompetitor() {
    const offline = store.offlineCompetition;
    if (!offline || offline.status !== "handoff") return;
    if (!offline.players[offline.playerIndex]) {
      offline.status = "reading";
      saveStore();
      renderCompetition();
      return;
    }
    offline.status = "answering";
    offline.questionIndex = 0;
    startOfflineQuestionTimer(offline);
    saveStore();
    renderCompetition();
  }

  function offlineNextReading() {
    const offline = store.offlineCompetition;
    if (!offline || offline.status === "completed") return;
    if (offline.materialIndex + 1 < offline.materials.length) {
      offline.materialIndex += 1;
      offline.playerIndex = 0;
      offline.questionIndex = 0;
      offline.status = "reading";
      saveStore();
      renderCompetition();
    } else {
      completeOfflineCompetition();
    }
  }

  function addMoreOfflineCompetitionQuestions() {
    const offline = store.offlineCompetition;
    if (!offline || offline.status !== "ready-to-complete") {
      window.alert("More questions can be added only after the current planned questions are all answered.");
      return;
    }
    const requested = window.prompt("How many more questions should be added from the same selected material?", String(offline.questionTarget || competitionQuestionTarget()));
    if (requested === null) return;
    const extraCount = Math.max(1, Math.min(COMPETITION_MAX_QUESTION_TARGET, Math.round(Number(requested) || 0)));
    if (!extraCount) {
      window.alert("Enter at least 1 question to add.");
      return;
    }
    const basePlanned = {};
    const startOffsets = {};
    offline.players.forEach((player) => {
      const planned = offlinePlayerPlannedQuestions(offline, player);
      basePlanned[player.id] = planned;
      startOffsets[player.id] = planned;
    });
    const moreMaterials = competitionPlannedMaterials(offline.players, {
      source: offline.source || selectedCompetitionSource,
      schoolLevel: offline.schoolLevel || selectedCompetitionSchoolLevel,
      questionTarget: extraCount,
      questionsPerRound: Number(offline.questionsPerRound || competitionQuestionsPerRound()),
      startOffsets,
      basePlanned
    });
    if (!moreMaterials.length) {
      window.alert("No more questions were found in the selected material. Choose Competition Completed to issue certificates.");
      return;
    }
    const startRound = offline.materials.length;
    moreMaterials.forEach((material, index) => {
      material.id = `offline-extra-${Date.now()}-${startRound + index}`;
      material.reference = `Competition Round ${startRound + index + 1}`;
    });
    offline.materials.push(...moreMaterials);
    offline.totalQuestions = Math.max(...offline.players.map((player) => offlinePlayerPlannedQuestions(offline, player)));
    offline.maxScore = offline.totalQuestions * POINTS_PER_QUESTION;
    offline.materialIndex = startRound;
    offline.playerIndex = 0;
    offline.questionIndex = 0;
    offline.status = "reading";
    offline.completedAt = null;
    saveStore();
    renderCompetition();
  }



  function completeOfflineCompetition(renderNow = true) {
    const offline = store.offlineCompetition;
    if (!offline) return;
    const blocker = offlineCompletionBlocker(offline);
    if (blocker) {
      window.alert(blocker);
      if (renderNow) renderCompetition();
      return;
    }
    offline.status = "completed";
    offline.completedAt = new Date().toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric" });
    clearOfflineQuestionTimer(offline);
    offline.players.forEach((player) => {
      player.partial = player.answers.length < offlinePlayerPlannedQuestions(offline, player);
    });
    rememberCompetitionHistory("offline", competitionCertificatesFromOffline(offline), offline);
    saveStore();
    if (renderNow) renderCompetition();
  }

  function repeatOfflineCompetition() {
    const offline = store.offlineCompetition;
    if (!offline || !offline.active || offline.status !== "completed") {
      competitionView = "offline";
      renderCompetition();
      return;
    }
    clearOfflineQuestionTimer(offline);
    const materials = cloneData(Array.isArray(offline.materials) ? offline.materials : []);
    const players = (Array.isArray(offline.players) ? offline.players : []).map((player, index) => ({
      ...player,
      score: 0,
      answers: [],
      partial: false,
      playerIndex: index
    }));
    const plannedQuestions = players.map((player) => offlinePlayerPlannedQuestions({ materials }, player));
    const maxPlannedQuestions = plannedQuestions.length ? Math.max(...plannedQuestions) : Number(offline.totalQuestions || 0);
    store.offlineCompetition = {
      ...offline,
      status: "reading",
      materials,
      materialIndex: 0,
      playerIndex: 0,
      questionIndex: 0,
      questionStartedAt: null,
      questionDeadlineAt: null,
      questionTimerKey: "",
      completedAt: null,
      totalQuestions: maxPlannedQuestions,
      maxScore: maxPlannedQuestions * POINTS_PER_QUESTION,
      players,
      startedAt: new Date().toISOString(),
      repeatedAt: new Date().toISOString()
    };
    competitionCoordinatorMessage = "Competition repeated. Same competitors and material are ready from the first reading.";
    saveStore();
    renderCompetition();
  }

  function canCompleteOfflineCompetition(offline) {
    return !offlineCompletionBlocker(offline);
  }

  function offlineCompletionBlocker(offline) {
    if (!offline || !Array.isArray(offline.players) || !offline.players.length) {
      return "Competition cannot be completed because no competitors are registered. Add competitors first, then start the competition.";
    }
    if (offline.status === "answering") {
      const player = offline.players[offline.playerIndex];
      return `Competition cannot be completed because ${player ? player.name : "a competitor"} is still answering this round. Finish the current competitor's questions first, then press Competition Completed again.`;
    }
    if (offline.status === "answer-feedback") {
      return "Competition cannot be completed because the captured answer is still being reviewed. Press Next first, then complete the competition when all answers are balanced.";
    }
    if (offline.status !== "ready-to-complete" && offline.status !== "completed") {
      if (offline.status === "reading") {
        return "Competition cannot be completed yet because the current round has not been answered. Press Start Answers, finish the planned questions, then complete.";
      }
      if (offline.status === "handoff") {
        const player = offline.players[offline.playerIndex];
        return `Competition cannot be completed because ${player ? player.name : "the next competitor"} has not started this round. Press the next competitor button and finish the round first.`;
      }
      return "Competition cannot be completed yet because the planned competition questions are still in progress.";
    }
    const answeredCounts = offline.players.map((player) => Array.isArray(player.answers) ? player.answers.length : 0);
    const highest = Math.max(...answeredCounts);
    if (highest <= 0) {
      return "Competition cannot be completed because no competitor has answered any question yet. Press Start Answers and capture answers first.";
    }
    const behind = offline.players
      .filter((player) => {
        const answered = Array.isArray(player.answers) ? player.answers.length : 0;
        const planned = offlinePlayerPlannedQuestions(offline, player);
        return answered < planned;
      })
      .map((player) => {
        const answered = Array.isArray(player.answers) ? player.answers.length : 0;
        const planned = offlinePlayerPlannedQuestions(offline, player);
        return `${player.name || "Competitor"} (${answered}/${planned})`;
      });
    if (behind.length) {
      return `Competition cannot be completed because planned questions are not finished. These competitors still need answers: ${behind.join(", ")}. Continue the competition, then press Competition Completed again.`;
    }
    return "";
  }

  function resetOfflineCompetition() {
    store.offlineCompetition = {
      active: false,
      players: []
    };
    saveStore();
    renderCompetition();
  }

  function isCompetitionAdminCredential(name, password) {
    const normalized = cleanText(name || "").toLowerCase();
    return (normalized === "tindiwensi" || normalized === "arther") && cleanText(password || "") === "000000";
  }



  function savedCompetitionCredentialMatches(record, name, password) {
    if (!record || !record.coordinator) return false;
    const coordinator = record.coordinator;
    const keys = competitionSessionKeysForCredentials(record.mode, name, password, record.competitionName || coordinator.competitionName || "");
    if (coordinator.sessionKey && keys.includes(coordinator.sessionKey)) return true;
    return cleanText(coordinator.name || "").toLowerCase() === cleanText(name || "").toLowerCase()
      && cleanText(coordinator.password || "") === cleanText(password || "");
  }



  function deleteSavedCompetition(recordId) {
    const records = competitionAccessRecords();
    const record = records.find((item) => item.id === recordId) || records.find((item) => item.id === selectedSavedCompetitionId);
    if (!record) {
      window.alert("No saved competition was selected.");
      return;
    }
    const name = window.prompt("Coordinator name to delete this saved competition") || "";
    const password = window.prompt("Coordinator password") || "";
    if (!isCompetitionAdminCredential(name, password) && !savedCompetitionCredentialMatches(record, name, password)) {
      window.alert("Saved competition was not deleted. Enter the coordinator password that opened it, or use the admin account.");
      return;
    }
    store.competitionSessions = store.competitionSessions && typeof store.competitionSessions === "object" ? store.competitionSessions : {};
    if (record.coordinator && record.coordinator.sessionKey) delete store.competitionSessions[record.coordinator.sessionKey];
    const savedKey = Object.keys(store.competitionSessions).find((key) => {
      const saved = store.competitionSessions[key];
      return saved && saved.record && competitionRecordName(saved.record) === record.competitionName && saved.mode === record.mode;
    });
    if (savedKey) delete store.competitionSessions[savedKey];
    if (record.mode === "offline" && store.offlineCompetition && competitionRecordName(store.offlineCompetition) === record.competitionName) {
      store.offlineCompetition = { active: false, players: [] };
    }
    selectedSavedCompetitionId = "";
    competitionUnlockMessage = "Saved competition deleted.";
    saveStore();
    competitionView = "saved";
    renderCompetition();
  }



  function startNewOfflineCompetition() {
    openFreshOfflineCompetition();
  }

  function openFreshOfflineCompetition() {
    archiveActiveCompetitionSession("offline");
    store.offlineCompetition = {
      active: false,
      players: []
    };
    competitionCoordinatorDrafts.offline = emptyCompetitionCoordinatorDraft();
    competitionCoordinatorMessage = "New offline competition ready. Enter coordinator name, password, and competition name.";
    competitionAccessKey = "";
    saveStore();
    competitionView = "offline";
    renderCompetition();
  }

  function archiveActiveCompetitionSession(mode) {
    store.competitionSessions = store.competitionSessions && typeof store.competitionSessions === "object" ? store.competitionSessions : {};
    const normalizedMode = mode === "online" ? "online" : "offline";
    const record = normalizedMode === "online" ? store.onlineCompetition : store.offlineCompetition;
    if (!record || !record.coordinator || !record.coordinator.sessionKey) return;
    record.competitionName = competitionRecordName(record);
    record.coordinator.competitionName = competitionRecordName(record);
    store.competitionSessions[record.coordinator.sessionKey] = {
      mode: normalizedMode,
      savedAt: new Date().toISOString(),
      record: cloneData(record)
    };
  }

  function ensureOfflineCompetitionDraft() {
    if (!store.offlineCompetition || store.offlineCompetition.status === "completed") {
      store.offlineCompetition = { active: false, players: [] };
    }
    store.offlineCompetition.players = Array.isArray(store.offlineCompetition.players) ? store.offlineCompetition.players : [];
    return store.offlineCompetition;
  }

  function competitionMaterials(source, levelId, lectiveLevel = selectedCompetitionLectiveLevel, lectiveSubject = selectedCompetitionLectiveSubject, lectiveScope = selectedCompetitionLectiveScope) {
    const normalizedSource = source === "lective" ? "lective" : "level";
    const normalizedLectiveLevel = normalizeLectiveLevel(lectiveLevel);
    const normalizedLectiveSubject = normalizeLectiveSubject(lectiveSubject, normalizedLectiveLevel);
    const wholeLectiveClass = normalizedSource === "lective" && lectiveScope === ELECTIVE_COMPETITION_SCOPE_CLASS;
    const bank = normalizedSource === "lective"
      ? wholeLectiveClass
        ? electiveOrderedSubjects(normalizedLectiveLevel).flatMap((subject) => lectiveStoryBank(normalizedLectiveLevel, subject).map((story) => ({ ...story, competitionSubject: subject })))
        : lectiveStoryBank(normalizedLectiveLevel, normalizedLectiveSubject).map((story) => ({ ...story, competitionSubject: normalizedLectiveSubject }))
      : courseStoriesForLevel(levelId);
    const groupSize = normalizedSource === "lective" ? 1 : competitionGroupSize(levelId);
    const sourceLabel = normalizedSource === "lective"
      ? wholeLectiveClass ? `${schoolDisplayName()} ${electiveClassLabel(normalizedLectiveLevel)} Whole Class` : lectiveFolderLabel(normalizedLectiveLevel, normalizedLectiveSubject)
      : courseLabel(levelId);
    const materials = [];
    for (let index = 0; index < bank.length; index += groupSize) {
      const chunk = bank.slice(index, index + groupSize);
      const questions = chunk.map((story) => story.questions[0]).filter(Boolean);
      if (!questions.length) continue;
      const storyBlocks = chunk.map((story) => ({
        title: normalizedSource === "lective" && story.competitionSubject ? `${story.competitionSubject}: ${story.title || story.reference || "Question"}` : story.title || story.reference || "Competition Reading",
        content: story.content || ""
      }));
      materials.push({
        id: `${normalizedSource}-${levelId}-${wholeLectiveClass ? normalizedLectiveLevel : lectiveFolderKey(normalizedLectiveLevel, normalizedLectiveSubject)}-${index}`,
        reference: normalizedSource === "lective" ? sourceLabel : `${courseLabel(levelId)} - Round ${materials.length + 1}`,
        content: storyBlocks.map((story) => `${story.title}\n\n${story.content}`).join("\n\n\n"),
        stories: storyBlocks,
        questions
      });
    }
    return materials;
  }

  function competitionGroupSize(levelId) {
    if (Number(levelId) === BABY_COURSE.id) return 1;
    const value = Math.max(1, Number(levelId) || 1);
    return Math.min(6, value + 1);
  }

  function lectiveStoryBank(level, subject) {
    const key = level && subject ? normalizeLectiveFolderKey(lectiveFolderKey(level, subject)) : "__all__";
    if (lectiveStoryBankCache.has(key)) return lectiveStoryBankCache.get(key);
    const source = level && subject
      ? lectiveFolderStories(key)
      : Object.values(store.lectiveFolders || {}).flat().concat(defaultLectiveStories);
    const bank = buildStoryBank(source).map(applyStoryTextEdit);
    lectiveStoryBankCache.set(key, bank);
    return bank;
  }

  function lectiveFolderStories(key) {
    const safeKey = normalizeLectiveFolderKey(key || lectiveFolderKey(DEFAULT_LECTIVE_LEVEL, DEFAULT_LECTIVE_SUBJECT));
    if (lectiveFolderStoriesCache.has(safeKey)) return lectiveFolderStoriesCache.get(safeKey);
    if (!store.lectiveFolders || typeof store.lectiveFolders !== "object") {
      store.lectiveFolders = normalizeLectiveFolders(store.lectiveFolders, store.lectiveStories);
      invalidateLectiveCaches();
    }
    const folders = store.lectiveFolders;
    const stories = (Array.isArray(folders[safeKey]) ? folders[safeKey] : []).concat(defaultLectiveFolderStories(safeKey));
    lectiveFolderStoriesCache.set(safeKey, stories);
    return stories;
  }

  function invalidateLectiveCaches() {
    lectiveFolderStoriesCache.clear();
    lectiveStoryBankCache.clear();
    lectiveQuestionListCache.clear();
    persistableLectiveFoldersCache = null;
    persistableLectiveStoriesCache = null;
  }

  function updateLectiveStoriesFromFolders() {
    store.lectiveStories = Object.values(store.lectiveFolders || {}).flat();
    invalidateLectiveCaches();
  }

  function ensureLectiveFoldersReady() {
    if (store.lectiveFolders && typeof store.lectiveFolders === "object") return;
    store.lectiveFolders = normalizeLectiveFolders(store.lectiveFolders, store.lectiveStories);
    updateLectiveStoriesFromFolders();
  }

  function saveLectiveStory(formData) {
    const level = normalizeLectiveLevel(formData.get("level") || selectedLectiveLevel);
    const subject = normalizeLectiveSubject(formData.get("subject") || selectedLectiveSubject, level);
    const question = cleanText(formData.get("question") || formData.get("content") || "");
    const content = question;
    const topic = cleanText(formData.get("topic") || "") || titleFromContent(question, "Elective Question");
    const rightAnswer = cleanText(formData.get("rightAnswer") || "");
    const wrongAnswer1 = cleanText(formData.get("wrongAnswer1") || "");
    const wrongAnswer2 = cleanText(formData.get("wrongAnswer2") || "");
    if (!topic || !content || !question || !rightAnswer || !wrongAnswer1 || !wrongAnswer2) {
      competitionLectiveMessage = "Fill the question, one correct answer, and two wrong answers.";
      renderLectiveCourse();
      return;
    }
    const key = lectiveFolderKey(level, subject);
    store.lectiveFolders = normalizeLectiveFolders(store.lectiveFolders, []);
    store.lectiveFolders[key] = Array.isArray(store.lectiveFolders[key]) ? store.lectiveFolders[key] : [];
    store.lectiveFolders[key].push({
      id: `lective-${Date.now()}`,
      course: "KGM School",
      title: topic,
      reference: lectiveFolderLabel(level, subject),
      level,
      subject,
      content,
      questions: [{
        question,
        answers: [
          { text: rightAnswer, correct: true },
          { text: wrongAnswer1, correct: false },
          { text: wrongAnswer2, correct: false }
        ]
      }]
    });
    selectedLectiveLevel = level;
    selectedLectiveSubject = subject;
    competitionLectiveMessage = `KGM School question saved in ${lectiveFolderLabel(level, subject)}.`;
    updateLectiveStoriesFromFolders();
    saveStore();
    lectiveView = level === ELECTIVE_OTHERS_LEVEL ? "others" : "subjects";
    renderLectiveCourse();
  }

  function deleteLectiveStory(key, id) {
    const safeKey = normalizeLectiveFolderKey(key);
    const storyId = String(id || "");
    if (!safeKey || !storyId) return;
    store.lectiveFolders = normalizeLectiveFolders(store.lectiveFolders, []);
    const list = Array.isArray(store.lectiveFolders[safeKey]) ? store.lectiveFolders[safeKey] : [];
    const story = list.find((item) => String(item && item.id) === storyId);
    if (!story) return;
    store.lectiveFolders[safeKey] = list.filter((item) => String(item && item.id) !== storyId);
    competitionLectiveMessage = "KGM School question deleted from this device.";
    updateLectiveStoriesFromFolders();
    saveStore();
    renderLectiveCourse();
  }

  function startElectiveSubject(level, subject) {
    const normalizedLevel = normalizeLectiveLevel(level || selectedLectiveStudyLevel);
    const normalizedSubject = normalizeLectiveSubject(subject || selectedLectiveStudySubject, normalizedLevel);
    selectedLectiveStudyLevel = normalizedLevel;
    selectedLectiveStudySubject = normalizedSubject;
    if (!activeUser()) {
      competitionLectiveMessage = "Register with name and password before starting this Elective Course.";
      route = "lective";
      lectiveView = "register";
      renderLectiveCourse();
      return;
    }
    if (normalizedLevel === ELECTIVE_OTHERS_LEVEL && !defaultLectiveDataLoaded) {
      route = "lective";
      lectiveView = "others";
      renderLectiveDataLoading("Loading Others questions...");
      ensureDefaultLectiveDataLoaded()
        .then(() => startElectiveSubject(normalizedLevel, normalizedSubject))
        .catch(() => renderLectiveCourse());
      return;
    }
    const subjectsToLoad = electiveOrderedSubjects(normalizedLevel, normalizedSubject);
    if (defaultLectiveSubjectsNeedLoading(normalizedLevel, subjectsToLoad)) {
      route = "lective";
      lectiveView = "subjects";
      renderLectiveDataLoading(`Loading ${electiveClassLabel(normalizedLevel)} questions...`);
      ensureDefaultLectiveSubjectsLoaded(normalizedLevel, subjectsToLoad)
        .then(() => startElectiveSubject(normalizedLevel, normalizedSubject))
        .catch(() => renderLectiveCourse());
      return;
    }
    const bank = lectiveStoryBank(normalizedLevel, normalizedSubject);
    if (!bank.length) {
      competitionLectiveMessage = `${electiveClassLabel(normalizedLevel)} ${normalizedSubject} is coming soon. Add or bring the question database later.`;
      route = "lective";
      lectiveView = normalizedLevel === ELECTIVE_OTHERS_LEVEL ? "others" : "subjects";
      renderLectiveCourse();
      return;
    }
    startLectiveStudyFromValues(normalizedLevel, normalizedSubject);
  }

  function startLectiveStudy(formData) {
    const level = normalizeLectiveLevel(formData.get("studyLevel") || selectedLectiveStudyLevel);
    const subject = normalizeLectiveSubject(formData.get("studySubject") || selectedLectiveStudySubject, level);
    startLectiveStudyFromValues(level, subject);
  }

  function startLectiveStudyFromValues(level, subject) {
    const user = activeUser();
    if (!user) {
      selectedLectiveStudyLevel = normalizeLectiveLevel(level);
      selectedLectiveStudySubject = normalizeLectiveSubject(subject, selectedLectiveStudyLevel);
      competitionLectiveMessage = "Register with name and password before starting this Elective Course.";
      route = "lective";
      lectiveView = "register";
      renderLectiveCourse();
      return;
    }
    level = normalizeLectiveLevel(level);
    subject = normalizeLectiveSubject(subject, level);
    const subjectsToLoad = electiveOrderedSubjects(level, subject);
    if (defaultLectiveSubjectsNeedLoading(level, subjectsToLoad)) {
      route = "lective";
      lectiveView = "subjects";
      renderLectiveDataLoading(`Loading ${electiveClassLabel(level)} questions...`);
      ensureDefaultLectiveSubjectsLoaded(level, subjectsToLoad)
        .then(() => startLectiveStudyFromValues(level, subject))
        .catch(() => renderLectiveCourse());
      return;
    }
    const subjectQuestions = lectiveQuestionList(level, subject);
    if (!subjectQuestions.length) {
      competitionLectiveMessage = `${electiveClassLabel(level)} ${subject} is coming soon. Add or bring the question database later.`;
      lectiveView = level === ELECTIVE_OTHERS_LEVEL ? "others" : "subjects";
      renderLectiveCourse();
      return;
    }
    selectedLectiveStudyLevel = level;
    selectedLectiveStudySubject = subject;
    const existing = (user.electiveStudy && !user.electiveStudy.completed ? user.electiveStudy : store.lectiveStudy);
    if (existing && !existing.completed && existing.userId === user.id && normalizeLectiveLevel(existing.level) === level) {
      existing.level = level;
      existing.userId = user.id;
      existing.phase = "quiz";
      normalizeElectiveStudySession(existing, subject);
      user.electiveStudy = existing;
      store.lectiveStudy = existing;
      saveStore();
      route = "lective-study";
      render();
      return;
    }
    store.lectiveStudy = createElectiveStudySession(user, level, subject);
    user.electiveStudy = store.lectiveStudy;
    saveStore();
    route = "lective-study";
    render();
  }

  function createElectiveStudySession(user, level, startSubject) {
    const normalizedLevel = normalizeLectiveLevel(level);
    const subjectOrder = electiveOrderedSubjects(normalizedLevel, startSubject);
    const firstSubject = nextElectiveSubjectFromOrder({
      level: normalizedLevel,
      subjectOrder,
      subjectOffsets: {},
      subjectCompleted: {}
    }, startSubject) || subjectOrder[0] || normalizeLectiveSubject(startSubject, normalizedLevel);
    return {
      key: lectiveFolderKey(normalizedLevel, firstSubject),
      userId: user.id,
      level: normalizedLevel,
      subject: firstSubject,
      subjectOrder,
      subjectOffsets: {},
      subjectScores: {},
      subjectMaxScores: {},
      subjectCompleted: {},
      checkpointSize: electiveCheckpointSize(normalizedLevel),
      midCourseCertificates: [],
      storyIndex: 0,
      questionIndex: 0,
      flatIndex: 0,
      phase: "quiz",
      answers: [],
      score: 0,
      startedAt: new Date().toISOString(),
      completed: false
    };
  }

  function normalizeElectiveStudySession(session, preferredSubject = "") {
    if (!session) return null;
    session.level = normalizeLectiveLevel(session.level);
    session.subject = normalizeLectiveSubject(session.subject || preferredSubject, session.level);
    const available = availableElectiveSubjectsForClass(session.level);
    session.subjectOrder = Array.isArray(session.subjectOrder) && session.subjectOrder.length
      ? session.subjectOrder.map((subject) => normalizeLectiveSubject(subject, session.level)).filter((subject, index, list) => available.includes(subject) && list.indexOf(subject) === index)
      : electiveOrderedSubjects(session.level, preferredSubject || session.subject);
    available.forEach((subject) => {
      if (!session.subjectOrder.includes(subject)) session.subjectOrder.push(subject);
    });
    session.subjectOffsets = session.subjectOffsets && typeof session.subjectOffsets === "object" ? session.subjectOffsets : {};
    session.subjectScores = session.subjectScores && typeof session.subjectScores === "object" ? session.subjectScores : {};
    session.subjectMaxScores = session.subjectMaxScores && typeof session.subjectMaxScores === "object" ? session.subjectMaxScores : {};
    session.subjectCompleted = session.subjectCompleted && typeof session.subjectCompleted === "object" ? session.subjectCompleted : {};
    session.midCourseCertificates = Array.isArray(session.midCourseCertificates) ? session.midCourseCertificates : [];
    session.answers = Array.isArray(session.answers) ? session.answers : [];
    session.checkpointSize = electiveCheckpointSize(session.level);
    session.score = Number(session.score || 0);
    if (!Object.keys(session.subjectOffsets).length && session.answers.length) {
      session.answers.forEach((answer) => {
        const subject = normalizeLectiveSubject(answer.subject || session.subject, session.level);
        const index = Number.isFinite(Number(answer.flatIndex)) ? Number(answer.flatIndex) + 1 : Number(session.subjectOffsets[subject] || 0) + 1;
        session.subjectOffsets[subject] = Math.max(Number(session.subjectOffsets[subject] || 0), index);
      });
    }
    session.subjectOrder.forEach((subject) => {
      session.subjectOffsets[subject] = clampNumber(session.subjectOffsets[subject], 0, lectiveQuestionList(session.level, subject).length);
      session.subjectScores[subject] = Number(session.subjectScores[subject] || 0);
      session.subjectMaxScores[subject] = Number(session.subjectMaxScores[subject] || 0);
      if (session.subjectOffsets[subject] >= lectiveQuestionList(session.level, subject).length && lectiveQuestionList(session.level, subject).length) {
        session.subjectCompleted[subject] = true;
      }
    });
    const nextSubject = nextElectiveSubjectFromOrder(session, session.subject) || nextElectiveSubjectFromOrder(session, preferredSubject) || session.subjectOrder[0] || session.subject;
    if (nextSubject) session.subject = nextSubject;
    session.key = lectiveFolderKey(session.level, session.subject);
    const current = currentElectiveQuestion(session);
    session.storyIndex = current ? current.storyIndex : Math.max(0, Number(session.storyIndex || 0));
    session.questionIndex = current ? current.questionIndex : Math.max(0, Number(session.questionIndex || 0));
    session.flatIndex = current ? current.flatIndex : Math.max(0, Number(session.flatIndex || 0));
    session.phase = "quiz";
    return session;
  }

  function clampNumber(value, min, max) {
    const number = Number(value || 0);
    if (!Number.isFinite(number)) return min;
    return Math.max(min, Math.min(max, number));
  }

  function nextElectiveSubjectFromOrder(session, afterSubject = "") {
    const order = Array.isArray(session.subjectOrder) && session.subjectOrder.length ? session.subjectOrder : electiveOrderedSubjects(session.level, afterSubject);
    if (!order.length) return "";
    const startIndex = Math.max(0, order.indexOf(normalizeLectiveSubject(afterSubject || order[0], session.level)));
    for (let step = 0; step < order.length; step += 1) {
      const subject = order[(startIndex + step) % order.length];
      const total = lectiveQuestionList(session.level, subject).length;
      const offset = Number(session.subjectOffsets && session.subjectOffsets[subject] || 0);
      if (total && offset < total) return subject;
    }
    return "";
  }

  function nextElectiveSubjectAfter(session, currentSubject = session.subject) {
    const order = Array.isArray(session.subjectOrder) && session.subjectOrder.length ? session.subjectOrder : electiveOrderedSubjects(session.level, currentSubject);
    if (!order.length) return "";
    const start = order.indexOf(currentSubject);
    for (let step = 1; step <= order.length; step += 1) {
      const subject = order[((start >= 0 ? start : 0) + step) % order.length];
      const total = lectiveQuestionList(session.level, subject).length;
      const offset = Number(session.subjectOffsets && session.subjectOffsets[subject] || 0);
      if (total && offset < total) return subject;
    }
    return "";
  }

  function currentElectiveQuestion(session) {
    if (!session) return null;
    const subject = normalizeLectiveSubject(session.subject, session.level);
    const list = lectiveQuestionList(session.level, subject);
    const offset = clampNumber(session.subjectOffsets && session.subjectOffsets[subject], 0, list.length);
    const item = list[offset] || null;
    return item ? { ...item, subject, flatIndex: offset } : null;
  }

  function validLectiveStudySession() {
    const user = activeUser();
    let session = store.lectiveStudy;
    if (user && (!session || session.completed || session.userId !== user.id) && user.electiveStudy && !user.electiveStudy.completed) {
      session = user.electiveStudy;
      store.lectiveStudy = session;
    }
    if (!session || session.completed) {
      route = "lective";
      renderLectiveCourse();
      return null;
    }
    if (user && session.userId && session.userId !== user.id) {
      route = "lective";
      lectiveView = "register";
      renderLectiveCourse();
      return null;
    }
    if (user) session.userId = user.id;
    return normalizeElectiveStudySession(session, session.subject);
  }

  function persistLectiveStudySession(session) {
    if (!session) return;
    const user = activeUser();
    if (user) {
      session.userId = user.id;
      user.electiveStudy = {
        ...session,
        subjectOffsets: { ...(session.subjectOffsets || {}) },
        subjectScores: { ...(session.subjectScores || {}) },
        subjectMaxScores: { ...(session.subjectMaxScores || {}) },
        subjectCompleted: { ...(session.subjectCompleted || {}) },
        subjectOrder: Array.isArray(session.subjectOrder) ? session.subjectOrder.slice() : [],
        answers: Array.isArray(session.answers) ? session.answers.slice() : [],
        midCourseCertificates: Array.isArray(session.midCourseCertificates) ? session.midCourseCertificates.slice() : []
      };
    }
    store.lectiveStudy = session;
    saveStore();
  }

  function beginLectiveStudyQuiz() {
    const session = validLectiveStudySession();
    if (!session) return;
    session.phase = "quiz";
    persistLectiveStudySession(session);
    renderLectiveStudy();
  }

  function lectiveQuestionTimerKey(session) {
    const current = currentElectiveQuestion(session);
    const question = current && current.question;
    if (!current || !question) return "";
    return [
      normalizeLectiveLevel(session.level),
      normalizeLectiveSubject(current.subject, session.level),
      Number(current.flatIndex || 0),
      question.id || question.question || ""
    ].join(":");
  }

  function ensureLectiveQuestionDeadline(session) {
    if (!session || session.phase !== "quiz" || currentLectiveAnswer(session)) return 0;
    const key = lectiveQuestionTimerKey(session);
    if (!key) return 0;
    if (session.questionTimerKey !== key || !Number(session.questionDeadline || 0)) {
      session.questionTimerKey = key;
      session.questionDeadline = Date.now() + ELECTIVE_SECONDS_PER_QUESTION * 1000;
      persistLectiveStudySession(session);
    }
    return Number(session.questionDeadline || 0);
  }

  function lectiveQuestionExpired(session) {
    if (!session || currentLectiveAnswer(session)) return false;
    const key = lectiveQuestionTimerKey(session);
    const deadline = Number(session.questionDeadline || 0);
    return Boolean(key && session.questionTimerKey === key && deadline && deadline <= Date.now());
  }

  function markExpiredLectiveQuestionIfNeeded(session) {
    if (!lectiveQuestionExpired(session)) return false;
    selectLectiveStudyAnswer(null, true);
    return true;
  }

  function electiveQuestionRemainingSeconds(session) {
    const deadline = ensureLectiveQuestionDeadline(session);
    if (!deadline) return ELECTIVE_SECONDS_PER_QUESTION;
    return Math.max(0, Math.min(ELECTIVE_SECONDS_PER_QUESTION, Math.ceil((deadline - Date.now()) / 1000)));
  }

  function startLectiveTimer() {
    const session = validLectiveStudySession();
    if (!session || session.phase !== "quiz" || currentLectiveAnswer(session)) return;
    ensureLectiveQuestionDeadline(session);
    stopLectiveTimer();
    lectiveTimerId = window.setInterval(() => {
      const active = validLectiveStudySession();
      if (!active || active.phase !== "quiz" || currentLectiveAnswer(active)) {
        stopLectiveTimer();
        return;
      }
      const deadline = ensureLectiveQuestionDeadline(active);
      const remaining = Math.ceil((deadline - Date.now()) / 1000);
      if (remaining <= 0) {
        stopLectiveTimer();
        selectLectiveStudyAnswer(null, true);
        return;
      }
      const ring = document.querySelector(".lective-timer-ring");
      if (ring) {
        ring.style.setProperty("--timer-angle", `${Math.round((remaining / ELECTIVE_SECONDS_PER_QUESTION) * 360)}deg`);
        const label = ring.querySelector("strong");
        if (label) label.textContent = formatClock(remaining);
      }
    }, 250);
  }

  function stopLectiveTimer() {
    if (lectiveTimerId) {
      window.clearInterval(lectiveTimerId);
      lectiveTimerId = null;
    }
  }

  function selectLectiveStudyAnswer(selectedIndex, timedOut = false) {
    const session = validLectiveStudySession();
    if (!session || session.phase !== "quiz") return;
    const current = currentElectiveQuestion(session);
    const story = current && current.story;
    const question = current && current.question;
    if (!question) return;
    const existing = currentLectiveAnswer(session);
    if (existing) return;
    if (!timedOut && lectiveQuestionExpired(session)) {
      selectLectiveStudyAnswer(null, true);
      return;
    }
    const choices = displayAnswersForQuestion(question, { levelId: -1, startedAt: session.startedAt });
    const correctIndex = choices.findIndex((choice) => choice.correct);
    const selectedChoice = choices[selectedIndex] || null;
    const correct = !timedOut && selectedIndex === correctIndex;
    const correctChoice = choices[correctIndex] || null;
    const subject = current.subject;
    session.answers.push({
      level: session.level,
      subject,
      storyIndex: current.storyIndex,
      questionIndex: current.questionIndex,
      flatIndex: current.flatIndex,
      questionId: question.id,
      questionText: question.question,
      selectedIndex,
      selectedOriginalIndex: selectedChoice ? selectedChoice.originalIndex : selectedIndex,
      correctIndex,
      correctOriginalIndex: choices[correctIndex] ? choices[correctIndex].originalIndex : correctIndex,
      correctText: correctChoice ? correctChoice.text : "",
      correct,
      timedOut,
      marks: correct ? POINTS_PER_QUESTION : 0,
      inspiration: correct ? "" : electiveFailureMessage(activeUser(), question, correctChoice),
      answeredAt: new Date().toISOString()
    });
    if (correct) session.score += POINTS_PER_QUESTION;
    session.subjectScores[subject] = Number(session.subjectScores[subject] || 0) + (correct ? POINTS_PER_QUESTION : 0);
    session.subjectMaxScores[subject] = Number(session.subjectMaxScores[subject] || 0) + POINTS_PER_QUESTION;
    session.storyIndex = current.storyIndex;
    session.questionIndex = current.questionIndex;
    session.flatIndex = current.flatIndex;
    playAppSound(correct ? "correct" : "wrong");
    session.questionDeadline = null;
    persistLectiveStudySession(session);
    stopLectiveTimer();
    renderLectiveStudy();
  }

  function nextLectiveStudyStep() {
    const session = validLectiveStudySession();
    if (!session) return;
    const current = currentElectiveQuestion(session);
    if (!current) {
      completeLectiveClass(session);
      return;
    }
    if (!currentLectiveAnswer(session)) return;
    session.questionDeadline = null;
    session.questionTimerKey = "";
    const advanced = advanceElectiveStudy(session, current);
    if (advanced === "certificate") return;
    if (advanced === "complete") {
      completeLectiveClass(session);
      return;
    }
    persistLectiveStudySession(session);
    renderLectiveStudy();
  }

  function currentLectiveAnswer(session) {
    const current = currentElectiveQuestion(session);
    if (!current) return null;
    return (session.answers || []).find((item) =>
      item.subject === current.subject &&
      Number(item.flatIndex) === Number(current.flatIndex)
    ) || null;
  }

  function advanceElectiveStudy(session, current) {
    const subject = current.subject;
    const subjectTotal = lectiveQuestionList(session.level, subject).length;
    const nextOffset = Math.min(subjectTotal, Number(session.subjectOffsets[subject] || 0) + 1);
    session.subjectOffsets[subject] = nextOffset;
    const subjectDone = subjectTotal > 0 && nextOffset >= subjectTotal;
    if (subjectDone) session.subjectCompleted[subject] = true;
    const checkpointSize = electiveCheckpointSize(session.level);
    const checkpointDue = subjectDone || (checkpointSize > 0 && nextOffset > 0 && nextOffset % checkpointSize === 0);
    if (checkpointDue) {
      const nextSubject = nextElectiveSubjectAfter(session, subject);
      const certificate = createElectiveMidCourseCertificate(session, subject, nextOffset, subjectDone, nextSubject);
      if (certificate) {
        issueElectiveCertificate(certificate, session, false);
        if (nextSubject) {
          session.subject = nextSubject;
          session.key = lectiveFolderKey(session.level, nextSubject);
        }
        persistLectiveStudySession(session);
        route = "certificate";
        render();
        return "certificate";
      }
    }
    const nextSubject = subjectDone ? nextElectiveSubjectAfter(session, subject) : subject;
    if (!nextSubject) return "complete";
    session.subject = nextSubject;
    session.key = lectiveFolderKey(session.level, nextSubject);
    const nextCurrent = currentElectiveQuestion(session);
    if (nextCurrent) {
      session.storyIndex = nextCurrent.storyIndex;
      session.questionIndex = nextCurrent.questionIndex;
      session.flatIndex = nextCurrent.flatIndex;
    }
    return "continue";
  }

  function createElectiveMidCourseCertificate(session, subject, nextOffset, subjectDone, nextSubject) {
    const size = electiveCheckpointSize(session.level);
    const chunkStart = subjectDone && nextOffset % size ? Math.floor(nextOffset / size) * size : Math.max(0, nextOffset - size);
    const chunkAnswers = (session.answers || []).filter((answer) =>
      answer.subject === subject &&
      Number(answer.flatIndex) >= chunkStart &&
      Number(answer.flatIndex) < nextOffset
    );
    if (!chunkAnswers.length) return null;
    const score = chunkAnswers.reduce((sum, answer) => sum + Number(answer.marks || 0), 0);
    const maxScore = chunkAnswers.length * POINTS_PER_QUESTION;
    const percent = maxScore ? Math.round((score / maxScore) * 100) : 0;
    const checkpointNumber = Math.floor(chunkStart / size) + 1;
    const classTotals = electiveSessionTotals(session);
    const subjectTotal = lectiveQuestionList(session.level, subject).length;
    const certificate = {
      certificateId: makeCertificateId("KGM-MID"),
      kind: "lective",
      isMidCourse: true,
      continueElective: true,
      levelId: 1,
      score,
      maxScore,
      percent,
      passed: percent >= ELECTIVE_PASS_PERCENT,
      passPercent: ELECTIVE_PASS_PERCENT,
      courseTitle: `${electiveClassLabel(session.level)} ${subject} Mid Course`,
      completedLabel: `${electiveClassLabel(session.level)} ${subject}`,
      lectiveLevel: session.level,
      lectiveSubject: subject,
      repeatFromOffset: chunkStart,
      repeatToOffset: nextOffset,
      starLine: subjectDone ? "SUBJECT COMPLETED" : `MID COURSE ${checkpointNumber}`,
      storyCount: chunkAnswers.length,
      storyLine: `Questions ${chunkStart + 1}-${nextOffset} of ${subjectTotal}`,
      questionsAsked: chunkAnswers.length,
      questionsLine: `Class total so far: ${classTotals.score.toLocaleString()} / ${classTotals.maxScore.toLocaleString()} marks`,
      promotionText: nextSubject ? "NEXT SUBJECT" : "READY TO COMPLETE CLASS",
      promotionLevel: nextSubject || electiveClassLabel(session.level),
      adviceText: percent === 100 ? "Excellent work. Continue strongly." : `Pass mark is ${ELECTIVE_PASS_PERCENT}%. Repeat later for 100%.`,
      date: new Date().toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric" })
    };
    session.midCourseCertificates.push({
        certificateId: certificate.certificateId,
        level: session.level,
        subject,
        checkpointNumber,
        fromQuestion: chunkStart + 1,
        toQuestion: nextOffset,
        fromOffset: chunkStart,
        toOffset: nextOffset,
        score,
      maxScore,
      percent,
      completedAt: new Date().toISOString()
    });
    return certificate;
  }

  function electiveSessionTotals(session) {
    const score = Number(session.score || 0);
    const answered = (session.answers || []).length;
    return {
      score,
      maxScore: answered * POINTS_PER_QUESTION,
      questionsAsked: answered,
      percent: answered ? Math.round((score / (answered * POINTS_PER_QUESTION)) * 100) : 0
    };
  }

  function issueElectiveCertificate(certificate, session, completedClass) {
    const user = activeUser();
    if (!user) return;
    user.lastCertificate = certificate;
    user.history.unshift({ ...certificate, completedAt: new Date().toISOString() });
    user.history = user.history.slice(0, 20);
    const classProgress = electiveProgressForUser(user, session.level);
    classProgress.midCourseCertificates = Array.isArray(classProgress.midCourseCertificates) ? classProgress.midCourseCertificates : [];
    if (certificate.isMidCourse) {
      classProgress.midCourseCertificates.unshift({
        certificateId: certificate.certificateId,
        subject: certificate.lectiveSubject,
        fromOffset: Number(certificate.repeatFromOffset || 0),
        toOffset: Number(certificate.repeatToOffset || 0),
        score: certificate.score,
        maxScore: certificate.maxScore,
        percent: certificate.percent,
        date: certificate.date
      });
      classProgress.midCourseCertificates = classProgress.midCourseCertificates.slice(0, 100);
    }
    if (completedClass) {
      classProgress.completed = Boolean(certificate.passed);
      classProgress.completedAt = new Date().toISOString();
    }
  }

  function lectiveStudyTotalQuestions(bank) {
    return (Array.isArray(bank) ? bank : []).reduce((sum, story) => sum + (Array.isArray(story.questions) ? story.questions.length : 0), 0);
  }

  function lectiveStudyIsLastQuestion(session, bank) {
    const story = bank[session.storyIndex];
    if (!story) return true;
    return session.storyIndex + 1 >= bank.length && session.questionIndex + 1 >= story.questions.length;
  }

  function completeLectiveStudy(session) {
    completeLectiveClass(session);
  }

  function completeLectiveClass(session) {
    const user = activeUser();
    if (!user || !session) {
      route = "login";
      render();
      return;
    }
    normalizeElectiveStudySession(session, session.subject);
    const classProgress = electiveProgressForUser(user, session.level);
    const availableSubjects = availableElectiveSubjectsForClass(session.level);
    const incomplete = availableSubjects.filter((subject) => Number(session.subjectOffsets[subject] || 0) < lectiveQuestionList(session.level, subject).length);
    if (incomplete.length) {
      const nextSubject = nextElectiveSubjectAfter(session, session.subject) || incomplete[0];
      session.subject = nextSubject;
      session.key = lectiveFolderKey(session.level, nextSubject);
      competitionLectiveMessage = `Continue ${electiveClassLabel(session.level)}. Remaining subjects: ${incomplete.join(", ")}.`;
      saveStore();
      persistLectiveStudySession(session);
      route = "lective-study";
      renderLectiveStudy();
      return;
    }

    session.subjectOrder.forEach((subject) => {
      const subjectTotal = lectiveQuestionList(session.level, subject).length;
      if (!subjectTotal) return;
      const subjectScore = Number(session.subjectScores[subject] || 0);
      const subjectMaxScore = Number(session.subjectMaxScores[subject] || subjectTotal * POINTS_PER_QUESTION);
      classProgress.subjects[subject] = {
        score: subjectScore,
        maxScore: subjectMaxScore,
        percent: subjectMaxScore ? Math.round((subjectScore / subjectMaxScore) * 100) : 0,
        passed: subjectMaxScore ? Math.round((subjectScore / subjectMaxScore) * 100) >= ELECTIVE_PASS_PERCENT : false,
        storyCount: lectiveStoryBank(session.level, subject).length,
        questionsAsked: subjectTotal,
        completedAt: new Date().toISOString()
      };
    });
    const summary = electiveClassCompletionSummary(user, session.level, false);
    const passed = summary.percent >= ELECTIVE_PASS_PERCENT;
    const nextClass = nextElectiveClass(session.level);
    if (passed) user.electiveProgress.completedClasses[session.level] = true;
    classProgress.completed = passed;
    classProgress.completedAt = new Date().toISOString();
    user.electiveProgress.currentClass = passed ? (nextClass || session.level) : session.level;
    store.lectiveStudy = { ...session, completed: true, completedAt: new Date().toISOString() };
    user.electiveStudy = null;
    const certificate = {
      certificateId: makeCertificateId("KGM-LECT"),
      kind: "lective",
      levelId: 1,
      score: summary.score,
      maxScore: summary.maxScore,
      percent: summary.percent,
      passed,
      passPercent: ELECTIVE_PASS_PERCENT,
      courseTitle: session.level === ELECTIVE_OTHERS_LEVEL ? "KGM School Others" : `KGM School ${electiveClassLabel(session.level)}`,
      completedLabel: "KGM School",
      lectiveLevel: session.level,
      lectiveSubject: session.level === ELECTIVE_OTHERS_LEVEL ? ELECTIVE_OTHERS_SUBJECT : "All Available Subjects",
      starLine: session.level === ELECTIVE_OTHERS_LEVEL ? "OTHERS" : `${electiveClassLabel(session.level)} COMPLETED`,
      storyCount: summary.storyCount,
      questionsAsked: summary.questionsAsked,
      questionsLine: `${summary.questionsAsked.toLocaleString()} questions answered in ${electiveClassLabel(session.level)} only`,
      promotionText: passed ? (session.level === ELECTIVE_OTHERS_LEVEL ? "ELECTIVE COURSE COMPLETED" : (nextClass ? "PROMOTED TO NEXT CLASS" : "ELECTIVE COURSE COMPLETED")) : "REPEAT CLASS FOR BETTER PERFORMANCE",
      promotionLevel: passed ? (session.level === ELECTIVE_OTHERS_LEVEL ? "OTHERS" : (nextClass || electiveClassLabel(session.level))) : electiveClassLabel(session.level),
      adviceText: summary.percent === 100 ? "Perfect score. Excellent work." : (passed ? "You passed. Repeat is available if you want 100%." : `Pass mark is ${ELECTIVE_PASS_PERCENT}%. Repeat this class and grow stronger.`),
      date: new Date().toLocaleDateString(undefined, { year: "numeric", month: "long", day: "numeric" })
    };
    issueElectiveCertificate(certificate, session, true);
    saveStore();
    route = "certificate";
    render();
  }

  function rebuildElectiveSessionScore(session) {
    if (!session) return;
    session.subjectScores = {};
    session.subjectMaxScores = {};
    session.score = 0;
    (session.answers || []).forEach((answer) => {
      const subject = normalizeLectiveSubject(answer.subject || session.subject, session.level);
      answer.subject = subject;
      const marks = Number(answer.marks || 0);
      session.score += marks;
      session.subjectScores[subject] = Number(session.subjectScores[subject] || 0) + marks;
      session.subjectMaxScores[subject] = Number(session.subjectMaxScores[subject] || 0) + POINTS_PER_QUESTION;
    });
  }

  function repeatElectiveCheckpoint() {
    const user = activeUser();
    const cert = user && user.lastCertificate;
    if (!user || !cert || cert.kind !== "lective" || !cert.isMidCourse) {
      route = "lective";
      renderLectiveCourse();
      return;
    }
    const sourceSession = user.electiveStudy && !user.electiveStudy.completed ? user.electiveStudy : store.lectiveStudy;
    if (!sourceSession || sourceSession.completed) {
      competitionLectiveMessage = "This checkpoint can no longer be repeated from here. Open KGM School and repeat the class if needed.";
      route = "lective";
      lectiveView = "subjects";
      renderLectiveCourse();
      return;
    }
    const session = normalizeElectiveStudySession(sourceSession, cert.lectiveSubject);
    const subject = normalizeLectiveSubject(cert.lectiveSubject || session.subject, session.level);
    const subjectTotal = lectiveQuestionList(session.level, subject).length;
    const fromOffset = clampNumber(cert.repeatFromOffset, 0, subjectTotal);
    const toOffset = clampNumber(cert.repeatToOffset || cert.toOffset || subjectTotal, fromOffset, subjectTotal);
    session.answers = (session.answers || []).filter((answer) =>
      normalizeLectiveSubject(answer.subject || session.subject, session.level) !== subject ||
      Number(answer.flatIndex) < fromOffset ||
      Number(answer.flatIndex) >= toOffset
    );
    session.subject = subject;
    session.key = lectiveFolderKey(session.level, subject);
    session.subjectOffsets[subject] = fromOffset;
    if (fromOffset < subjectTotal) delete session.subjectCompleted[subject];
    session.questionDeadline = null;
    session.questionTimerKey = "";
    session.phase = "quiz";
    rebuildElectiveSessionScore(session);
    const current = currentElectiveQuestion(session);
    if (current) {
      session.storyIndex = current.storyIndex;
      session.questionIndex = current.questionIndex;
      session.flatIndex = current.flatIndex;
    }
    user.electiveStudy = session;
    store.lectiveStudy = session;
    competitionLectiveMessage = `Repeat ${electiveClassLabel(session.level)} ${subject} questions ${fromOffset + 1}-${toOffset}.`;
    saveStore();
    route = "lective-study";
    render();
  }

  function continueElectiveStudyAfterCertificate() {
    const user = activeUser();
    if (!user) {
      route = "login";
      render();
      return;
    }
    const session = user.electiveStudy && !user.electiveStudy.completed ? user.electiveStudy : store.lectiveStudy;
    if (session && !session.completed) {
      store.lectiveStudy = normalizeElectiveStudySession(session, session.subject);
      user.electiveStudy = store.lectiveStudy;
      saveStore();
      route = "lective-study";
      render();
      return;
    }
    route = "lective";
    lectiveView = "subjects";
    renderLectiveCourse();
  }

  function repeatElectiveClass(level) {
    const user = activeUser();
    if (!user) return;
    const normalizedLevel = normalizeLectiveLevel(level || selectedLectiveStudyLevel);
    ensureUserDefaults(user);
    user.electiveProgress.classes[normalizedLevel] = { subjects: {}, midCourseCertificates: [], completed: false, repeatedAt: new Date().toISOString() };
    if (user.electiveProgress.completedClasses) delete user.electiveProgress.completedClasses[normalizedLevel];
    if (user.electiveStudy && normalizeLectiveLevel(user.electiveStudy.level) === normalizedLevel) user.electiveStudy = null;
    if (store.lectiveStudy && normalizeLectiveLevel(store.lectiveStudy.level) === normalizedLevel && store.lectiveStudy.userId === user.id) store.lectiveStudy = null;
    selectedLectiveStudyLevel = normalizedLevel;
    selectedLectiveStudySubject = electiveOrderedSubjects(normalizedLevel)[0] || normalizeLectiveSubject("", normalizedLevel);
    competitionLectiveMessage = `${electiveClassLabel(normalizedLevel)} has been reset for a fresh attempt. Aim for 100%.`;
    saveStore();
    route = "lective";
    lectiveView = normalizedLevel === ELECTIVE_OTHERS_LEVEL ? "others" : "subjects";
    renderLectiveCourse();
  }

  function makeCompetitionLink(joinUrl) {
    if (COMPETITION_ENDPOINT) {
      return `KGM-COMPETITION|online|${encodeURIComponent(joinUrl)}`;
    }
    return `KGM-COMPETITION|local|${encodeURIComponent(joinUrl)}`;
  }

  function parseCompetitionLink(raw) {
    const value = String(raw || "").trim();
    if (value.startsWith("KGM-COMPETITION|")) {
    const parts = value.split("|");
      return decodeURIComponent(parts[2] || "");
    }
    return value.replace(/\/+$/, "");
  }

  function trackRoute() {
    const snapshot = currentRouteSnapshot();
    const key = routeSnapshotKey(snapshot);
    if (!lastRouteSnapshot) {
      lastRouteSnapshot = snapshot;
      return;
    }
    if (routeSnapshotKey(lastRouteSnapshot) === key) return;
    if (!handlingBack) {
      routeStack.push(lastRouteSnapshot);
      routeStack = routeStack.slice(-40);
    }
    lastRouteSnapshot = snapshot;
  }

  function currentRouteSnapshot() {
    return {
      route,
      competitionView,
      lectiveView,
      selectedLectiveStudyLevel,
      selectedLectiveStudySubject,
      selectedLectiveLevel,
      selectedLectiveSubject
    };
  }

  function routeSnapshotKey(snapshot) {
    if (!snapshot) return "";
    if (snapshot.route === "competition") return `${snapshot.route}:${snapshot.competitionView || ""}`;
    if (snapshot.route === "lective") {
      return [
        snapshot.route,
        snapshot.lectiveView || "",
        snapshot.selectedLectiveStudyLevel || "",
        snapshot.selectedLectiveStudySubject || "",
        snapshot.selectedLectiveLevel || "",
        snapshot.selectedLectiveSubject || ""
      ].join(":");
    }
    return `${snapshot.route}:`;
  }

  function restoreRouteSnapshot(snapshot) {
    route = snapshot && snapshot.route ? snapshot.route : "login";
    if (snapshot && snapshot.competitionView) competitionView = snapshot.competitionView;
    if (snapshot && snapshot.lectiveView) lectiveView = snapshot.lectiveView;
    if (snapshot && snapshot.selectedLectiveStudyLevel) selectedLectiveStudyLevel = snapshot.selectedLectiveStudyLevel;
    if (snapshot && snapshot.selectedLectiveStudySubject) selectedLectiveStudySubject = snapshot.selectedLectiveStudySubject;
    if (snapshot && snapshot.selectedLectiveLevel) selectedLectiveLevel = snapshot.selectedLectiveLevel;
    if (snapshot && snapshot.selectedLectiveSubject) selectedLectiveSubject = snapshot.selectedLectiveSubject;
  }

  function handleDeviceBack() {
    if (document.querySelector("[data-renewal-dialog]")) {
      closeRenewalDialog();
      return "handled";
    }
    if (routeStack.length) {
      const snapshot = routeStack.pop();
      handlingBack = true;
      restoreRouteSnapshot(snapshot);
      render();
      handlingBack = false;
      return "handled";
    }
    if (route !== "login") {
      handlingBack = true;
      route = store.activeUser ? "home" : "login";
      competitionView = "setup";
      render();
      handlingBack = false;
      return "handled";
    }
    return "exit";
  }

  function importLectiveDocument(file) {
    if (!file) return;
    const reader = new FileReader();
    reader.onload = () => {
      const content = cleanText(reader.result || "");
      const topic = document.querySelector("[data-lective-form] input[name='topic']");
      const textarea = document.querySelector("[data-lective-form] textarea[name='question']");
      if (topic && !topic.value.trim()) {
        topic.value = file.name.replace(/\.[^.]+$/, "").replace(/[_-]+/g, " ");
      }
      if (textarea) {
        const previous = textarea.value.trim();
        textarea.value = previous ? `${previous}\n\n${content}` : content;
        textarea.focus();
      }
      competitionLectiveMessage = "Document text added as a question. You can edit, paste, and correct spelling before saving.";
      const note = document.querySelector(".competition-full-note");
      if (note) note.textContent = competitionLectiveMessage;
    };
    reader.onerror = () => {
      competitionLectiveMessage = "This document could not be read as text. Open it, copy the question, and paste it here.";
      renderLectiveCourse();
    };
    reader.readAsText(file);
  }

  function serviceClientId() {
    if (serviceState && serviceState.clientId) return serviceState.clientId;
    refreshServiceState();
    return serviceState && serviceState.clientId ? serviceState.clientId : "KGM-UNKNOWN";
  }

  function makeClientId() {
    const random = Math.random().toString(36).slice(2, 8).toUpperCase();
    const time = Date.now().toString(36).slice(-6).toUpperCase();
    return `KGM-${time}-${random}`;
  }

  function getDeviceSerial() {
    const clientId = serviceClientId();
    if (clientId && clientId !== "KGM-UNKNOWN") return clientId;
    const saved = JSON.parse(localStorage.getItem(SERVICE_STORAGE_KEY) || "{}");
    if (saved.clientId) return saved.clientId;
    const clientIdFallback = makeClientId();
    localStorage.setItem(SERVICE_STORAGE_KEY, JSON.stringify({ ...saved, clientId: clientIdFallback }));
    refreshServiceState();
    return clientIdFallback;
  }

  function deviceReasonCode() {
    return deviceReasonCodeFromSerial(getDeviceSerial());
  }

  function deviceReasonCodeFromSerial(serial) {
    let clean = normalizeDeviceSerial(serial).slice(-6);
    while (clean.length < 6) clean = `0${clean}`;
    return clean;
  }

  function formatRenewalActivationCode() {
    const field = document.querySelector("[data-renewal-token]");
    if (!field) return false;
    formatRenewalTokenField(field);
    fitRenewalActivationInput();
    return false;
  }

  function formatRenewalTokenField(field) {
    if (!field) return false;
    const digits = normalizeToken(field.value).slice(0, 20);
    let formatted = "";
    for (let index = 0; index < digits.length; index += 1) {
      if (index > 0 && index % 4 === 0) formatted += " ";
      formatted += digits.charAt(index);
    }
    field.value = formatted;
    return false;
  }

  function fitRenewalActivationInput() {
    const field = document.querySelector("[data-renewal-token]");
    if (!field || !field.style) return false;
    field.style.fontSize = "";
    let size = 24;
    if (window.getComputedStyle) {
      const computed = window.getComputedStyle(field, null);
      size = parseFloat(computed && computed.fontSize ? computed.fontSize : "") || size;
    }
    if (!field.clientWidth || !field.scrollWidth) return false;
    while (field.scrollWidth > field.clientWidth - 6 && size > 16) {
      size -= 1;
      field.style.fontSize = `${size}px`;
    }
    return false;
  }

  function normalizeToken(value) {
    return String(value || "").replace(/\D/g, "");
  }

  function normalizeDeviceSerial(value) {
    return String(value || "").toUpperCase().replace(/[^A-Z0-9]/g, "");
  }

  function encodedDevicePartForOrder(serial, order) {
    const boxes = expandedDeviceBoxes(serial);
    if (boxes.length !== 6 || !order || order.length !== 6) return "";
    let out = "";
    for (let index = 0; index < order.length; index += 1) {
      out += boxes[Number(order.charAt(index)) - 1] || "";
    }
    return out;
  }

  function expandedDeviceBoxes(serial) {
    const source = normalizeDeviceSerial(serial).slice(-6);
    if (source.length < 6) return [];
    return source.split("").map((char) => charToTwoDigit(char));
  }

  function charToTwoDigit(char) {
    if (/[0-9]/.test(char)) return padNumber(Number(char), 2);
    if (/[A-Z]/.test(char)) return padNumber(char.charCodeAt(0) - 64, 2);
    return "00";
  }

  function padNumber(value, size) {
    let text = String(Math.floor(Math.abs(Number(value) || 0)));
    while (text.length < size) text = `0${text}`;
    return text;
  }

  function buildSixDigitPermutations() {
    const results = [];
    const walk = (prefix, rest) => {
      if (!rest.length) {
        results.push(prefix);
        return;
      }
      for (let index = 0; index < rest.length; index += 1) {
        walk(prefix + rest.charAt(index), rest.slice(0, index) + rest.slice(index + 1));
      }
    };
    walk("", "123456");
    return results;
  }

  function bouncingCodeHtml(code) {
    return String(code || "").toUpperCase().split("").map((char, index) => (
      `<span class="renewal-code-char renewal-code-char-${index + 1}">${escapeHtml(char)}</span>`
    )).join("");
  }

  function activationNowMs() {
    if (window.KnowGodMoreService && typeof window.KnowGodMoreService.elapsedRealtimeMs === "function") {
      try {
        return Number(window.KnowGodMoreService.elapsedRealtimeMs()) || Date.now();
      } catch (error) {}
    }
    return Date.now();
  }

  function activationLockState() {
    const guard = store.activationGuard || {};
    const now = activationNowMs();
    if (guard.lockUntil && now < guard.lockUntil) {
      return { locked: true, message: activationWaitMessage(guard.stage, guard.lockUntil - now, getDeviceSerial()) };
    }
    if (guard.stage >= 3 && guard.lockUntil && now >= guard.lockUntil) {
      store.activationGuard = { stage: 0, attempts: [] };
      saveStore();
    }
    return { locked: false, message: "" };
  }

  function registerActivationFailure(serial, baseMessage) {
    const now = activationNowMs();
    let guard = store.activationGuard || { stage: 0, attempts: [] };
    if (guard.lockUntil && now < guard.lockUntil) return activationWaitMessage(guard.stage, guard.lockUntil - now, serial);
    if (guard.stage >= 3 && guard.lockUntil && now >= guard.lockUntil) guard = { stage: 0, attempts: [] };
    if (guard.stage === 1) {
      guard = { stage: 2, lockUntil: now + 3 * 60 * 60 * 1000, attempts: [] };
      store.activationGuard = guard;
      saveStore();
      return activationWaitMessage(2, guard.lockUntil - now, serial);
    }
    if (guard.stage === 2) {
      guard = { stage: 3, lockUntil: now + 24 * 60 * 60 * 1000, attempts: [] };
      store.activationGuard = guard;
      saveStore();
      return activationWaitMessage(3, guard.lockUntil - now, serial);
    }
    guard.stage = 0;
    guard.attempts = (guard.attempts || []).filter((stamp) => now - Number(stamp || 0) < 60 * 60 * 1000);
    guard.attempts.push(now);
    if (guard.attempts.length >= 3) {
      guard = { stage: 1, lockUntil: now + 60 * 60 * 1000, attempts: [] };
      store.activationGuard = guard;
      saveStore();
      return activationWaitMessage(1, guard.lockUntil - now, serial);
    }
    store.activationGuard = guard;
    saveStore();
    return `${baseMessage || "wrong token try again later"}. ${3 - guard.attempts.length} attempt(s) left before waiting.`;
  }

  function activationWaitMessage(stage, remainingMs, serial) {
    if (Number(stage) === 1) return `Please wait ${formatWait(remainingMs)}. Call or WhatsApp +256782225970.`;
    if (Number(stage) === 2) return `Try again in ${formatWait(remainingMs)}. WhatsApp or call +256782225970.`;
    return `Try again after ${formatWait(remainingMs)} with a fresh token. Code: ${deviceReasonCodeFromSerial(serial)}. Call or WhatsApp +256782225970.`;
  }

  function formatWait(ms) {
    const minutes = Math.max(1, Math.ceil(Number(ms || 0) / 60000));
    if (minutes < 60) return `${minutes} minute(s)`;
    return `${Math.ceil(minutes / 60)} hour(s)`;
  }

  function updateRenewalUi() {
    updateServiceRemainingPreview();
    const button = document.querySelector("[data-action='renew-service']");
    if (button) {
      button.disabled = renewalBusy;
      button.textContent = renewalBusy ? "Activating..." : "Renew";
    }
    let message = document.querySelector(".login-renew-message:not(.login-update-install-button)");
    const form = document.querySelector("[data-login-form]");
    if (!message && form && renewalMessage && !updateInstallReady) {
      message = document.createElement("div");
      message.className = "login-renew-message";
      form.appendChild(message);
    }
    if (message) {
      message.textContent = renewalMessage;
      message.style.display = renewalMessage ? "" : "none";
    }
    const renewalSubmit = document.querySelector("[data-renewal-form] button[type='submit']");
    if (renewalSubmit) {
      renewalSubmit.disabled = renewalBusy;
      renewalSubmit.textContent = renewalBusy ? "Activating..." : "ACTIVATE";
    }
  }

  function exportBackup() {
    const backup = {
      app: "Know God More",
      version: APP_VERSION,
      build: APP_BUILD,
      exportedAt: new Date().toISOString(),
      store: persistentStoreSnapshot(store),
      serviceState
    };
    const text = JSON.stringify(backup, null, 2);
    const fileName = `know-god-more-backup-${new Date().toISOString().slice(0, 10)}.json`;
    if (navigator.share && navigator.canShare) {
      try {
        const blob = new Blob([text], { type: "application/json" });
        const file = new File([blob], fileName, { type: "application/json" });
        if (navigator.canShare({ files: [file] })) {
          navigator.share({ title: "Know God More Backup", files: [file] }).catch(() => {});
          return;
        }
      } catch (error) {}
    }
    const link = document.createElement("a");
    link.href = `data:application/json;charset=utf-8,${encodeURIComponent(text)}`;
    link.download = fileName;
    link.click();
    if (navigator.clipboard && navigator.clipboard.writeText) {
      navigator.clipboard.writeText(text).catch(() => {});
    }
    toastMessage = "Backup created. Keep it private.";
    render();
  }

  function importBackup(file) {
    const reader = new FileReader();
    reader.onload = () => {
      try {
        const backup = JSON.parse(String(reader.result || "{}"));
        if (!backup || backup.app !== "Know God More" || !backup.store || typeof backup.store !== "object") {
          throw new Error("not a Know God More backup");
        }
        store = {
          ...readStore(),
          ...backup.store,
          users: backup.store.users || {},
          lectiveStories: Array.isArray(backup.store.lectiveStories) ? backup.store.lectiveStories : [],
          onlineCompetition: sanitizeCompetitionRecordForStorage(backup.store.onlineCompetition || null),
          offlineCompetition: sanitizeCompetitionRecordForStorage(backup.store.offlineCompetition || { active: false, players: [] }),
          competitionSessions: sanitizeCompetitionSessionsForStorage(backup.store.competitionSessions),
          searchProfile: normalizeSearchProfile(backup.store.searchProfile),
          competitionHistory: Array.isArray(backup.store.competitionHistory) ? backup.store.competitionHistory : [],
          competitionTimerSeconds: clampCompetitionSeconds(backup.store.competitionTimerSeconds),
          schoolCustomization: normalizeSchoolCustomization(backup.store.schoolCustomization)
        };
        Object.values(store.users || {}).forEach((user) => ensureUserDefaults(user));
        store.lectiveFolders = normalizeLectiveFolders(store.lectiveFolders, store.lectiveStories);
        updateLectiveStoriesFromFolders();
        saveStore();
        toastMessage = "Backup restored. Progress is ready on this device.";
      } catch (error) {
        toastMessage = "This backup could not be restored. Choose a valid Know God More backup file.";
      }
      route = "settings";
      render();
    };
    reader.readAsText(file);
  }

  function installCrashLogger() {
    window.addEventListener("error", (event) => {
      saveCrashLog(`${new Date().toISOString()} ${event.message || "App error"} at ${event.filename || "app"}:${event.lineno || 0}`);
    });
    window.addEventListener("unhandledrejection", (event) => {
      saveCrashLog(`${new Date().toISOString()} ${event.reason && event.reason.message ? event.reason.message : "Promise error"}`);
    });
  }

  function saveCrashLog(line) {
    const current = readCrashLog();
    localStorage.setItem(CRASH_STORAGE_KEY, `${line}\n${current}`.slice(0, 2400));
  }

  function readCrashLog() {
    try {
      return localStorage.getItem(CRASH_STORAGE_KEY) || "";
    } catch (error) {
      return "";
    }
  }

  function installWindowsShareBridge() {
    if (window.KnowGodMoreShare) return;
    if (!/^https?:$/.test(location.protocol)) return;
    if (!/^(127\.0\.0\.1|localhost)$/i.test(location.hostname || "")) return;

    const post = (endpoint, payload, quiet) => {
      return fetch(endpoint, {
        method: "POST",
        headers: { "Content-Type": "application/json" },
        body: JSON.stringify(payload || {})
      })
        .then((response) => response.json())
        .then((result) => {
          if (!result || result.ok === false) throw new Error(result && result.message ? result.message : "Windows action failed");
          if (!quiet && result && result.message) {
            toastMessage = result.message;
            if (route === "competition") {
              renderCompetition();
            } else {
              render();
            }
          }
          return result;
        })
        .catch((error) => {
          throw error;
        });
    };

    window.KnowGodMoreShare = {
      isLocalWindowsBridge: true,
      saveJpeg(fileName, dataUrl, quietOrMessage, destination) {
        const quiet = quietOrMessage === true;
        return post("/kgm-save-jpeg", { fileName, dataUrl, destination: destination || (quiet ? "downloads" : "pictures") }, quiet);
      },
      autoSaveJpeg(fileName, dataUrl) {
        return post("/kgm-save-jpeg", { fileName, dataUrl, destination: "downloads" }, true);
      },
      saveJpegToPictures(fileName, dataUrl, quietOrMessage) {
        const quiet = quietOrMessage === true;
        return post("/kgm-save-jpeg", { fileName, dataUrl, destination: "pictures" }, quiet);
      },
      shareJpeg(subject, text, fileName, dataUrl) {
        return post("/kgm-share-jpeg", { subject, text, fileName, dataUrl }, false);
      },
      printJpeg(fileName, dataUrl) {
        return post("/kgm-print-jpeg", { fileName, dataUrl }, false);
      },
      printHtml(jobName, html) {
        return post("/kgm-print-html", { jobName, html }, false);
      },
      share(subject, text) {
        if (navigator.clipboard && navigator.clipboard.writeText) {
          navigator.clipboard.writeText(text || "").catch(() => {});
        }
        window.alert(`${subject || "Know God More"}\n\n${text || ""}`);
      }
    };
  }

  function lectiveFolderKey(level, subject) {
    const normalizedLevel = normalizeLectiveLevel(level);
    return `${normalizedLevel}|${normalizeLectiveSubject(subject, normalizedLevel)}`;
  }

  function normalizeLectiveFolderKey(key) {
    const text = String(key || "").trim();
    const parts = text.split("|");
    if (parts.length >= 2) return lectiveFolderKey(parts[0], parts.slice(1).join("|"));
    return lectiveFolderKey(DEFAULT_LECTIVE_LEVEL, DEFAULT_LECTIVE_SUBJECT);
  }

  function normalizeLectiveLevel(value) {
    const text = String(value || "").trim().toUpperCase();
    return LECTIVE_LEVELS.includes(text) ? text : DEFAULT_LECTIVE_LEVEL;
  }

  function normalizeLectiveSubject(value, level = DEFAULT_LECTIVE_LEVEL) {
    const subjects = lectiveSubjectsForLevel(level);
    const text = cleanText(value || "").toLowerCase();
    const match = subjects.find((subject) => subject.toLowerCase() === text);
    return match || subjects[0] || DEFAULT_LECTIVE_SUBJECT;
  }

  function lectiveFolderLabel(level, subject) {
    const normalizedLevel = normalizeLectiveLevel(level);
    return `KGM School ${normalizedLevel} ${normalizeLectiveSubject(subject, normalizedLevel)}`;
  }

  function lectiveFolderLabelFromKey(key) {
    const parts = normalizeLectiveFolderKey(key).split("|");
    return lectiveFolderLabel(parts[0], parts[1]);
  }

  function emptyLectiveFolders() {
    const folders = {};
    LECTIVE_LEVELS.forEach((level) => {
      lectiveSubjectsForLevel(level).forEach((subject) => {
        folders[lectiveFolderKey(level, subject)] = [];
      });
    });
    return folders;
  }

  function normalizeLectiveStory(story, fallbackLevel = DEFAULT_LECTIVE_LEVEL, fallbackSubject = DEFAULT_LECTIVE_SUBJECT) {
    if (!story || typeof story !== "object") return null;
    const level = normalizeLectiveLevel(story.level || fallbackLevel);
    const subject = normalizeLectiveSubject(story.subject || fallbackSubject, level);
    const id = story.id || `lective-${Date.now()}-${Math.random().toString(36).slice(2, 7)}`;
    return {
      ...story,
      id,
      course: story.course || "KGM School",
      title: cleanText(story.title || story.reference || "KGM School Question"),
      reference: cleanText(story.reference || lectiveFolderLabel(level, subject)),
      level,
      subject,
      content: cleanText(story.content || ""),
      questions: Array.isArray(story.questions) ? story.questions : []
    };
  }

  function normalizeLectiveFolders(rawFolders, legacyStories = []) {
    const folders = emptyLectiveFolders();
    const seen = new Set();
    const addStory = (story, fallbackLevel, fallbackSubject) => {
      const normalized = normalizeLectiveStory(story, fallbackLevel, fallbackSubject);
      if (!normalized || !Array.isArray(normalized.questions) || !normalized.questions.length) return;
      const id = String(normalized.id);
      if (seen.has(id)) return;
      seen.add(id);
      const key = lectiveFolderKey(normalized.level, normalized.subject);
      folders[key].push(normalized);
    };

    if (rawFolders && typeof rawFolders === "object") {
      Object.keys(rawFolders).forEach((key) => {
        const parts = normalizeLectiveFolderKey(key).split("|");
        const list = Array.isArray(rawFolders[key]) ? rawFolders[key] : [];
        list.forEach((story) => addStory(story, parts[0], parts[1]));
      });
    }
    (Array.isArray(legacyStories) ? legacyStories : []).forEach((story) => {
      addStory(story, story && story.level ? story.level : DEFAULT_LECTIVE_LEVEL, story && story.subject ? story.subject : DEFAULT_LECTIVE_SUBJECT);
    });
    return folders;
  }

  function migrateLectiveMissionCourseToOthers(nextStore) {
    const missionPrefixes = ["lective-2026-05-30-", "pc-star1-mission-"];
    const missionUnits = new Set(["30TH MAY 2026", "Star 1 Mission Course"]);
    const targetKey = lectiveFolderKey(ELECTIVE_OTHERS_LEVEL, ELECTIVE_OTHERS_SUBJECT);
    nextStore.lectiveFolders = normalizeLectiveFolders(nextStore.lectiveFolders, nextStore.lectiveStories);
    const moved = [];
    Object.keys(nextStore.lectiveFolders).forEach((key) => {
      const list = Array.isArray(nextStore.lectiveFolders[key]) ? nextStore.lectiveFolders[key] : [];
      const kept = [];
      list.forEach((story) => {
        const storyId = String(story && story.id || "");
        const unit = String(story && (story.unit || story.reference) || "");
        const isMissionStory = missionPrefixes.some((prefix) => storyId.startsWith(prefix)) || missionUnits.has(unit);
        if (!isMissionStory) {
          kept.push(story);
          return;
        }
        moved.push({
          ...story,
          course: "KGM School",
          unit: story.unit || "Mission And Unity Course",
          reference: "KGM School Others",
          level: ELECTIVE_OTHERS_LEVEL,
          subject: ELECTIVE_OTHERS_SUBJECT
        });
      });
      nextStore.lectiveFolders[key] = kept;
    });
    if (moved.length) {
      const targetList = Array.isArray(nextStore.lectiveFolders[targetKey]) ? nextStore.lectiveFolders[targetKey] : [];
      const targetIds = new Set(targetList.map((story) => String(story && story.id)));
      moved.forEach((story) => {
        if (!targetIds.has(String(story.id))) {
          targetIds.add(String(story.id));
          targetList.push(story);
        }
      });
      nextStore.lectiveFolders[targetKey] = targetList;
    }
    nextStore.lectiveStories = flattenLectiveFolders(nextStore.lectiveFolders);
    return nextStore;
  }

  function flattenLectiveFolders(folders) {
    const normalized = normalizeLectiveFolders(folders, []);
    return Object.values(normalized).flat();
  }

  function readStore() {
    try {
      const stored = JSON.parse(localStorage.getItem(STORAGE_KEY) || "{}");
      const users = stored.users || {};
      Object.values(users).forEach((user) => ensureUserDefaults(user));
      const lectiveFolders = normalizeLectiveFolders(stored.lectiveFolders, stored.lectiveStories);
      return {
        activeUser: stored.activeUser || null,
        users,
        lectiveFolders,
        lectiveStories: flattenLectiveFolders(lectiveFolders),
        lectiveStudy: stored.lectiveStudy || null,
        onlineCompetition: sanitizeCompetitionRecordForStorage(stored.onlineCompetition || null),
        offlineCompetition: sanitizeCompetitionRecordForStorage(stored.offlineCompetition || { active: false, players: [] }),
        competitionSessions: sanitizeCompetitionSessionsForStorage(stored.competitionSessions),
        competitionHistory: Array.isArray(stored.competitionHistory) ? stored.competitionHistory : [],
        searchProfile: normalizeSearchProfile(stored.searchProfile),
        searchLastQuery: cleanText(stored.searchLastQuery || ""),
        searchScope: normalizeSearchScope(stored.searchScope || "bible"),
        searchQueries: normalizeSearchQueries(stored.searchQueries),
        searchUpload: normalizeSearchUpload(stored.searchUpload),
        savedCertificates: normalizeSavedCertificateRecords(stored.savedCertificates),
        textEdits: normalizeTextEdits(stored.textEdits),
        schoolCustomization: normalizeSchoolCustomization(stored.schoolCustomization),
        competitionTimerSeconds: clampCompetitionSeconds(stored.competitionTimerSeconds),
        audioMuted: Boolean(stored.audioMuted)
      };
    } catch (error) {
      const lectiveFolders = emptyLectiveFolders();
      return { activeUser: null, users: {}, lectiveFolders, lectiveStories: [], lectiveStudy: null, onlineCompetition: null, offlineCompetition: { active: false, players: [] }, competitionSessions: {}, competitionHistory: [], searchProfile: null, searchLastQuery: "", searchScope: "bible", searchQueries: [], searchUpload: { lastAttemptAt: 0, lastSuccessAt: 0 }, savedCertificates: [], textEdits: normalizeTextEdits(null), schoolCustomization: normalizeSchoolCustomization(null), competitionTimerSeconds: COMPETITION_TIMER_DEFAULT_SECONDS, audioMuted: false };
    }
  }

  function seedDefaultLectiveStories(nextStore) {
    nextStore.lectiveFolders = normalizeLectiveFolders(nextStore.lectiveFolders, nextStore.lectiveStories);
    nextStore = migrateLectiveMissionCourseToOthers(nextStore);
    nextStore.lectiveStories = flattenLectiveFolders(nextStore.lectiveFolders);
    if (!defaultLectiveStories.length) return nextStore;
    const lectiveStories = flattenLectiveFolders(nextStore.lectiveFolders);
    const existingIds = new Set(lectiveStories.map((story) => String(story && story.id)));
    const additions = defaultLectiveStories.filter((story) => story && story.id && !existingIds.has(String(story.id)) && normalizeLectiveLevel(story.level) === ELECTIVE_OTHERS_LEVEL);
    if (!additions.length) {
      nextStore.lectiveStories = lectiveStories;
      return nextStore;
    }
    additions.forEach((story) => {
      const normalized = normalizeLectiveStory(story, DEFAULT_LECTIVE_LEVEL, DEFAULT_LECTIVE_SUBJECT);
      if (!normalized) return;
      const key = lectiveFolderKey(normalized.level, normalized.subject);
      nextStore.lectiveFolders[key].push(normalized);
    });
    nextStore.lectiveStories = flattenLectiveFolders(nextStore.lectiveFolders);
    return nextStore;
  }

  function persistableLectiveFolders(folders) {
    if (folders === store.lectiveFolders && persistableLectiveFoldersCache) return persistableLectiveFoldersCache;
    const normalized = normalizeLectiveFolders(folders, []);
    Object.keys(normalized).forEach((key) => {
      normalized[key] = normalized[key].filter((story) => !defaultLectiveStoryIds.has(String(story && story.id || "")));
    });
    if (folders === store.lectiveFolders) persistableLectiveFoldersCache = normalized;
    return normalized;
  }

  function persistentStoreSnapshot(nextStore) {
    const lectiveFolders = persistableLectiveFolders(nextStore.lectiveFolders);
    const lectiveStories = nextStore === store && persistableLectiveStoriesCache
      ? persistableLectiveStoriesCache
      : flattenLectiveFolders(lectiveFolders);
    if (nextStore === store) persistableLectiveStoriesCache = lectiveStories;
    const users = Object.fromEntries(Object.entries(nextStore.users || {}).map(([id, user]) => {
      const normalizedUser = cloneData(user);
      ensureUserDefaults(normalizedUser);
      return [id, normalizedUser];
    }));
    return {
      ...nextStore,
      users,
      lectiveFolders,
      lectiveStories,
      onlineCompetition: sanitizeCompetitionRecordForStorage(nextStore.onlineCompetition),
      offlineCompetition: sanitizeCompetitionRecordForStorage(nextStore.offlineCompetition),
      competitionSessions: sanitizeCompetitionSessionsForStorage(nextStore.competitionSessions),
      searchProfile: normalizeSearchProfile(nextStore.searchProfile),
      searchScope: normalizeSearchScope(nextStore.searchScope || "bible"),
      savedCertificates: normalizeSavedCertificateRecords(nextStore.savedCertificates),
      textEdits: normalizeTextEdits(nextStore.textEdits),
      schoolCustomization: normalizeSchoolCustomization(nextStore.schoolCustomization)
    };
  }

  function saveStore() {
    ensureLectiveFoldersReady();
    Object.values(store.users || {}).forEach((user) => ensureUserDefaults(user));
    store.competitionSessions = store.competitionSessions && typeof store.competitionSessions === "object" ? store.competitionSessions : {};
    store.searchProfile = normalizeSearchProfile(store.searchProfile);
    store.searchLastQuery = cleanText(store.searchLastQuery || "");
    store.searchScope = normalizeSearchScope(store.searchScope || "bible");
    store.searchQueries = normalizeSearchQueries(store.searchQueries);
    store.searchUpload = normalizeSearchUpload(store.searchUpload);
    store.savedCertificates = normalizeSavedCertificateRecords(store.savedCertificates);
    store.textEdits = normalizeTextEdits(store.textEdits);
    store.schoolCustomization = normalizeSchoolCustomization(store.schoolCustomization);
    syncActiveCompetitionSessions();
    try {
      localStorage.setItem(STORAGE_KEY, JSON.stringify(persistentStoreSnapshot(store)));
    } catch (error) {
      console.warn("Could not save Know God More progress.", error);
    }
  }

  function normalizeUserId(name) {
    return name.toLowerCase().replace(/[^a-z0-9]+/g, "-").replace(/^-|-$/g, "") || "guest";
  }

  function slugify(value) {
    return String(value || "")
      .trim()
      .toLowerCase()
      .replace(/&/g, "and")
      .replace(/[^a-z0-9]+/g, "-")
      .replace(/^-+|-+$/g, "")
      .slice(0, 90) || "item";
  }

  function initials(name) {
    return String(name || "KGM")
      .split(/\s+/)
      .filter(Boolean)
      .slice(0, 2)
      .map((part) => part[0].toUpperCase())
      .join("");
  }

  function formatClock(seconds) {
    const safeSeconds = Math.max(0, Math.floor(Number(seconds) || 0));
    const minutes = Math.floor(safeSeconds / 60);
    const remainingSeconds = safeSeconds % 60;
    return `${String(minutes).padStart(2, "0")}:${String(remainingSeconds).padStart(2, "0")}`;
  }

  function formatPassage(text) {
    return escapeHtml(cleanText(text)).replace(/\n{2,}/g, "</p><p>").replace(/\n/g, "<br>");
  }

  function escapeHtml(value) {
    return displayBrandText(value)
      .replace(/&/g, "&amp;")
      .replace(/</g, "&lt;")
      .replace(/>/g, "&gt;")
      .replace(/"/g, "&quot;")
      .replace(/'/g, "&#039;");
  }

  function displayBrandText(value) {
    let text = cleanText(value);
    let customization = normalizeSchoolCustomization(null);
    try {
      if (store) customization = normalizeSchoolCustomization(store.schoolCustomization);
    } catch (error) {}
    if (customization.name && customization.name !== CUSTOM_SCHOOL_NAME_DEFAULT) {
      text = text
        .replace(/\bKGM\s+SCHOOL\b/g, customization.name)
        .replace(/\bKGM\s+School\b/g, customization.name)
        .replace(/\bkgm\s+school\b/gi, customization.name);
    }
    return text;
  }

  function cleanText(value) {
    return String(value ?? "")
      .replace(/\u00e2\u20ac\u0153/g, "\"")
      .replace(/\u00e2\u20ac\u009d/g, "\"")
      .replace(/\u00e2\u20ac\u02dc/g, "'")
      .replace(/\u00e2\u20ac\u2122/g, "'")
      .replace(/\u00e2\u20ac\u2018/g, "-")
      .replace(/\u00e2\u20ac\u201d/g, "-")
      .replace(/\u00e2\u20ac\u00a6/g, "...")
      .replace(/\u00c2/g, "")
      .replace(/[\u2010-\u2015]/g, "-")
      .replace(/[\u2018\u2019]/g, "'")
      .replace(/[\u201c\u201d]/g, "\"")
      .replace(/\s+/g, " ")
      .trim();
  }

  function escapeRegExp(value) {
    return String(value || "").replace(/[.*+?^${}()|[\]\\]/g, "\\$&");
  }

  function titleFromContent(content, fallback = "Reading") {
    const words = cleanText(content).split(/\s+/).filter(Boolean).slice(0, 7).join(" ");
    return words || fallback;
  }

  function registerOfflineCache() {
    if (!("serviceWorker" in navigator)) return;
    if (!/^https?:$/.test(location.protocol)) return;
    if (/^(127\.0\.0\.1|localhost)$/i.test(location.hostname || "")) return;
    navigator.serviceWorker.register("service-worker.js").catch(() => {});
  }
})();
