// --- Question Timer Function ---
function startQuestionTimer() {
if (currentQuestionIndex !== null && questions[currentQuestionIndex]) {
const question = questions[currentQuestionIndex];
const questionKey = `${currentPart}-${question.originalNumber}`;
if (!questionStartTimes[questionKey]) {
questionStartTimes[questionKey] = Date.now();
}
}
}
// --- Initialize Watermark ---
function initializeWatermark() {
const watermarkContainers = document.querySelectorAll('.watermark-pt-container');
watermarkContainers.forEach(container => {
const containerWidth = container.offsetWidth;
const containerHeight = container.offsetHeight;
const watermarkText = '8016820***';
const spacingX = 150;
const spacingY = 100;
for (let y = -50; y < containerHeight + 50; y += spacingY) {
for (let x = -50; x < containerWidth + 50; x += spacingX) {
const watermark = document.createElement('div');
watermark.className = 'watermark-pt-text';
watermark.textContent = watermarkText;
watermark.style.left = `${x}px`;
watermark.style.top = `${y}px`;
container.appendChild(watermark);
}
}
});
}
// --- Render Question Grid ---
function renderQuestionGrid(part) {
questionGridEl.innerHTML = '';
solutionQuestionGridEl.innerHTML = '';
const questionsInPart = allQuestionsByPart[part] || [];
questionsInPart.forEach((question, index) => {
const gridItem = document.createElement('div');
gridItem.className = `question-grid-item ${question.status} ${index === currentQuestionIndex && part === currentPart ? 'current' : ''}`;
gridItem.textContent = question.originalNumber;
gridItem.dataset.index = index;
gridItem.dataset.part = part;
gridItem.innerHTML += '
';
gridItem.addEventListener('click', () => {
saveCurrentQuestionTime();
currentPart = part;
currentQuestionIndex = index;
questions = allQuestionsByPart[part];
updateQuestionDisplay();
updateQuestionGrid();
startQuestionTimer();
});
questionGridEl.appendChild(gridItem);
const solutionGridItem = document.createElement('div');
const { bgColor, textColor } = getQuestionPaletteColorClasses(question);
solutionGridItem.className = `question-grid-item ${index === currentQuestionIndex && part === currentPart ? 'current' : ''} ${bgColor} ${textColor}`;
solutionGridItem.textContent = question.originalNumber;
solutionGridItem.dataset.index = index;
solutionGridItem.dataset.part = part;
solutionGridItem.innerHTML += '
';
solutionGridItem.addEventListener('click', () => {
currentPart = part;
currentQuestionIndex = index;
questions = allQuestionsByPart[part];
updateSolutionDisplay();
updateSolutionQuestionGrid();
});
solutionQuestionGridEl.appendChild(solutionGridItem);
});
}
// --- Update Question Display ---
function updateQuestionDisplay() {
if (!questions[currentQuestionIndex]) return;
const question = questions[currentQuestionIndex];
questionNumberSpan.textContent = question.originalNumber;
questionTextCell.innerHTML = question.question;
optionsTableBody.innerHTML = '';
question.options.forEach((option, index) => {
const optionLetter = String.fromCharCode(97 + index);
const row = document.createElement('tr');
row.innerHTML = `
|
${option} |
`;
optionsTableBody.appendChild(row);
});
questionTextCell.style.fontSize = `${currentQuestionTextFontSize}px`;
optionsTableBody.querySelectorAll('.text-cell').forEach(cell => {
cell.style.fontSize = `${currentOptionTextFontSize}px`;
});
markReviewBtn.classList.toggle('marked-active', question.status.includes('marked'));
question.status = question.status === 'not-visited' ? 'not-answered' : question.status;
renderKatex();
updateAnalysisSummary();
}
// --- Update Solution Display ---
function updateSolutionDisplay() {
if (!questions[currentQuestionIndex]) return;
const question = questions[currentQuestionIndex];
solutionQuestionNumberSpan.textContent = question.originalNumber;
solutionQuestionTextCell.innerHTML = question.question;
solutionOptionsTableBody.innerHTML = '';
question.options.forEach((option, index) => {
const optionLetter = String.fromCharCode(97 + index);
const isCorrect = option.includes(`(${question.answer})`);
const isUserAnswer = question.userAnswer === option;
const optionClass = isCorrect ? 'solution-option-correct' : (isUserAnswer && !isCorrect ? 'solution-option-incorrect' : 'solution-option-normal');
const row = document.createElement('tr');
row.innerHTML = `
|
${option} |
`;
solutionOptionsTableBody.appendChild(row);
});
solutionDummyContent.innerHTML = question.solution;
solutionQuestionTextCell.style.fontSize = `${currentQuestionTextFontSize}px`;
solutionOptionsTableBody.querySelectorAll('.text-cell').forEach(cell => {
cell.style.fontSize = `${currentOptionTextFontSize}px`;
});
renderKatex();
updateSolutionAnalysisSummary();
}
// --- Update Analysis Summary ---
function updateAnalysisSummary() {
const questionsInPart = allQuestionsByPart[currentPart] || [];
const answeredCount = questionsInPart.filter(q => q.userAnswer !== null).length;
const notAnsweredCount = questionsInPart.length - answeredCount;
answeredCountEl.textContent = answeredCount;
notAnsweredCountEl.textContent = notAnsweredCount;
totalAnsweredEl.textContent = Object.values(allQuestionsByPart).flat().filter(q => q.userAnswer !== null).length;
currentPartAnalysisTitle.textContent = partNames[currentPart];
partTitleEl.textContent = partNames[currentPart];
}
// --- Update Solution Analysis Summary ---
function updateSolutionAnalysisSummary() {
const questionsInPart = allQuestionsByPart[currentPart] || [];
const answeredCount = questionsInPart.filter(q => q.userAnswer !== null).length;
const notAnsweredCount = questionsInPart.length - answeredCount;
solutionAnsweredCountEl.textContent = answeredCount;
solutionNotAnsweredCountEl.textContent = notAnsweredCount;
solutionTotalAnsweredEl.textContent = Object.values(allQuestionsByPart).flat().filter(q => q.userAnswer !== null).length;
solutionCurrentPartAnalysisTitle.textContent = partNames[currentPart];
solutionPartTitleEl.textContent = partNames[currentPart];
}
// --- Render KaTeX ---
function renderKatex() {
[questionTextCell, optionsTableBody, solutionQuestionTextCell, solutionOptionsTableBody, solutionDummyContent].forEach(element => {
if (element) {
renderMathInElement(element, {
delimiters: [
{ left: "$$", right: "$$", display: true },
{ left: "$", right: "$", display: false },
{ left: "\\(", right: "\\)", display: false },
{ left: "\\[", right: "\\]", display: true }
],
throwOnError: false
});
}
});
}
// --- Start Total Timer ---
function startTotalTimer() {
totalTestStartTime = Date.now();
const totalDurationMs = TOTAL_TEST_DURATION_MINUTES * 60 * 1000;
totalTimerInterval = setInterval(() => {
const elapsed = Date.now() - totalTestStartTime;
const remaining = Math.max(totalDurationMs - elapsed, 0);
timeLeftEl.textContent = formatHoursMinutesSeconds(remaining);
lastMinutesDisplay.textContent = Math.ceil(remaining / 60000);
if (remaining <= 0) {
clearInterval(totalTimerInterval);
showAlert('Time is up! Submitting test.', 'Test Ended', (confirm) => {
if (confirm) submitTest();
});
}
}, 1000);
}
// --- Populate Review Submit Modal ---
function populateReviewSubmitModal() {
reviewSummaryTableBody.innerHTML = '';
partOrder.forEach(part => {
const questionsInPart = allQuestionsByPart[part] || [];
const answered = questionsInPart.filter(q => q.userAnswer !== null).length;
const notAnswered = questionsInPart.length - answered;
const markedAnswered = questionsInPart.filter(q => q.status === 'answered-marked').length;
const markedForReview = questionsInPart.filter(q => q.status.includes('marked')).length;
const visited = questionsInPart.filter(q => q.status !== 'not-visited').length;
const notVisited = questionsInPart.length - visited;
const row = document.createElement('tr');
row.innerHTML = `
${partNames[part]} |
${questionsInPart.length} |
${answered} |
${notAnswered} |
${markedAnswered} |
${markedForReview} |
${visited} |
${notVisited} |
`;
reviewSummaryTableBody.appendChild(row);
});
reviewSubmitModal.style.display = 'flex';
}
// --- Submit Test ---
function submitTest() {
saveCurrentQuestionTime();
clearInterval(totalTimerInterval);
totalTestEndTime = Date.now();
testCompletionDateTime = new Date();
solutionCompletionTimeEl.textContent = `${formatDate(testCompletionDateTime)} ${formatTime(testCompletionDateTime)}`;
mocktestPage.classList.add('hidden');
solutionPage.classList.remove('hidden');
inSolutionMode = true;
currentPart = partOrder[0];
questions = allQuestionsByPart[currentPart];
currentQuestionIndex = 0;
updateSolutionDisplay();
updateSolutionQuestionGrid();
updateSolutionAnalysisSummary();
initializeWatermark();
}
// --- Generate PDF ---
async function generatePDF() {
const { jsPDF } = window.jspdf;
const doc = new jsPDF({ orientation: 'portrait', unit: 'px', format: 'a4' });
pdfContentWrapper.innerHTML = '';
Object.keys(allQuestionsByPart).forEach(part => {
const questionsInPart = allQuestionsByPart[part] || [];
if (questionsInPart.length > 0) {
const partHeader = document.createElement('h2');
partHeader.textContent = `Part ${part}: ${partNames[part]}`;
pdfContentWrapper.appendChild(partHeader);
questionsInPart.forEach((question, index) => {
const questionDiv = document.createElement('div');
questionDiv.className = 'question-feedback-item';
questionDiv.innerHTML = `
Question ${question.originalNumber}: ${partNames[part]}
${question.question}
${question.options.map((option, i) => {
const isCorrect = option.includes(`(${question.answer})`);
const isUserAnswer = question.userAnswer === option;
const optionClass = isCorrect ? 'correct' : (isUserAnswer && !isCorrect ? 'incorrect' : '');
return `
${option}
`;
}).join('')}
Solution: ${question.solution}
`;
pdfContentWrapper.appendChild(questionDiv);
});
}
});
const charts = document.querySelectorAll('.chart-container canvas');
for (let i = 0; i < charts.length; i++) {
const canvas = charts[i];
const chartImg = canvas.toDataURL('image/png');
const imgDiv = document.createElement('div');
imgDiv.className = 'chart-container pdf-chart';
imgDiv.innerHTML = `

`;
pdfContentWrapper.appendChild(imgDiv);
}
const elements = pdfContentWrapper.querySelectorAll('.question-feedback-item, h2, .chart-container');
let yPosition = 20;
for (let element of elements) {
const tempDiv = document.createElement('div');
tempDiv.style.width = '754px';
tempDiv.style.padding = '10px';
tempDiv.innerHTML = element.outerHTML;
document.body.appendChild(tempDiv);
const canvas = await html2canvas(tempDiv, { scale: 2 });
const imgData = canvas.toDataURL('image/png');
const imgProps = doc.getImageProperties(imgData);
const pdfWidth = doc.internal.pageSize.getWidth() - 40;
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
if (yPosition + pdfHeight > doc.internal.pageSize.getHeight() - 20) {
doc.addPage();
yPosition = 20;
}
doc.addImage(imgData, 'PNG', 20, yPosition, pdfWidth, pdfHeight);
yPosition += pdfHeight + 20;
document.body.removeChild(tempDiv);
}
doc.save(`${currentTopic || 'Mock_Test'}_Feedback.pdf`);
pdfContentWrapper.innerHTML = '';
}
// --- Event Listeners ---
document.addEventListener('DOMContentLoaded', async () => {
await fetchQuestions();
initializeWatermark();
questions = allQuestionsByPart[currentPart] || [];
if (questions.length > 0) {
renderQuestionGrid(currentPart);
updateQuestionDisplay();
} else {
questionGridEl.innerHTML = '
No questions available.
';
}
updateAnalysisSummary();
});
startMocktestBtn.addEventListener('click', () => {
allQuestionsByPart['A'] = parseQuestions(questionInputPartA.value);
allQuestionsByPart['B'] = parseQuestions(questionInputPartB.value);
allQuestionsByPart['C'] = parseQuestions(questionInputPartC.value);
allQuestionsByPart['D'] = parseQuestions(questionInputPartD.value);
currentTopic = topicInput.value.trim() || 'General';
const totalQuestions = Object.values(allQuestionsByPart).flat().length;
if (totalQuestions === 0) {
showAlert('Please enter at least one valid question to start the test.');
return;
}
inputPage.classList.add('hidden');
mocktestPage.classList.remove('hidden');
currentPart = partOrder[0];
questions = allQuestionsByPart[currentPart];
currentQuestionIndex = 0;
renderQuestionGrid(currentPart);
updateQuestionDisplay();
updateAnalysisSummary();
startTotalTimer();
startQuestionTimer();
initializeWatermark();
});
optionsTableBody.addEventListener('change', (e) => {
if (e.target.type === 'radio') {
questions[currentQuestionIndex].userAnswer = e.target.value;
questions[currentQuestionIndex].status = questions[currentQuestionIndex].status.includes('marked') ? 'answered-marked' : 'answered';
updateQuestionGrid();
updateAnalysisSummary();
}
});
partTabs.forEach(tab => {
tab.addEventListener('click', () => {
saveCurrentQuestionTime();
partTabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
currentPart = tab.dataset.part;
questions = allQuestionsByPart[currentPart] || [];
currentQuestionIndex = 0;
renderQuestionGrid(currentPart);
updateQuestionDisplay();
updateAnalysisSummary();
startQuestionTimer();
});
});
solutionPartTabs.forEach(tab => {
tab.addEventListener('click', () => {
currentPart = tab.dataset.part;
questions = allQuestionsByPart[currentPart] || [];
currentQuestionIndex = 0;
solutionPartTabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
renderQuestionGrid(currentPart);
updateSolutionDisplay();
updateSolutionAnalysisSummary();
});
});
prevBtn.addEventListener('click', () => {
saveCurrentQuestionTime();
if (currentQuestionIndex > 0) {
currentQuestionIndex--;
} else {
const currentPartIndex = partOrder.indexOf(currentPart);
if (currentPartIndex > 0) {
currentPart = partOrder[currentPartIndex - 1];
questions = allQuestionsByPart[currentPart];
currentQuestionIndex = questions.length - 1;
partTabs.forEach(tab => tab.classList.toggle('active', tab.dataset.part === currentPart));
}
}
updateQuestionDisplay();
updateQuestionGrid();
startQuestionTimer();
});
saveNextBtn.addEventListener('click', () => {
saveCurrentQuestionTime();
if (currentQuestionIndex < questions.length - 1) {
currentQuestionIndex++;
} else {
const currentPartIndex = partOrder.indexOf(currentPart);
if (currentPartIndex < partOrder.length - 1) {
currentPart = partOrder[currentPartIndex + 1];
questions = allQuestionsByPart[currentPart];
currentQuestionIndex = 0;
partTabs.forEach(tab => tab.classList.toggle('active', tab.dataset.part === currentPart));
}
}
updateQuestionDisplay();
updateQuestionGrid();
startQuestionTimer();
});
markReviewBtn.addEventListener('click', () => {
const question = questions[currentQuestionIndex];
if (question.status.includes('marked')) {
question.status = question.userAnswer ? 'answered' : 'not-answered';
} else {
question.status = question.userAnswer ? 'answered-marked' : 'marked';
}
updateQuestionGrid();
updateQuestionDisplay();
});
submitTestBtn.addEventListener('click', () => {
populateReviewSubmitModal();
});
confirmSubmitTestBtn.addEventListener('click', () => {
reviewSubmitModal.style.display = 'none';
submitTest();
});
zoomInBtn.addEventListener('click', () => {
currentQuestionTextFontSize += 2;
currentOptionTextFontSize += 2;
updateQuestionDisplay();
});
zoomOutBtn.addEventListener('click', () => {
if (currentQuestionTextFontSize > 12) {
currentQuestionTextFontSize -= 2;
currentOptionTextFontSize -= 2;
updateQuestionDisplay();
}
});
solutionZoomInBtn.addEventListener('click', () => {
currentQuestionTextFontSize += 2;
currentOptionTextFontSize += 2;
updateSolutionDisplay();
});
solutionZoomOutBtn.addEventListener('click', () => {
if (currentQuestionTextFontSize > 12) {
currentQuestionTextFontSize -= 2;
currentOptionTextFontSize -= 2;
updateSolutionDisplay();
}
});
fullscreenBtn.addEventListener('click', () => {
if (!document.fullscreenElement) {
mocktestPage.requestFullscreen().then(() => {
fullscreenBtn.textContent = 'Exit Fullscreen';
}).catch(err => console.error('Fullscreen error:', err));
} else {
document.exitFullscreen().then(() => {
fullscreenBtn.textContent = 'Show Fullscreen';
});
}
});
solutionFullscreenBtn.addEventListener('click', () => {
if (!document.fullscreenElement) {
solutionPage.requestFullscreen().then(() => {
solutionFullscreenBtn.textContent = 'Exit Fullscreen';
}).catch(err => console.error('Fullscreen error:', err));
} else {
document.exitFullscreen().then(() => {
solutionFullscreenBtn.textContent = 'Show Fullscreen';
});
}
});
hideLeftPanelBtn.addEventListener('click', () => {
const isHidden = leftPanel.style.width === '0px';
leftPanel.style.width = isHidden ? '360px' : '0px';
rightPanel.style.width = isHidden ? 'calc(100% - 360px)' : '100%';
hideLeftPanelBtn.querySelector('img').src = isHidden ?
'https://www.testranking.in/next_images/test/pt-left.png' :
'https://www.testranking.in/next_images/test/pt-right.png';
});
solutionHideLeftPanelBtn.addEventListener('click', () => {
const isHidden = solutionLeftPanel.style.width === '0px';
solutionLeftPanel.style.width = isHidden ? '360px' : '0px';
solutionRightPanel.style.width = isHidden ? 'calc(100% - 360px)' : '100%';
solutionHideLeftPanelBtn.querySelector('img').src = isHidden ?
'https://www.testranking.in/next_images/test/pt-left.png' :
'https://www.testranking.in/next_images/test/pt-right.png';
});
solutionPrevBtn.addEventListener('click', () => {
if (currentQuestionIndex > 0) {
currentQuestionIndex--;
} else {
const currentPartIndex = partOrder.indexOf(currentPart);
if (currentPartIndex > 0) {
currentPart = partOrder[currentPartIndex - 1];
questions = allQuestionsByPart[currentPart];
currentQuestionIndex = questions.length - 1;
solutionPartTabs.forEach(tab => tab.classList.toggle('active', tab.dataset.part === currentPart));
}
}
updateSolutionDisplay();
updateSolutionQuestionGrid();
});
solutionNextBtn.addEventListener('click', () => {
if (currentQuestionIndex < questions.length - 1) {
currentQuestionIndex++;
} else {
const currentPartIndex = partOrder.indexOf(currentPart);
if (currentPartIndex < partOrder.length - 1) {
currentPart = partOrder[currentPartIndex + 1];
questions = allQuestionsByPart[currentPart];
currentQuestionIndex = 0;
solutionPartTabs.forEach(tab => tab.classList.toggle('active', tab.dataset.part === currentPart));
}
}
updateSolutionDisplay();
updateSolutionQuestionGrid();
});
backToResultBtn.addEventListener('click', () => {
solutionPage.classList.add('hidden');
resultPage.classList.remove('hidden');
// Result page logic can be added here if needed
});
symbolsTab.addEventListener('click', () => {
symbolsModal.style.display = 'block';
isSymbolsModalOpen = true;
});
instructionsTab.addEventListener('click', () => {
showAlert('Instructions: Select one option per question. Use the navigation buttons to move between questions. Save and mark questions as needed.');
});
closeModalBtn.addEventListener('click', () => {
symbolsModal.style.display = 'none';
isSymbolsModalOpen = false;
});
// --- Initialize ---
document.addEventListener('DOMContentLoaded', () => {
initializeWatermark();
});