<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Math Mock Test</title>
<script src="https://cdn.tailwindcss.com"></script>
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
<link href="https://fonts.googleapis.com/css2?family=Roboto:wght@400;700&display=swap" rel="stylesheet">
<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.css">
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/katex.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/katex@0.16.9/dist/contrib/auto-render.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
</head>
<style>
/* Original styles for input and result pages */
*, *::before, *::after {
box-sizing: border-box;
}
body {
font-family: "Inter", sans-serif;
background-color: #f0f2f5;
margin: 0;
padding: 0;
overflow-x: hidden;
}
textarea::-webkit-scrollbar {
width: 8px;
}
textarea::-webkit-scrollbar-track {
background: #f1f1f1;
border-radius: 10px;
}
textarea::-webkit-scrollbar-thumb {
background: #888;
border-radius: 10px;
}
textarea::-webkit-scrollbar-thumb:hover {
background: #555;
border-radius: 10px;
}
.page-container {
width: 100%;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: center;
padding: 1rem;
}
.page-card {
background-color: white;
padding: 2rem;
border-radius: 0.5rem;
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
width: 100%;
max-width: 768px;
}
.chart-container {
position: relative;
width: 100%;
max-width: 800px;
margin: 0 auto;
padding-bottom: 2rem;
}
.chart-container canvas {
width: 100% !important;
height: auto !important;
}
.horizontal-chart-wrapper {
overflow-x: auto;
width: 100%;
padding-bottom: 10px;
min-height: 200px;
}
#question-feedback-modal {
position: fixed;
inset: 0;
background-color: rgba(0, 0, 0, 0.75);
display: none;
align-items: center;
justify-content: center;
padding: 1rem;
z-index: 2000;
}
#question-feedback-modal .modal-content {
background-color: white;
padding: 1.5rem;
border-radius: 0.5rem;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
width: 100%;
max-width: 600px;
max-height: 90vh;
overflow-y: auto;
position: relative;
}
#question-feedback-modal .modal-close-btn {
position: absolute;
top: 10px;
right: 10px;
padding: 8px;
background: none;
border: none;
cursor: pointer;
color: #4a5568;
font-size: 1.5rem;
}
.time-breakdown-list {
list-style: none;
padding: 0;
margin: 0;
max-height: calc(10 * 2.5rem);
overflow-y: auto;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
padding: 0.5rem;
}
.time-breakdown-list li {
display: flex;
justify-content: space-between;
align-items: center;
padding: 0.5rem 0.75rem;
border-bottom: 1px solid #f0f2f5;
white-space: nowrap;
}
.time-breakdown-list li:last-child {
border-bottom: none;
}
#question-palette-result {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(48px, 1fr));
gap: 0.5rem;
padding: 1rem;
background-color: #f8fafc;
border-radius: 0.5rem;
box-shadow: inset 0 2px 4px rgba(0, 0, 0, 0.06);
margin-top: 1.5rem;
margin-bottom: 1.5rem;
}
#question-palette-result .question-palette-btn {
width: 48px;
height: 48px;
border-radius: 9999px;
display: flex;
align-items: center;
justify-content: center;
font-weight: 600;
font-size: 0.875rem;
cursor: pointer;
transition: all 0.2s ease-in-out;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
}
#question-palette-result .question-palette-btn:hover {
transform: translateY(-1px);
box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1), 0 2px 4px rgba(0, 0, 0, 0.06);
}
#pdf-content-wrapper {
position: absolute;
left: -9999px;
top: -9999px;
width: 794px;
padding: 20px;
background-color: white;
color: #333;
}
#pdf-content-wrapper .question-feedback-item {
margin-bottom: 1.5rem;
padding: 1rem;
border: 1px solid #e2e8f0;
border-radius: 0.5rem;
background-color: #f8fafc;
}
#pdf-content-wrapper .question-feedback-item .option-item {
padding: 0.5rem;
border-radius: 0.25rem;
margin-bottom: 0.25rem;
border: 1px solid transparent;
}
#pdf-content-wrapper .question-feedback-item .option-item.correct {
background-color: #d1fae5;
border-color: #34d399;
}
#pdf-content-wrapper .question-feedback-item .option-item.incorrect {
background-color: #fee2e2;
border-color: #ef4444;
}
#pdf-content-wrapper .chart-container.pdf-chart {
width: 100%;
height: auto;
max-width: 500px;
margin: 1rem auto;
}
/* New mock test interface styles from provided code */
#mocktest-page, #solution-page {
/* Apply styles to both mocktest and solution pages */
font-family: "Roboto", sans-serif;
background-color: #e0e0e0;
width: 100vw;
min-height: 100vh;
display: flex;
justify-content: center;
align-items: flex-start;
}
.hidden {
display: none !important;
}
#mocktest-page .container, #solution-page .container {
width: 1800px;
max-width: 1800px;
background-color: #ffffff;
box-shadow: 0 0 10px rgba(0, 0, 0, 0.1);
display: flex;
flex-direction: column;
min-height: 100vh;
}
#mocktest-page .header, #solution-page .header {
background-color: #f0f0f0;
border-bottom: 1px solid #cccccc;
color: #333;
padding: 0.5rem 1rem; /* Adjusted padding */
display: flex;
flex-direction: column;
align-items: center;
justify-content: space-between;
}
@media (min-width: 768px) {
#mocktest-page .header, #solution-page .header {
flex-direction: row;
}
}
#mocktest-page .header-left, #mocktest-page .header-right, #solution-page .header-left, #solution-page .header-right {
display: flex;
align-items: center;
}
#mocktest-page .header-right, #solution-page .header-right {
margin-top: 0.5rem;
justify-content: flex-end;
width: 100%;
}
@media (min-width: 768px) {
#mocktest-page .header-right, #solution-page .header-right {
margin-top: 0;
width: auto;
}
}
#mocktest-page .time-left-box, #solution-page .time-left-box {
background-color: #ffeb3b;
border: 1px solid #ccc;
padding: 0.2rem 0.5rem;
border-radius: 4px;
font-weight: bold;
color: #e53e3e;
margin-left: 1rem;
white-space: nowrap;
}
#mocktest-page .instructions-warning, #solution-page .instructions-warning {
background-color: #ffffff;
border-bottom: 1px solid #e2e8f0;
color: #333;
padding: 0.5rem 1rem; /* Adjusted padding */
display: flex;
flex-direction: column;
align-items: flex-start;
}
@media (min-width: 768px) {
#mocktest-page .instructions-warning, #solution-page .instructions-warning {
flex-direction: row;
justify-content: space-between;
align-items: center;
}
}
#mocktest-page .warning-message, #solution-page .warning-message {
color: #e53e3e;
font-weight: bold;
text-align: center;
margin-top: 0.5rem;
}
@media (min-width: 768px) {
#mocktest-page .warning-message, #solution-page .warning-message {
margin-top: 0;
}
}
#mocktest-page .main-content, #solution-page .main-content {
flex-grow: 1;
display: flex;
flex-direction: column;
}
@media (min-width: 768px) {
#mocktest-page .main-content, #solution-page .main-content {
flex-direction: row;
}
}
#mocktest-page .left-panel, #solution-page .left-panel {
background-color: #ffffff;
padding: 1rem;
width: 100%;
flex-shrink: 0;
transition: width 0.3s ease-in-out; /* Smooth transition for width */
}
@media (min-width: 768px) {
#mocktest-page .left-panel, #solution-page .left-panel {
width: 360px;
}
}
/* Section Tabs (PART-A to PART-D) */
#mocktest-page .part-tabs, #solution-page .part-tabs {
display: flex;
margin-bottom: 1rem;
width: 100%;
justify-content: center;
}
@media (min-width: 768px) {
#mocktest-page .part-tabs, #solution-page .part-tabs {
justify-content: flex-start;
}
}
#mocktest-page .part-tab-button, #solution-page .part-tab-button {
padding: 0.2rem 0.75rem; /* Adjusted padding */
font-weight: bold;
font-size: 0.875rem;
border: 1px solid #333;
color: white; /* flex-grow: 1; */
flex-wrap: nowrap;
text-align: center;
border-radius: 4px; /* No default border-radius */
}
#mocktest-page .part-tab-button:first-child, #solution-page .part-tab-button:first-child {
border-top-left-radius: 4px;
border-bottom-left-radius: 4px;
}
#mocktest-page .part-tab-button:last-child, #solution-page .part-tab-button:last-child {
border-top-right-radius: 4px;
border-bottom-right-radius: 4px;
}
#mocktest-page .part-tab-button.active, #solution-page .part-tab-button.active {
background-color: #008000; /* Green for active tab */
border-color: #38a169;
}
#mocktest-page .part-tab-button:not(.active), #solution-page .part-tab-button:not(.active) {
background-color: blue;
border-color: blue;
}
/* Question Grid - Updated CSS for Numbers Layout */
#mocktest-page .question-grid, #solution-page .question-grid {
display: grid;
grid-template-columns: repeat(auto-fill, minmax(40px, 1fr)); /* Adjusted for new button size */
gap: 1.2rem; /* margin-bottom: 1rem; */
width: 100%;
margin-left: auto;
margin-right: auto;
height: 260px;
overflow: auto;
padding:4px
}
#mocktest-page .question-grid-item, #solution-page .question-grid-item {
position: relative;
width: 40px; /* New width */
height: 18px; /* New height */
border-radius: 5px; /* New border-radius */
display: flex;
align-items: center;
justify-content: center;
font-weight: bold;
font-size: 0.875rem;
color: white;
cursor: pointer;
box-sizing: border-box;
transition: background-color 0.2s, box-shadow 0.2s;
box-shadow: 0 1px 2px rgba(0, 0, 0, 0.05);
margin-bottom:20px;
}
/* Colors for question grid items */
#mocktest-page .question-grid-item.not-visited, #mocktest-page .question-grid-item.not-answered {
background-color: blue; /* Deep Blue */
}
#mocktest-page .question-grid-item.answered {
background-color: green; /* Green */
}
#mocktest-page .question-grid-item.marked {
background-color: #e53e3e; /* Red */
}
#mocktest-page .question-grid-item.answered-marked {
background-color: #FFEB3B; /* Yellow */
color: #333; /* Black text for yellow background */
}
/* Solution Page specific colors for question grid items */
#solution-page .question-grid-item.correct {
background-color: green; /* Green for correct answers */
}
#solution-page .question-grid-item.incorrect {
background-color: red; /* Red for incorrect answers */
}
#solution-page .question-grid-item.unattempted {
background-color: blue; /* Blue for unattempted questions */
}
#mocktest-page .question-grid-item .triangle, #solution-page .question-grid-item .triangle {
position: absolute;
bottom: -15px; /* Position under the number */
left: 50%;
transform: translateX(-50%);
width: 20px; /* Width of the image */
height: 12px; /* Height of the image */
background-image: url('https://www.testranking.in/next_images/test/arw.gif');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
display: none; /* Hidden by default, shown based on logic */
}
/* The triangle will now be shown if the question is current OR marked */
#mocktest-page .question-grid-item.current .triangle,
#mocktest-page .question-grid-item.marked .triangle,
#mocktest-page .question-grid-item.answered-marked .triangle,
#solution-page .question-grid-item.current .triangle { /* Only current for solution page */
display: block;
}
#mocktest-page .analysis-summary p, #solution-page .analysis-summary p {
color: #333;
font-weight: normal;
}
#mocktest-page .analysis-summary span, #solution-page .analysis-summary span {
font-weight: bold;
color: red;
}
#mocktest-page .right-panel, #solution-page .right-panel {
flex-grow: 1;
background-color: #ffffff;
padding: 1rem;
position: relative; /* For watermark positioning */
transition: width 0.3s ease-in-out, flex-basis 0.3s ease-in-out; /* Smooth transition for width */
}
/* Watermark styles */
.watermark-pt-container {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
overflow: hidden;
pointer-events: none; /* Ensure clicks pass through */
z-index: 1; /* Below content */
}
.watermark-pt-text {
position: absolute;
color: #ccc; /* Light grey */
font-size: 8px; /* Small font size */
opacity: 0.3; /* Semi-transparent */
white-space: nowrap;
transform: rotate(-45deg); /* Rotate for diagonal effect */
transform-origin: 0 0; /* Rotate from top-left */
user-select: none; /* Prevent selection */
}
#mocktest-page .top-action-buttons, #solution-page .top-action-buttons {
display: flex;
justify-content: flex-end;
gap: 0.5rem;
margin-bottom: 1rem;
flex-wrap: wrap;
}
#mocktest-page .top-action-buttons button, #solution-page .top-action-buttons button {
background-color: #007bff;
color: white;
font-weight: bold;
padding: 0.3rem 1rem; /* Adjusted padding */
border-radius: 8px;
border: none;
cursor: pointer;
transition: background-color 0.2s;
white-space: nowrap;
}
#mocktest-page .top-action-buttons button:hover, #solution-page .top-action-buttons button:hover {
background-color: #0056b3;
}
#mocktest-page .top-action-buttons button#mark-review-btn.marked-active, #solution-page .top-action-buttons button#mark-review-btn.marked-active {
background-color: #a0aec0;
}
#mocktest-page .question-status-language, #solution-page .question-status-language {
display: flex;
flex-direction: column;
justify-content: flex-end;
align-items: flex-end;
margin-bottom: 1rem;
}
@media (min-width: 768px) {
#mocktest-page .question-status-language, #solution-page .question-status-language {
flex-direction: row;
align-items: center;
justify-content: flex-end;
}
}
#mocktest-page .question-status-language p, #solution-page .question-status-language p {
color: #333;
font-size: 0.9rem;
margin-bottom: 0.5rem;
}
@media (min-width: 768px) {
#mocktest-page .question-status-language p, #solution-page .question-status-language p {
margin-bottom: 0;
}
}
#mocktest-page .question-status-language span, #solution-page .question-status-language span {
font-weight: bold;
color: #48bb78;
}
#mocktest-page .language-select-container, #solution-page .language-select-container {
display: flex;
align-items: center;
margin-top: 0.5rem;
}
@media (min-width: 768px) {
#mocktest-page .language-select-container, #solution-page .language-select-container {
margin-top: 0;
}
}
#mocktest-page .language-select-container select, #solution-page .language-select-container select {
background-color: #edf2f7;
border: 1px solid #e2e8f0;
color: #333;
padding: 0.25rem 0.5rem;
border-radius: 4px;
}
/* Main Question Display Panel - NEW STYLES */
.question-panel-container {
border: 1px solid #e2e8f0; /* Light gray solid border */
border-radius: 4px;
background-color: #ffffff;
margin-bottom: 1rem;
padding: 0; /* Remove padding from container, add to cells */
position: relative; /* For watermark */
}
.question-panel-header {
display: flex;
justify-content: space-between;
align-items: center;
color: red;
font-size: 14px;
padding: 5px 10px;
border-bottom: 1px solid #ccc;
}
.question-panel-header .question-label {
margin: 0;
color: #333; /* Make it dark like the original */
font-weight: bold;
}
.question-panel-header .last-minutes-text {
color: #e53e3e; /* Red color for last minutes */
font-weight: bold;
}
.question-table {
width: 100%;
border-collapse: collapse;
position: relative;
}
.question-table td {
border: 1px solid #ccc;
padding: 10px;
vertical-align: top;
position: relative;
}
/* Remove the old ::before watermark */
.question-table td::before {
content: none;
}
.question-table .language-cell {
text-align: right;
padding-right: 10px;
}
.question-table .language-cell select {
width: 100px; /* Smaller width to match screenshot */
padding: 5px;
border: 1px solid #ccc;
box-sizing: border-box;
background-color: #edf2f7; /* Match original select background */
color: #333;
border-radius: 4px;
}
.question-table .question-cell {
font-weight: 400;
color: #333;
font-size: 20px; /* Dark text */
}
.question-table .radio-cell {
border-right: 1px solid #ccc;
width: 20px;
text-align: center;
vertical-align: middle;
}
.question-table .text-cell {
padding-left: 10px;
color: #333; /* Dark text */
}
.question-table input[type="radio"] {
margin: 0;
accent-color: #007BFF; /* Dark blue filled radio button */
transform: scale(1.2); /* Make radio button slightly larger */
}
/* Solution Page specific option highlighting */
.solution-option-correct {
background-color: #d1fae5; /* Light green */
border: 1px solid #34d399; /* Green border */
padding: 0.5rem;
border-radius: 0.25rem;
margin-bottom: 0.25rem;
}
.solution-option-incorrect {
background-color: #fee2e2; /* Light red */
border: 1px solid #ef4444; /* Red border */
padding: 0.5rem;
border-radius: 0.25rem;
margin-bottom: 0.25rem;
}
.solution-option-normal {
padding: 0.5rem;
border-radius: 0.25rem;
margin-bottom: 0.25rem;
border: 1px solid transparent;
}
.solution-text {
margin-top: 1rem;
padding-top: 1rem;
border-top: 1px solid #e2e8f0;
font-size: 0.95rem;
color: #333;
}
/* KaTeX specific styling for better integration */
#mocktest-page .katex, #solution-page .katex {
font-size: 1em;
line-height: normal;
}
#mocktest-page .katex-display, #solution-page .katex-display {
margin-top: 0.5em;
margin-bottom: 0.5em;
}
/* Modal for Symbols Legend */
#mocktest-page .modal, #solution-page .modal {
display: none;
position: fixed;
z-index: 1000;
left: 0;
top: 0;
width: 100%;
height: 100%;
overflow: auto;
background-color: rgba(0, 0, 0, 0.4);
align-items: center;
justify-content: center;
}
#mocktest-page .modal-content, #solution-page .modal-content {
background-color: #fefefe;
margin: auto;
padding: 20px;
border: 1px solid #888;
width: 90%;
max-width: 600px;
border-radius: 8px;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
position: relative;
}
#mocktest-page .close-button, #solution-page .close-button {
color: #aaa;
float: right;
font-size: 28px;
font-weight: bold;
position: absolute;
top: 10px;
right: 20px;
cursor: pointer;
}
#mocktest-page .close-button:hover, #mocktest-page .close-button:focus, #solution-page .close-button:hover, #solution-page .close-button:focus {
color: black;
text-decoration: none;
cursor: pointer;
}
#mocktest-page .modal-table, #solution-page .modal-table {
width: 100%;
border-collapse: collapse;
margin-top: 1rem;
}
#mocktest-page .modal-table th, #mocktest-page .modal-table td, #solution-page .modal-table th, #solution-page .modal-table td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
#mocktest-page .modal-table th, #solution-page .modal-table th {
background-color: #f2f2f2;
font-weight: bold;
}
#mocktest-page .modal-table .symbol-cell, #solution-page .modal-table .symbol-cell {
width: 80px;
text-align: center;
font-weight: bold;
}
#mocktest-page .modal-table .symbol-cell .question-grid-item-legend, #solution-page .modal-table .symbol-cell .question-grid-item-legend {
display: inline-flex;
width: 25px; /* Smaller size for legend */
height: 25px;
align-items: center;
justify-content: center;
font-weight: bold;
color: white;
border: 1px solid white;
box-sizing: border-box;
margin: 0 auto;
border-radius: 0; /* Sharp corners */
}
#mocktest-page .modal-table .symbol-cell .question-grid-item-legend.blue, #solution-page .modal-table .symbol-cell .question-grid-item-legend.blue {
background-color: #007BFF;
}
#mocktest-page .modal-table .symbol-cell .question-grid-item-legend.green, #solution-page .modal-table .symbol-cell .question-grid-item-legend.green {
background-color: #48bb78;
}
#mocktest-page .modal-table .symbol-cell .question-grid-item-legend.red, #solution-page .modal-table .symbol-cell .question-grid-item-legend.red {
background-color: #e53e3e;
}
#mocktest-page .modal-table .symbol-cell .question-grid-item-legend.yellow, #solution-page .modal-table .symbol-cell .question-grid-item-legend.yellow {
background-color: #FFEB3B;
color: #333;
}
#mocktest-page .modal-table .symbol-cell .triangle-legend, #solution-page .modal-table .symbol-cell .triangle-legend {
display: inline-block;
width: 20px; /* Width of the image */
height: 12px; /* Height of the image */
background-image: url('https://www.testranking.in/next_images/test/arw.gif');
background-size: contain;
background-repeat: no-repeat;
background-position: center;
vertical-align: middle;
margin-right: 5px;
}
/* Zoom buttons from provided code */
.zoom-button {
background-color: blue;
color: #ffffff;
padding: 0.3rem 0.5rem;
border: 1px solid #a0aec0;
border-radius: 10px;
font-size: 0.8rem;
margin: 0 0.25rem;
font-weight: 600;
}
/* Left panel toggle button */
.toggle-left-panel-btn {
background-color: #f0f0f0; /* Light gray background */
border: 1px solid #ccc; /* Light gray border */
border-radius: 4px;
cursor: pointer;
padding: 5px;
display: flex;
align-items: center;
justify-content: center;
transition: background-color 0.2s;
}
.toggle-left-panel-btn:hover {
background-color: #e0e0e0;
}
/* Review before submit modal */
#review-submit-modal {
position: fixed;
inset: 0;
background-color: rgba(0, 0, 0, 0.75);
display: none;
align-items: center;
justify-content: center;
padding: 1rem;
z-index: 2000;
}
#review-submit-modal .modal-content {
background-color: white;
padding: 1.5rem;
border-radius: 0.5rem;
box-shadow: 0 5px 15px rgba(0, 0, 0, 0.3);
width: 100%;
max-width: 800px;
max-height: 90vh;
overflow-y: auto;
position: relative;
}
#review-submit-modal .modal-close-btn {
position: absolute;
top: 10px;
right: 10px;
padding: 8px;
background: none;
border: none;
cursor: pointer;
color: #4a5568;
font-size: 1.5rem;
}
#review-submit-modal table {
width: 100%;
border-collapse: collapse;
margin-top: 1rem;
}
#review-submit-modal th, #review-submit-modal td {
border: 1px solid #ddd;
padding: 8px;
text-align: left;
}
#review-submit-modal th {
background-color: #f2f2f2;
font-weight: bold;
}
</style>
<body class="">
<!-- Page 1: Input Page -->
<div id="input-page" class="page-container">
<div class="page-card">
<h1 class="text-2xl font-bold text-center mb-6 text-gray-800">
<span class="text-green-600">Powered by</span> Joharul Hasan
</h1>
<p class="text-center text-gray-600 mb-8">Paste your MCQ questions below and generate an interactive quiz </p>
<div class="mb-4">
<label for="topic-input" class="block text-gray-700 text-lg font-medium mb-3">Topic</label>
<input type="text" id="topic-input" class="w-full p-4 border-2 border-blue-500 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent text-gray-800" placeholder="e.g., Algebra, Geometry, Calculus">
</div>
<div class="mb-6">
<label for="question-input-part-a" class="block text-gray-700 text-lg font-medium mb-3">Enter MCQ Questions for Part A</label>
<textarea id="question-input-part-a" rows="7" class="w-full p-4 border-2 border-blue-500 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent text-gray-800 resize-y" placeholder="Paste questions for Part A here."></textarea>
</div>
<div class="mb-6">
<label for="question-input-part-b" class="block text-gray-700 text-lg font-medium mb-3">Enter MCQ Questions for Part B</label>
<textarea id="question-input-part-b" rows="7" class="w-full p-4 border-2 border-blue-500 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent text-gray-800 resize-y" placeholder="Paste questions for Part B here."></textarea>
</div>
<div class="mb-6">
<label for="question-input-part-c" class="block text-gray-700 text-lg font-medium mb-3">Enter MCQ Questions for Part C</label>
<textarea id="question-input-part-c" rows="7" class="w-full p-4 border-2 border-blue-500 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent text-gray-800 resize-y" placeholder="Paste questions for Part C here."></textarea>
</div>
<div class="mb-6">
<label for="question-input-part-d" class="block text-gray-700 text-lg font-medium mb-3">Enter MCQ Questions for Part D</label>
<textarea id="question-input-part-d" rows="7" class="w-full p-4 border-2 border-blue-500 rounded-lg focus:outline-none focus:ring-2 focus:ring-blue-400 focus:border-transparent text-gray-800 resize-y" placeholder="Paste questions for Part D here."></textarea>
</div>
<button id="start-mocktest-btn" class="w-full bg-gradient-to-r from-blue-500 to-purple-600 text-white font-semibold py-3 px-6 rounded-lg shadow-lg hover:from-blue-600 hover:to-purple-700 transition duration-300 transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75">
Start Mock Test
</button>
</div>
</div>
<!-- Page 2: Mock Test Page -->
<div id="mocktest-page" class="hidden">
<div class="container">
<header class="header">
<div class="header-left flex items-center justify-between w-full px-4">
<!-- Left section -->
<div class="flex items-center">
<img src="https://placehold.co/40x40/000000/FFFFFF?text=SSC" alt="SSC Logo" class="h-10 w-10 mr-4 rounded-full border border-gray-400">
<span class="text-gray-700 font-semibold text-sm mr-4">SSC-Mock Test</span>
<button class="zoom-button" id="zoom-in-btn">Zoom (+)</button>
<button class="zoom-button" id="zoom-out-btn">Zoom (-)</button>
</div>
<!-- Center section (Roll No) -->
<div class="absolute left-1/2 transform -translate-x-1/2 text-center">
<span class="text-gray-700 font-semibold text-lg block"> SSC CGL Tier | 2025 - Free Test </span>
<span class="text-gray-700 font-semibold text-xs block"> Roll No: 100181161242163 (Candidate Name) </span>
</div>
<!-- Right placeholder (optional to maintain spacing) -->
<div class="flex items-center space-x-4">
<button id="fullscreen-btn" class="zoom-button">Show Fullscreen</button>
<!-- Time Box -->
<div class="text-right">
<p class="text-gray-600 text-xs">Time Left</p>
<div class="time-left-box">
<span id="total-time-left-panel">00:00:00</span>
</div>
</div>
<!-- Avatars -->
<div class="flex space-x-2 pr-10">
<img class="inline-block h-14 w-14 ring-2 ring-white user-avatar" src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSAkjktNk_waKZ6A064JikKQRYLxoKPNIUR_g&s" alt="User 1">
<img class="inline-block h-14 w-14 ring-2 ring-white user-avatar" src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSAkjktNk_waKZ6A064JikKQRYLxoKPNIUR_g&s" alt="User 2">
</div>
</div>
</div>
</header>
<div class="instructions-warning1">
<div class="flex mb-2 md:mb-0">
<button id="symbols-tab" class="part-tab-button active rounded-t-md" style="border-radius: 4px 4px 0 0;">SYMBOLS</button>
<button id="instructions-tab" class="part-tab-button rounded-t-md" style="border-radius: 4px 4px 0 0;">INSTRUCTIONS</button>
</div>
</div>
<div class="flex mb-2 md:mb-0 ml-2">
<button class="px-2 py-2 bg-white text-orange-600 font-semibold rounded-tl-md rounded-tr-md underline focus:outline-none"> SYMBOLS </button>
<button class="px-2 py-2 bg-white text-orange-600 font-semibold rounded-tl-md rounded-tr-md underline focus:outline-none ml-1"> INSTRUCTIONS </button>
</div>
<div class="main-content flex-grow">
<div id="left-panel" class="left-panel p-4 flex flex-col items-center md:items-start">
<!-- Updated Part Tabs -->
<div class="part-tabs space-x-2">
<button class="part-tab-button active whitespace-normal" data-part="A">PART-A</button>
<button class="part-tab-button" data-part="B">PART-B</button>
<button class="part-tab-button" data-part="C">PART-C</button>
<button class="part-tab-button" data-part="D">PART-D</button>
<!-- Left panel toggle button -->
<div class="col-3 text-right ml-auto">
<div class="cursor-pointer toggle-left-panel-btn" id="hide-left-panel-btn">
<img src="https://www.testranking.in/next_images/test/pt-left.png" height="30" width="20" style="height: 30px; width: auto;">
</div>
</div>
</div>
<p id="part-title" class="text-gray-700 mb-4 font-semibold text-center w-full"></p>
<div id="question-grid" class="question-grid"></div>
<div class="analysis-summary w-full text-center md:text-left mt-2">
<h3 class="text-lg font-semibold text-black text-center bg-gray-300 px-4 py-2 shadow-sm my-2">
<span id="current-part-analysis-title"></span> Analysis
</h3>
<p class="text-sm font-medium"> Answered: <span id="answered-count" class="bg-yellow-200 text-red-400 px-1 py-1 inline-block"> 0 </span> </p>
<p class="text-sm font-medium"> Not Answered: <span id="not-answered-count" class="bg-yellow-200 text-red-600 px-1 py-1 inline-block"> 0 </span> </p>
<button id="submit-test-btn" class="mt-4 w-full mx-4 bg-blue-600 text-white font-semibold py-2 px-4 rounded-lg hover:bg-blue-700 transition duration-300">SUBMIT TEST</button>
</div>
</div>
<div id="right-panel" class="right-panel p-4">
<!-- Watermark Container -->
<div class="watermark-pt-container">
<!-- Watermark content will be generated dynamically by JS -->
</div>
<div class="flex flex-col sm:flex-row justify-end items-center mb-4 space-y-2 sm:space-y-0">
<div class="flex top-action-buttons">
<button class="actionBtn" id="prev-btn">Previous</button>
<button class="actionBtn" id="mark-review-btn">Mark for Review</button>
<button class="actionBtn" id="save-next-btn">Save & Next</button>
</div>
<p class="text-gray-700 text-lg mb-2 sm:mb-0 flex flex-col sm:flex-row justify-end items-center ml-24 font-bold"> Total Questions Answered: <span id="total-answered" class="ml-2 px-1 py-1 bg-yellow-200 text-red-600 rounded"> 0 </span> </p>
</div>
<!-- Updated Question Panel -->
<div id="question-panel-container" class="question-panel-container">
<div class="question-panel-header">
<span class="question-label font-semibold text-lg">Question : <span id="question-number">1</span></span>
<span class="last-minutes-text text-xl font-bold">Last <span class="text-blue-800" id="last-minutes-display">60</span> Minutes</span>
</div>
<table class="question-table">
<tr>
<td class="language-cell" colspan="2">
Select Language:
<select id="language-select">
<option value="english">English</option>
</select>
</td>
</tr>
<tr>
<td id="question-text-cell" class="question-cell" colspan="2">
<!-- Question text will be inserted here -->
</td>
</tr>
<!-- Options will be dynamically inserted here -->
<tbody id="options-table-body">
</tbody>
</table>
<div id="solution-section" class="solution-text p-4 hidden">
<h4 class="font-bold text-lg mb-2">Solution:</h4>
<p id="solution-content"></p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Page 4: Solution Page -->
<div id="solution-page" class="hidden">
<div class="container">
<header class="header">
<div class="header-left flex items-center justify-between w-full px-4">
<!-- Left section -->
<div class="flex items-center">
<img src="https://placehold.co/40x40/000000/FFFFFF?text=SSC" alt="SSC Logo" class="h-10 w-10 mr-4 rounded-full border border-gray-400">
<span class="text-gray-700 font-semibold text-sm mr-4">SSC-Mock Test - Solutions</span>
<button class="zoom-button" id="solution-zoom-in-btn">Zoom (+)</button>
<button class="zoom-button" id="solution-zoom-out-btn">Zoom (-)</button>
</div>
<!-- Center section (Roll No) -->
<div class="absolute left-1/2 transform -translate-x-1/2 text-center">
<span class="text-gray-700 font-semibold text-lg block"> SSC CGL Tier | 2025 - Free Test </span>
<span class="text-gray-700 font-semibold text-xs block"> Roll No: 100181161242163 (Candidate Name) </span>
</div>
<!-- Right placeholder (optional to maintain spacing) -->
<div class="flex items-center space-x-4">
<button id="solution-fullscreen-btn" class="zoom-button">Show Fullscreen</button>
<div class="text-right">
<p class="text-gray-600 text-xs">Test Completed</p>
<div class="time-left-box">
<span id="solution-completion-time"></span>
</div>
</div>
<!-- Avatars -->
<div class="flex space-x-2 pr-10">
<img class="inline-block h-14 w-14 ring-2 ring-white user-avatar" src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSAkjktNk_waKZ6A064JikKQRYLxoKPNIUR_g&s" alt="User 1">
<img class="inline-block h-14 w-14 ring-2 ring-white user-avatar" src="https://encrypted-tbn0.gstatic.com/images?q=tbn:ANd9GcSAkjktNk_waKZ6A064JikKQRYLxoKPNIUR_g&s" alt="User 2">
</div>
</div>
</div>
</header>
<div class="instructions-warning1">
<div class="flex mb-2 md:mb-0">
<button id="solution-symbols-tab" class="part-tab-button active rounded-t-md" style="border-radius: 4px 4px 0 0;">SYMBOLS</button>
<button id="solution-instructions-tab" class="part-tab-button rounded-t-md" style="border-radius: 4px 4px 0 0;">INSTRUCTIONS</button>
</div>
</div>
<div class="flex mb-2 md:mb-0 ml-2">
<button class="px-2 py-2 bg-white text-orange-600 font-semibold rounded-tl-md rounded-tr-md underline focus:outline-none"> SYMBOLS </button>
<button class="px-2 py-2 bg-white text-orange-600 font-semibold rounded-tl-md rounded-tr-md underline focus:outline-none ml-1"> INSTRUCTIONS </button>
</div>
<div class="main-content flex-grow">
<div id="solution-left-panel" class="left-panel p-4 flex flex-col items-center md:items-start">
<!-- Updated Part Tabs -->
<div class="part-tabs space-x-2">
<button class="part-tab-button active whitespace-normal" data-part="A">PART-A</button>
<button class="part-tab-button" data-part="B">PART-B</button>
<button class="part-tab-button" data-part="C">PART-C</button>
<button class="part-tab-button" data-part="D">PART-D</button>
<!-- Left panel toggle button for solution page -->
<div class="col-3 text-right ml-auto">
<div class="cursor-pointer toggle-left-panel-btn" id="solution-hide-left-panel-btn">
<img src="https://www.testranking.in/next_images/test/pt-left.png" height="30" width="20" style="height: 30px; width: auto;">
</div>
</div>
</div>
<p id="solution-part-title" class="text-gray-700 mb-4 font-semibold text-center w-full"></p>
<div id="solution-question-grid" class="question-grid"></div>
<div class="analysis-summary w-full text-center md:text-left mt-2">
<h3 class="text-lg font-semibold text-black text-center bg-gray-300 px-4 py-2 shadow-sm my-2">
<span id="solution-current-part-analysis-title"></span> Analysis
</h3>
<p class="text-sm font-medium"> Answered: <span id="solution-answered-count" class="bg-yellow-200 text-red-400 px-1 py-1 inline-block"> 0 </span> </p>
<p class="text-sm font-medium"> Not Answered: <span id="solution-not-answered-count" class="bg-yellow-200 text-red-600 px-1 py-1 inline-block"> 0 </span> </p>
<button id="back-to-result-btn" class="mt-4 w-full mx-4 bg-blue-600 text-white font-semibold py-2 px-4 rounded-lg hover:bg-blue-700 transition duration-300">Back to Results</button>
</div>
</div>
<div id="solution-right-panel" class="right-panel p-4">
<!-- Watermark Container for solution page -->
<div class="watermark-pt-container">
<!-- Watermark content will be generated dynamically by JS -->
</div>
<div class="flex flex-col sm:flex-row justify-end items-center mb-4 space-y-2 sm:space-y-0">
<div class="flex top-action-buttons">
<button class="actionBtn" id="solution-prev-btn">Previous</button>
<button class="actionBtn" id="solution-next-btn">Next</button>
</div>
<p class="text-gray-700 text-lg mb-2 sm:mb-0 flex flex-col sm:flex-row justify-end items-center ml-24 font-bold"> Total Questions Answered: <span id="solution-total-answered" class="ml-2 px-1 py-1 bg-yellow-200 text-red-600 rounded"> 0 </span> </p>
</div>
<!-- Question Panel for Solutions -->
<div id="solution-question-panel-container" class="question-panel-container">
<div class="question-panel-header">
<span class="question-label font-semibold text-lg">Question : <span id="solution-question-number">1</span></span>
<span class="last-minutes-text text-xl font-bold">Review Mode</span>
</div>
<table class="question-table">
<tr>
<td class="language-cell" colspan="2">
Select Language:
<select id="solution-language-select" disabled>
<option value="english">English</option>
</select>
</td>
</tr>
<tr>
<td id="solution-question-text-cell" class="question-cell" colspan="2">
<!-- Question text will be inserted here -->
</td>
</tr>
<!-- Options will be dynamically inserted here -->
<tbody id="solution-options-table-body">
</tbody>
</table>
<div id="solution-section-content" class="solution-text p-4">
<h4 class="font-bold text-lg mb-2">Solution:</h4>
<p id="solution-dummy-content"></p>
</div>
</div>
</div>
</div>
</div>
</div>
<!-- Page 3: Result Page -->
<div id="result-page" class="hidden page-container"></div>
<!-- Custom Alert Modal -->
<div id="custom-alert-modal" class="hidden fixed inset-0 bg-gray-900 bg-opacity-75 flex items-center justify-center p-4 z-50">
<div class="bg-white p-6 rounded-lg shadow-xl text-center w-full max-w-sm">
<p id="custom-alert-message" class="text-lg font-semibold text-gray-800 mb-6"></p>
<div class="flex justify-center space-x-4">
<button id="custom-alert-ok-btn" class="bg-blue-500 text-white font-semibold py-2 px-6 rounded-lg shadow hover:bg-blue-600 transition duration-200">OK</button>
<button id="custom-alert-cancel-btn" class="bg-gray-300 text-gray-800 font-semibold py-2 px-6 rounded-lg shadow hover:bg-gray-400 transition duration-200 hidden">Cancel</button>
</div>
</div>
</div>
<!-- Question Feedback Modal -->
<div id="question-feedback-modal" class="hidden">
<div class="modal-content">
<button class="modal-close-btn" onclick="document.getElementById('question-feedback-modal').style.display='none'">×</button>
<h3 id="feedback-question-title" class="text-xl font-bold text-gray-800 mb-4"></h3>
<p id="feedback-question-text" class="text-lg text-gray-700 mb-2"></p>
<div id="feedback-status-tag" class="text-sm font-semibold px-2 py-1 rounded-full mb-4 inline-block"></div>
<div id="feedback-time-taken" class="flex items-center text-sm ml-2 mb-4">
<svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-1 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">
<path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M12 8v4l3 3m6-3a9 9 0 11-18 0 9 9 0 0118 0z" />
</svg>
<span id="feedback-time-value" class="font-medium"></span>
</div>
<div id="feedback-options-container" class="space-y-2 mb-4"></div>
<div class="flex justify-between mt-4">
<button id="feedback-prev-btn" class="bg-gray-300 text-gray-800 font-semibold py-2 px-4 rounded-lg hover:bg-gray-400 transition">Previous</button>
<button id="feedback-next-btn" class="bg-blue-600 text-white font-semibold py-2 px-4 rounded-lg hover:bg-blue-700 transition">Next</button>
</div>
</div>
</div>
<!-- Review before Submit Modal -->
<div id="review-submit-modal" class="hidden">
<div class="modal-content">
<button class="modal-close-btn" onclick="document.getElementById('review-submit-modal').style.display='none'">×</button>
<h3 class="text-xl font-bold text-gray-800 mb-4">Review test before submit</h3>
<table id="review-summary-table">
<thead>
<tr>
<th>Section name</th>
<th>Questions count</th>
<th>Answered</th>
<th>Not Answered</th>
<th>Marked Answered</th>
<th>Marked for Review</th>
<th>Visited</th>
<th>Not Visited</th>
</tr>
</thead>
<tbody>
<!-- Table rows will be populated by JavaScript -->
</tbody>
</table>
<div class="flex justify-center mt-6">
<button id="confirm-submit-test-btn" class="bg-blue-600 text-white font-semibold py-2 px-6 rounded-lg shadow hover:bg-blue-700 transition duration-300">Submit Test</button>
</div>
</div>
</div>
<!-- Hidden container for PDF content generation -->
<div id="pdf-content-wrapper"></div>
<script>
// --- Global Variables ---
let questions = []; // This will hold questions for the currently active part
let currentQuestionIndex = 0;
let totalTestStartTime;
let totalTestEndTime;
let totalTimerInterval;
let currentQuestionTimerInterval; // Timer for current question's time tracking
let filteredQuestionIndices = [];
let currentFeedbackIndex = 0;
let testCompletionDateTime;
let currentTopic = '';
let isSymbolsModalOpen = false;
const allQuestionsByPart = { 'A': [], 'B': [], 'C': [], 'D': [] };
const partNames = { 'A': 'General English', 'B': 'General Awareness', 'C': 'General Intelligence', 'D': 'General Quantitative Aptitude' };
const partOrder = ['A', 'B', 'C', 'D'];
let currentPart = 'A'; // Default active part
let currentQuestionTextFontSize = 20; // Initial font size for question text
let currentOptionTextFontSize = 16; // Initial font size for option text
let inSolutionMode = false; // Flag to indicate if we are in solution mode
let questionStartTimes = {}; // Object to store start times for each question
// --- DOM Elements ---
const inputPage = document.getElementById('input-page');
const mocktestPage = document.getElementById('mocktest-page');
const solutionPage = document.getElementById('solution-page'); // New solution page
const resultPage = document.getElementById('result-page');
const questionInputPartA = document.getElementById('question-input-part-a');
const questionInputPartB = document.getElementById('question-input-part-b');
const questionInputPartC = document.getElementById('question-input-part-c');
const questionInputPartD = document.getElementById('question-input-part-d');
const topicInput = document.getElementById('topic-input');
const startMocktestBtn = document.getElementById('start-mocktest-btn');
const questionPanelContainer = document.getElementById('question-panel-container');
const questionPanelHeader = questionPanelContainer.querySelector('.question-panel-header');
const questionNumberSpan = questionPanelHeader.querySelector('#question-number');
const questionTextCell = document.getElementById('question-text-cell');
const optionsTableBody = document.getElementById('options-table-body');
const languageSelect = document.getElementById('language-select');
const questionGridEl = document.getElementById('question-grid');
const answeredCountEl = document.getElementById('answered-count');
const notAnsweredCountEl = document.getElementById('not-answered-count');
const totalAnsweredEl = document.getElementById('total-answered');
const prevBtn = document.getElementById('prev-btn');
const markReviewBtn = document.getElementById('mark-review-btn');
const saveNextBtn = document.getElementById('save-next-btn');
const submitTestBtn = document.getElementById('submit-test-btn');
const timeLeftEl = document.getElementById('total-time-left-panel');
const lastMinutesDisplay = document.getElementById('last-minutes-display'); // For countdown
const symbolsTab = document.getElementById('symbols-tab');
const instructionsTab = document.getElementById('instructions-tab');
const symbolsModal = document.getElementById('symbols-modal');
const closeModalBtn = symbolsModal.querySelector('.close-button');
const feedbackQuestionTitle = document.getElementById('feedback-question-title');
const feedbackQuestionText = document.getElementById('feedback-question-text');
const feedbackStatusTag = document.getElementById('feedback-status-tag');
const feedbackTimeValue = document.getElementById('feedback-time-value');
const feedbackOptionsContainer = document.getElementById('feedback-options-container');
const feedbackPrevBtn = document.getElementById('feedback-prev-btn');
const feedbackNextBtn = document.getElementById('feedback-next-btn');
const questionFeedbackModal = document.getElementById('question-feedback-modal');
const pdfContentWrapper = document.getElementById('pdf-content-wrapper');
const partTabs = document.querySelectorAll('#mocktest-page .part-tab-button');
const partTitleEl = document.getElementById('part-title');
const currentPartAnalysisTitle = document.getElementById('current-part-analysis-title'); // For part-wise analysis title
// Zoom buttons
const zoomInBtn = document.getElementById('zoom-in-btn');
const zoomOutBtn = document.getElementById('zoom-out-btn');
const fullscreenBtn = document.getElementById('fullscreen-btn');
// Solution Page Elements
const solutionQuestionGridEl = document.getElementById('solution-question-grid');
const solutionPartTabs = document.querySelectorAll('#solution-page .part-tab-button');
const solutionPartTitleEl = document.getElementById('solution-part-title');
const solutionQuestionNumberSpan = document.getElementById('solution-question-number');
const solutionQuestionTextCell = document.getElementById('solution-question-text-cell');
const solutionOptionsTableBody = document.getElementById('solution-options-table-body');
const solutionDummyContent = document.getElementById('solution-dummy-content');
const solutionPrevBtn = document.getElementById('solution-prev-btn');
const solutionNextBtn = document.getElementById('solution-next-btn');
const backToResultBtn = document.getElementById('back-to-result-btn');
const solutionCompletionTimeEl = document.getElementById('solution-completion-time');
const solutionZoomInBtn = document.getElementById('solution-zoom-in-btn');
const solutionZoomOutBtn = document.getElementById('solution-zoom-out-btn');
const solutionFullscreenBtn = document.getElementById('solution-fullscreen-btn');
const solutionAnsweredCountEl = document.getElementById('solution-answered-count');
const solutionNotAnsweredCountEl = document.getElementById('solution-not-answered-count');
const solutionTotalAnsweredEl = document.getElementById('solution-total-answered');
const solutionCurrentPartAnalysisTitle = document.getElementById('solution-current-part-analysis-title');
// Left panel toggle elements
const leftPanel = document.getElementById('left-panel');
const rightPanel = document.getElementById('right-panel');
const hideLeftPanelBtn = document.getElementById('hide-left-panel-btn');
const solutionLeftPanel = document.getElementById('solution-left-panel');
const solutionRightPanel = document.getElementById('solution-right-panel');
const solutionHideLeftPanelBtn = document.getElementById('solution-hide-left-panel-btn');
// Review before submit modal elements
const reviewSubmitModal = document.getElementById('review-submit-modal');
const reviewSummaryTableBody = document.querySelector('#review-summary-table tbody');
const confirmSubmitTestBtn = document.getElementById('confirm-submit-test-btn');
// --- Constants ---
const CORRECT_MARKS = 2;
const WRONG_MARKS = -0.5;
const TOTAL_TEST_DURATION_MINUTES = 60;
const ESTIMATED_EXAM_QUESTIONS = 25;
const REATTEMPT_DIFFICULT_TIME_LIMIT_MINUTES = 25;
// --- Fetch API Functions ---
async function fetchQuestions() {
try {
const response = await fetch('https://sscapi.trixofly.com/api/questions/');
if (!response.ok) throw new Error('Network response was not ok');
const data = await response.json();
console.log("API Data:", data);
if (data && data.length > 0) {
currentTopic = data[0].subject;
const partInputMap = { 'A': questionInputPartA, 'B': questionInputPartB, 'C': questionInputPartC, 'D': questionInputPartD };
partOrder.forEach((partKey, index) => {
if (data[index] && data[index].data && partInputMap[partKey]) {
let formattedQuestions = '';
data[index].data.forEach(question => {
formattedQuestions += `${question.serial}. ${question.question}\n`;
formattedQuestions += `(a) ${question.options.a}\n`;
formattedQuestions += `(b) ${question.options.b}\n`;
formattedQuestions += `(c) ${question.options.c}\n`;
formattedQuestions += `(d) ${question.options.d}\n`;
formattedQuestions += `Answer: (${question.answer}) ${question.options[question.answer]}\n\n`;
});
partInputMap[partKey].value = formattedQuestions;
} else if (partInputMap[partKey]) {
partInputMap[partKey].value = '';
}
});
}
} catch (error) {
console.error('Fetch error:', error);
}
}
// --- Utility Functions ---
function showAlert(message, title = "Alert", onConfirm = null) {
const modal = document.getElementById('custom-alert-modal');
const msgElement = document.getElementById('custom-alert-message');
const okBtn = document.getElementById('custom-alert-ok-btn');
const cancelBtn = document.getElementById('custom-alert-cancel-btn');
msgElement.textContent = message;
modal.classList.remove('hidden');
if (onConfirm) {
okBtn.textContent = 'Yes Submit';
cancelBtn.textContent = 'Close';
cancelBtn.classList.remove('hidden');
okBtn.onclick = () => {
modal.classList.add('hidden');
onConfirm(true);
};
cancelBtn.onclick = () => {
modal.classList.add('hidden');
onConfirm(false);
};
} else {
okBtn.textContent = 'OK';
cancelBtn.classList.add('hidden');
okBtn.onclick = () => modal.classList.add('hidden');
}
}
/**
* Parses raw text input into a structured array of question objects.
*/
function parseQuestions(rawText) {
const lines = rawText.split('\n').map(line => line.trim()).filter(line => line.length > 0);
const parsed = [];
let currentQuestion = null;
let optionsCounter = 0;
lines.forEach(line => {
const questionMatch = line.match(/^(\d+)\.\s*(.+)/);
if (questionMatch) {
if (currentQuestion) {
parsed.push(currentQuestion);
}
currentQuestion = {
originalNumber: parseInt(questionMatch[1]),
question: questionMatch[2].trim(),
options: [],
answer: '',
userAnswer: null,
status: 'not-visited',
timeTaken: 0, // Initialize timeTaken for each question
solution: `This is a dummy solution for Question ${questionMatch[1]}. The actual solution would involve detailed steps and explanations. For example, if it's an algebra question, the solution might show the derivation of the formula or the step-by-step calculation. If it's a geometry question, it might include diagrams and proofs. This dummy text serves as a placeholder to demonstrate the solution display functionality.`,
};
optionsCounter = 0;
} else if (currentQuestion && optionsCounter < 4) {
const optionMatch = line.match(/^\(([a-d])\)\s*(.*)/);
if (optionMatch) {
currentQuestion.options.push(line);
optionsCounter++;
} else if (currentQuestion.options.length === 0 && !currentQuestion.answer) {
// If it's a continuation of the question text before options
currentQuestion.question += ' ' + line;
}
} else if (line.startsWith('Answer:') && currentQuestion) {
const match = line.match(/\(([a-d])\)/);
if (match && match[1]) {
currentQuestion.answer = match[1];
}
}
});
if (currentQuestion) {
parsed.push(currentQuestion);
}
return parsed.filter(q => q.options.length === 4 && q.answer);
}
function formatMillisecondsToWords(totalMilliseconds) {
const totalSeconds = Math.round(totalMilliseconds / 1000);
const minutes = Math.floor(totalSeconds / 60);
const seconds = totalSeconds % 60;
let result = '';
if (minutes > 0) {
result += `${minutes} minute${minutes > 1 ? 's' : ''}`;
}
if (seconds > 0) {
if (result) result += ' ';
result += `${seconds} sec`;
} else if (totalSeconds === 0) {
result = '0 sec';
}
return result;
}
function formatHoursMinutesSeconds(totalMilliseconds) {
const totalSeconds = Math.round(totalMilliseconds / 1000);
const hours = Math.floor(totalSeconds / 3600);
const minutes = Math.floor((totalSeconds % 3600) / 60);
const seconds = totalSeconds % 60;
const pad = (num) => String(num).padStart(2, '0');
return `${pad(hours)}:${pad(minutes)}:${pad(seconds)}`;
}
function formatDate(date) {
const day = String(date.getDate()).padStart(2, '0');
const month = String(date.getMonth() + 1).padStart(2, '0');
const year = date.getFullYear();
return `${day}/${month}/${year}`;
}
function formatTime(date) {
let hours = date.getHours();
const minutes = String(date.getMinutes()).padStart(2, '0');
const seconds = String(date.getSeconds()).padStart(2, '0');
const ampm = hours >= 12 ? 'PM' : 'AM';
hours = hours % 12 || 12;
return `${String(hours).padStart(2, '0')}:${minutes}:${seconds} ${ampm}`;
}
function getQuestionTimeDisplayColorClass(totalMilliseconds, question) {
const seconds = Math.round(totalMilliseconds / 1000);
if (question.userAnswer === null) return 'text-gray-500';
if (seconds <= 60) return 'text-green-600';
else if (seconds <= 90) return 'text-yellow-600';
else return 'text-red-600';
}
function getQuestionPaletteColorClasses(question) {
const selectedAnswer = question.userAnswer;
const correctAnswer = question.answer;
const timeTakenMilliseconds = question.timeTaken || 0;
const timeTakenSeconds = Math.round(timeTakenMilliseconds / 1000);
const isAttempted = selectedAnswer !== null;
const isCorrect = isAttempted && selectedAnswer.includes(`(${correctAnswer})`);
if (!isAttempted) {
return { bgColor: 'bg-gray-300', textColor: 'text-gray-800' };
} else if (isCorrect) {
if (timeTakenSeconds > 120) return { bgColor: 'bg-orange-500', textColor: 'text-white' };
else if (timeTakenSeconds > 90) return { bgColor: 'bg-yellow-500', textColor: 'text-yellow-900' };
else return { bgColor: 'bg-green-500', textColor: 'text-white' };
} else {
return { bgColor: 'bg-red-500', textColor: 'text-white' };
}
}
function getChartColor(totalMilliseconds, question) {
const seconds = Math.round(totalMilliseconds / 1000);
if (question.userAnswer === null) return '#9CA3AF';
if (seconds <= 60) return '#10B981';
else if (seconds <= 90) return '#F59E0B';
else return '#EF4444';
}
function getChartBorderColor(totalMilliseconds, question) {
const seconds = Math.round(totalMilliseconds / 1000);
if (question.userAnswer === null) return '#6B7280';
if (seconds <= 60) return '#059669';
else if (seconds <= 90) return '#D97706';
else return '#DC2626';
}
function saveCurrentQuestionTime() {
if (currentQuestionIndex !== null && questions[currentQuestionIndex]) {
const question = questions[currentQuestionIndex];
// Use originalNumber as key for questionStartTimes
const questionKey = `${currentPart}-${question.originalNumber}`;
if (questionStartTimes[questionKey] !== undefined) {
const sessionDuration = Date.now() - questionStartTimes[questionKey];
question.timeTaken += sessionDuration;
questionStartTimes[questionKey] = undefined; // Reset start time for next session
}
}
}
function startQuestionTimer() {
if (currentQuestionIndex !== null && questions[currentQuestionIndex]) {
const question = questions[currentQuestionIndex];
const questionKey = `${currentPart}-${question.originalNumber}`;
if (questionStartTimes[questionKey] === undefined || questionStartTimes[questionKey] === null) {
questionStartTimes[questionKey] = Date.now();
}
}
if (currentQuestionTimerInterval) clearInterval(currentQuestionTimerInterval); // No need for a per-question display timer in the current UI
}
function stopQuestionTimer() {
if (currentQuestionTimerInterval) clearInterval(currentQuestionTimerInterval);
}
function startTotalTestTimer() {
totalTimerInterval = setInterval(() => {
if (isSymbolsModalOpen) return;
const elapsed = Date.now() - totalTestStartTime;
const totalTestDurationMilliseconds = TOTAL_TEST_DURATION_MINUTES * 60 * 1000;
const timeLeft = Math.max(0, totalTestDurationMilliseconds - elapsed);
// Update total time left display (MM:SS)
const minutes = Math.floor(timeLeft / (1000 * 60));
const seconds = Math.floor((timeLeft % (1000 * 60)) / 1000);
timeLeftEl.textContent = `${minutes.toString().padStart(2, '0')} : ${seconds.toString().padStart(2, '0')}`;
// Update "Last X Minutes" display
const remainingMinutes = Math.ceil(timeLeft / (1000 * 60));
lastMinutesDisplay.textContent = remainingMinutes;
if (timeLeft <= 0) {
showReviewSubmitModal(true); // Show review modal on time's up
}
}, 1000);
}
function stopTotalTestTimer() {
if (totalTimerInterval) {
clearInterval(totalTimerInterval);
totalTestEndTime = Date.now();
testCompletionDateTime = new Date();
}
}
function renderMath(element) {
// Ensure KaTeX renders algebraic identities correctly
renderMathInElement(element, {
delimiters: [
{ left: '\\(', right: '\\)', display: false },
{ left: '\\[', right: '\\]', display: true },
{ left: '$$', right: '$$', display: true }, // For block equations
{ left: '$', right: '$', display: false }, // For inline equations
],
throwOnError: false
});
}
function displayQuestion(index = currentQuestionIndex, mode = 'mocktest') {
const targetQuestionGridEl = mode === 'mocktest' ? questionGridEl : solutionQuestionGridEl;
const targetQuestionNumberSpan = mode === 'mocktest' ? questionNumberSpan : solutionQuestionNumberSpan;
const targetQuestionTextCell = mode === 'mocktest' ? questionTextCell : solutionQuestionTextCell;
const targetOptionsTableBody = mode === 'mocktest' ? optionsTableBody : solutionOptionsTableBody;
const targetMarkReviewBtn = markReviewBtn; // Only on mocktest page
const targetSolutionSection = document.getElementById('solution-section'); // For mocktest page
const targetSolutionContent = document.getElementById('solution-content'); // For mocktest page
if (!questions || !Array.isArray(questions) || questions.length === 0) {
targetQuestionNumberSpan.textContent = 'N/A';
targetQuestionTextCell.innerHTML = 'No questions available for this section.';
targetOptionsTableBody.innerHTML = '';
updateQuestionGrid(mode);
updateAnalysisCounts(mode);
return;
}
if (mode === 'mocktest') {
saveCurrentQuestionTime();
}
if (index < 0 || index >= questions.length) {
showAlert("Question index out of bounds.");
return;
}
currentQuestionIndex = index;
const question = questions[currentQuestionIndex];
if (mode === 'mocktest' && question.status === 'not-visited') {
question.status = 'not-answered';
}
targetQuestionNumberSpan.textContent = question.originalNumber;
targetQuestionTextCell.innerHTML = question.question;
targetOptionsTableBody.innerHTML = '';
// Apply font sizes
targetQuestionTextCell.style.fontSize = `${currentQuestionTextFontSize}px`;
question.options.forEach((optionText, idx) => {
const optionId = `q${question.originalNumber}-option${idx}-${mode}`;
const isChecked = question.userAnswer === optionText;
const tr = document.createElement('tr');
const radioCell = document.createElement('td');
radioCell.className = 'radio-cell';
const input = document.createElement('input');
input.type = 'radio';
input.name = `question-${question.originalNumber}`;
input.id = optionId;
input.value = optionText;
input.checked = isChecked;
const textCell = document.createElement('td');
textCell.className = 'text-cell';
textCell.innerHTML = optionText;
textCell.style.fontSize = `${currentOptionTextFontSize}px`; // Apply font size
if (mode === 'mocktest') {
tr.className = 'option-row';
input.addEventListener('change', (event) => {
questions[currentQuestionIndex].userAnswer = event.target.value;
const currentStatus = questions[currentQuestionIndex].status;
if (currentStatus === 'not-answered' || currentStatus === 'not-visited') {
questions[currentQuestionIndex].status = 'answered';
} else if (currentStatus === 'marked') {
questions[currentQuestionIndex].status = 'answered-marked';
}
updateQuestionGrid(mode);
updateAnalysisCounts(mode);
updateTotalAnsweredQuestionsDisplay(); // Update total answered questions
});
input.addEventListener('dblclick', (event) => {
if (questions[currentQuestionIndex].userAnswer === optionText) {
questions[currentQuestionIndex].userAnswer = null;
input.checked = false;
const currentStatus = questions[currentQuestionIndex].status;
if (currentStatus === 'answered') {
questions[currentQuestionIndex].status = 'not-answered';
} else if (currentStatus === 'answered-marked') {
questions[currentQuestionIndex].status = 'marked';
}
updateQuestionGrid(mode);
updateAnalysisCounts(mode);
updateTotalAnsweredQuestionsDisplay(); // Update total answered questions
}
});
} else {
// Solution mode
input.disabled = true; // Disable radio buttons in solution mode
const isCorrectOption = optionText.includes(`(${question.answer})`);
const isSelectedOption = question.userAnswer === optionText;
tr.classList.add('option-row'); // Add this for consistency
if (isCorrectOption) {
tr.classList.add('solution-option-correct');
} else if (isSelectedOption && !isCorrectOption) {
tr.classList.add('solution-option-incorrect');
} else {
tr.classList.add('solution-option-normal');
}
}
radioCell.appendChild(input);
tr.appendChild(radioCell);
tr.appendChild(textCell);
targetOptionsTableBody.appendChild(tr);
});
if (mode === 'mocktest') {
targetMarkReviewBtn.classList.remove('marked-active');
if (question.status === 'marked' || question.status === 'answered-marked') {
targetMarkReviewBtn.classList.add('marked-active');
}
targetSolutionSection.classList.add('hidden'); // Hide solution section in mocktest mode
} else {
// Solution mode
// Display dummy solution
document.getElementById('solution-dummy-content').innerHTML = question.solution;
document.getElementById('solution-section-content').classList.remove('hidden'); // Show solution section
}
if (mode === 'mocktest') {
startQuestionTimer();
}
updateQuestionGrid(mode);
renderMath(targetQuestionTextCell);
renderMath(targetOptionsTableBody);
if (mode === 'solution') {
renderMath(document.getElementById('solution-dummy-content'));
}
}
function initializeQuestionGrid(mode = 'mocktest') {
const targetQuestionGridEl = mode === 'mocktest' ? questionGridEl : solutionQuestionGridEl;
targetQuestionGridEl.innerHTML = '';
const currentPartQuestions = allQuestionsByPart[currentPart];
for (let i = 0; i < currentPartQuestions.length; i++) {
const question = currentPartQuestions[i];
const buttonContainer = document.createElement('div');
buttonContainer.className = 'question-grid-item';
buttonContainer.dataset.index = i;
const triangle = document.createElement('div');
triangle.className = 'triangle';
buttonContainer.appendChild(triangle);
const buttonText = document.createElement('span');
buttonText.textContent = question.originalNumber;
buttonContainer.appendChild(buttonText);
buttonContainer.addEventListener('click', () => {
displayQuestion(parseInt(buttonContainer.dataset.index), mode);
});
targetQuestionGridEl.appendChild(buttonContainer);
}
updateQuestionGrid(mode);
}
function updateQuestionGrid(mode = 'mocktest') {
const targetQuestionGridEl = mode === 'mocktest' ? questionGridEl : solutionQuestionGridEl;
if (!targetQuestionGridEl) return;
const buttons = targetQuestionGridEl.children;
const currentPartQuestions = allQuestionsByPart[currentPart];
for (let i = 0; i < currentPartQuestions.length; i++) {
const question = currentPartQuestions[i];
const buttonContainer = buttons[i];
if (buttonContainer) {
const triangle = buttonContainer.querySelector('.triangle');
buttonContainer.classList.remove('not-visited', 'not-answered', 'answered', 'marked', 'answered-marked', 'current', 'correct', 'incorrect', 'unattempted');
if (mode === 'mocktest') {
buttonContainer.classList.add(question.status);
if (triangle) {
if (i === currentQuestionIndex || question.status === 'marked' || question.status === 'answered-marked') {
triangle.style.display = 'block';
} else {
triangle.style.display = 'none';
}
}
if (i === currentQuestionIndex) {
buttonContainer.classList.add('current');
}
} else {
// Solution mode
const selectedAnswer = question.userAnswer;
const correctAnswerLetter = question.answer;
const isCorrect = selectedAnswer !== null && selectedAnswer.includes(`(${correctAnswerLetter})`);
if (selectedAnswer === null) {
buttonContainer.classList.add('unattempted');
} else if (isCorrect) {
buttonContainer.classList.add('correct');
} else {
buttonContainer.classList.add('incorrect');
}
// In solution mode, triangle only indicates the currently viewed question
if (triangle) {
if (i === currentQuestionIndex) {
triangle.style.display = 'block';
} else {
triangle.style.display = 'none';
}
}
if (i === currentQuestionIndex) {
buttonContainer.classList.add('current');
}
}
}
}
updateAnalysisCounts(mode);
}
function updateAnalysisCounts(mode = 'mocktest') {
const currentPartQuestions = allQuestionsByPart[currentPart];
let answered = 0;
let notAnswered = 0;
currentPartQuestions.forEach(q => {
if (q.userAnswer !== null) {
answered++;
} else {
notAnswered++;
}
});
if (mode === 'mocktest') {
answeredCountEl.textContent = answered;
notAnsweredCountEl.textContent = notAnswered;
// totalAnsweredEl.textContent = answered; // This is the total for the current part
currentPartAnalysisTitle.textContent = `${partNames[currentPart]}`; // Update part analysis title
} else { // Solution mode
solutionAnsweredCountEl.textContent = answered;
solutionNotAnsweredCountEl.textContent = notAnswered;
// solutionTotalAnsweredEl.textContent = answered;
solutionCurrentPartAnalysisTitle.textContent = `${partNames[currentPart]}`;
}
updateTotalAnsweredQuestionsDisplay(); // Always update the global total
}
function updateTotalAnsweredQuestionsDisplay() {
let totalAnswered = 0;
partOrder.forEach(partKey => {
allQuestionsByPart[partKey].forEach(q => {
if (q.userAnswer !== null) {
totalAnswered++;
}
});
});
totalAnsweredEl.textContent = totalAnswered;
solutionTotalAnsweredEl.textContent = totalAnswered;
}
function goToNextQuestionWithSave() {
saveCurrentQuestionTime();
const question = questions[currentQuestionIndex];
if (question.userAnswer !== null) {
if (question.status === 'marked') {
question.status = 'answered-marked';
} else if (question.status === 'not-visited' || question.status === 'not-answered') {
question.status = 'answered';
}
} else if (question.status === 'not-visited') {
question.status = 'not-answered';
}
updateQuestionGrid();
if (currentQuestionIndex < questions.length - 1) {
displayQuestion(currentQuestionIndex + 1);
} else {
const currentPartIndex = partOrder.indexOf(currentPart);
let nextPartFound = false;
for (let i = currentPartIndex + 1; i < partOrder.length; i++) {
const nextPartKey = partOrder[i];
if (allQuestionsByPart[nextPartKey] && allQuestionsByPart[nextPartKey].length > 0) {
currentPart = nextPartKey;
questions = allQuestionsByPart[currentPart];
currentQuestionIndex = 0;
partTabs.forEach(tab => tab.classList.remove('active'));
document.querySelector(`#mocktest-page .part-tab-button[data-part="${currentPart}"]`).classList.add('active');
partTitleEl.textContent = partNames[currentPart];
initializeQuestionGrid();
displayQuestion(0);
nextPartFound = true;
break;
}
}
if (!nextPartFound) {
showReviewSubmitModal(); // Show review modal when all questions are covered
}
}
}
function goToPreviousQuestion() {
saveCurrentQuestionTime();
if (currentQuestionIndex > 0) {
displayQuestion(currentQuestionIndex - 1);
} else {
showAlert("You are at the first question in this section.");
}
}
function markForReviewToggle() {
const question = questions[currentQuestionIndex];
if (!question) return;
if (question.status === 'marked') {
question.status = 'not-answered';
markReviewBtn.classList.remove('marked-active');
} else if (question.status === 'answered-marked') {
question.status = 'answered';
markReviewBtn.classList.remove('marked-active');
} else if (question.userAnswer === null) {
question.status = 'marked';
markReviewBtn.classList.add('marked-active');
} else {
question.status = 'answered-marked';
markReviewBtn.classList.add('marked-active');
}
updateQuestionGrid();
}
function showQuestionFeedback(displayIndex, fromFilter = false) {
let questionIndexToShow;
if (fromFilter) {
questionIndexToShow = filteredQuestionIndices[displayIndex];
currentFeedbackIndex = displayIndex;
} else {
questionIndexToShow = displayIndex;
filteredQuestionIndices = [questionIndexToShow];
currentFeedbackIndex = 0;
}
// Find the actual question object from allQuestionsByPart
let question = null;
let foundPart = null;
for (const partKey of partOrder) {
const partQuestions = allQuestionsByPart[partKey];
const foundQuestion = partQuestions.find(q => q.originalNumber === questions[questionIndexToShow].originalNumber);
if (foundQuestion) {
question = foundQuestion;
foundPart = partKey;
break;
}
}
if (!question) {
showAlert("Question not found for feedback.");
return;
}
const selectedAnswer = question.userAnswer;
const correctAnswer = question.answer;
const timeTaken = question.timeTaken || 0;
feedbackQuestionTitle.textContent = `Question ${question.originalNumber} Feedback`;
feedbackQuestionText.innerHTML = question.question;
if (selectedAnswer !== null) {
feedbackStatusTag.textContent = selectedAnswer.includes(correctAnswer) ? "Correct" : "Incorrect";
feedbackStatusTag.className = `text-sm font-semibold px-2 py-1 rounded-full inline-block ${selectedAnswer.includes(correctAnswer) ? 'bg-green-200 text-green-700' : 'bg-red-200 text-red-700'} mb-4`;
} else {
feedbackStatusTag.textContent = "Unattempted";
feedbackStatusTag.className = "text-sm font-semibold px-2 py-1 rounded-full inline-block bg-yellow-200 text-yellow-700 mb-4";
}
feedbackTimeValue.textContent = formatMillisecondsToWords(timeTaken);
feedbackTimeValue.className = `font-medium ${getQuestionTimeDisplayColorClass(timeTaken, question)}`;
feedbackOptionsContainer.innerHTML = '';
question.options.forEach((optionText) => {
const isCorrectOption = optionText.includes(`(${question.answer})`);
const isSelectedOption = selectedAnswer === optionText;
let colorClass = 'text-gray-800';
let borderColorClass = 'border-gray-200';
if (isCorrectOption) {
colorClass = 'bg-green-100 text-green-700 font-semibold';
borderColorClass = 'border-green-500';
} else if (isSelectedOption && !isCorrectOption) {
colorClass = 'bg-red-100 text-red-700 font-semibold';
borderColorClass = 'border-red-500';
}
const optionElement = document.createElement('p');
optionElement.className = `p-2 border rounded-md ${colorClass} ${borderColorClass}`;
optionElement.innerHTML = optionText;
feedbackOptionsContainer.appendChild(optionElement);
});
feedbackPrevBtn.disabled = currentFeedbackIndex === 0;
feedbackNextBtn.disabled = currentFeedbackIndex === filteredQuestionIndices.length - 1;
questionFeedbackModal.style.display = 'flex';
renderMath(feedbackQuestionText);
renderMath(feedbackOptionsContainer);
}
function navigateFeedbackPrev() {
if (currentFeedbackIndex > 0) {
showQuestionFeedback(currentFeedbackIndex - 1, true);
}
}
function navigateFeedbackNext() {
if (currentFeedbackIndex < filteredQuestionIndices.length - 1) {
showQuestionFeedback(currentFeedbackIndex + 1, true);
}
}
function showFeedbackFiltered(type) {
filteredQuestionIndices = [];
const allQuestionsFlat = [];
partOrder.forEach(partKey => {
allQuestionsByPart[partKey].forEach(q => allQuestionsFlat.push(q));
});
allQuestionsFlat.forEach((question, index) => {
const selectedAnswer = question.userAnswer;
const correctAnswerLetter = question.answer;
const isCorrect = selectedAnswer !== null && selectedAnswer.includes(`(${correctAnswerLetter})`);
if (type === 'correct' && isCorrect) {
filteredQuestionIndices.push(index);
} else if (type === 'incorrect' && selectedAnswer !== null && !isCorrect) {
filteredQuestionIndices.push(index);
} else if (type === 'unattempted' && selectedAnswer === null) {
filteredQuestionIndices.push(index);
} else if (type === 'attempted' && selectedAnswer !== null) {
filteredQuestionIndices.push(index);
}
});
if (filteredQuestionIndices.length > 0) {
showQuestionFeedback(0, true);
} else {
showAlert(`No ${type} questions found.`);
}
}
function renderQuestionStatusChart(correctCount, incorrectCount, unattemptedCount, forPdf = false) {
const ctx = document.getElementById(forPdf ? 'pdfQuestionStatusChart' : 'questionStatusChart').getContext('2d');
const chartInstanceName = forPdf ? 'questionStatusChartInstancePdf' : 'questionStatusChartInstance';
if (window[chartInstanceName]) window[chartInstanceName].destroy();
window[chartInstanceName] = new Chart(ctx, {
type: 'doughnut',
data: {
labels: ['Correct', 'Incorrect', 'Unattempted'],
datasets: [{
data: [correctCount, incorrectCount, unattemptedCount],
backgroundColor: ['#10B981', '#EF4444', '#F59E0B'],
hoverOffset: 4
}]
},
options: {
responsive: true,
maintainAspectRatio: true,
aspectRatio: forPdf ? 1.5 : 1.2,
plugins: {
legend: {
position: forPdf ? 'bottom' : 'top',
labels: {
font: {
size: forPdf ? 10 : 12
}
}
}
}
}
});
}
function renderTimePerQuestionChart(questionTimeDurations, forPdf = false) {
const ctx = document.getElementById(forPdf ? 'pdfTimePerQuestionChart' : 'timePerQuestionChartCanvas').getContext('2d');
const chartInstanceName = forPdf ? 'timePerQuestionChartInstancePdf' : 'timePerQuestionChartInstance';
if (window[chartInstanceName]) window[chartInstanceName].destroy();
window[chartInstanceName] = new Chart(ctx, {
type: 'bar',
data: {
labels: questions.map((_, i) => `Q${i + 1}`),
datasets: [{
label: 'Time Taken (seconds)',
data: questionTimeDurations.map(ms => Math.round(ms / 1000)),
backgroundColor: questions.map(q => getChartColor(q.timeTaken, q)),
borderColor: questions.map(q => getChartBorderColor(q.timeTaken, q)),
borderWidth: 1
}]
},
options: {
responsive: true,
maintainAspectRatio: false,
indexAxis: 'y',
scales: {
x: {
beginAtZero: true,
title: {
display: true,
text: 'Time (seconds)',
font: {
size: forPdf ? 10 : 12
}
},
ticks: {
font: {
size: forPdf ? 8 : 10
}
}
},
y: {
title: {
display: true,
text: 'Question',
font: {
size: forPdf ? 10 : 12
}
},
ticks: {
font: {
size: forPdf ? 8 : 10
}
}
}
},
plugins: {
legend: {
display: false
},
tooltip: {
callbacks: {
label: function (context) {
return `Time: ${context.parsed.x} seconds`;
}
}
}
}
}
});
}
function renderQuestionResultPalette() {
const palette = document.getElementById('question-palette-result');
palette.innerHTML = '';
const allQuestionsFlat = [];
partOrder.forEach(partKey => {
allQuestionsByPart[partKey].forEach(q => allQuestionsFlat.push(q));
});
allQuestionsFlat.forEach((question, index) => {
const { bgColor, textColor } = getQuestionPaletteColorClasses(question);
const button = document.createElement('button');
button.className = `question-palette-btn ${bgColor} ${textColor}`;
button.textContent = question.originalNumber;
button.onclick = () => showQuestionFeedback(index);
palette.appendChild(button);
});
}
function takeScreenshot() {
const element = document.getElementById('result-page');
html2canvas(element, { scale: 2 }).then(canvas => {
const imgData = canvas.toDataURL('image/png');
const link = document.createElement('a');
link.href = imgData;
link.download = 'mock_test_result.png';
link.click();
});
}
function downloadPdf() {
const { jsPDF } = window.jspdf;
const doc = new jsPDF({ orientation: 'portrait', unit: 'mm', format: 'a4' });
const displayTopic = currentTopic || 'General';
doc.setFontSize(16);
doc.text('Mock Test Result', 105, 20, { align: 'center' });
doc.setFontSize(12);
doc.text(`Topic: ${displayTopic}`, 20, 30);
doc.text(`Completed On: ${formatDate(testCompletionDateTime)} at ${formatTime(testCompletionDateTime)}`, 20, 40);
let correctCount = 0;
let incorrectCount = 0;
let score = 0;
// Aggregate all questions from all parts for final scoring and PDF
const allQuestionsFlat = [];
partOrder.forEach(partKey => {
allQuestionsByPart[partKey].forEach(q => allQuestionsFlat.push(q));
});
allQuestionsFlat.forEach((question) => {
const selectedAnswer = question.userAnswer;
if (selectedAnswer) {
if (selectedAnswer.includes(`(${question.answer})`)) {
correctCount++;
score += CORRECT_MARKS;
} else {
incorrectCount++;
score += WRONG_MARKS;
}
}
});
const totalQuestions = allQuestionsFlat.length;
const attemptedQuestions = correctCount + incorrectCount;
const unattemptedQuestions = totalQuestions - attemptedQuestions;
doc.text(`Total Score: ${score.toFixed(2)} / ${(totalQuestions * CORRECT_MARKS).toFixed(2)}`, 20, 50);
doc.text(`Accuracy: ${((correctCount / (attemptedQuestions || 1)) * 100).toFixed(2)}%`, 20, 60);
doc.text(`Correct: ${correctCount}`, 20, 70);
doc.text(`Incorrect: ${incorrectCount}`, 110, 70);
doc.text(`Unattempted: ${unattemptedQuestions}`, 20, 80);
const totalTimeTakenMilliseconds = totalTestEndTime - totalTestStartTime;
const avgTimePerQuestionMilliseconds = totalQuestions > 0 ? totalTimeTakenMilliseconds / totalQuestions : 0;
const estimatedExamTimeMilliseconds = avgTimePerQuestionMilliseconds * ESTIMATED_EXAM_QUESTIONS;
const numQuestionsAttemptedIn25Min = avgTimePerQuestionMilliseconds > 0 ? REATTEMPT_DIFFICULT_TIME_LIMIT_MINUTES * 60 * 1000 / avgTimePerQuestionMilliseconds : 0;
doc.text(`Total Time Taken: ${formatHoursMinutesSeconds(totalTimeTakenMilliseconds)}`, 20, 90);
doc.text(`Avg Time per Question: ${formatMillisecondsToWords(avgTimePerQuestionMilliseconds)}`, 20, 100);
doc.text(`Est. Exam Time (${ESTIMATED_EXAM_QUESTIONS} Qs): ${formatMillisecondsToWords(estimatedExamTimeMilliseconds)}`, 20, 110);
doc.text(`Est. Questions in ${REATTEMPT_DIFFICULT_TIME_LIMIT_MINUTES} min: ${numQuestionsAttemptedIn25Min.toFixed(0)}`, 20, 120);
pdfContentWrapper.innerHTML = `
<div class="chart-container pdf-chart">
<canvas id="pdfQuestionStatusChart"></canvas>
</div>
<div class="chart-container pdf-chart">
<canvas id="pdfTimePerQuestionChart"></canvas>
</div>
${allQuestionsFlat.map((question) => { // Use allQuestionsFlat here
const selectedAnswer = question.userAnswer;
const correctAnswerLetter = question.answer;
const isCorrect = selectedAnswer !== null && selectedAnswer.includes(`(${correctAnswerLetter})`);
return `
<div class="question-feedback-item">
<h3 class="font-semibold text-lg">Question ${question.originalNumber}</h3>
<p class="text-gray-700 mb-2">${question.question}</p>
<p class="text-sm font-medium mb-1">Status: <span class="${selectedAnswer ? (isCorrect ? 'text-green-600' : 'text-red-600') : 'text-yellow-600'}">${selectedAnswer ? (isCorrect ? 'Correct' : 'Incorrect') : 'Unattempted'}</span></p>
<p class="text-sm font-medium mb-2">Time Taken: <span class="${getQuestionTimeDisplayColorClass(question.timeTaken || 0, question)}">${formatMillisecondsToWords(question.timeTaken || 0)}</span></p>
<div class="space-y-1">
${question.options.map((opt) => {
const optLetterMatch = opt.match(/^\(([a-d])\)/);
const optLetter = optLetterMatch ? optLetterMatch[1] : '';
const isCurrentOptionCorrect = optLetter === correctAnswerLetter;
const isCurrentOptionSelected = selectedAnswer === opt;
let className = 'option-item';
if (isCurrentOptionCorrect) className += ' correct';
else if (isCurrentOptionSelected && !isCurrentOptionCorrect) className += ' incorrect';
return `<p class="${className}">${opt}</p>`;
}).join('')}
</div>
</div>
`;
}).join('')}
`;
renderMath(pdfContentWrapper); // Render math in PDF content wrapper
renderQuestionStatusChart(correctCount, incorrectCount, unattemptedQuestions, true);
renderTimePerQuestionChart(allQuestionsFlat.map(q => q.timeTaken || 0), true); // Pass timeTaken values
html2canvas(pdfContentWrapper, { scale: 2 }).then(canvas => {
const imgData = canvas.toDataURL('image/png');
const imgProps = doc.getImageProperties(imgData);
const pdfWidth = doc.internal.pageSize.getWidth();
const pdfHeight = (imgProps.height * pdfWidth) / imgProps.width;
let yPosition = 130; // Starting Y position after initial text
// Add the first image
doc.addImage(imgData, 'PNG', 0, yPosition, pdfWidth, pdfHeight);
// Calculate and add subsequent pages if content overflows
let remainingHeight = pdfHeight;
let currentPageHeight = doc.internal.pageSize.getHeight() - yPosition; // Height remaining on first page
while (remainingHeight > currentPageHeight) {
doc.addPage();
remainingHeight -= currentPageHeight;
yPosition = 0; // Reset Y position for new page
currentPageHeight = doc.internal.pageSize.getHeight(); // Full page height for subsequent pages
doc.addImage(imgData, 'PNG', 0, - (pdfHeight - remainingHeight), pdfWidth, pdfHeight); // Adjust Y to show correct part of image
}
doc.save('mock_test_result.pdf');
});
}
function reattemptDifficultQuestions() {
const difficultQuestions = [];
partOrder.forEach(partKey => {
allQuestionsByPart[partKey].forEach((question) => {
const selectedAnswer = question.userAnswer;
const correctAnswerLetter = question.answer;
const isCorrect = selectedAnswer !== null && selectedAnswer.includes(`(${correctAnswerLetter})`);
const timeTaken = question.timeTaken || 0;
if (!isCorrect || timeTaken > 90 * 1000) {
difficultQuestions.push({ ...question, userAnswer: null, status: 'not-visited', timeTaken: 0 }); // Reset for reattempt
}
});
});
if (difficultQuestions.length === 0) {
showAlert('No difficult questions to reattempt.');
return;
}
// For reattempt, we'll put all difficult questions into Part A for simplicity.
// A more complex implementation would distribute them back into their original parts
// or create a new "Difficult Questions" part.
allQuestionsByPart['A'] = difficultQuestions;
partOrder.forEach(partKey => {
if (partKey !== 'A') {
allQuestionsByPart[partKey] = []; // Clear other parts
}
});
currentPart = 'A';
questions = allQuestionsByPart[currentPart];
currentQuestionIndex = 0;
totalTestStartTime = Date.now();
totalTestEndTime = undefined;
testCompletionDateTime = undefined;
questionStartTimes = {}; // Reset question start times for reattempt
resultPage.classList.add('hidden');
mocktestPage.classList.remove('hidden');
solutionPage.classList.add('hidden'); // Hide solution page if visible
partTabs.forEach(tab => tab.classList.remove('active'));
document.querySelector(`#mocktest-page .part-tab-button[data-part="A"]`).classList.add('active');
partTitleEl.textContent = partNames['A'];
updatePartTabVisibility();
initializeQuestionGrid();
displayQuestion(0);
startTotalTestTimer();
}
function resetApp() {
questions = [];
partOrder.forEach(partKey => {
allQuestionsByPart[partKey] = [];
});
currentQuestionIndex = 0;
totalTestStartTime = undefined;
totalTestEndTime = undefined;
testCompletionDateTime = undefined;
currentTopic = '';
currentPart = 'A';
inSolutionMode = false; // Reset solution mode flag
questionStartTimes = {}; // Reset question start times
if (totalTimerInterval) clearInterval(totalTimerInterval);
if (currentQuestionTimerInterval) clearInterval(currentQuestionTimerInterval);
resultPage.classList.add('hidden');
mocktestPage.classList.add('hidden');
solutionPage.classList.add('hidden'); // Hide solution page
inputPage.classList.remove('hidden');
questionInputPartA.value = '';
questionInputPartB.value = '';
questionInputPartC.value = '';
questionInputPartD.value = '';
topicInput.value = '';
partTabs.forEach(tab => tab.classList.remove('active'));
document.querySelector(`#mocktest-page .part-tab-button[data-part="A"]`).classList.add('active');
partTitleEl.textContent = partNames['A'];
updatePartTabVisibility();
// Reset font sizes
currentQuestionTextFontSize = 20;
currentOptionTextFontSize = 16;
questionTextCell.style.fontSize = `${currentQuestionTextFontSize}px`; // Options font size will be set in displayQuestion
}
function submitTest() {
saveCurrentQuestionTime();
stopTotalTestTimer();
stopQuestionTimer();
let correctCount = 0;
let incorrectCount = 0;
let score = 0;
const allQuestionsFlat = [];
partOrder.forEach(partKey => {
allQuestionsByPart[partKey].forEach(q => allQuestionsFlat.push(q));
});
allQuestionsFlat.forEach((question) => {
const selectedAnswer = question.userAnswer;
if (selectedAnswer) {
if (selectedAnswer.includes(`(${question.answer})`)) {
correctCount++;
score += CORRECT_MARKS;
} else {
incorrectCount++;
score += WRONG_MARKS;
}
}
});
const totalQuestions = allQuestionsFlat.length;
const attemptedQuestions = correctCount + incorrectCount;
const unattemptedQuestions = totalQuestions - attemptedQuestions;
const accuracy = attemptedQuestions > 0 ? (correctCount / attemptedQuestions) * 100 : 0;
const totalTimeTakenMilliseconds = totalTestEndTime - totalTestStartTime;
// Sum up timeTaken from all questions
let sumOfQuestionTimesMilliseconds = allQuestionsFlat.reduce((sum, q) => sum + (q.timeTaken || 0), 0);
const avgTimePerQuestionMilliseconds = totalQuestions > 0 ? sumOfQuestionTimesMilliseconds / totalQuestions : 0;
const estimatedExamTimeMilliseconds = avgTimePerQuestionMilliseconds * ESTIMATED_EXAM_QUESTIONS;
const numQuestionsAttemptedIn25Min = avgTimePerQuestionMilliseconds > 0 ? REATTEMPT_DIFFICULT_TIME_LIMIT_MINUTES * 60 * 1000 / avgTimePerQuestionMilliseconds : 0;
mocktestPage.classList.add('hidden');
inputPage.classList.add('hidden');
solutionPage.classList.add('hidden'); // Hide solution page
resultPage.classList.remove('hidden');
const completionDate = testCompletionDateTime ? formatDate(testCompletionDateTime) : 'N/A';
const completionTime = testCompletionDateTime ? formatTime(testCompletionDateTime) : 'N/A';
const displayTopic = currentTopic || 'General';
resultPage.innerHTML = `
<div class="page-card">
<h2 class="text-3xl font-bold text-center mb-2 text-gray-800">Mock Test Result</h2>
<p class="text-center text-gray-600 mb-4">Topic: <span class="font-semibold">${displayTopic}</span></p>
<p class="text-center text-gray-600 mb-6">Completed On: <span class="font-semibold">${completionDate}</span> at <span class="font-semibold">${completionTime}</span></p>
<div class="grid grid-cols-1 md:grid-cols-2 gap-6 mb-8">
<div class="bg-blue-100 p-6 rounded-lg text-center">
<p class="text-2xl font-bold text-blue-700">${score.toFixed(2)} / ${(totalQuestions * CORRECT_MARKS).toFixed(2)}</p>
<p class="text-gray-600">Total Score</p>
<div class="w-full bg-gray-200 rounded-full h-2.5 mt-2">
<div class="bg-blue-600 h-2.5 rounded-full" style="width: ${((score / (totalQuestions * CORRECT_MARKS)) * 100) || 0}%"></div>
</div>
</div>
<div class="bg-green-100 p-6 rounded-lg text-center">
<p class="text-2xl font-bold text-green-700">${accuracy.toFixed(2)}%</p>
<p class="text-gray-600">Accuracy</p>
<div class="w-full bg-gray-200 rounded-full h-2.5 mt-2">
<div class="bg-green-600 h-2.5 rounded-full" style="width: ${accuracy}%"></div>
</div>
</div>
</div>
<div class="bg-gray-50 p-6 rounded-lg mb-8">
<h3 class="text-xl font-semibold text-gray-800 mb-4">Question Breakdown</h3>
<div class="grid grid-cols-2 sm:grid-cols-4 gap-4 text-center">
<div class="cursor-pointer hover:bg-gray-100 p-2 rounded-lg" onclick="showFeedbackFiltered('correct')">
<p class="text-3xl font-bold text-green-600">${correctCount}</p>
<p class="text-gray-600">Correct</p>
</div>
<div class="cursor-pointer hover:bg-gray-100 p-2 rounded-lg" onclick="showFeedbackFiltered('incorrect')">
<p class="text-3xl font-bold text-red-600">${incorrectCount}</p>
<p class="text-gray-600">Incorrect</p>
</div>
<div class="cursor-pointer hover:bg-gray-100 p-2 rounded-lg" onclick="showFeedbackFiltered('unattempted')">
<p class="text-3xl font-bold text-yellow-600">${unattemptedQuestions}</p>
<p class="text-gray-600">Unattempted</p>
</div>
<div class="cursor-pointer hover:bg-gray-100 p-2 rounded-lg" onclick="showFeedbackFiltered('attempted')">
<p class="text-3xl font-bold text-indigo-600">${attemptedQuestions}</p>
<p class="text-gray-600">Attempted</p>
</div>
</div>
<div class="chart-container mt-8">
<canvas id="questionStatusChart"></canvas>
</div>
</div>
<div class="bg-white p-6 rounded-lg mb-8">
<h3 class="text-xl font-semibold text-gray-800 mb-4">Question Status Palette</h3>
<div id="question-palette-result"></div>
</div>
<div class="bg-gray-50 p-6 rounded-lg mb-8">
<h3 class="text-xl font-semibold text-gray-800 mb-4">Time Breakdown</h3>
<div class="horizontal-chart-wrapper">
<div class="chart-container">
<canvas id="timePerQuestionChartCanvas"></canvas>
</div>
</div>
<ul class="time-breakdown-list mt-4">
${allQuestionsFlat.map((question) => `
<li>
<span>Question ${question.originalNumber}</span>
<span class="${getQuestionTimeDisplayColorClass(question.timeTaken || 0, question)}">${formatMillisecondsToWords(question.timeTaken || 0)}</span>
</li>
`).join('')}
</ul>
</div>
<div class="bg-gray-50 p-6 rounded-lg mb-8">
<h3 class="text-xl font-semibold text-gray-800 mb-4">Performance Summary</h3>
<p class="text-gray-700 mb-2">Total Time Taken: <span class="font-semibold">${formatHoursMinutesSeconds(totalTimeTakenMilliseconds)}</span></p>
<p class="text-gray-700 mb-2">Average Time per Question: <span class="font-semibold">${formatMillisecondsToWords(avgTimePerQuestionMilliseconds)}</span></p>
<p class="text-gray-700 mb-2">Estimated Exam Completion Time (${ESTIMATED_EXAM_QUESTIONS} Questions): <span class="font-semibold">${formatMillisecondsToWords(estimatedExamTimeMilliseconds)}</span></p>
<p class="text-gray-700">Estimated Questions Attempted in ${REATTEMPT_DIFFICULT_TIME_LIMIT_MINUTES} Minutes: <span class="font-semibold">${numQuestionsAttemptedIn25Min.toFixed(0)}</span></p>
</div>
<div class="flex flex-col sm:flex-row justify-center gap-4 mt-8">
<button id="view-solution-btn" class="bg-purple-600 text-white font-semibold py-3 px-6 rounded-lg shadow-md hover:bg-purple-700 transition duration-300 transform hover:scale-105">
View Solution
</button>
<button onclick="takeScreenshot()" class="bg-blue-600 text-white font-semibold py-3 px-6 rounded-lg shadow-md hover:bg-blue-700 transition duration-300 transform hover:scale-105">
Download Screenshot
</button>
<button onclick="downloadPdf()" class="bg-green-600 text-white font-semibold py-3 px-6 rounded-lg shadow-md hover:bg-green-700 transition duration-300 transform hover:scale-105">
Download PDF Report
</button>
<button onclick="resetApp()" class="bg-gray-600 text-white font-semibold py-3 px-6 rounded-lg shadow-md hover:bg-gray-700 transition duration-300 transform hover:scale-105">
Start New Test
</button>
</div>
</div>
`;
renderQuestionStatusChart(correctCount, incorrectCount, unattemptedQuestions);
renderTimePerQuestionChart(allQuestionsFlat.map(q => q.timeTaken || 0)); // Pass timeTaken values
}
// Function to update visibility of Part tabs based on question availability
function updatePartTabVisibility() {
const targetPartTabs = inSolutionMode ? solutionPartTabs : partTabs;
targetPartTabs.forEach(tab => {
const partKey = tab.dataset.part;
if (allQuestionsByPart[partKey] && allQuestionsByPart[partKey].length > 0) {
tab.style.display = 'flex'; // Show the tab
} else {
tab.style.display = 'none'; // Hide the tab
}
});
}
// --- Solution Page Functions ---
function showSolutionPage() {
inSolutionMode = true;
resultPage.classList.add('hidden');
mocktestPage.classList.add('hidden');
solutionPage.classList.remove('hidden');
// Set completion time in solution header
const completionTime = testCompletionDateTime ? formatTime(testCompletionDateTime) : 'N/A';
solutionCompletionTimeEl.textContent = completionTime;
// Reset currentPart and questions for solution page to start from Part A
currentPart = 'A';
questions = allQuestionsByPart[currentPart];
currentQuestionIndex = 0;
solutionPartTabs.forEach(tab => tab.classList.remove('active'));
document.querySelector(`#solution-page .part-tab-button[data-part="${currentPart}"]`).classList.add('active');
solutionPartTitleEl.textContent = partNames[currentPart];
updatePartTabVisibility(); // Update visibility for solution page tabs
initializeQuestionGrid('solution'); // Initialize grid for solution mode
displayQuestion(0, 'solution'); // Display first question in solution mode
}
function navigateSolutionPrev() {
if (currentQuestionIndex > 0) {
displayQuestion(currentQuestionIndex - 1, 'solution');
} else {
showAlert("You are at the first question in this section.");
}
}
function navigateSolutionNext() {
if (currentQuestionIndex < questions.length - 1) {
displayQuestion(currentQuestionIndex + 1, 'solution');
} else {
const currentPartIndex = partOrder.indexOf(currentPart);
let nextPartFound = false;
for (let i = currentPartIndex + 1; i < partOrder.length; i++) {
const nextPartKey = partOrder[i];
if (allQuestionsByPart[nextPartKey] && allQuestionsByPart[nextPartKey].length > 0) {
currentPart = nextPartKey;
questions = allQuestionsByPart[currentPart];
currentQuestionIndex = 0;
solutionPartTabs.forEach(tab => tab.classList.remove('active'));
document.querySelector(`#solution-page .part-tab-button[data-part="${currentPart}"]`).classList.add('active');
solutionPartTitleEl.textContent = partNames[currentPart];
initializeQuestionGrid('solution');
displayQuestion(0, 'solution');
nextPartFound = true;
break;
}
}
if (!nextPartFound) {
showAlert("You have reached the last question in the solution. Click 'Back to Results' to go back.");
}
}
}
// --- Fullscreen Functions ---
function toggleFullscreen(element) {
if (!document.fullscreenElement) {
if (element.requestFullscreen) {
element.requestFullscreen();
} else if (element.mozRequestFullScreen) { /* Firefox */
element.mozRequestFullScreen();
} else if (element.webkitRequestFullscreen) { /* Chrome, Safari and Opera */
element.webkitRequestFullscreen();
} else if (element.msRequestFullscreen) { /* IE/Edge */
element.msRequestFullscreen();
}
fullscreenBtn.textContent = 'Exit Fullscreen';
solutionFullscreenBtn.textContent = 'Exit Fullscreen';
} else {
if (document.exitFullscreen) {
document.exitFullscreen();
} else if (document.mozCancelFullScreen) { /* Firefox */
document.mozCancelFullScreen();
} else if (document.webkitExitFullscreen) { /* Chrome, Safari and Opera */
document.webkitExitFullscreen();
} else if (document.msExitFullscreen) { /* IE/Edge */
document.msExitFullscreen();
}
fullscreenBtn.textContent = 'Show Fullscreen';
solutionFullscreenBtn.textContent = 'Show Fullscreen';
}
}
// --- Left Panel Toggle Functions ---
function toggleLeftPanel(mode) {
const currentLeftPanel = mode === 'mocktest' ? leftPanel : solutionLeftPanel;
const currentRightPanel = mode === 'mocktest' ? rightPanel : solutionRightPanel;
const currentToggleButton = mode === 'mocktest' ? hideLeftPanelBtn : solutionHideLeftPanelBtn;
if (currentLeftPanel.classList.contains('hidden')) {
// Show left panel
currentLeftPanel.classList.remove('hidden');
currentLeftPanel.style.width = '360px'; // Restore original width
currentRightPanel.style.width = 'calc(100% - 360px)'; // Adjust right panel width
currentToggleButton.querySelector('img').src = 'https://www.testranking.in/next_images/test/pt-left.png';
} else {
// Hide left panel
currentLeftPanel.classList.add('hidden');
currentLeftPanel.style.width = '0'; // Collapse left panel
currentRightPanel.style.width = '100%'; // Expand right panel
currentToggleButton.querySelector('img').src = 'https://www.testranking.in/next_images/test/pt-right.png'; // Change image to show right arrow
}
}
// --- Watermark Generation ---
function generateWatermark(containerId, watermarkText = '8016820***') {
const container = document.querySelector(`#${containerId} .watermark-pt-container`);
if (!container) return;
container.innerHTML = ''; // Clear existing watermarks
// Get the dimensions of the container to fill it with watermarks
const containerRect = container.getBoundingClientRect();
const containerWidth = containerRect.width;
const containerHeight = containerRect.height;
const textWidthEstimate = 100; // Approximate width of the watermark text
const textHeightEstimate = 20; // Approximate height of the watermark text (after rotation)
const horizontalSpacing = 200; // Spacing between watermarks horizontally
const verticalSpacing = 120; // Spacing between watermarks vertically
// Calculate how many watermarks can fit
const numCols = Math.ceil(containerWidth / horizontalSpacing) + 2; // Add some extra for rotation
const numRows = Math.ceil(containerHeight / verticalSpacing) + 2; // Add some extra for rotation
for (let r = 0; r < numRows; r++) {
for (let c = 0; c < numCols; c++) {
const watermarkDiv = document.createElement('div');
watermarkDiv.className = 'watermark-pt-text';
watermarkDiv.textContent = watermarkText;
watermarkDiv.style.top = `${r * verticalSpacing}px`;
watermarkDiv.style.left = `${c * horizontalSpacing}px`;
container.appendChild(watermarkDiv);
}
}
}
// --- Review Before Submit Modal Functions ---
function showReviewSubmitModal(isTimeUp = false) {
saveCurrentQuestionTime(); // Ensure current question time is saved
stopTotalTestTimer(); // Stop the timer when review modal is shown
reviewSummaryTableBody.innerHTML = ''; // Clear previous data
let totalQuestionsCount = 0;
let totalAnswered = 0;
let totalNotAnswered = 0;
let totalMarkedAnswered = 0;
let totalMarkedForReview = 0;
let totalVisited = 0;
let totalNotVisited = 0;
partOrder.forEach(partKey => {
const partQuestions = allQuestionsByPart[partKey];
const sectionName = partNames[partKey];
let questionsCount = partQuestions.length;
let answered = 0;
let notAnswered = 0;
let markedAnswered = 0;
let markedForReview = 0;
let visited = 0;
let notVisited = 0;
partQuestions.forEach(q => {
if (q.userAnswer !== null) {
answered++;
} else {
notAnswered++;
}
if (q.status === 'answered-marked') {
markedAnswered++;
} else if (q.status === 'marked') {
markedForReview++;
}
if (q.status !== 'not-visited') {
visited++;
} else {
notVisited++;
}
});
totalQuestionsCount += questionsCount;
totalAnswered += answered;
totalNotAnswered += notAnswered;
totalMarkedAnswered += markedAnswered;
totalMarkedForReview += markedForReview;
totalVisited += visited;
totalNotVisited += notVisited;
const row = reviewSummaryTableBody.insertRow();
row.insertCell().textContent = sectionName;
row.insertCell().textContent = questionsCount;
row.insertCell().textContent = answered;
row.insertCell().textContent = notAnswered;
row.insertCell().textContent = markedAnswered;
row.insertCell().textContent = markedForReview;
row.insertCell().textContent = visited;
row.insertCell().textContent = notVisited;
});
// Add a total row
const totalRow = reviewSummaryTableBody.insertRow();
totalRow.style.fontWeight = 'bold';
totalRow.insertCell().textContent = 'Total';
totalRow.insertCell().textContent = totalQuestionsCount;
totalRow.insertCell().textContent = totalAnswered;
totalRow.insertCell().textContent = totalNotAnswered;
totalRow.insertCell().textContent = totalMarkedAnswered;
totalRow.insertCell().textContent = totalMarkedForReview;
totalRow.insertCell().textContent = totalVisited;
totalRow.insertCell().textContent = totalNotVisited;
reviewSubmitModal.style.display = 'flex';
}
// --- Event Listeners ---
startMocktestBtn.addEventListener('click', async () => {
try {
currentTopic = topicInput.value.trim();
allQuestionsByPart['A'] = parseQuestions(questionInputPartA.value);
allQuestionsByPart['B'] = parseQuestions(questionInputPartB.value);
allQuestionsByPart['C'] = parseQuestions(questionInputPartC.value);
allQuestionsByPart['D'] = parseQuestions(questionInputPartD.value);
let firstPartWithQuestions = partOrder.find(partKey => allQuestionsByPart[partKey].length > 0);
if (!firstPartWithQuestions) {
showAlert("Please enter questions in at least one part to start the mock test.");
return;
}
currentPart = firstPartWithQuestions;
questions = allQuestionsByPart[currentPart];
// Initialize timeTaken for all questions across all parts
partOrder.forEach(partKey => {
allQuestionsByPart[partKey].forEach(q => {
q.userAnswer = null;
q.status = 'not-visited';
q.timeTaken = 0; // Ensure timeTaken is reset
});
});
questionStartTimes = {}; // Reset question start times for new test
currentQuestionIndex = 0;
totalTestStartTime = Date.now();
totalTestEndTime = undefined;
testCompletionDateTime = undefined;
inSolutionMode = false; // Ensure not in solution mode
inputPage.classList.add('hidden');
mocktestPage.classList.remove('hidden');
solutionPage.classList.add('hidden'); // Ensure solution page is hidden
partTabs.forEach(tab => tab.classList.remove('active'));
document.querySelector(`#mocktest-page .part-tab-button[data-part="${currentPart}"]`).classList.add('active');
partTitleEl.textContent = partNames[currentPart];
updatePartTabVisibility();
initializeQuestionGrid();
displayQuestion(0);
startTotalTestTimer();
generateWatermark('question-panel-container', '8016820***'); // Generate watermark for mocktest
} catch (error) {
console.error("Error starting test:", error);
showAlert("Failed to start test: " + error.message);
}
});
prevBtn.addEventListener('click', goToPreviousQuestion);
saveNextBtn.addEventListener('click', goToNextQuestionWithSave);
markReviewBtn.addEventListener('click', markForReviewToggle);
submitTestBtn.addEventListener('click', () => {
showReviewSubmitModal(); // Show the new review before submit modal
});
confirmSubmitTestBtn.addEventListener('click', () => {
reviewSubmitModal.style.display = 'none'; // Hide the review modal
submitTest(); // Proceed with test submission
});
symbolsTab.addEventListener('click', () => {
symbolsTab.classList.add('active');
instructionsTab.classList.remove('active');
symbolsModal.style.display = 'flex';
isSymbolsModalOpen = true;
});
closeModalBtn.addEventListener('click', () => {
symbolsModal.style.display = 'none';
isSymbolsModalOpen = false;
});
window.addEventListener('click', (event) => {
if (event.target == symbolsModal) {
symbolsModal.style.display = 'none';
isSymbolsModalOpen = false;
}
});
instructionsTab.addEventListener('click', () => {
instructionsTab.classList.add('active');
symbolsTab.classList.remove('active');
showAlert("Instructions", "This is where the test instructions would be displayed. For this mock, please refer to the Symbols tab for color meanings.");
});
feedbackPrevBtn.addEventListener('click', navigateFeedbackPrev);
feedbackNextBtn.addEventListener('click', navigateFeedbackNext);
partTabs.forEach(tab => {
tab.addEventListener('click', () => {
saveCurrentQuestionTime(); // Save time for the question before switching parts
partTabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
currentPart = tab.dataset.part;
questions = allQuestionsByPart[currentPart];
if (questions.length === 0) {
showAlert(`No questions available for Part ${currentPart}.`);
}
currentQuestionIndex = 0; // Reset index for new part
partTitleEl.textContent = partNames[currentPart];
initializeQuestionGrid();
displayQuestion(0);
updateAnalysisCounts();
});
});
// Solution Page Part Tabs
solutionPartTabs.forEach(tab => {
tab.addEventListener('click', () => {
solutionPartTabs.forEach(t => t.classList.remove('active'));
tab.classList.add('active');
currentPart = tab.dataset.part;
questions = allQuestionsByPart[currentPart];
if (questions.length === 0) {
showAlert(`No questions available for Part ${currentPart}.`);
}
currentQuestionIndex = 0;
solutionPartTitleEl.textContent = partNames[currentPart];
initializeQuestionGrid('solution');
displayQuestion(0, 'solution');
updateAnalysisCounts('solution');
});
});
// Zoom functionality
zoomInBtn.addEventListener('click', () => {
currentQuestionTextFontSize += 1;
currentOptionTextFontSize += 1;
displayQuestion(currentQuestionIndex, 'mocktest'); // Re-render with new font size
});
zoomOutBtn.addEventListener('click', () => {
if (currentQuestionTextFontSize > 10) { // Set a minimum font size
currentQuestionTextFontSize -= 1;
}
if (currentOptionTextFontSize > 8) { // Set a minimum font size
currentOptionTextFontSize -= 1;
}
displayQuestion(currentQuestionIndex, 'mocktest'); // Re-render with new font size
});
solutionZoomInBtn.addEventListener('click', () => {
currentQuestionTextFontSize += 1;
currentOptionTextFontSize += 1;
displayQuestion(currentQuestionIndex, 'solution'); // Re-render with new font size
});
solutionZoomOutBtn.addEventListener('click', () => {
if (currentQuestionTextFontSize > 10) {
currentQuestionTextFontSize -= 1;
}
if (currentOptionTextFontSize > 8) {
currentOptionTextFontSize -= 1;
}
displayQuestion(currentQuestionIndex, 'solution'); // Re-render with new font size
});
// Fullscreen buttons
fullscreenBtn.addEventListener('click', () => toggleFullscreen(document.documentElement));
solutionFullscreenBtn.addEventListener('click', () => toggleFullscreen(document.documentElement));
// View Solution button listener (added after result page HTML is rendered)
document.addEventListener('click', (event) => {
if (event.target && event.target.id === 'view-solution-btn') {
showSolutionPage();
generateWatermark('solution-question-panel-container', '8016820***'); // Generate watermark for solution page
}
});
// Solution page navigation buttons
solutionPrevBtn.addEventListener('click', navigateSolutionPrev);
solutionNextBtn.addEventListener('click', navigateSolutionNext);
backToResultBtn.addEventListener('click', () => {
solutionPage.classList.add('hidden');
resultPage.classList.remove('hidden');
inSolutionMode = false; // Exit solution mode
});
// Left panel toggle button listeners
hideLeftPanelBtn.addEventListener('click', () => toggleLeftPanel('mocktest'));
solutionHideLeftPanelBtn.addEventListener('click', () => toggleLeftPanel('solution'));
// Initial setup on page load
document.addEventListener('DOMContentLoaded', async () => {
await fetchQuestions(); // Populate input fields from API
updatePartTabVisibility(); // Set initial visibility of part tabs
// After fetching, try to set the initial questions for Part A
// This is crucial for the very first load to ensure 'questions' array is populated
if (allQuestionsByPart['A'].length > 0) {
currentPart = 'A';
questions = allQuestionsByPart[currentPart];
partTitleEl.textContent = partNames[currentPart];
initializeQuestionGrid();
displayQuestion(0);
} else {
// If Part A has no questions, find the first available part and set it
let firstAvailablePart = partOrder.find(partKey => allQuestionsByPart[partKey].length > 0);
if (firstAvailablePart) {
currentPart = firstAvailablePart;
questions = allQuestionsByPart[currentPart];
partTitleEl.textContent = partNames[currentPart];
partTabs.forEach(tab => tab.classList.remove('active'));
document.querySelector(`#mocktest-page .part-tab-button[data-part="${currentPart}"]`).classList.add('active');
initializeQuestionGrid();
displayQuestion(0);
} else {
// No questions in any part
questionNumberSpan.textContent = 'N/A';
questionTextCell.innerHTML = 'No questions available for any section. Please paste questions on the input page.';
optionsTableBody.innerHTML = '';
updateQuestionGrid();
updateAnalysisCounts();
}
}
updateTotalAnsweredQuestionsDisplay(); // Initial update for total answered questions
// Initial render of math on the body for any static content
renderMath(document.body);
});
</script>
</body>
</html>
0 Comments