β
Group Discount for Schools & Centres
JAMB Scores Arenβt Luck. They are a Calculation.
Nigeriaβs Only AI-Native Mastery Engine. Master the syllabus, own your future.
VIDEO
π TRY THE LIVE DEMO NOW
English β’ Physics β’ Chemistry β’ Biology β’ Government β’ Economics β’ Accounting β’ Agric Science β’ Mathematics β’ Literature β’ Commerce
The Problem: The Past Question Trap
Every year, candidates solve thousands of random past questions without deconstructing the syllabus first. This leads to "JAMB Fever." Success shouldn't be a gamble. AnchorLearn treats your score as a surgical calculation.
The Solution: The 80% Mastery Rule
On AnchorLearn, you donβt finish a topic; you Master it.
The engine only grants you "Mastered" status when you hit the 80% threshold in our curriculum assessments.
Your Board of Examiners
βοΈ Justice Aisha Mohammed (Government) Senior Jurist teaching the Mechanics of Power.
π¬ Prof. Adebayo (Physics) Senior Examiner breaking down complex laws into simple principles.
π Prof. O. J. Mahmud (Economics) Policy Visionary mentoring the next generation of CEOs.
Mastery Map
LOG OUT
π Syllabus Deep Mastery Guide
0-79% Gearing Up
Cyan: Passed 1x (Surface Mastery)
Purple: Passed 2x (Consolidated)
Gold: Passed 3x (Deep Mastery Engine)
β BACK
π Final CBT Exam Simulator
Achieve 80% overall subject mastery to unlock the 40-seconds-per-question Exam Engine.
π Locked (Requires 80% Mastery)
Audio
Visuals
Flashcards
Ask Mentor
Listen to High-Yield Concept Explanations:
ποΈ Audio breakdown for this section is compiling. Proceed with Study Slides below!
Ready to Prove Mastery?
Select a layout view option below to anchor understanding:
πΌοΈ View Infographic Guide
π View Core Review Slides
β Zoom In
β Zoom Out
π Reset View
Ready to Prove Mastery?
β‘ FLASHCARDS IN PRODUCTION
Your active recall cards for this section are currently being compiled. Please use the Audio Summaries and Study Slides to anchor your understanding!
Ready to Prove Mastery?
window.vault = {
activeMetadata: { topicTitle: "", videoUrl: "", audioUrl: "", infographicUrl: "", slidesUrl: "" },
activeConfig: null,
activeModuleIdx: 0,
zoomLevel: 1.0,
activeCards: []
};
window.vault.fetchVaultData = async function(topicCode, subjectName) {
try {
let cleanToken = topicCode.trim().toLowerCase();
if (cleanToken.includes("section")) {
cleanToken = cleanToken.replace(/section/g, "").trim();
}
const strictAlphaToken = cleanToken.replace(/[^a-z0-9]/g, '');
let targetSubject = subjectName.trim().toLowerCase();
if (targetSubject.includes("account")) targetSubject = "accounts";
if (targetSubject.includes("english")) targetSubject = "use of english";
if (targetSubject.includes("agric") || targetSubject.includes("agricultur")) targetSubject = "agric";
if (targetSubject.includes("biol") || targetSubject.includes("biology")) targetSubject = "biology";
const { collection, getDocs } = await import("https://www.gstatic.com/firebasejs/10.7.1/firebase-firestore.js");
const resourcesRef = collection(window.db, "learning_resources");
const querySnapshot = await getDocs(resourcesRef);
// π’ HARD FORCED RESET BEFORE SCANNING
window.vault.activeMetadata = {
topicTitle: `Section ${topicCode.replace(/section/gi, "").trim()}`,
videoUrl: "",
audioUrl: "",
infographicUrl: "",
slidesUrl: ""
};
window.vault.activeCards = [];
querySnapshot.forEach(doc => {
const data = doc.data();
const dbSubject = data.subject ? data.subject.toString().trim().toLowerCase() : "";
const dbRawCode = data.videoCode ? data.videoCode.toString().trim().toLowerCase() : "";
const dbCleanCode = dbRawCode.replace(/[^a-z0-9]/g, '');
const rType = data.resourceType ? data.resourceType.toLowerCase().trim() : "";
const subjectMatches = (dbSubject === targetSubject || dbSubject.includes(targetSubject) || targetSubject.includes(dbSubject) ||
(targetSubject === "biology" && (dbSubject.includes("biol") || dbSubject.includes("bio"))) ||
(targetSubject === "agric" && (dbSubject.includes("agric") || dbSubject.includes("agri"))));
if (subjectMatches) {
if (rType.includes("video")) {
// π― TRACK 1: PERFECT DYNAMIC BOUNDARY VIDEO MATCHING
const normalizedDbCode = dbRawCode.toLowerCase().replace(/section/gi, "").replace(/[^a-z0-9]/g, "").trim();
const normalizedButtonCode = cleanToken.toLowerCase().replace(/section/gi, "").replace(/[^a-z0-9]/g, "").trim();
// Strip Roman numerals (e.g., 'i1a' -> '1a')
const coreDbCode = normalizedDbCode.replace(/^[ivxlcdm]+/, '');
const coreButtonCode = normalizedButtonCode.replace(/^[ivxlcdm]+/, '');
// Strict validation paths
const isExactMatch = (normalizedDbCode === normalizedButtonCode || dbCleanCode === strictAlphaToken);
const isRomanBridgeMatch = (coreDbCode === coreButtonCode && coreButtonCode.length > 0);
if (isExactMatch || isRomanBridgeMatch) {
window.vault.activeMetadata.videoUrl = data.url;
}
}
else {
// π TRACK 2: BALANCED ALPHANUMERIC METADATA FILTER MATRIX
const dbCleanNorm = dbRawCode.toLowerCase().replace(/section/g, "").replace(/[^a-z0-9]/g, "").trim();
const targetCleanNorm = cleanToken.toLowerCase().replace(/section/g, "").replace(/[^a-z0-9]/g, "").trim();
// Strip off Roman numeral prefixes to align Mathematics & Government (e.g., 'iii4b' -> '4b')
const dbCore = dbCleanNorm.replace(/^[ivxlcdm]+/, '');
const targetCore = targetCleanNorm.replace(/^[ivxlcdm]+/, '');
// Alphanumeric clean boundaries comparison checks
const isCoreMatch = (targetCore.length > 0 && dbCore === targetCore) || (dbCleanNorm === targetCleanNorm);
const isContained = dbCleanNorm.includes(targetCleanNorm) || targetCleanNorm.includes(dbCleanNorm);
// Absolute exact check boundaries for isolation (blocks 1ai from bleeding into 1aii)
let safeGuardBlock = true;
if (targetCleanNorm === "1ai" && dbCleanNorm === "1aii") safeGuardBlock = false;
if (targetCleanNorm === "1ei" && dbCleanNorm === "1eii") safeGuardBlock = false;
if (targetCleanNorm.length > 0 && dbCleanNorm.includes(targetCleanNorm + "i") && !targetCleanNorm.endsWith("i")) safeGuardBlock = false;
const assetLooseMatch = (isCoreMatch || (isContained && safeGuardBlock));
if (assetLooseMatch) {
if (rType.includes("audio")) window.vault.activeMetadata.audioUrl = data.url;
else if (rType.includes("slide")) window.vault.activeMetadata.slidesUrl = data.url;
else if (rType.includes("graphic") || rType.includes("info")) window.vault.activeMetadata.infographicUrl = data.url;
}
}
}
});
try {
const flashcardsRef = collection(window.db, "flashcards");
const cardsSnapshot = await getDocs(flashcardsRef);
cardsSnapshot.forEach(doc => {
const cardData = doc.data();
const cardSubject = cardData.subject ? cardData.subject.toString().trim().toLowerCase() : "";
const cardRawCode = cardData.videoCode ? cardData.videoCode.toString().trim().toLowerCase() : "";
const cardCleanCode = cardRawCode.replace(/[^a-z0-9]/g, '');
const subMatches = (cardSubject === targetSubject || targetSubject.includes(cardSubject) || cardSubject.includes(targetSubject) ||
(targetSubject === "biology" && (cardSubject.includes("biol") || cardSubject.includes("bio"))));
// π BALANCED FLASHCARD ALPHANUMERIC METADATA FILTER MATRIX
const cardCleanNorm = cardRawCode.toLowerCase().replace(/section/g, "").replace(/[^a-z0-9]/g, "").trim();
const targetCleanNorm = cleanToken.toLowerCase().replace(/section/g, "").replace(/[^a-z0-9]/g, "").trim();
const cardCore = cardCleanNorm.replace(/^[ivxlcdm]+/, '');
const targetCore = targetCleanNorm.replace(/^[ivxlcdm]+/, '');
const isCardCoreMatch = (targetCore.length > 0 && cardCore === targetCore) || (cardCleanNorm === targetCleanNorm);
const isCardContained = cardCleanNorm.includes(targetCleanNorm) || targetCleanNorm.includes(cardCleanNorm);
let cardSafeGuard = true;
if (targetCleanNorm === "1ai" && cardCleanNorm === "1aii") cardSafeGuard = false;
if (targetCleanNorm === "1ei" && cardCleanNorm === "1eii") cardSafeGuard = false;
if (targetCleanNorm.length > 0 && cardCleanNorm.includes(targetCleanNorm + "i") && !targetCleanNorm.endsWith("i")) cardSafeGuard = false;
const cardCodeMatches = (isCardCoreMatch || (isCardContained && cardSafeGuard));
if (subMatches && cardCodeMatches && cardData.cards) {
window.vault.activeCards = cardData.cards;
}
});
} catch (cardErr) {
console.error("Flashcard Sync Snag:", cardErr);
}
return true;
} catch (e) {
console.error("Vault Query Failure Snag:", e);
return false;
}
};
window.vault.renderResourceUI = function(idx) {
const meta = window.vault.activeMetadata || {};
window.vault.activeModuleIdx = idx;
window.vault.resetZoom();
const vaultScreen = document.getElementById('vault-resource-screen');
if (vaultScreen) vaultScreen.style.setProperty('display', 'block', 'important');
window.app.view('vault-resource-screen');
document.getElementById('v-topic-title').innerText = meta.topicTitle || "Learning Vault";
// β‘ BULLETPROOF EXPANDABLE MOBILE-FIRST CROSS-VAULT DROPDOWN PROTOCOL
const partnerContainer = document.getElementById('v-partner-nav-container');
if (partnerContainer) {
partnerContainer.innerHTML = ""; // Clear existing elements Safely
const activeModule = window.app.activeModules[idx];
if (activeModule && activeModule.codes) {
// Strictly match the topic title provided in fetchVaultData
const currentTokenText = activeModule.codes.find(code =>
meta.topicTitle.toLowerCase().replace(/section/g, "").trim() === code.trim().toLowerCase()
) || activeModule.codes[0];
// 1. CREATE THE MASTER TRIGGER TOGGLE BUTTON (Calibrated Dashboard Tabs Twin Layout)
const mainTriggerBtn = document.createElement('button');
mainTriggerBtn.className = "btn-main";
mainTriggerBtn.style.cssText = `width: 100%; height: 100%; display: inline-flex; align-items: center; justify-content: center; gap: 4px; padding: 0 8px; font-size: 0.9rem; font-weight: 800; background: var(--bg); border: 2px solid var(--cyan); color: var(--cyan); border-radius: 12px; cursor: pointer; box-shadow: 0 4px 15px rgba(0, 229, 255, 0.1); transition: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; box-sizing: border-box;`;
mainTriggerBtn.innerHTML = `π§ Vault:
${currentTokenText.trim()} βΌ `;
// 2. CREATE THE ISOLATED DROPDOWN MENU PANEL OVERLAY
const dropdownMenu = document.createElement('div');
dropdownMenu.style.cssText = `display: none; position: absolute; top: calc(100% + 8px); left: 0; background: #13193E; border: 2px solid var(--border); border-radius: 20px; box-shadow: 0 15px 40px rgba(0,0,0,0.5); width: 280px; padding: 10px; overflow: hidden;`;
// Toggle visibility without causing screen flicker loops
mainTriggerBtn.onclick = (e) => {
e.preventDefault();
e.stopPropagation();
const isHidden = dropdownMenu.style.display === "none" || dropdownMenu.style.display === "";
dropdownMenu.style.display = isHidden ? "block" : "none";
mainTriggerBtn.style.borderColor = isHidden ? "var(--gold)" : "var(--cyan)";
mainTriggerBtn.style.color = isHidden ? "var(--gold)" : "var(--cyan)";
};
// Close automatically if the user clicks anywhere else on the screen layout
document.addEventListener('click', () => {
dropdownMenu.style.display = "none";
mainTriggerBtn.style.borderColor = "var(--cyan)";
mainTriggerBtn.style.color = "var(--cyan)";
}, { once: false });
// 3. GENERATE THE TARGET PASS COMPANION ROW BUTTONS
activeModule.codes.forEach(code => {
const cleanCodeToken = code.trim();
const isCurrentActive = meta.topicTitle.toLowerCase().includes(cleanCodeToken.toLowerCase());
const itemRow = document.createElement('button');
itemRow.style.cssText = `width: 100%; display: block; padding: 18px 20px; font-size: 1.1rem; font-weight: 700; text-align: left; background: transparent; border: none; border-radius: 12px; margin-bottom: 4px; transition: none; box-sizing: border-box;`;
if (isCurrentActive) {
itemRow.style.color = "var(--gold)";
itemRow.style.background = "rgba(255, 215, 0, 0.08)";
itemRow.innerHTML = `π Active Vault: ${cleanCodeToken}`;
} else {
itemRow.style.color = "var(--text-light)";
itemRow.style.cursor = "pointer";
itemRow.innerHTML = `π Jump to Section ${cleanCodeToken}`;
itemRow.onmouseover = () => { itemRow.style.background = "rgba(0, 229, 255, 0.08)"; itemRow.style.color = "var(--cyan)"; };
itemRow.onmouseout = () => { itemRow.style.background = "transparent"; itemRow.style.color = "var(--text-light)"; };
itemRow.onclick = async (evt) => {
evt.preventDefault();
evt.stopPropagation();
dropdownMenu.style.display = "none";
mainTriggerBtn.style.borderColor = "var(--cyan)";
mainTriggerBtn.innerHTML = `β³ Synchronizing Section ${cleanCodeToken}...`;
await window.app.triggerVault(cleanCodeToken, idx, cleanCodeToken);
};
}
dropdownMenu.appendChild(itemRow);
});
partnerContainer.appendChild(mainTriggerBtn);
partnerContainer.appendChild(dropdownMenu);
}
}
const videoContainer = document.querySelector('.video-container');
const videoPlayer = document.getElementById('v-player');
if (videoContainer && videoPlayer) {
// π’ INSTANT CACHE RESET BEFORE MOUNTING
videoPlayer.src = "";
videoContainer.style.setProperty('display', 'none', 'important');
if (meta.videoUrl && meta.videoUrl.trim() !== "") {
videoContainer.style.setProperty('display', 'block', 'important');
videoPlayer.src = `https://www.youtube.com/embed/${meta.videoUrl}?rel=0&modestbranding=1`;
}
}
const docViewerBox = document.getElementById('v-embedded-document-viewer-box');
const infoWrapper = document.getElementById('v-info-zoom-wrapper');
const infoIframe = document.getElementById('v-info-iframe');
const slidesIframe = document.getElementById('v-slides-iframe');
const zoomToolbar = document.getElementById('v-zoom-toolbar');
if (docViewerBox) docViewerBox.style.setProperty('display', 'none');
if (zoomToolbar) zoomToolbar.style.setProperty('display', 'none');
if (infoWrapper) infoWrapper.style.setProperty('display', 'none');
if (infoIframe) infoIframe.src = "";
if (slidesIframe) { slidesIframe.src = ""; slidesIframe.style.setProperty('display', 'none'); }
const audioPlayer = document.getElementById('v-audio');
const audioFallback = document.getElementById('v-audio-fallback');
if (audioPlayer && audioFallback) {
if (meta.audioUrl && meta.audioUrl.trim() !== "") {
audioPlayer.src = meta.audioUrl;
audioPlayer.style.setProperty('display', 'block', 'important');
audioFallback.style.display = "none";
audioPlayer.load();
} else {
audioPlayer.src = "";
audioPlayer.style.setProperty('display', 'none', 'important');
audioFallback.style.display = "block";
}
}
document.getElementById('v-btn-info').onclick = (e) => {
e.preventDefault();
if (meta.infographicUrl && meta.infographicUrl.trim() !== "") {
if (infoIframe && slidesIframe && docViewerBox && zoomToolbar && infoWrapper) {
window.vault.resetZoom();
slidesIframe.style.setProperty('display', 'none');
const cleanUrl = meta.infographicUrl.split('?')[0].toLowerCase();
if (cleanUrl.endsWith('.png') || cleanUrl.endsWith('.jpg') || cleanUrl.endsWith('.jpeg') || cleanUrl.endsWith('.webp')) {
infoIframe.src = "about:blank";
setTimeout(() => {
const iframeDoc = infoIframe.contentDocument || infoIframe.contentWindow.document;
if (iframeDoc) {
iframeDoc.open();
iframeDoc.write(`
`);
iframeDoc.close();
}
}, 50);
infoWrapper.style.setProperty('display', 'block', 'important');
zoomToolbar.style.setProperty('display', 'flex', 'important');
} else {
infoIframe.src = `https://docs.google.com/viewer?url=${encodeURIComponent(meta.infographicUrl)}&embedded=true`;
infoWrapper.style.setProperty('display', 'block', 'important');
zoomToolbar.style.setProperty('display', 'none');
}
docViewerBox.style.setProperty('display', 'block', 'important');
docViewerBox.scrollIntoView({ behavior: 'smooth' });
}
} else { alert("This infographic guide is currently being compiled by your Board of Examiners."); }
};
document.getElementById('v-btn-slides').onclick = (e) => {
e.preventDefault();
if (meta.slidesUrl && meta.slidesUrl.trim() !== "") {
if (slidesIframe && infoWrapper && docViewerBox && zoomToolbar) {
zoomToolbar.style.setProperty('display', 'none'); infoWrapper.style.setProperty('display', 'none');
slidesIframe.src = `https://docs.google.com/viewer?url=${encodeURIComponent(meta.slidesUrl)}&embedded=true`;
slidesIframe.style.setProperty('display', 'block', 'important');
docViewerBox.style.setProperty('display', 'block', 'important');
docViewerBox.scrollIntoView({ behavior: 'smooth' });
}
} else { alert("The high-yield review slide deck for this module is dropping soon!"); }
};
const flashArea = document.getElementById('v-flash-area');
if (flashArea) {
if (window.vault.activeCards && window.vault.activeCards.length > 0) {
let cardIndex = 0;
const renderCardState = () => {
const currentCard = window.vault.activeCards[cardIndex];
const rawFront = currentCard.front || currentCard.Front || "";
const rawBack = currentCard.back || currentCard.Back || "";
const compiledFront = window.app.math(rawFront);
const compiledBack = window.app.math(rawBack);
flashArea.innerHTML = `
β ${compiledFront}
π‘ Click Card to Flip
β¬
οΈ Previous
Card ${cardIndex + 1} of ${window.vault.activeCards.length}
Next β‘οΈ
`;
let isFlipped = false;
const flipbox = document.getElementById('f-card-flipbox');
const textContainer = document.getElementById('f-card-text');
flipbox.onclick = () => {
isFlipped = !isFlipped;
flipbox.style.transform = isFlipped ? "rotateY(180deg)" : "rotateY(0deg)";
setTimeout(() => {
if (isFlipped) {
flipbox.style.borderColor = "var(--gold)";
textContainer.style.transform = "rotateY(180deg)";
textContainer.innerHTML = `π―
ANSWER: ${compiledBack}
`;
} else {
flipbox.style.borderColor = "var(--cyan)";
textContainer.style.transform = "rotateY(0deg)";
textContainer.innerHTML = `β ${compiledFront}`;
}
}, 180);
};
document.getElementById('f-prev').onclick = () => {
if (cardIndex > 0) { cardIndex--; renderCardState(); }
};
document.getElementById('f-next').onclick = () => {
if (cardIndex < window.vault.activeCards.length - 1) { cardIndex++; renderCardState(); }
};
};
renderCardState();
} else {
flashArea.innerHTML = `
β‘ FLASHCARDS IN PRODUCTION
Active recall cards are currently being compiled. Use the Audio summary and Study Slides to anchor understanding!
`;
}
}
};
window.vault.adjustZoom = function(amount) {
this.zoomLevel = Math.max(1.0, Math.min(this.zoomLevel + amount, 3.0));
const infoIframe = document.getElementById('v-info-iframe');
if (infoIframe) {
const iframeDoc = infoIframe.contentDocument || infoIframe.contentWindow.document;
if (iframeDoc) {
const img = iframeDoc.getElementById('v-target-img');
if (img) {
img.style.transform = `scale(${this.zoomLevel})`;
if (this.zoomLevel === 1.0) {
img.style.maxWidth = "100%";
} else {
img.style.maxWidth = "none";
}
}
}
}
};
window.vault.resetZoom = function() {
this.zoomLevel = 1.0;
const infoIframe = document.getElementById('v-info-iframe');
if (infoIframe) {
const iframeDoc = infoIframe.contentDocument || infoIframe.contentWindow.document;
if (iframeDoc) {
const img = iframeDoc.getElementById('v-target-img');
if (img) {
img.style.transform = "scale(1)";
img.style.maxWidth = "100%";
}
}
}
};
window.vault.triggerMasteryTestGate = function() {
const audioPlayer = document.getElementById('v-audio');
if (audioPlayer) {
audioPlayer.pause();
audioPlayer.src = "";
}
const vPlayer = document.getElementById('v-player');
if (vPlayer) vPlayer.src = "";
const vaultScreen = document.getElementById('vault-resource-screen');
if (vaultScreen) {
vaultScreen.style.setProperty('display', 'none', 'important');
}
window.app.launchModule(window.vault.activeModuleIdx);
};
window.vault.exitVaultBackToMap = function() {
const audioPlayer = document.getElementById('v-audio');
if (audioPlayer) {
audioPlayer.pause();
audioPlayer.src = "";
}
const vPlayer = document.getElementById('v-player');
if (vPlayer) vPlayer.src = "";
const vaultScreen = document.getElementById('vault-resource-screen');
if (vaultScreen) {
vaultScreen.style.setProperty('display', 'none', 'important');
}
window.app.view('topics-screen');
};
window.switchVaultPane = function(paneId, btn) {
// π‘οΈ SECURITY INTERCEPT: Block unauthorized access to interactive Socratic Mentor chat
if (paneId === 'mentor' && window.app.mode !== 'platinum' && window.app.mode !== 'admin') {
window.app.showModal(`π
Platinum Tier Required Unlimited interactive, Socratic chat sessions with
Subject Experts are reserved exclusively for top-tier Platinum members.
Please contact our help desk to upgrade your access license package!`);
return;
}
document.querySelectorAll('.v-tab-btn').forEach(b => b.classList.remove('active'));
btn.classList.add('active');
document.querySelectorAll('.pane').forEach(p => p.classList.remove('active'));
const targetPane = document.getElementById('pane-' + paneId);
if (targetPane) {
targetPane.classList.add('active');
}
};
const SUBS = ['Use of English', 'Mathematics', 'Physics', 'Chemistry', 'Biology', 'Economics', 'Government', 'Literature in English', 'Principles of Accounts', 'Commerce', 'Agricultural Science'];
// Global variable container to store true quiz counts per subject dynamically
window.app_totals = { counts: {}, subjectsMap: {} };
const precalculateAllSubjectModules = async () => {
try {
for (const s of SUBS) {
const dbPath = s.toLowerCase().replace(/\s/g, '');
const snap = await get(child(ref(rtdb), `/${dbPath}`));
const data = snap.val();
if (data) {
const raw = Object.values(data).filter(q => q && typeof q === 'object' && (q.question || q["question text"]));
const collator = new Intl.Collator(undefined, { numeric: true });
raw.sort((a, b) => collator.compare(a.section_code || "A", b.section_code || "A"));
const blocksByCode = {};
raw.forEach(q => {
const code = q.section_code || "A";
if (!blocksByCode[code]) blocksByCode[code] = [];
blocksByCode[code].push(q);
});
const sortedUniqueCodes = Object.keys(blocksByCode).sort((a, b) => collator.compare(a, b));
const modulesConfigList = [];
let currentBatchQs = [];
let currentBatchCodes = [];
let topicNameTracker = {};
sortedUniqueCodes.forEach(code => {
const codeQs = blocksByCode[code];
if (codeQs.length >= 40) {
if (currentBatchQs.length > 0) {
const bName = currentBatchQs[0].topic_name || "Module";
if (!topicNameTracker[bName]) topicNameTracker[bName] = 0;
topicNameTracker[bName]++;
modulesConfigList.push({ totalCount: currentBatchQs.length });
currentBatchQs = []; currentBatchCodes = [];
}
let sliceIdx = 0;
const baseName = codeQs[0].topic_name || "Module";
while (sliceIdx < codeQs.length) {
const chunk = codeQs.slice(sliceIdx, sliceIdx + 20);
if (!topicNameTracker[baseName]) topicNameTracker[baseName] = 0;
topicNameTracker[baseName]++;
modulesConfigList.push({ totalCount: chunk.length });
sliceIdx += 20;
}
return;
}
currentBatchQs = currentBatchQs.concat(codeQs);
currentBatchCodes.push(code);
if (currentBatchQs.length >= 20) {
const bName = currentBatchQs[0].topic_name || "Module";
if (!topicNameTracker[bName]) topicNameTracker[bName] = 0;
topicNameTracker[bName]++;
modulesConfigList.push({ totalCount: currentBatchQs.length });
currentBatchQs = []; currentBatchCodes = [];
}
});
if (currentBatchQs.length > 0) {
const bName = currentBatchQs[0].topic_name || "Module";
if (!topicNameTracker[bName]) topicNameTracker[bName] = 0;
topicNameTracker[bName]++;
modulesConfigList.push({ totalCount: currentBatchQs.length });
}
window.app_totals.counts[s] = modulesConfigList.length || 1;
window.app_totals.subjectsMap[s] = modulesConfigList;
} else {
window.app_totals.counts[s] = 1;
window.app_totals.subjectsMap[s] = [];
}
}
} catch (e) {
console.error("Critical Subject Breakdown Count Sync Exception:", e);
}
};
window.app = {
email: null, prog: {}, sub: "", stream: "JAMB", activeModules: [], qIdx: 0, pool: [], ans: [], bId: "",
mode: "none",
mentorHistory: [],
// ADDED: Explicitly define copyCode here inside the window.app object
copyCode: () => {
navigator.clipboard.writeText("TRIAL").then(() => {
const b = document.getElementById('btn-copy-code');
if (b) {
b.innerText = "COPIED!";
setTimeout(() => { b.innerText = "COPY"; }, 2000);
}
});
},
sendMessage: async () => {
const input = document.getElementById('mentor-input');
const chatArea = document.getElementById('mentor-chat-area');
const text = input.value.trim();
if (!text) return;
// 1. Show student message
chatArea.innerHTML += `
${text}
`;
input.value = "";
chatArea.scrollTop = chatArea.scrollHeight;
try {
// 2. Call the Function
const response = await askUncleTunde({
message: text,
history: window.app.mentorHistory
});
console.log("Cloud Function Data:", response.data);
if (response.data.status === "LIMIT_REACHED") {
window.app.showModal(`π¨
CREDITS USED ${response.data.message}`);
return;
}
// 3. FIX: Extract message with a fallback
const rawText = response.data.message || "Uncle Tunde is nodding his head, but no words came out. Check backend deployment!";
const tundeText = window.app.math(rawText);
// 4. Show Uncle Tunde's response
chatArea.innerHTML += `
Uncle Tunde: ${tundeText}
`;
chatArea.scrollTop = chatArea.scrollHeight;
// 5. Update history for context
window.app.mentorHistory.push({ role: "user", parts: [{ text: text }] });
window.app.mentorHistory.push({ role: "model", parts: [{ text: rawText }] });
} catch (err) {
console.error("Auth Failure Log:", err);
// This will display the actual error message (e.g., "Invalid Access Code")
window.app.showModal(`π‘
Authentication Failure ${err.message}`);
}
},
view: (id) => { document.querySelectorAll('.screen').forEach(s => s.classList.remove('active')); document.getElementById(id).classList.add('active'); window.scrollTo(0,0); },
generateQuestionGrid: (totalQuestions) => {
const grid = document.getElementById('question-grid');
if (!grid) return;
grid.innerHTML = '';
for (let i = 1; i <= totalQuestions; i++) {
const btn = document.createElement('button');
btn.id = `q-btn-${i}`;
btn.style.cssText = `padding: 10px 5px; font-weight: 800; border-radius: 8px; background: #6c757d; border: 1px solid #444; color: white; cursor: pointer; font-size: 0.95rem; text-align: center; font-family: 'Inter', sans-serif; transition: 0.2s;`;
btn.innerText = i;
btn.onclick = (e) => {
e.preventDefault();
window.app.loadQuestion(i - 1);
};
grid.appendChild(btn);
}
},
shuffleQuestions: (array) => {
let currentIndex = array.length, randomIndex;
while (currentIndex !== 0) {
randomIndex = Math.floor(Math.random() * currentIndex);
currentIndex--;
[array[currentIndex], array[randomIndex]] = [array[randomIndex], array[array[currentIndex]]];
}
return array;
},
// π CENTRALIZED SIMULATOR MATRIX CONTROLLER
startExamSession: (subjectPool, subjectName) => {
console.log("Launching simulation engine for subject:", subjectName);
alert("SUBJECT POOL LENGTH: " + (subjectPool ? subjectPool.length : "UNDEFINED"));
alert("ACTIVE MODULES LENGTH: " + (window.app.activeModules ? window.app.activeModules.length : "UNDEFINED"));
const cleanSubName = subjectName.toLowerCase().replace(/[^a-z]/g, '');
const isEnglish = (cleanSubName === 'useofenglish' || cleanSubName === 'english');
let livePool = [];
if (window.app.activeModules && window.app.activeModules.length > 0) {
window.app.activeModules.forEach(m => {
if (m && m.qs && m.qs.length > 0) {
m.qs.forEach(q => {
let composedQ = {...q};
composedQ.topicName = m.name || "Syllabus Segment Focus";
livePool.push(composedQ);
});
}
});
}
if (livePool.length === 0 && subjectPool && subjectPool.length > 0) {
livePool = [...subjectPool];
}
if (!livePool || livePool.length === 0) {
alert("β οΈ Question repository pool is empty or initializing. Please ensure questions are loaded into your array before launching.");
return;
}
const totalToDraw = isEnglish ? 60 : 40;
const shuffledPool = window.app.shuffleQuestions([...livePool]);
const selectedQuestions = shuffledPool.slice(0, totalToDraw);
const sessionData = {
subject: subjectName,
questions: selectedQuestions,
startTime: Date.now(),
durationSeconds: isEnglish ? 2400 : 1600,
answers: new Array(selectedQuestions.length).fill(null),
visited: new Array(selectedQuestions.length).fill(false)
};
window.app.currentExamQuestions = selectedQuestions;
window.app.currentExamAnswers = new Array(selectedQuestions.length).fill(null);
sessionStorage.setItem('cbt_active_session', JSON.stringify(sessionData));
const titleEl = document.getElementById('exam-title');
if (titleEl) titleEl.innerHTML = `${subjectName.toUpperCase()} CBT Simulator`;
window.app.generateQuestionGrid(selectedQuestions.length);
// View transition and delayed question load to ensure DOM ready
window.app.view('exam-screen');
setTimeout(() => {
window.app.loadQuestion(0);
}, 100);
if (window.app.examInterval) clearInterval(window.app.examInterval);
let remainingTime = sessionData.durationSeconds;
window.app.examRemainingSeconds = remainingTime;
window.app.examInterval = setInterval(() => {
remainingTime--;
window.app.examRemainingSeconds = remainingTime;
const timerEl = document.getElementById('exam-timer');
if (remainingTime <= 0) {
clearInterval(window.app.examInterval);
if (timerEl) timerEl.innerHTML = "00:00";
alert("β° Time is up! Your exam session is being automatically compiled.");
window.app.submitExamSession();
} else {
const mins = Math.floor(remainingTime / 60).toString().padStart(2, '0');
const secs = (remainingTime % 60).toString().padStart(2, '0');
if (timerEl) timerEl.innerHTML = `${mins}:${secs}`;
}
}, 1000);
},
window.app.view('exam-report-screen');
},
copyCode: () => {
if (navigator.clipboard && navigator.clipboard.writeText) {
navigator.clipboard.writeText("TRIAL").then(() => {
const b = document.getElementById('btn-copy-code') || document.querySelector('.btn-copy');
if (b) {
const originalText = b.innerText;
b.innerText = "COPIED!";
setTimeout(() => { b.innerText = originalText; }, 2000);
} else {
alert("π Voucher Code 'TRIAL' copied securely!");
}
}).catch(err => {
console.error('Clipboard action failed:', err);
});
} else {
alert("π Voucher Code 'TRIAL' copied to clipboard!");
}
},
triggerVault: async (topicCode, idx, title) => {
triggerVault: async (topicCode, idx, title) => {
// π‘οΈ SECURITY INTERCEPT: Block Silver, and restrict Trial tiers to the first topic only
if (window.app.mode === 'silver' || (window.app.mode === 'trial' && idx > 0)) {
window.app.showModal(`π
Gold Upgrade Required Syllabus video overviews, audio concepts, review slides, and active recall flashcards are exclusive to
Gold and
Platinum scholars.
Please upgrade your membership to unlock the full Learning Vault layout!`);
return;
}
let cleanToken = topicCode.trim();
if (cleanToken.toLowerCase().includes("section")) {
cleanToken = cleanToken.replace(/section/gi, "").trim();
}
const success = await window.vault.fetchVaultData(cleanToken, window.app.sub);
if (success) {
if (window.vault.activeMetadata) {
window.vault.activeMetadata.topicTitle = `Section ${cleanToken}`;
}
window.vault.renderResourceUI(idx);
} else {
window.app.showModal(`π½οΈ
Vault Code: ${cleanToken} The cinematic resources for this specific section are currently in production.
Please proceed to the
Mastery Test to check your current proficiency.`);
}
},
showModal: (msg) => {
const m = document.getElementById('mastery-modal');
if(m) { document.getElementById('modal-msg-txt').innerHTML = msg; m.classList.add('active'); }
else { alert(msg); }
},
closeModal: () => { document.getElementById('mastery-modal').classList.remove('active'); },
login: async () => {
const e = document.getElementById('u-mail-field').value.trim().toLowerCase();
const p = document.getElementById('u-pass-field').value.trim().toUpperCase();
if(!e || !p) { window.app.showModal("π¨ Email and Passcode are required."); return; }
try {
const loginResult = await processLogin(e, p, db, rtdb);
window.app.mode = loginResult.mode;
window.app.email = e;
const userDocRef = doc(db, "users", e);
const userSnap = await getDoc(userDocRef);
if (userSnap.exists()) {
const uData = userSnap.data();
window.app.uName = uData.name || "Scholar";
window.app.prog = uData.progress || {};
window.app.view('stream-screen');
setTimeout(() => { if(typeof precalculateAllSubjectModules === 'function') precalculateAllSubjectModules(); }, 100);
} else {
window.app.prog = {};
if (window.app.mode === 'trial') {
window.app.uName = "Scholar";
window.app.view('stream-screen');
setTimeout(() => { if(typeof precalculateAllSubjectModules === 'function') precalculateAllSubjectModules(); }, 50);
} else {
const modalOverlay = document.getElementById('mastery-modal');
const modalMessage = document.getElementById('modal-msg-txt');
const modalButton = modalOverlay.querySelector('.btn-main');
modalMessage.innerHTML = `
Welcome to AnchorLearn! π
Please enter your first name to activate your license profile:
`;
modalButton.innerText = "ACTIVATE PROFILE π";
modalOverlay.classList.add('active');
modalButton.onclick = async (evt) => {
evt.preventDefault();
const inputName = document.getElementById('v-activation-name-field').value.trim();
const finalName = inputName !== "" ? inputName : "Scholar";
modalButton.disabled = true;
await setDoc(userDocRef, {
email: window.app.email,
name: finalName,
progress: {},
createdAt: new Date().toISOString()
});
window.app.uName = finalName;
modalOverlay.classList.remove('active');
modalButton.disabled = false;
modalButton.onclick = () => window.app.closeModal();
window.app.view('stream-screen');
setTimeout(() => { if(typeof precalculateAllSubjectModules === 'function') precalculateAllSubjectModules(); }, 50);
};
}
}
} catch (err) {
console.error("Auth Failure Log:", err);
window.app.showModal(`π‘
Authentication Failure ${err.message}`);
}
},
setStream: (s) => {
if (s === 'WAEC') {
window.app.showModal("π
WAEC Stream Coming Soon! Our Board of Examiners is currently compiling the high-yield syllabus maps and active recall assets for WAEC.
For now, launch the
JAMB Mastery Engine to lock into your calculations!");
return;
}
window.app.stream = s;
document.getElementById('stream-title-lbl').innerText = `${s} Mastery Map`;
window.app.renderDash();
window.app.view('dashboard-screen');
},
renderDash: () => {
const target = document.getElementById('sub-list-render'); target.innerHTML = "";
const mapTitle = document.getElementById('stream-title-lbl');
if (mapTitle && window.app.uName) {
mapTitle.innerHTML = `π― Welcome,
${window.app.uName} | ${window.app.stream} Map`;
}
SUBS.forEach(s => {
const subProgressObj = window.app.prog[s] || {};
const maxQuizzesForSubject = window.app_totals.counts[s] || 20;
let countPassedOnce = 0;
let countPassedTwice = 0;
let countPassedThrice = 0;
Object.keys(subProgressObj).forEach(modKey => {
const val = subProgressObj[modKey];
let passesCount = 0;
if (Array.isArray(val)) {
passesCount = Math.min(val.filter(score => score >= 80).length, 3);
} else if (typeof val === 'number' && val >= 80) {
passesCount = 1;
}
if (passesCount === 1) countPassedOnce++;
if (passesCount === 2) countPassedTwice++;
if (passesCount >= 3) countPassedThrice++;
});
const totalUniqueMastered = countPassedOnce + countPassedTwice + countPassedThrice;
const totalPct = Math.min(Math.round((totalUniqueMastered / maxQuizzesForSubject) * 100), 100);
const goldWidth = Math.round((countPassedThrice / maxQuizzesForSubject) * 100);
const purpleWidth = Math.round((countPassedTwice / maxQuizzesForSubject) * 100);
const cyanWidth = Math.round((countPassedOnce / maxQuizzesForSubject) * 100);
const scoreboardText = `π©΅ ${countPassedOnce} Cyan | π ${countPassedTwice} Purple | π ${countPassedThrice} Gold β (${totalUniqueMastered} / ${maxQuizzesForSubject} Quizzes Mastered)`;
const goldBar = goldWidth > 0 ? '
' : '';
const purpleBar = purpleWidth > 0 ? '
' : '';
const cyanBar = cyanWidth > 0 ? '
' : '';
target.innerHTML += `
${s}
${totalPct}%
${goldBar}
${purpleBar}
${cyanBar}
${scoreboardText}
ENTER SUBJECT β
`;
});
},
loadSyllabus: async (sName) => {
try {
window.app.sub = sName;
document.getElementById('sub-title-render').innerText = sName;
const dbPath = sName.toLowerCase().replace(/\s/g, '');
const snap = await get(child(ref(rtdb), `/${dbPath}`));
const data = snap.val();
if(!data) throw new Error();
const raw = Object.values(data).filter(q => q && typeof q === 'object' && (q.question || q["question text"]));
const collator = new Intl.Collator(undefined, { numeric: true });
raw.sort((a, b) => collator.compare(a.section_code || "A", b.section_code || "A"));
const blocksByCode = {};
raw.forEach(q => {
const code = q.section_code || "A";
if (!blocksByCode[code]) blocksByCode[code] = [];
blocksByCode[code].push(q);
});
const sortedUniqueCodes = Object.keys(blocksByCode).sort((a, b) => collator.compare(a, b));
const modules = [];
let currentBatchQs = [];
let currentBatchCodes = [];
let topicNameTracker = {};
sortedUniqueCodes.forEach(code => {
const codeQs = blocksByCode[code];
if (codeQs.length >= 40) {
if (currentBatchQs.length > 0) {
const baseName = currentBatchQs[0].topic_name || "Module";
if (!topicNameTracker[baseName]) topicNameTracker[baseName] = 0;
topicNameTracker[baseName]++;
modules.push({
name: `${baseName} [${currentBatchCodes.join(', ')}]`,
sec: currentBatchQs[0].section_code || "A",
codes: [...currentBatchCodes],
qs: [...currentBatchQs]
});
currentBatchQs = []; currentBatchCodes = [];
}
let sliceIdx = 0;
const baseName = codeQs[0].topic_name || "Module";
while (sliceIdx < codeQs.length) {
const chunk = codeQs.slice(sliceIdx, sliceIdx + 20);
if (!topicNameTracker[baseName]) topicNameTracker[baseName] = 0;
topicNameTracker[baseName]++;
modules.push({
name: `${baseName} [${code}] β Part ${topicNameTracker[baseName]}`,
sec: chunk[0].section_code || "A",
codes: [code],
qs: chunk
});
sliceIdx += 20;
}
return;
}
currentBatchQs = currentBatchQs.concat(codeQs);
currentBatchCodes.push(code);
if (currentBatchQs.length >= 20) {
const baseName = currentBatchQs[0].topic_name || "Module";
if (!topicNameTracker[baseName]) topicNameTracker[baseName] = 0;
topicNameTracker[baseName]++;
modules.push({
name: `${baseName} [${currentBatchCodes.join(', ')}]`,
sec: currentBatchQs[0].section_code || "A",
codes: [...currentBatchCodes],
qs: [...currentBatchQs]
});
currentBatchQs = []; currentBatchCodes = [];
}
});
if (currentBatchQs.length > 0) {
const baseName = currentBatchQs[0].topic_name || "Module";
if (!topicNameTracker[baseName]) topicNameTracker[baseName] = 0;
topicNameTracker[baseName]++;
modules.push({
name: `${baseName} [${currentBatchCodes.join(', ')}]`,
sec: currentBatchQs[0].section_code || "A",
codes: [...currentBatchCodes],
qs: [...currentBatchQs]
});
}
window.app.activeModules = modules;
const target = document.getElementById('topics-render');
target.innerHTML = "";
const { collection, getDocs } = await import("https://www.gstatic.com/firebasejs/10.7.1/firebase-firestore.js");
const resRef = collection(window.db, "learning_resources");
const resSnap = await getDocs(resRef);
const globalResources = [];
resSnap.forEach(doc => globalResources.push(doc.data()));
let curSec = "";
modules.forEach((mod, mIdx) => {
if (mod.sec !== curSec) {
curSec = mod.sec;
target.innerHTML += `