Quizizz Hypertool

Extracts text/image from Quizizz, adds DDG links, Gemini AI helper, prompt copier. Now polls page info and includes UI toggle.

// ==UserScript==
// @name         Quizizz Hypertool
// @namespace    http://tampermonkey.net/
// @version      1.4.4
// @description  Extracts text/image from Quizizz, adds DDG links, Gemini AI helper, prompt copier. Now polls page info and includes UI toggle.
// @author       Nyxie
// @license      GPL-3.0
// @match        https://quizizz.com/*
// @grant        GM_addStyle
// @grant        GM_log
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_xmlhttpRequest
// @grant        GM_registerMenuCommand
// @connect      media.quizizz.com
// @connect      generativelanguage.googleapis.com
// ==/UserScript==

(function() {
    'use strict';

    const GEMINI_API_KEY_STORAGE = 'GEMINI_API_KEY';
    const GEMINI_MODEL = 'gemini-2.0-flash'; // Consider 'gemini-1.5-flash-latest' if available and preferred

    // --- State for UI Modifications ---
    const UI_MODS_ENABLED_KEY = 'qht_ui_modifications_enabled';
    let uiModificationsEnabled = GM_getValue(UI_MODS_ENABLED_KEY, true);

    GM_addStyle(`
        .quizizz-ddg-link, .quizizz-gemini-button, .quizizz-copy-prompt-button {
            display: inline-block; padding: 5px 8px;
            color: white; text-decoration: none; border-radius: 4px; font-size: 0.8em;
            cursor: pointer; text-align: center; vertical-align: middle;
            transition: opacity 0.2s ease-in-out;
        }
        .quizizz-ddg-link:hover, .quizizz-gemini-button:hover, .quizizz-copy-prompt-button:hover { opacity: 0.85; }

        .quizizz-ddg-link { background-color: #4CAF50; } /* DDG Green */
        .quizizz-gemini-button { background-color: #007bff; margin-left: 5px; } /* Gemini Blue */
        .quizizz-copy-prompt-button { background-color: #FF9800; margin-left: 5px; } /* Copy Orange */


        /* --- Option Button Wrapper and DDG Link Styling --- */
        .quizizz-option-wrapper {
            display: flex;
            flex-direction: column;
            align-items: stretch;
            justify-content: space-between;
            height: 100%;
        }
        .quizizz-option-wrapper > button.option {
            display: flex;
            flex-direction: column;
            flex-grow: 1;
            min-height: 0;
            width: 100%;
        }
        .quizizz-ddg-link-option-item {
            width: 100%;
            box-sizing: border-box;
            margin-top: 6px;
            padding: 6px 0;
            border-radius: 0 0 4px 4px;
            flex-shrink: 0;
        }
        /* --- End Option Button Styling --- */

        .quizizz-ddg-link-main-question,
        .quizizz-gemini-button-main-question,
        .quizizz-copy-prompt-button-main-question {
            display: block; width: fit-content; margin: 8px auto 0 auto;
        }

        /* Gemini Response Popup - Dark Mode (Unchanged) */
        .quizizz-gemini-response-popup {
            position: fixed; top: 50%; left: 50%; transform: translate(-50%, -50%);
            background-color: #2d3748; color: #e2e8f0; border: 1px solid #4a5568;
            border-radius: 8px; padding: 20px; z-index: 10001; min-width: 380px;
            max-width: 650px; max-height: 80vh; overflow-y: auto;
            box-shadow: 0 10px 25px rgba(0,0,0,0.35), 0 6px 10px rgba(0,0,0,0.25);
            font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Oxygen-Sans, Ubuntu, Cantarell, "Helvetica Neue", sans-serif;
            font-size: 15px;
        }
        .quizizz-gemini-response-popup-header {
            display: flex; justify-content: space-between; align-items: center;
            margin-bottom: 15px; padding-bottom: 10px; border-bottom: 1px solid #4a5568;
        }
        .quizizz-gemini-response-popup-title { font-weight: 600; font-size: 1.2em; color: #a0aec0; }
        .quizizz-gemini-response-popup-close {
            background: none; border: none; font-size: 1.9em; line-height: 1;
            cursor: pointer; color: #a0aec0; padding: 0 5px; transition: color 0.2s ease-in-out;
        }
        .quizizz-gemini-response-popup-close:hover { color: #cbd5e0; }
        .quizizz-gemini-response-popup-content {
            white-space: pre-wrap; font-size: 1em; line-height: 1.65; color: #cbd5e0;
        }
        .quizizz-gemini-response-popup-content strong,
        .quizizz-gemini-response-popup-content b { color: #e2e8f0; font-weight: 600; }
        .quizizz-gemini-response-popup-loading {
            text-align: center; font-style: italic; color: #a0aec0; padding: 25px 0; font-size: 1.05em;
        }
        .quizizz-gemini-response-popup::-webkit-scrollbar { width: 8px; }
        .quizizz-gemini-response-popup::-webkit-scrollbar-track { background: #2d3748; }
        .quizizz-gemini-response-popup::-webkit-scrollbar-thumb {
            background-color: #4a5568; border-radius: 4px; border: 2px solid #2d3748;
        }
        .quizizz-gemini-response-popup::-webkit-scrollbar-thumb:hover { background-color: #718096; }

        /* UI Toggle Button */
       #qht-toggle-ui-button {
            position: fixed;
            bottom: 15px;
            left: 15px; /* CHANGED from right */
            z-index: 10002; /* Above Gemini popup */
            background-color: #607D8B; /* Blue Grey - default for mods ON */
            border: none;
            border-radius: 50%;
            width: 44px;
            height: 44px;
            cursor: pointer;
            box-shadow: 0 3px 6px rgba(0,0,0,0.16), 0 3px 6px rgba(0,0,0,0.23);
            transition: background-color 0.2s ease-in-out, transform 0.2s ease;
            user-select: none; /* Prevent text selection on double click */
            padding: 6px; /* Space for the icon inside */
            box-sizing: border-box; /* Padding included in width/height */
            display: flex; /* To center the image */
            align-items: center; /* To center the image */
            justify-content: center; /* To center the image */
        }
        #qht-toggle-ui-button:hover {
            background-color: #546E7A; /* Darker Blue Grey */
            transform: scale(1.05);
        }
        #qht-toggle-ui-button.qht-mods-hidden-state { /* When mods are OFF (hidden) */
            background-color: #4CAF50; /* Green */
        }
        #qht-toggle-ui-button.qht-mods-hidden-state:hover {
            background-color: #43A047; /* Darker Green */
        }
    `);

    let currentQuestionImageUrl = null;

    GM_registerMenuCommand('Set Gemini API Key', () => {
        const currentKey = GM_getValue(GEMINI_API_KEY_STORAGE, '');
        const newKey = prompt('Enter your Gemini API Key:', currentKey);
        if (newKey !== null) {
            GM_setValue(GEMINI_API_KEY_STORAGE, newKey.trim());
            GM_log('Gemini API Key updated.');
            alert('Gemini API Key saved!');
        }
    });

    async function getApiKey() {
        let apiKey = GM_getValue(GEMINI_API_KEY_STORAGE, null);
        if (!apiKey || apiKey.trim() === '') {
            apiKey = prompt('Gemini API Key not set. Please enter your API Key:');
            if (apiKey && apiKey.trim() !== '') {
                GM_setValue(GEMINI_API_KEY_STORAGE, apiKey.trim());
                return apiKey.trim();
            } else {
                alert('Gemini API Key is required. Set it via the Tampermonkey menu.');
                return null;
            }
        }
        return apiKey.trim();
    }

    function fetchImageAsBase64(imageUrl) {
        // ... (implementation unchanged)
        return new Promise((resolve, reject) => {
            GM_log(`Fetching image: ${imageUrl}`);
            GM_xmlhttpRequest({
                method: 'GET',
                url: imageUrl,
                responseType: 'blob',
                onload: function(response) {
                    if (response.status >= 200 && response.status < 300) {
                        const blob = response.response;
                        const reader = new FileReader();
                        reader.onloadend = () => {
                            const dataUrl = reader.result;
                            const mimeType = dataUrl.substring(dataUrl.indexOf(':') + 1, dataUrl.indexOf(';'));
                            const base64Data = dataUrl.substring(dataUrl.indexOf(',') + 1);
                            GM_log(`Image fetched successfully. MIME type: ${mimeType}, Size: ~${(base64Data.length * 0.75 / 1024).toFixed(2)} KB`);
                            resolve({ base64Data, mimeType });
                        };
                        reader.onerror = (error) => {
                            GM_log('FileReader error: ' + error);
                            reject('FileReader error while processing image.');
                        };
                        reader.readAsDataURL(blob);
                    } else {
                        GM_log(`Failed to fetch image. Status: ${response.status}`);
                        reject(`Failed to fetch image. Status: ${response.status}`);
                    }
                },
                onerror: function(error) {
                    GM_log('GM_xmlhttpRequest error fetching image: ' + JSON.stringify(error));
                    reject('Network error while fetching image.');
                },
                ontimeout: function() {
                    GM_log('Image fetch request timed out.');
                    reject('Image fetch request timed out.');
                }
            });
        });
    }

    function buildGeminiPrompt(question, options, hasImage) {
        const promptText = `
Context: You are an AI assistant helping a user with a Quizizz quiz.
The user needs to identify the correct answer(s) from the given options for the following question.
${hasImage ? "An image is associated with this question; please consider it in your analysis." : ""}

Question: "${question}"

Available Options:
${options.map((opt, i) => `${i + 1}. ${opt}`).join('\n')}

Please perform the following:
1. Identify the correct answer or answers from the "Available Options" list.
2. Provide a concise reasoning for your choice(s).
3. Format your response clearly. Start with "Correct Answer(s):" followed by the answer(s) (you can refer to them by option number or text), and then "Reasoning:" followed by your explanation. Be brief and to the point. If the options seem unrelated or the question cannot be answered with the provided information (including the image if present), please state that clearly in your reasoning.
`;
        return promptText; // Intentionally not trimming, to match original behavior if line breaks matter to API.
    }


    function askGemini(apiKey, question, options, imageData) {
        const promptText = buildGeminiPrompt(question, options, !!imageData); // Use helper and pass boolean for image presence

        const apiUrl = `https://generativelanguage.googleapis.com/v1beta/models/${GEMINI_MODEL}:generateContent?key=${apiKey}`;
        let requestPayloadContents = [{ parts: [{ text: promptText }] }];
        if (imageData && imageData.base64Data && imageData.mimeType) {
            requestPayloadContents[0].parts.push({
                inline_data: {
                    mime_type: imageData.mimeType,
                    data: imageData.base64Data
                }
            });
        }
        const apiPayload = {
            contents: requestPayloadContents,
            generationConfig: {
                temperature: 0.2, maxOutputTokens: 500, topP: 0.95, topK: 40
            },
            safetySettings: [
                { category: "HARM_CATEGORY_HARASSMENT", threshold: "BLOCK_MEDIUM_AND_ABOVE" },
                { category: "HARM_CATEGORY_HATE_SPEECH", threshold: "BLOCK_MEDIUM_AND_ABOVE" },
                { category: "HARM_CATEGORY_SEXUALLY_EXPLICIT", threshold: "BLOCK_MEDIUM_AND_ABOVE" },
                { category: "HARM_CATEGORY_DANGEROUS_CONTENT", threshold: "BLOCK_MEDIUM_AND_ABOVE" }
            ]
        };
        GM_log('--- Sending to Gemini API ---');
        showGeminiResponsePopup("Loading Gemini's insights...", true);
        GM_xmlhttpRequest({
            method: 'POST', url: apiUrl, headers: { 'Content-Type': 'application/json' },
            data: JSON.stringify(apiPayload),
            onload: function(response) {
                GM_log('--- Received from Gemini API ---');
                GM_log('Response Status: ' + response.status);
                try {
                    const result = JSON.parse(response.responseText);
                    if (result.candidates && result.candidates.length > 0 &&
                        result.candidates[0].content && result.candidates[0].content.parts &&
                        result.candidates[0].content.parts.length > 0) {
                        const geminiText = result.candidates[0].content.parts[0].text;
                        showGeminiResponsePopup(geminiText);
                    } else if (result.promptFeedback && result.promptFeedback.blockReason) {
                         showGeminiResponsePopup(`Gemini API Error: Blocked due to ${result.promptFeedback.blockReason}.\nDetails: ${JSON.stringify(result.promptFeedback.safetyRatings)}`);
                    } else if (result.error) {
                        showGeminiResponsePopup(`Gemini API Error: ${result.error.message}\nDetails: ${JSON.stringify(result.error.details)}`);
                    } else {
                        showGeminiResponsePopup('Gemini API Error: Could not parse a valid response.');
                    }
                } catch (e) {
                    showGeminiResponsePopup('Gemini API Error: Failed to parse JSON response.\n' + e.message + '\nRaw response: ' + response.responseText);
                }
            },
            onerror: function(response) {
                GM_log('--- Gemini API Error (onerror) ---');
                GM_log('Response Status: ' + response.status);
                GM_log('Response Text: ' + response.responseText);
                showGeminiResponsePopup(`Gemini API Error: Request failed. Status: ${response.status}`);
            },
            ontimeout: function() {
                GM_log('--- Gemini API Error (ontimeout) ---');
                showGeminiResponsePopup('Gemini API Error: Request timed out.');
            }
        });
    }

    function showGeminiResponsePopup(content, isLoading = false) {
        if (!uiModificationsEnabled) { // If UI is globally disabled, don't show popups
             const existingPopup = document.getElementById('quizizz-gemini-popup');
             if (existingPopup) existingPopup.remove(); // Ensure it's gone
             return;
        }
        // ... (rest of implementation unchanged)
        let popup = document.getElementById('quizizz-gemini-popup');
        if (!popup) {
            popup = document.createElement('div');
            popup.id = 'quizizz-gemini-popup';
            popup.classList.add('quizizz-gemini-response-popup');
            const header = document.createElement('div');
            header.classList.add('quizizz-gemini-response-popup-header');
            const title = document.createElement('span');
            title.classList.add('quizizz-gemini-response-popup-title');
            title.textContent = "Gemini AI Response";
            const closeButton = document.createElement('button');
            closeButton.classList.add('quizizz-gemini-response-popup-close');
            closeButton.innerHTML = '×';
            closeButton.onclick = () => popup.remove();
            header.appendChild(title);
            header.appendChild(closeButton);
            popup.appendChild(header);
            const contentDiv = document.createElement('div');
            contentDiv.classList.add('quizizz-gemini-response-popup-content');
            popup.appendChild(contentDiv);
            document.body.appendChild(popup);
        }
        const contentDiv = popup.querySelector('.quizizz-gemini-response-popup-content');
        if (isLoading) {
            contentDiv.innerHTML = `<div class="quizizz-gemini-response-popup-loading">${content}</div>`;
        } else {
            let formattedContent = content.replace(/^(Correct Answer\(s\):)/gmi, '<strong>$1</strong>');
            formattedContent = formattedContent.replace(/^(Reasoning:)/gmi, '<br><br><strong>$1</strong>');
            contentDiv.innerHTML = formattedContent;
        }
        popup.style.display = 'block';
    }

    function extractAndProcess() {
        GM_log(`Quizizz Hypertool: Running extraction. UI Mods Enabled: ${uiModificationsEnabled}`);

        // 1. Universal Cleanup: Always remove script's previous UI elements
        document.querySelectorAll('.quizizz-ddg-link, .quizizz-gemini-button, .quizizz-copy-prompt-button').forEach(el => el.remove());
        document.querySelectorAll('.quizizz-option-wrapper').forEach(wrapper => {
            const button = wrapper.querySelector('button.option');
            const parent = wrapper.parentNode;
            if (button && parent) {
                parent.insertBefore(button, wrapper); // Move button out of wrapper
            }
            wrapper.remove(); // Remove the script's wrapper
        });
        const geminiPopup = document.getElementById('quizizz-gemini-popup');
        if (geminiPopup) {
            geminiPopup.remove(); // Remove it completely, will be recreated if needed
        }

        // 2. If UI modifications are disabled, stop here.
        if (!uiModificationsEnabled) {
            GM_log('UI modifications disabled. Cleanup complete. No new UI will be added.');
            return;
        }

        // 3. If UI modifications are enabled, proceed to add elements.
        GM_log('UI modifications enabled. Proceeding to add elements.');

        let questionTitle = '';
        let optionTexts = [];
        currentQuestionImageUrl = null; // Reset for each extraction

        const questionImageElement = document.querySelector(
            'div[data-testid="question-container-text"] img, ' +
            'div[class*="question-media-container"] img, ' +
            'img[data-testid="question-container-image"], ' +
            '.question-image'
        );
        if (questionImageElement && questionImageElement.src) {
            currentQuestionImageUrl = questionImageElement.src;
            if (currentQuestionImageUrl.startsWith('/')) {
                currentQuestionImageUrl = window.location.origin + currentQuestionImageUrl;
            }
            GM_log('Found question image URL: ' + currentQuestionImageUrl);
        } else {
            GM_log('No question image found or image src is missing.');
        }

        const questionTitleTextElement = document.querySelector(
            'div[data-testid="question-container-text"] div#questionText p, div[data-cy="question-text-color"] p'
        );
        const questionTextOuterContainer = document.querySelector('div[data-testid="question-container-text"]');

        if (questionTitleTextElement && questionTextOuterContainer) {
            questionTitle = questionTitleTextElement.textContent.trim();
            GM_log('Question Title: ' + questionTitle);

            if (questionTitle || currentQuestionImageUrl) { // Only add buttons if there's a question text or image
                // DDG Q Link
                const ddgQLink = document.createElement('a');
                const ddgQuery = questionTitle ? questionTitle : (currentQuestionImageUrl ? "image in question" : "Quizizz Question");
                ddgQLink.href = `https://duckduckgo.com/?q=${encodeURIComponent(ddgQuery)}`;
                ddgQLink.textContent = 'DDG Q';
                ddgQLink.target = '_blank';
                ddgQLink.rel = 'noopener noreferrer';
                ddgQLink.classList.add('quizizz-ddg-link', 'quizizz-ddg-link-main-question');
                questionTextOuterContainer.appendChild(ddgQLink);

                // Copy Gemini Prompt Button
                const copyPromptButton = document.createElement('button');
                copyPromptButton.textContent = 'Copy Gemini Prompt';
                copyPromptButton.classList.add('quizizz-copy-prompt-button', 'quizizz-copy-prompt-button-main-question');
                copyPromptButton.onclick = async () => {
                    if (!questionTitle && !currentQuestionImageUrl && optionTexts.length === 0) {
                        alert("Could not find question text, image, or options to build the prompt.");
                        GM_log("Copy Prompt: no content found to build prompt."); return;
                    }
                    const promptToCopy = buildGeminiPrompt(
                        questionTitle || (currentQuestionImageUrl ? "(See attached image)" : "No text question"),
                        optionTexts,
                        !!currentQuestionImageUrl // Pass boolean for hasImage
                    );
                    try {
                        await navigator.clipboard.writeText(promptToCopy);
                        const originalText = copyPromptButton.textContent;
                        copyPromptButton.textContent = 'Copied!';
                        copyPromptButton.style.backgroundColor = '#4CAF50'; // Temp success color
                        GM_log('Gemini prompt copied to clipboard.');
                        setTimeout(() => {
                            copyPromptButton.textContent = originalText;
                            copyPromptButton.style.backgroundColor = ''; // Revert to original CSS color
                        }, 2000);
                    } catch (err) {
                        GM_log('Failed to copy prompt: ', err);
                        alert('Failed to copy prompt. See console for details.');
                    }
                };
                questionTextOuterContainer.appendChild(copyPromptButton);

                // Ask Gemini Button
                const geminiButton = document.createElement('button');
                geminiButton.textContent = 'Ask Gemini';
                geminiButton.classList.add('quizizz-gemini-button', 'quizizz-gemini-button-main-question');
                geminiButton.onclick = async () => {
                    const apiKey = await getApiKey();
                    if (!apiKey) { GM_log("Gemini: API key missing."); return; }
                    if (!questionTitle && !currentQuestionImageUrl && optionTexts.length === 0) {
                        alert("Could not find question text, image, or options to send to Gemini.");
                        GM_log("Gemini: no content found to send."); return;
                    }
                    showGeminiResponsePopup("Preparing data for Gemini...", true);
                    let imageData = null;
                    if (currentQuestionImageUrl) {
                        try {
                            imageData = await fetchImageAsBase64(currentQuestionImageUrl);
                        } catch (error) {
                            GM_log('Error fetching image for Gemini: ' + error);
                            showGeminiResponsePopup(`Failed to fetch image: ${error}\nProceeding with text only.`, false);
                        }
                    }
                    askGemini(apiKey, questionTitle || (currentQuestionImageUrl ? "(See attached image)" : "No text question"), optionTexts, imageData);
                };
                questionTextOuterContainer.appendChild(geminiButton);
            }
        } else {
            GM_log('Question Title or its container not found.');
        }

        optionTexts = []; // Reset for each extraction
        const optionButtons = document.querySelectorAll('button.option');
        if (optionButtons.length > 0) {
            Array.from(optionButtons).forEach((button) => {
                const optionTextElement = button.querySelector('div#optionText p, .option-text p, .resizeable p');
                if (optionTextElement) {
                    const optionText = optionTextElement.textContent.trim();
                    if (optionText) optionTexts.push(optionText);

                    if (optionText) { // Only add DDG link if option text exists
                        const ddgLink = document.createElement('a');
                        ddgLink.href = `https://duckduckgo.com/?q=${encodeURIComponent(optionText)}`;
                        ddgLink.textContent = 'DDG';
                        ddgLink.target = '_blank';
                        ddgLink.rel = 'noopener noreferrer';
                        ddgLink.classList.add('quizizz-ddg-link', 'quizizz-ddg-link-option-item');

                        const wrapper = document.createElement('div');
                        wrapper.classList.add('quizizz-option-wrapper');
                        wrapper.style.flexBasis = button.style.flexBasis || '100%'; // Preserve flex basis if set

                        if (button.parentNode) {
                            button.parentNode.insertBefore(wrapper, button);
                            wrapper.appendChild(button);
                            wrapper.appendChild(ddgLink);
                        }
                    }
                }
            });
            GM_log(`Processed ${optionButtons.length} option buttons. Options found: ${optionTexts.length > 0 ? optionTexts.join('; ') : 'None'}`);
        } else {
            GM_log('No option buttons found.');
        }
        GM_log('--- Extraction complete ---');
    }

    // --- Polling and UI Toggle Logic ---
    let lastPageInfo = "INITIAL_STATE_PAGE_INFO_MAGIC_STRING_FOR_FIRST_RUN_DETECTION";
    let toggleButton = null;

    function updateToggleButtonAppearance() {
        if (!toggleButton) return;
        if (uiModificationsEnabled) {
            toggleButton.innerHTML = '✕'; // Heavy X (Icon for "Hide/Disable Mods")
            toggleButton.title = 'Hide Hypertool Modifications';
            toggleButton.classList.remove('qht-mods-hidden-state');
        } else {
            toggleButton.innerHTML = '🛠️'; // Hammer and Wrench (Icon for "Show/Enable Mods")
            toggleButton.title = 'Show Hypertool Modifications';
            toggleButton.classList.add('qht-mods-hidden-state');
        }
    }

    function handleToggleUiClick() {
        uiModificationsEnabled = !uiModificationsEnabled;
        GM_setValue(UI_MODS_ENABLED_KEY, uiModificationsEnabled);
        GM_log(`UI Modifications ${uiModificationsEnabled ? 'Enabled' : 'Disabled'}`);
        updateToggleButtonAppearance();
        // Force a re-evaluation and re-processing of the page
        lastPageInfo = "FORCE_REPROCESS_MAGIC_STRING_" + Date.now(); // Ensure it's different
        checkPageInfoAndReprocess();
    }

    function createToggleButton() {
        if (document.getElementById('qht-toggle-ui-button')) return;

        toggleButton = document.createElement('button');
        toggleButton.id = 'qht-toggle-ui-button';
        updateToggleButtonAppearance(); // Set initial text/icon
        toggleButton.addEventListener('click', handleToggleUiClick);
        document.body.appendChild(toggleButton);
    }

    function checkPageInfoAndReprocess() {
        const pageInfoElement = document.querySelector('div.pill p, div[class*="question-counter"] p');
        let currentPageInfoText = "";

        if (pageInfoElement) {
            currentPageInfoText = pageInfoElement.textContent.trim();
        }

        if (currentPageInfoText !== lastPageInfo) {
            if (currentPageInfoText === "" && lastPageInfo === "INITIAL_STATE_PAGE_INFO_MAGIC_STRING_FOR_FIRST_RUN_DETECTION") {
                GM_log('Initial poll: Page info element not yet found or is empty. Setting baseline and waiting for content to appear.');
                lastPageInfo = currentPageInfoText; // Set current empty state as baseline
                // Call extractAndProcess once on first load if UI is enabled, even if pageInfo is empty initially,
                // as some quiz UIs might not have a counter but still have questions.
                if (uiModificationsEnabled) {
                    extractAndProcess();
                }
                return;
            }
            GM_log(`Page info change detected or forced reprocess. Old: "${lastPageInfo}", New: "${currentPageInfoText}". Triggering UI update.`);
            lastPageInfo = currentPageInfoText;
            extractAndProcess(); // This will handle cleanup and re-adding based on uiModificationsEnabled
        }
    }

    // --- Initialization ---
    if (document.body) {
        createToggleButton();
        // Initial run for pages that might already be loaded without immediate page info change
        checkPageInfoAndReprocess();
    } else {
        window.addEventListener('DOMContentLoaded', () => {
            createToggleButton();
            checkPageInfoAndReprocess();
        });
    }

    setInterval(checkPageInfoAndReprocess, 1500); // Poll for changes
    GM_log(`Quizizz Hypertool (v${GM_info.script.version}) loaded. Monitoring page info for changes.`);

})();