Advertisement

Responsive Advertisement

Dekho

<!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">

    <script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

    <style>

        /* Apply box-sizing globally for consistent layout behavior */

        *, *::before, *::after {

            box-sizing: border-box;

        }


        body {

            font-family: 'Inter', sans-serif;

            background-color: #f0f2f5;

            margin: 0;

            padding: 0;

            overflow-x: hidden; /* Prevent horizontal scroll due to drawer positioning */

        }

        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;

        }


        /* Input and Result pages container for full width */

        .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%; /* Full width on mobile */

            max-width: 768px; /* Max width for larger screens */

        }


        /* Mocktest page specific styles for full width without scrolling */

        #mocktest-page {

            width: 100%;

            height: 100vh;

            margin: 0;

            padding: 0;

            display: flex;

            flex-direction: column;

        }

        #mocktest-page .mocktest-content-wrapper {

            width: 100%;

            height: 100%;

            display: flex;

            flex-direction: column;

            background-color: white;

            overflow: hidden; /* Manage overflow of content within wrapper */

        }


        /* Sliding Panel styles */

        #question-panel {

            position: fixed;

            top: 0;

            right: 0;

            width: 80%; /* Adjust width for mobile */

            max-width: 300px; /* Max width for larger screens */

            height: 100vh;

            background-color: #f8fafc;

            transform: translateX(100%);

            transition: transform 0.3s ease-in-out;

            z-index: 1000;

            box-shadow: -4px 0 10px rgba(0, 0, 0, 0.2);

            padding: 1rem;

            display: flex;

            flex-direction: column;

        }

        #question-panel.open {

            transform: translateX(0);

        }


        /* Overlay for when panel is open */

        #panel-overlay {

            position: fixed;

            top: 0;

            left: 0;

            width: 100%;

            height: 100vh;

            background-color: rgba(0, 0, 0, 0.5);

            z-index: 999;

            display: none;

        }

        #panel-overlay.active {

            display: block;

        }


        /* Chart container for responsiveness */

        .chart-container {

            position: relative;

            height: 300px;

            width: 100%;

        }

        /* Container for horizontal bar chart to manage its scrolling */

        .horizontal-chart-wrapper {

            overflow-x: auto;

            width: 100%;

            padding-bottom: 10px;

        }

        #timePerQuestionChartCanvas {

            min-height: 200px;

        }


        /* Specific styles for feedback modal */

        #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;

        }


        /* Adjust top bar spacing to ensure icons are not cut off */

        .mocktest-content-wrapper .top-bar-icons {

            display: flex;

            align-items: center;

            padding-right: 0.5rem;

        }


        /* Ensure question text doesn't overflow horizontally */

        .mocktest-content-wrapper .question-text-area {

            word-wrap: break-word;

            overflow-wrap: break-word;

            padding-left: 1rem;

            padding-right: 1rem;

        }

    </style>

</head>

<body class="flex flex-col min-h-screen">

    <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-6">

                <label for="question-input" class="block text-gray-700 text-lg font-medium mb-3">Enter MCQ Questions</label>

                <textarea id="question-input" rows="15"

                          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 your questions here. Example format:

8. Shatabdi Express left Delhi 40 min late. After covering half the distance, it increased its usual speed by 1⁄6 and reached Chandigarh on scheduled time. Find usual time for the journey.

(a) 560 min

(b) 520 min

(c) 540 min

(d) 420 min

Answer: (a) 560 min


14. A boy goes to school at 3 km/hr and returns at 2 km/hr. If he takes 5 hours total, find the distance to school.

(a) 5 km

(b) 6 km

(c) 7 km

(d) 8 km

Answer: (b) 6 km

"></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>


    <div id="mocktest-page" class="hidden">

        <!-- Mock test content will be injected here by JavaScript -->

    </div>


    <div id="result-page" class="hidden page-container">

        <!-- Result summary will be injected here by JavaScript -->

    </div>


    <!-- Custom Modal for Alerts (instead of alert()) -->

    <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>

            <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>

        </div>

    </div>


    <!-- Panel Overlay -->

    <div id="panel-overlay" class="hidden"></div>


    <!-- Question Number Panel (Sliding Drawer) -->

    <div id="question-panel" class="">

        <!-- Time Left summary at the top of the panel -->

        <div class="mb-4 w-full bg-white p-4 rounded-lg shadow-inner">

            <div class="flex justify-between items-center mb-2">

                <div class="flex items-center text-gray-700">

                    <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2 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>

                    Time Left

                </div>

                <span id="total-time-left-panel" class="font-semibold text-blue-600">00:00:00</span>

            </div>

        </div>


        <div class="flex justify-between items-center mb-4">

            <h3 class="text-xl font-semibold text-gray-800">Questions</h3>

            <button id="close-panel-btn" class="text-gray-500 hover:text-gray-700">

                <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">

                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" />

                </svg>

            </button>

        </div>


        <!-- Attempted, Unattempted, Marked at the top of the panel, under Time Left -->

        <div class="mb-4 w-full bg-white p-4 rounded-lg shadow-inner flex flex-col items-center">

            <div class="flex justify-between items-center w-full mb-2">

                <div class="flex items-center text-gray-700">

                    <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">

                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M9 12l2 2 4-4m6 2a9 9 0 11-18 0 9 9 0 0118 0z" />

                    </svg>

                    Attempted

                </div>

                <span id="attempted-count-panel" class="font-semibold text-green-600">0</span>

            </div>

            <div class="flex justify-between items-center w-full mb-2">

                <div class="flex items-center text-gray-700">

                    <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">

                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M10 14l2-2m0 0l2-2m-2 2l-2-2m2 2l2 2m7-2a9 9 0 11-18 0 9 9 0 0118 0z" />

                </svg>

                    Unattempted

                </div>

                <span id="unattempted-count-panel" class="font-semibold text-red-600">0</span>

            </div>

            <div class="flex justify-between items-center w-full">

                <div class="flex items-center text-gray-700">

                    <svg xmlns="http://www.w3.org/2000/svg" class="h-5 w-5 mr-2 text-gray-500" fill="none" viewBox="0 0 24 24" stroke="currentColor">

                        <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M11.049 2.927c.3-.921 1.603-.921 1.902 0l1.519 4.674a1 1 0 00.95.69h4.915c.969 0 1.371 1.24.588 1.81l-3.976 2.888a1 1 0 00-.324 1.108l1.519 4.674c.3.921-.755 1.688-1.539 1.108l-3.976-2.888a1 1 0 00-1.108 0l-3.976 2.888c-.784.58-1.838-.187-1.539-1.108l1.519-4.674a1 1 0 00-.324-1.108L2.245 9.401c-.783-.57-.381-1.81.588-1.81h4.915a1 1 0 00.95-.69l1.519-4.674z" />

                    </svg>

                    Marked

                </div>

                <span id="marked-count-panel" class="font-semibold text-orange-500">0</span>

            </div>

        </div>


        <div id="panel-question-grid" class="grid grid-cols-4 gap-2 overflow-y-auto flex-grow">

            <!-- Question numbers will be injected here -->

        </div>


        <!-- Submit Test button at the bottom of the panel -->

        <button id="submit-test-panel-btn"

                class="mt-4 w-full bg-red-600 text-white font-semibold py-3 px-6 rounded-lg shadow-lg hover:bg-red-700 transition duration-300 transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-red-400 focus:ring-opacity-75">

            SUBMIT TEST

        </button>

    </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'">

                &times;

            </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">

                <!-- Options will be injected here -->

            </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>



    <script>

        // --- Custom Alert Function ---

        function showAlert(message) {

            const modal = document.getElementById('custom-alert-modal');

            const msgElement = document.getElementById('custom-alert-message');

            const okBtn = document.getElementById('custom-alert-ok-btn');


            msgElement.textContent = message;

            modal.classList.remove('hidden');


            okBtn.onclick = () => {

                modal.classList.add('hidden');

            };

        }


        // --- DOM Elements ---

        const inputPage = document.getElementById('input-page');

        const mocktestPage = document.getElementById('mocktest-page');

        const resultPage = document.getElementById('result-page');

        const startMockTestBtn = document.getElementById('start-mocktest-btn');

        const questionInput = document.getElementById('question-input');

        const questionPanel = document.getElementById('question-panel');

        const closePanelBtn = document.getElementById('close-panel-btn');

        const panelOverlay = document.getElementById('panel-overlay');

        const submitTestPanelBtn = document.getElementById('submit-test-panel-btn');


        // For feedback modal

        const questionFeedbackModal = document.getElementById('question-feedback-modal');

        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');


        // --- Global Variables ---

        let questions = [];

        let currentQuestionIndex = 0;

        let questionStartTimes = [];

        let questionEndTimes = [];

        let totalTestStartTime;

        let totalTestEndTime;

        let selectedAnswers = [];

        let totalTimerInterval;

        let currentQuestionTimerInterval;


        let filteredQuestionIndices = [];

        let currentFeedbackIndex = 0;


        // --- Constants for Scoring ---

        const CORRECT_MARKS = 2;

        const WRONG_MARKS = -0.5;

        const TOTAL_TEST_DURATION_MINUTES = 60;

        const ESTIMATED_EXAM_QUESTIONS = 25;


        // --- Utility Functions ---


        /**

         * Parses the raw text input into an array of question objects.

         * @param {string} rawText - The raw text input from the textarea.

         * @returns {Array<Object>} An array of parsed 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: ''

                    };

                    optionsCounter = 0;

                } else if (line.match(/^\([a-d]\)\s/) && currentQuestion && optionsCounter < 4) {

                    currentQuestion.options.push(line);

                    optionsCounter++;

                } else if (line.startsWith('Answer:')) {

                    if (currentQuestion) {

                        const match = line.match(/\(([a-d])\)/);

                        if (match && match[1]) {

                            currentQuestion.answer = match[1];

                        }

                    }

                } else {

                    if (currentQuestion && currentQuestion.options.length === 0 && !currentQuestion.answer) {

                        currentQuestion.question += ' ' + line;

                    }

                }

            });


            if (currentQuestion) {

                parsed.push(currentQuestion);

            }

            return parsed;

        }


        /**

         * Formats total seconds into a human-readable string like "35sec" or "1minute 25sec".

         * Does not include milliseconds or decimal points.

         * @param {number} totalSeconds - The total number of seconds.

         * @returns {string} Formatted time string.

         */

        function formatSecondsToWords(totalSeconds) {

            const minutes = Math.floor(totalSeconds / 60);

            const seconds = totalSeconds % 60;


            let result = '';

            if (minutes > 0) {

                result += `${minutes}minute`;

                if (minutes > 1) result += 's'; // Pluralize minutes

            }

            if (seconds > 0) {

                if (result.length > 0) result += ' '; // Add space if minutes exist

                result += `${seconds}sec`;

            } else if (totalSeconds === 0 && result.length === 0) {

                result = '0sec'; // Display 0sec if total is 0

            }

            return result;

        }



        /**

         * Formats a time in seconds into HH:MM:SS format.

         * @param {number} totalSeconds - The total number of seconds.

         * @returns {string} Formatted time string (HH:MM:SS).

         */

        function formatHoursMinutesSeconds(totalSeconds) {

            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)}`;

        }


        /**

         * Determines the color for time display based on duration and attempt status.

         * @param {number} seconds - Time duration in seconds.

         * @param {number} questionIndex - The internal 0-based index of the question.

         * @returns {string} Tailwind CSS color class (e.g., 'text-green-600').

         */

        function getTimeColorClass(seconds, questionIndex) {

            // Check if the question was unattempted

            if (selectedAnswers[questionIndex] === null) {

                return 'text-gray-500'; // Always grey for unattempted

            }

            // Otherwise, use time-based color

            if (seconds <= 60) {

                return 'text-green-600';

            } else if (seconds <= 90) {

                return 'text-yellow-600';

            } else {

                return 'text-red-600';

            }

        }


         /**

         * Returns hex color code for chart based on duration and attempt status.

         * @param {number} seconds - Time duration in seconds.

         * @param {number} questionIndex - The internal 0-based index of the question.

         * @returns {string} Hex color code.

         */

        function getChartColor(seconds, questionIndex) {

            if (selectedAnswers[questionIndex] === null) {

                return '#9CA3AF'; // Tailwind gray-400 equivalent for bars

            }

            if (seconds <= 60) {

                return '#10B981'; // Green

            } else if (seconds <= 90) {

                return '#F59E0B'; // Yellow

            } else {

                return '#EF4444'; // Red

            }

        }


        /**

         * Returns darker hex color code for chart border based on duration and attempt status.

         * @param {number} seconds - Time duration in seconds.

         * @param {number} questionIndex - The internal 0-based index of the question.

         * @returns {string} Hex color code.

         */

        function getChartBorderColor(seconds, questionIndex) {

            if (selectedAnswers[questionIndex] === null) {

                return '#6B7280'; // Tailwind gray-600 equivalent

            }

            if (seconds <= 60) {

                return '#059669'; // Darker Green

            } else if (seconds <= 90) {

                return '#D97706'; // Darker Yellow

            } else {

                return '#DC2626'; // Darker Red

            }

        }



        /**

         * Starts the timer for the current question and updates the display.

         */

        function startQuestionTimer() {

            if (questionStartTimes[currentQuestionIndex] === undefined) {

                questionStartTimes[currentQuestionIndex] = Date.now();

            }


            if (currentQuestionTimerInterval) {

                clearInterval(currentQuestionTimerInterval);

            }


            currentQuestionTimerInterval = setInterval(() => {

                const elapsed = Math.floor((Date.now() - questionStartTimes[currentQuestionIndex]) / 1000);

                const questionTimerDisplay = document.getElementById('question-timer');

                if (questionTimerDisplay) {

                    questionTimerDisplay.textContent = formatSecondsToWords(elapsed); // New format here

                }

            }, 1000);

        }


        /**

         * Stops the timer for the current question and records the end time.

         */

        function stopQuestionTimer() {

            if (currentQuestionTimerInterval) {

                clearInterval(currentQuestionTimerInterval);

                if (questionEndTimes[currentQuestionIndex] === undefined) {

                     questionEndTimes[currentQuestionIndex] = Date.now();

                }

            }

        }


        /**

         * Starts the total test timer.

         */

        function startTotalTestTimer() {

            totalTestStartTime = Date.now();

            totalTimerInterval = setInterval(() => {

                const elapsed = Math.floor((Date.now() - totalTestStartTime) / 1000);

                const totalTestDurationSeconds = TOTAL_TEST_DURATION_MINUTES * 60;

                const timeLeft = Math.max(0, totalTestDurationSeconds - elapsed);


                // Update time left in panel

                const totalTimerDisplayPanel = document.getElementById('total-time-left-panel');

                if (totalTimerDisplayPanel) {

                    totalTimerDisplayPanel.textContent = formatHoursMinutesSeconds(timeLeft);

                }


                if (timeLeft <= 0) {

                    showAlert("Time's up! The test has been submitted automatically.");

                    submitTest();

                }

            }, 1000);

        }


        /**

         * Stops the total test timer.

         */

        function stopTotalTestTimer() {

            if (totalTimerInterval) {

                clearInterval(totalTimerInterval);

                totalTestEndTime = Date.now();

            }

        }


        /**

         * Toggles the visibility of the question number side panel.

         */

        function toggleQuestionPanel() {

            questionPanel.classList.toggle('open');

            panelOverlay.classList.toggle('active');

            // Update panel summary when it opens

            if(questionPanel.classList.contains('open')) {

                updateMockTestSummary();

            }

        }


        /**

         * Displays a question on the mock test page.

         * @param {number} index - The internal 0-based index of the question to display.

         */

        function displayQuestion(index) {

            // Close panel if it's currently open

            if (questionPanel.classList.contains('open')) {

                toggleQuestionPanel();

            }

            // Stop timer for previous question if it was running and record its end time

            if (currentQuestionIndex !== index && questionStartTimes[currentQuestionIndex] !== undefined) {

                 stopQuestionTimer();

            }



            if (index < 0 || index >= questions.length) {

                showAlert("Question index out of bounds.");

                return;

            }


            currentQuestionIndex = index;

            const question = questions[currentQuestionIndex];


            const optionLetters = ['a', 'b', 'c', 'd'];

            let optionsHtml = '';


            question.options.forEach((optionText, idx) => {

                const optionValue = optionLetters[idx];

                const isSelected = selectedAnswers[currentQuestionIndex] === optionValue;

                optionsHtml += `

                    <div class="flex items-center mb-3">

                        <input type="radio" id="option-${currentQuestionIndex}-${optionValue}" name="question-${currentQuestionIndex}" value="${optionValue}"

                               class="hidden peer" ${isSelected ? 'checked' : ''}>

                        <label for="option-${currentQuestionIndex}-${optionValue}"

                               class="flex items-center w-full p-4 border rounded-lg cursor-pointer

                                      peer-checked:bg-blue-100 peer-checked:border-blue-500 peer-checked:text-blue-700

                                      hover:bg-gray-50 transition duration-200"

                                      ondblclick="handleOptionDblClick(event, ${currentQuestionIndex}, '${optionValue}')"

                                      onclick="handleOptionClick(event, ${currentQuestionIndex}, '${optionValue}')">

                            <span class="text-gray-800 text-lg">${optionText}</span>

                        </label>

                    </div>

                `;

            });


            const elapsedQuestionTime = questionStartTimes[currentQuestionIndex] ?

                Math.floor((Date.now() - questionStartTimes[currentQuestionIndex]) / 1000) : 0;


            mocktestPage.innerHTML = `

                <div class="mocktest-content-wrapper">

                    <!-- Top Bar -->

                    <div class="flex items-center justify-between p-4 bg-gray-100 border-b border-gray-200 shadow-sm">

                        <div class="flex items-center text-gray-700 font-medium">

                            <span class="mr-2 text-xl">${question.originalNumber}</span>

                            <div class="flex items-center text-sm ml-2">

                                <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="question-timer">${formatSecondsToWords(elapsedQuestionTime)}</span> <!-- New format -->

                            </div>

                            <span class="ml-4 px-2 py-1 rounded-full text-green-700 bg-green-200 text-xs font-semibold">+ 2.0</span>

                            <span class="ml-2 px-2 py-1 rounded-full text-red-700 bg-red-200 text-xs font-semibold">- 0.5</span>

                        </div>

                        <div class="flex items-center top-bar-icons">

                            <!-- Nav icon for panel on right side of top -->

                            <button id="open-panel-btn-top"

                                    class="text-gray-500 hover:text-gray-700 p-2 rounded-full hover:bg-gray-200">

                                <svg xmlns="http://www.w3.org/2000/svg" class="h-6 w-6" fill="none" viewBox="0 0 24 24" stroke="currentColor">

                                    <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M4 6h16M4 12h16M4 18h16" />

                                </svg>

                            </button>

                        </div>

                    </div>


                    <!-- Question Body and Buttons -->

                    <div class="flex-grow p-4 overflow-y-auto flex flex-col">

                        <div class="mb-6 question-text-area">

                            <p class="text-xl text-gray-900 mb-6">${question.originalNumber}. ${question.question}</p>

                            <div>

                                ${optionsHtml}

                            </div>

                        </div>


                        <!-- Action Buttons below options -->

                        <div class="flex flex-wrap justify-center gap-3 mt-4 mb-6">

                            <button id="mark-next-btn"

                                    class="flex-1 min-w-[120px] max-w-[180px] bg-gray-200 text-gray-800 font-semibold py-3 px-4 rounded-lg shadow-sm hover:bg-gray-300 transition duration-200 focus:outline-none">

                                Mark & Next

                            </button>

                            <button id="save-next-btn"

                                    class="flex-1 min-w-[120px] max-w-[180px] bg-blue-600 text-white font-semibold py-3 px-4 rounded-lg shadow-md hover:bg-blue-700 transition duration-300 focus:outline-none">

                                Save & Next

                            </button>

                        </div>

                    </div>

                </div>

            `;


            // Attach event listeners for the newly created elements

            document.getElementById('mark-next-btn').onclick = () => {

                moveToNextQuestion();

            };

            document.getElementById('save-next-btn').onclick = () => {

                moveToNextQuestion();

            };

            document.getElementById('open-panel-btn-top').onclick = toggleQuestionPanel;



            startQuestionTimer();

            updateMockTestSummary();

            renderQuestionPanel();

        }


        /**

         * Handles double-click on an option label to clear selection.

         * @param {Event} event - The double click event.

         * @param {number} qIndex - The internal 0-based index of the question.

         * @param {string} optionValue - The value of the option (e.g., 'a').

         */

        function handleOptionDblClick(event, qIndex, optionValue) {

            event.preventDefault();


            if (selectedAnswers[qIndex] === optionValue) {

                selectedAnswers[qIndex] = null;

                const radioInput = document.getElementById(`option-${qIndex}-${optionValue}`);

                if (radioInput) {

                    radioInput.checked = false;

                }

                updateMockTestSummary();

            }

        }


         /**

         * Handles single-click on an option label to select.

         * @param {Event} event - The click event.

         * @param {number} qIndex - The internal 0-based index of the question.

         * @param {string} optionValue - The value of the option (e.g., 'a').

         */

        function handleOptionClick(event, qIndex, optionValue) {

            const radioInput = document.getElementById(`option-${qIndex}-${optionValue}`);

            if (radioInput && !radioInput.checked) {

                radioInput.checked = true;

                selectedAnswers[qIndex] = optionValue;

                updateMockTestSummary();

            }

        }



        /**

         * Shows the question feedback modal with details for a specific question.

         * This function is now overloaded to handle direct calls or filtered calls.

         * @param {number} displayIndex - The index of the question to display, either internal or index within filteredQuestionIndices.

         * @param {boolean} fromFilter - True if called from a filtered list (Correct, Incorrect, etc.).

         */

        function showQuestionFeedback(displayIndex, fromFilter = false) {

            let questionIndexToShow;

            if (fromFilter) {

                questionIndexToShow = filteredQuestionIndices[displayIndex];

                currentFeedbackIndex = displayIndex;

            } else {

                questionIndexToShow = displayIndex;

                filteredQuestionIndices = [questionIndexToShow];

                currentFeedbackIndex = 0;

            }


            const question = questions[questionIndexToShow];

            const selectedAnswer = selectedAnswers[questionIndexToShow];

            const correctAnswer = question.answer;

            const timeTaken = window.questionTimeDurations[questionIndexToShow] || 0; // Retrieve actual time


            feedbackQuestionTitle.textContent = `Question ${question.originalNumber} Feedback`;

            feedbackQuestionText.textContent = question.question;


            // Set Attempted/Unattempted tag

            if (selectedAnswer !== null) {

                feedbackStatusTag.textContent = "Attempted";

                feedbackStatusTag.className = "text-sm font-semibold px-2 py-1 rounded-full inline-block bg-green-200 text-green-700 mb-4";

            } else {

                feedbackStatusTag.textContent = "Unattempted";

                feedbackStatusTag.className = "text-sm font-semibold px-2 py-1 rounded-full inline-block bg-red-200 text-red-700 mb-4";

            }


            // Update time taken stamp in feedback modal - Use new formatSecondsToWords

            feedbackTimeValue.textContent = formatSecondsToWords(timeTaken);

            feedbackTimeValue.className = `font-medium ${getTimeColorClass(timeTaken, questionIndexToShow)}`; // Pass questionIndex for unattempted color logic



            feedbackOptionsContainer.innerHTML = '';


            const optionLetters = ['a', 'b', 'c', 'd'];

            question.options.forEach((optionText, idx) => {

                const optionValue = optionLetters[idx];

                let colorClass = 'text-gray-800';

                let borderColorClass = 'border-gray-200';


                if (optionValue === correctAnswer) {

                    colorClass = 'bg-green-100 text-green-700 font-semibold';

                    borderColorClass = 'border-green-500';

                } else if (selectedAnswer !== null && optionValue === selectedAnswer && optionValue !== correctAnswer) {

                    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 = `<span>(${optionValue})</span> ${optionText.substring(optionText.indexOf(')') + 1).trim()}`;

                feedbackOptionsContainer.appendChild(optionElement);

            });


            // Update Next/Previous button visibility

            feedbackPrevBtn.disabled = currentFeedbackIndex === 0;

            feedbackNextBtn.disabled = currentFeedbackIndex === filteredQuestionIndices.length - 1;


            questionFeedbackModal.style.display = 'flex';

        }


        /**

         * Navigates to the previous question in the filtered feedback list.

         */

        function navigateFeedbackPrev() {

            if (currentFeedbackIndex > 0) {

                showQuestionFeedback(currentFeedbackIndex - 1, true);

            }

        }


        /**

         * Navigates to the next question in the filtered feedback list.

         */

        function navigateFeedbackNext() {

            if (currentFeedbackIndex < filteredQuestionIndices.length - 1) {

                showQuestionFeedback(currentFeedbackIndex + 1, true);

            }

        }


        /**

         * Moves to the next question or submits the test if it's the last question.

         */

        function moveToNextQuestion() {

            stopQuestionTimer();


            if (currentQuestionIndex < questions.length - 1) {

                displayQuestion(currentQuestionIndex + 1);

            } else {

                submitTest();

            }

        }


        /**

         * Updates the attempted and unattempted counts on the mock test page and panel.

         */

        function updateMockTestSummary() {

            const attemptedCount = selectedAnswers.filter(a => a !== null).length;

            const unattemptedCount = questions.length - attemptedCount;


            // Update panel summary

            const attemptedDisplayPanel = document.getElementById('attempted-count-panel');

            const unattemptedDisplayPanel = document.getElementById('unattempted-count-panel');

            const markedDisplayPanel = document.getElementById('marked-count-panel');


            if (attemptedDisplayPanel) attemptedDisplayPanel.textContent = attemptedCount;

            if (unattemptedDisplayPanel) unattemptedDisplayPanel.textContent = unattemptedCount;

            if (markedDisplayPanel) markedDisplayPanel.textContent = 0;


            renderQuestionPanel();

        }


        /**

         * Renders the question number grid inside the sliding panel.

         */

        function renderQuestionPanel() {

            const panelQuestionGrid = document.getElementById('panel-question-grid');

            if (panelQuestionGrid) {

                panelQuestionGrid.innerHTML = questions.map((q, i) => {

                    let buttonClass = 'bg-gray-300 text-gray-800 hover:bg-gray-400';

                    if (i === currentQuestionIndex) {

                        buttonClass = 'bg-blue-600 text-white shadow-md';

                    } else if (selectedAnswers[i] !== null) {

                        buttonClass = 'bg-green-500 text-white';

                    }

                    return `

                        <button class="w-12 h-12 rounded-full flex items-center justify-center font-semibold text-base

                            ${buttonClass} transition duration-200"

                            onclick="displayQuestion(${i})">

                            ${q.originalNumber}

                        </button>

                    `;

                }).join('');

            }

        }



        /**

         * Calculates the score and displays the result summary.

         */

        function submitTest() {

            stopTotalTestTimer();

            stopQuestionTimer();


            let correctCount = 0;

            let incorrectCount = 0;

            let score = 0;


            questions.forEach((question, index) => {

                const selectedAnswer = selectedAnswers[index];

                if (selectedAnswer) {

                    if (selectedAnswer === question.answer) {

                        correctCount++;

                        score += CORRECT_MARKS;

                    } else {

                        incorrectCount++;

                        score += WRONG_MARKS;

                    }

                }

            });


            const totalQuestions = questions.length;

            const attemptedQuestions = correctCount + incorrectCount;

            const unattemptedQuestions = totalQuestions - attemptedQuestions;

            const accuracy = attemptedQuestions > 0 ? (correctCount / attemptedQuestions) * 100 : 0;


            const totalTimeTakenSeconds = Math.floor((totalTestEndTime - totalTestStartTime) / 1000);


            // This array is used by `showQuestionFeedback` and `renderTimePerQuestionChart`

            window.questionTimeDurations = questions.map((_, i) => {

                const start = questionStartTimes[i];

                const end = questionEndTimes[i];


                // If question was never started, or if it's the current question on submission,

                // or if it was started and ended, calculate time spent.

                // It will now store actual time, even for unattempted questions if time was spent.

                if (start === undefined) { // Never visited

                    return 0;

                }

                if (end === undefined) { // Visited, but not navigated away from (e.g., test submitted while on it)

                    return Math.floor((Date.now() - start) / 1000);

                }

                return Math.floor((end - start) / 1000);

            });


            // Calculate Avg Time per Question and Estimated Exam Time

            // Adjusted logic for avg time: if no questions attempted, avg is 0.

            const avgTimePerQuestionSeconds = attemptedQuestions > 0 ? totalTimeTakenSeconds / attemptedQuestions : 0;

            const estimatedExamTimeSeconds = avgTimePerQuestionSeconds * ESTIMATED_EXAM_QUESTIONS;



            inputPage.classList.add('hidden');

            mocktestPage.classList.add('hidden');

            questionPanel.classList.remove('open');

            panelOverlay.classList.remove('active');

            resultPage.classList.remove('hidden');


            resultPage.innerHTML = `

                <div class="page-card">

                    <h2 class="text-3xl font-bold text-center mb-8 text-gray-800">Mock Test Result Summary</h2>


                    <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}</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}%"></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-gray-50 p-6 rounded-lg mb-8">

                        <h3 class="text-xl font-semibold text-gray-800 mb-4">Time Analysis</h3>

                        <div class="flex items-center justify-between mb-3">

                            <p class="text-gray-700">Total Time Taken:</p>

                            <p class="font-bold text-lg text-purple-700">${formatHoursMinutesSeconds(totalTimeTakenSeconds)}</p>

                        </div>

                        <!-- New Time Analysis Calculations -->

                        <div class="flex items-center justify-between mb-2 p-2 rounded-lg bg-blue-50">

                            <p class="text-gray-700">Avg Time per Question:</p>

                            <p class="font-bold text-md text-blue-800">${formatSecondsToWords(avgTimePerQuestionSeconds)}</p>

                        </div>

                        <div class="flex items-center justify-between mb-3 p-2 rounded-lg bg-purple-50">

                            <p class="text-gray-700">Estimated Exam Time (25 Qs):</p>

                            <p class="font-bold text-md text-purple-800">${formatSecondsToWords(estimatedExamTimeSeconds)}</p>

                        </div>

                        

                        <p class="text-gray-700 mb-2 mt-4">Time per Question Breakdown:</p>

                        <ul class="list-disc list-inside text-gray-700 max-h-48 overflow-y-auto border p-2 rounded-md">

                            ${questions.map((q, i) => {

                                const time = window.questionTimeDurations[i];

                                const colorClass = getTimeColorClass(time, i); // Pass index

                                return `

                                <li class="flex justify-between items-center py-1 cursor-pointer hover:bg-gray-100" onclick="showQuestionFeedback(${i})">

                                    <span>Question ${q.originalNumber}:</span>

                                    <span class="font-medium ${colorClass}">${formatSecondsToWords(time)}</span> <!-- New format -->

                                </li>

                            `;

                            }).join('')}

                        </ul>

                        <div class="horizontal-chart-wrapper mt-8">

                            <canvas id="timePerQuestionChartCanvas"></canvas>

                        </div>

                    </div>


                    <div class="text-center">

                        <button id="restart-test-btn"

                                class="bg-blue-600 text-white font-semibold py-3 px-8 rounded-lg shadow-lg hover:bg-blue-700 transition duration-300 transform hover:scale-105 focus:outline-none focus:ring-2 focus:ring-blue-400 focus:ring-opacity-75">

                            Restart Test

                        </button>

                    </div>

                </div>

            `;


            // Render Charts

            renderQuestionStatusChart(correctCount, incorrectCount, unattemptedQuestions);

            renderTimePerQuestionChart(window.questionTimeDurations);


            document.getElementById('restart-test-btn').onclick = resetApp;

            // Attach event listeners for feedback modal navigation

            feedbackPrevBtn.onclick = navigateFeedbackPrev;

            feedbackNextBtn.onclick = navigateFeedbackNext;

        }


        /**

         * Renders the question status doughnut chart.

         * @param {number} correctCount

         * @param {number} incorrectCount

         * @param {number} unattemptedCount

         */

        function renderQuestionStatusChart(correctCount, incorrectCount, unattemptedCount) {

            const ctx = document.getElementById('questionStatusChart').getContext('2d');

            if (window.questionStatusChartInstance) {

                window.questionStatusChartInstance.destroy();

            }

            window.questionStatusChartInstance = 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,

                    animation: {

                        animateScale: true,

                        animateRotate: true

                    },

                    plugins: {

                        legend: {

                            position: 'bottom',

                        },

                        title: {

                            display: true,

                            text: 'Question Status Breakdown'

                        }

                    }

                }

            });

        }


        /**

         * Renders the time per question horizontal bar chart.

         * @param {Array<number>} timeDurations - Array of time taken for each question in seconds.

         */

        function renderTimePerQuestionChart(timeDurations) {

            const ctx = document.getElementById('timePerQuestionChartCanvas').getContext('2d');

            const labels = questions.map((q) => `Q${q.originalNumber}`);


            if (window.timePerQuestionChartInstance) {

                window.timePerQuestionChartInstance.destroy();

            }


            const calculatedHeight = Math.max(300, labels.length * 30);

            ctx.canvas.style.height = `${calculatedHeight}px`;


            window.timePerQuestionChartInstance = new Chart(ctx, {

                type: 'bar',

                data: {

                    labels: labels,

                    datasets: [{

                        label: 'Time Taken', // Label changed to be more general

                        data: timeDurations,

                        backgroundColor: timeDurations.map((time, i) => getChartColor(time, i)),

                        borderColor: timeDurations.map((time, i) => getChartBorderColor(time, i)),

                        borderWidth: 1

                    }]

                },

                options: {

                    indexAxis: 'y',

                    responsive: true,

                    maintainAspectRatio: false,

                    onClick: (evt, elements) => {

                        if (elements.length > 0) {

                            const clickedIndex = elements[0].index;

                            showQuestionFeedback(clickedIndex);

                        }

                    },

                    plugins: {

                        legend: {

                            display: false,

                        },

                        title: {

                            display: true,

                            text: 'Time Taken Per Question'

                        },

                        tooltip: {

                            callbacks: {

                                label: function(context) {

                                    let label = context.dataset.label || '';

                                    if (label) {

                                        label += ': ';

                                    }

                                    if (context.parsed.x !== null) {

                                        label += formatSecondsToWords(context.parsed.x); // New format in tooltip

                                    }

                                    return label;

                                }

                            }

                        }

                    },

                    scales: {

                        x: {

                            beginAtZero: true,

                            title: {

                                display: true,

                                text: 'Time' // Text changed to be more general

                            },

                            ticks: {

                                callback: function(value, index, ticks) {

                                    return formatSecondsToWords(value); // New format for X-axis ticks

                                }

                            }

                        },

                        y: {

                            title: {

                                display: true,

                                text: 'Question Number'

                            },

                            ticks: {

                                autoSkip: false,

                                maxRotation: 0,

                                minRotation: 0,

                                color: (context) => {

                                    const value = timeDurations[context.index];

                                    return getChartColor(value, context.index);

                                }

                            }

                        }

                    }

                }

            });

        }


        /**

         * Resets the app to the initial input page state.

         */

        function resetApp() {

            questions = [];

            currentQuestionIndex = 0;

            questionStartTimes = [];

            questionEndTimes = [];

            totalTestStartTime = null;

            totalTestEndTime = null;

            selectedAnswers = [];

            clearInterval(totalTimerInterval);

            clearInterval(currentQuestionTimerInterval);

            filteredQuestionIndices = [];

            currentFeedbackIndex = 0;


            if (window.questionStatusChartInstance) {

                window.questionStatusChartInstance.destroy();

                window.questionStatusChartInstance = null;

            }

            if (window.timePerQuestionChartInstance) {

                window.timePerQuestionChartInstance.destroy();

                window.timePerQuestionChartInstance = null;

            }


            questionInput.value = '';

            inputPage.classList.remove('hidden');

            mocktestPage.classList.add('hidden');

            resultPage.classList.add('hidden');

            questionPanel.classList.remove('open');

            panelOverlay.classList.remove('active');

        }


        // --- Event Listeners ---

        startMockTestBtn.addEventListener('click', () => {

            const rawQuestions = questionInput.value.trim();

            if (!rawQuestions) {

                showAlert("Please paste your Math questions before starting the test.");

                return;

            }


            questions = parseQuestions(rawQuestions);


            if (questions.length === 0) {

                showAlert("Could not parse any questions. Please ensure they follow the specified format.");

                return;

            }


            selectedAnswers = new Array(questions.length).fill(null);

            questionStartTimes = new Array(questions.length).fill(undefined);

            questionEndTimes = new Array(questions.length).fill(undefined);


            inputPage.classList.add('hidden');

            mocktestPage.classList.remove('hidden');

            startTotalTestTimer();

            displayQuestion(0);

        });


        // Event listeners for panel

        closePanelBtn.addEventListener('click', toggleQuestionPanel);

        panelOverlay.addEventListener('click', toggleQuestionPanel);

        submitTestPanelBtn.addEventListener('click', submitTest);


        // Event listeners for feedback modal navigation

        feedbackPrevBtn.addEventListener('click', navigateFeedbackPrev);

        feedbackNextBtn.addEventListener('click', navigateFeedbackNext);


        /**

         * Filters questions and opens feedback modal with the first filtered question.

         * @param {string} filterType - 'correct', 'incorrect', 'unattempted', 'attempted'

         */

        function showFeedbackFiltered(filterType) {

            filteredQuestionIndices = [];

            questions.forEach((q, i) => {

                const isAttempted = selectedAnswers[i] !== null;

                const isCorrect = isAttempted && selectedAnswers[i] === q.answer;


                if (filterType === 'correct' && isCorrect) {

                    filteredQuestionIndices.push(i);

                } else if (filterType === 'incorrect' && isAttempted && !isCorrect) {

                    filteredQuestionIndices.push(i);

                } else if (filterType === 'unattempted' && !isAttempted) {

                    filteredQuestionIndices.push(i);

                } else if (filterType === 'attempted' && isAttempted) {

                    filteredQuestionIndices.push(i);

                }

            });


            if (filteredQuestionIndices.length > 0) {

                showQuestionFeedback(0, true);

            } else {

                showAlert(`No ${filterType} questions to show.`);

            }

        }

    </script>

</body>

</html>


Post a Comment

0 Comments