Whitelist Key Lock V2

Whitelist lock screen with key verification, animated settings, and changelog

// ==UserScript==
// @name         Whitelist Key Lock V2
// @namespace    http://tampermonkey.net/
// @version      1.2.2
// @description  Whitelist lock screen with key verification, animated settings, and changelog
// @match        https://www.fluentu.com/spanish/learning/*
// @grant        GM_xmlhttpRequest
// @connect      drive.google.com
// @connect      docs.google.com
// @connect      googleusercontent.com
// @connect      *
// ==/UserScript==

// Global state
let WHITELIST = null;
let isLoadingWhitelist = false;
let whitelistCallbacks = [];
const FILE_ID = '1QL-7XnZWLY5iVdVOKWCxPLaqZoerYRa7';
const RAW_URL = `https://drive.google.com/uc?id=${FILE_ID}&export=download`;

// Global state
let overlay = null;
let statusCircle = null;
let isOverlayVisible = false;
let autoAnswerEnabled = false;
let hasValidKey = false;
let keyPromptShown = false;
let lastQuestionSnapshot = '';
let whitelistLoaded = false;

// Add a global flag to prevent multiple simultaneous fills
let isFillingDataWord = false;

// --- CONFIG ---
const CHANGELOG_FILE_ID = '1M5ALLc3lx1m23gOEhoHraeX0kiK8lw3W';
const CHANGELOG_URL = `https://drive.google.com/uc?id=${CHANGELOG_FILE_ID}&export=download`;
const CHANGELOG_LOCAL_KEY = 'wl_last_seen_changelog_version';
const AUTO_MODE_KEY = 'wl_auto_mode';
const DARK_MODE_KEY = 'wl_dark_mode';

// SVGs for icons (inline for portability)
const COG_ICON = `<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg"><circle cx="14" cy="14" r="5" fill="#888"/><g stroke="#444" stroke-width="2" stroke-linecap="round"><path d="M14 2v3M14 23v3M4.93 4.93l2.12 2.12M20.95 20.95l2.12 2.12M2 14h3M23 14h3M4.93 23.07l2.12-2.12M20.95 7.05l2.12-2.12"/></g><circle cx="14" cy="14" r="9" stroke="#444" stroke-width="2"/></svg>`;
const CHANGELOG_ICON = `<svg width="28" height="28" viewBox="0 0 28 28" fill="none" xmlns="http://www.w3.org/2000/svg"><rect x="4" y="4" width="20" height="20" rx="5" fill="#fff" stroke="#222" stroke-width="2"/><rect x="7" y="8" width="14" height="2" rx="1" fill="#2196F3"/><rect x="7" y="12" width="10" height="2" rx="1" fill="#2196F3"/><rect x="7" y="16" width="6" height="2" rx="1" fill="#2196F3"/><rect x="7" y="20" width="2" height="2" rx="1" fill="#2196F3"/></svg>`;

// 1. Stop auto-answer interval when no questions are present
let autoAnswerInterval = null;

// Add whitelist caching for faster load
const WHITELIST_CACHE_KEY = 'wl_cached_whitelist_v2';
const WHITELIST_CACHE_TTL = 1000 * 60 * 60 * 6; // 6 hours

function getCachedWhitelist() {
    try {
        const cached = JSON.parse(localStorage.getItem(WHITELIST_CACHE_KEY));
        if (cached && cached.data && Date.now() - cached.time < WHITELIST_CACHE_TTL) {
            return cached.data;
        }
    } catch (e) {}
    return null;
}

function setCachedWhitelist(data) {
    localStorage.setItem(WHITELIST_CACHE_KEY, JSON.stringify({ data, time: Date.now() }));
}

function fetchWhitelistFromNetwork(callback) {
    GM_xmlhttpRequest({
        method: 'GET',
        url: RAW_URL,
        headers: { 'Accept': '*/*', 'Cache-Control': 'no-cache' },
        nocache: true,
        onload: function(response) {
            try {
                const content = response.responseText;
                let parsed = null;
                try {
                    const whitelist = JSON.parse(content);
                    if (Array.isArray(whitelist)) {
                        parsed = whitelist;
                    }
                } catch (e) {
                    const arrayMatch = content.match(/\[\s*"[^"]+(?:",\s*"[^"]+)*"\s*\]/);
                    if (arrayMatch) {
                        parsed = JSON.parse(arrayMatch[0]);
                    }
                }
                callback(Array.isArray(parsed) ? parsed : null);
            } catch (error) {
                callback(null);
            }
        },
        onerror: function() { callback(null); }
    });
}

function loadWhitelist(callback) {
    // Try cache first
    const cached = getCachedWhitelist();
    if (cached) {
        callback(cached);
        // Also update in background
        fetchWhitelistFromNetwork((fresh) => {
            if (fresh) setCachedWhitelist(fresh);
        });
        return;
    }
    // Otherwise, fetch from network
    fetchWhitelistFromNetwork((fresh) => {
        if (fresh) setCachedWhitelist(fresh);
        callback(fresh);
    });
}

function verifyKeyWithWhitelist(key, callback) {
    console.log('=== VERIFYING KEY ===');

    if (!key || typeof key !== 'string') {
        console.log('Invalid key format');
        callback(false);
        return;
    }

    const trimmedKey = key.trim();

    loadWhitelist(whitelist => {
        if (!whitelist || !Array.isArray(whitelist)) {
            console.log('Invalid key');
            callback(false);
            return;
        }

        // Case-sensitive comparison of keys
        const isValid = whitelist.some(validKey => {
            if (typeof validKey !== 'string') return false;
            return validKey.trim() === trimmedKey;
        });

        if (!isValid) {
            console.log('Invalid key');
        }
        callback(isValid);
    });
}

function checkWhitelistKey(callback) {
    console.log('=== CHECKING SAVED KEY ===');
    const savedKey = localStorage.getItem('my_wl_key');

    if (!savedKey) {
        console.log('No saved key found');
        callback(false);
        return;
    }

    verifyKeyWithWhitelist(savedKey, (isValid) => {
        console.log('Saved key valid?', isValid);
        hasValidKey = isValid;
        callback(isValid);
    });
}

function removeScriptFeatures() {
    if (statusCircle) statusCircle.remove();
    if (overlay) overlay.remove();
    statusCircle = null;
    overlay = null;
    autoAnswerEnabled = false;
    isOverlayVisible = false;
}

function showLockScreen() {
    if (!window.location.href.startsWith('https://www.fluentu.com/spanish/learning/')) return;
    if (keyPromptShown) return;

    keyPromptShown = true;
    removeScriptFeatures();
    createLockScreenUI();
}

function createLockScreenUI() {
    const overlay = document.createElement('div');
    overlay.style = `
        position: fixed;
        top: 0;
        left: 0;
        width: 100vw;
        height: 100vh;
        z-index: 999999;
        background: rgba(0, 0, 0, 0.85);
        display: flex;
        flex-direction: column;
        align-items: center;
        justify-content: center;
        font-family: Arial, sans-serif;
    `;
    overlay.innerHTML = `
        <div style="
            background: #fff;
            padding: 2rem;
            border-radius: 10px;
            box-shadow: 0 4px 6px rgba(0, 0, 0, 0.1);
            text-align: center;
            max-width: 90vw;
            width: 400px;
            position: relative;
        ">
            <div id="wlclose" style="
                position: absolute;
                top: 10px;
                right: 10px;
                cursor: pointer;
                font-size: 20px;
                width: 24px;
                height: 24px;
                line-height: 24px;
                text-align: center;
                border-radius: 12px;
                background: #f0f0f0;
                color: #666;
                transition: all 0.2s;
            ">×</div>
            <div id="wlicon" style="font-size: 64px; margin-bottom: 24px;">🔒</div>
            <h2 style="color: #333; margin: 0 0 1rem 0; font-size: 24px;">Enter Whitelist Key</h2>
            <input id="wlkey" type="text" style="
                width: 100%;
                padding: 12px;
                border: 2px solid #ddd;
                border-radius: 6px;
                font-size: 16px;
                margin-bottom: 1rem;
                box-sizing: border-box;
                outline: none;
            " placeholder="Enter your key here" autofocus>
            <div id="wlerr" style="
                color: #ff4444;
                min-height: 20px;
                margin-bottom: 1rem;
                font-size: 14px;
            "></div>
            <button id="wlsubmit" style="
                background: #4CAF50;
                color: white;
                border: none;
                padding: 12px 24px;
                border-radius: 6px;
                font-size: 16px;
                cursor: pointer;
                transition: background 0.2s;
                margin-right: 10px;
            ">Unlock</button>
            <button id="wlcontinue" style="
                display: none;
                background: #2196F3;
                color: white;
                border: none;
                padding: 12px 24px;
                border-radius: 6px;
                font-size: 16px;
                cursor: pointer;
                transition: background 0.2s;
            ">Continue</button>
            <button id="wlskip" style="
                background: #666;
                color: white;
                border: none;
                padding: 12px 24px;
                border-radius: 6px;
                font-size: 16px;
                cursor: pointer;
                transition: background 0.2s;
            ">Continue Without Key</button>
            <div style="
                margin-top: 1rem;
                font-size: 12px;
                color: #666;
            ">A valid whitelist key is required to use the script's features.</div>
        </div>
    `;
    document.body.appendChild(overlay);

    const keyInput = overlay.querySelector('#wlkey');
    const submitBtn = overlay.querySelector('#wlsubmit');
    const continueBtn = overlay.querySelector('#wlcontinue');
    const skipBtn = overlay.querySelector('#wlskip');
    const errorDiv = overlay.querySelector('#wlerr');
    const iconDiv = overlay.querySelector('#wlicon');
    const closeBtn = overlay.querySelector('#wlclose');

    function validateAndSubmit() {
        const key = keyInput.value.trim();
        if (!key) {
            errorDiv.textContent = 'Please enter a key';
            errorDiv.style.color = '#ff4444';
            return;
        }

        verifyKeyWithWhitelist(key, (isValid) => {
            if (isValid) {
                // Valid key - show success UI
                iconDiv.textContent = '✅';
                errorDiv.textContent = 'Valid key! Click Continue to proceed.';
                errorDiv.style.color = '#4CAF50';
                submitBtn.style.display = 'none';
                skipBtn.style.display = 'none';
                continueBtn.style.display = 'inline-block';
                keyInput.disabled = true;

                // Set up continue button
                continueBtn.onclick = () => {
                    localStorage.setItem('my_wl_key', key);
                    hasValidKey = true;
                    overlay.remove();
                    keyPromptShown = false;
                    initializeScriptFeatures();
                };
            } else {
                errorDiv.textContent = 'Invalid key. Please try again or continue without a key.';
                errorDiv.style.color = '#ff4444';
                keyInput.select();
            }
        });
    }

    submitBtn.onclick = validateAndSubmit;
    skipBtn.onclick = () => {
        hasValidKey = false;
        localStorage.removeItem('my_wl_key');
        removeScriptFeatures();
        overlay.remove();
        keyPromptShown = false;
    };

    closeBtn.onclick = () => {
        overlay.remove();
        keyPromptShown = false;
        // If we have a valid key, make sure script features are initialized
        if (hasValidKey) {
            initializeScriptFeatures();
        }
    };

    closeBtn.onmouseover = () => {
        closeBtn.style.background = '#e0e0e0';
        closeBtn.style.color = '#333';
    };

    closeBtn.onmouseout = () => {
        closeBtn.style.background = '#f0f0f0';
        closeBtn.style.color = '#666';
    };

    keyInput.onkeypress = (e) => {
        if (e.key === 'Enter') validateAndSubmit();
    };
}

function createStatusCircle() {
    statusCircle = document.createElement('div');
    statusCircle.style = `
        position: fixed;
        top: 10px;
        left: 10px;
        width: 12px;
        height: 12px;
        background: #ff4040;
        border-radius: 50%;
        z-index: 9998;
        cursor: pointer;
        transition: background-color 0.3s;
    `;
    statusCircle.title = 'Click to toggle auto-answer';
    statusCircle.onclick = () => {
        if (!hasValidKey) {
            showLockScreen();
            return;
        }
        autoAnswerEnabled = !autoAnswerEnabled;
        updateOverlayWithSecretPhrase();
        updateStatusCircle();
    };
    document.body.appendChild(statusCircle);
}

function createOverlay() {
    overlay = document.createElement('div');
    overlay.style = `
        position: fixed;
        top: 10px;
        left: 30px;
        background: rgba(0, 0, 0, 0.8);
        color: white;
        padding: 10px;
        border-radius: 5px;
        font-family: Arial, sans-serif;
        font-size: 14px;
        z-index: 9999;
        display: none;
        transition: opacity 0.3s;
    `;
    document.body.appendChild(overlay);
}

function updateStatusCircle() {
    if (!statusCircle) return;
    statusCircle.style.background = autoAnswerEnabled ? '#40ff40' : '#ff4040';
}

function updateOverlayWithSecretPhrase() {
    let phrases = [];
    document.querySelectorAll('div[data-studied-phrase]').forEach(div => {
        const phrase = div.getAttribute('data-studied-phrase');
        if (phrase && !phrases.includes(phrase)) phrases.push(phrase);
    });
    document.querySelectorAll('.trans-content').forEach(div => {
        const phrase = div.textContent.trim();
        if (phrase && !phrases.includes(phrase)) phrases.push(phrase);
    });

    if (!overlay) createOverlay();
    const autoLabel = autoAnswerEnabled ? '<span style="color:#00ff00;">ON</span>' : '<span style="color:#ff4040;">OFF</span>';
    if (phrases.length === 0) {
        overlay.innerHTML = `<div>Secret phrase not found</div>
                           <div style="margin-top:6px;">Auto Answer: ${autoLabel}</div>`;
    } else {
        overlay.innerHTML = `<div>${phrases[0]?.trim() || 'Secret phrase not found'}</div>
                           <div style="margin-top:6px;">Auto Answer: ${autoLabel}</div>`;
    }
    updateStatusCircle();
}

function getAnswerWord() {
    const word = document.querySelector('.quiz-clip-preview-active-word[data-word]')?.getAttribute('data-word')?.trim() || '';
    // Clean up any ellipses and extra spaces
    return word.replace('...', ' ').replace(/\s+/g, ' ').trim();
}

function normalizeText(text) {
    if (!text) return '';
    return text
        .replace(/\.{3,}/g, ' ') // Replace ellipses with space
        .replace(/\s+/g, ' ') // Normalize multiple spaces
        .replace(/[.,!¡?¿;:()\[\]{}*_]/g, '') // Remove punctuation
        .trim()
        .toLowerCase();
}

async function simulateInputEvents(element, value) {
    // Focus the element
    element.focus();
    element.dispatchEvent(new Event('focus', { bubbles: true }));

    // Clear existing value
    element.value = '';
    element.dispatchEvent(new Event('input', { bubbles: true }));

    // Simulate typing each character with proper events
    for (let i = 0; i < value.length; i++) {
        const char = value[i];

        // Create events with proper properties
        const keyDown = new KeyboardEvent('keydown', {
            key: char,
            code: `Key${char.toUpperCase()}`,
            bubbles: true,
            cancelable: true,
            composed: true,
            keyCode: char.charCodeAt(0),
            which: char.charCodeAt(0)
        });

        const keyPress = new KeyboardEvent('keypress', {
            key: char,
            code: `Key${char.toUpperCase()}`,
            bubbles: true,
            cancelable: true,
            composed: true,
            keyCode: char.charCodeAt(0),
            which: char.charCodeAt(0),
            charCode: char.charCodeAt(0)
        });

        const keyUp = new KeyboardEvent('keyup', {
            key: char,
            code: `Key${char.toUpperCase()}`,
            bubbles: true,
            cancelable: true,
            composed: true,
            keyCode: char.charCodeAt(0),
            which: char.charCodeAt(0)
        });

        // Update value and dispatch events in sequence
        element.dispatchEvent(keyDown);
        element.dispatchEvent(keyPress);

        // Update the value
        element.value = value.substring(0, i + 1);

        // Create and dispatch input event with proper data
        const inputEvent = new InputEvent('input', {
            bubbles: true,
            cancelable: true,
            composed: true,
            inputType: 'insertText',
            data: char
        });
        element.dispatchEvent(inputEvent);

        // Dispatch keyup
        element.dispatchEvent(keyUp);

        // Small delay between characters to mimic human typing
        if (i < value.length - 1) {
            await new Promise(resolve => setTimeout(resolve, 10));
        }
    }

    // Final change event
    element.dispatchEvent(new Event('change', { bubbles: true }));

    // Blur the element
    element.dispatchEvent(new Event('blur', { bubbles: true }));

    // Trigger React's synthetic events if needed
    if (window.React && window.React.__SECRET_INTERNALS_DO_NOT_USE_OR_YOU_WILL_BE_FIRED) {
        const nativeInputEvent = new Event('input', { bubbles: true });
        element.dispatchEvent(nativeInputEvent);
    }
}

function getMCStringForClick() {
    let answer = document.querySelector('.quiz-clip-preview-active-word[data-word]')?.getAttribute('data-word');
    if (!answer) {
        answer = document.querySelector('.quiz-clip-preview-active-word[data-native-phrase], .pinyin-speak-click-element[data-native-phrase]')?.getAttribute('data-native-phrase');
    }
    if (!answer && overlay) {
        answer = overlay.textContent.split('\n')[0].trim();
    }
    return normalizeText(answer || '');
}

async function fillBlankFromDataWord() {
    if (isFillingDataWord) return;
    isFillingDataWord = true;
    try {
        const letterInputs = [...document.querySelectorAll('input[id^="word-input-"][class*="letter"], input[id^="word-input-"][class*="space"]')].filter(inp => !inp.disabled);
        if (!letterInputs.length) { isFillingDataWord = false; return; }

        // Check if already filled
        const filledInputs = letterInputs.filter(input => input.value.trim() !== '');
        if (filledInputs.length === letterInputs.length) {
            const checkBtn = document.querySelector('button[title="Shortcut: Enter"], a[title="Shortcut: Enter"]');
            if (checkBtn && !checkBtn.disabled) {
                checkBtn.click();
                if (autoAnswerEnabled) {
                    setTimeout(() => {
                        const contBtn = document.querySelector('#quiz-next-round button[title="Shortcut: Enter"]');
                        if (contBtn) contBtn.click();
                    }, 100);
                }
            }
            isFillingDataWord = false;
            return;
        }

        // Get the full answer phrase
        let answer = '';
        const activeWord = document.querySelector('.quiz-clip-preview-active-word[data-word]');
        if (activeWord) {
            answer = activeWord.getAttribute('data-word');
        } else {
            const studiedPhrase = document.querySelector('[data-studied-phrase]');
            if (studiedPhrase) {
                answer = studiedPhrase.getAttribute('data-studied-phrase');
            }
        }

        answer = normalizeText(answer);
        if (!answer) { isFillingDataWord = false; return; }

        // Map out which inputs are space inputs
        const spaceInputs = letterInputs.map(input => input.className.includes('space'));

        // Convert answer into array of characters, preserving spaces
        const chars = answer.split('');
        let currentInputIndex = 0;
        let filledCount = 0;

        // Instantly fill all inputs
        for (let i = 0; i < chars.length && currentInputIndex < letterInputs.length; i++) {
            const input = letterInputs[currentInputIndex];
            const char = chars[i];
            if (spaceInputs[currentInputIndex] && char !== ' ') continue;
            if (!spaceInputs[currentInputIndex] && char === ' ') continue;
            input.value = char;
            filledCount++;
            currentInputIndex++;
        }

        if (filledCount > 0) {
            // Simulate a single key event on the last input to trigger UI update
            const lastInput = letterInputs[letterInputs.length - 1];
            lastInput.focus();
            lastInput.dispatchEvent(new KeyboardEvent('keydown', {key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true, cancelable: true, composed: true}));
            lastInput.dispatchEvent(new KeyboardEvent('keyup', {key: 'Enter', code: 'Enter', keyCode: 13, which: 13, bubbles: true, cancelable: true, composed: true}));
            lastInput.dispatchEvent(new InputEvent('input', {bubbles: true, cancelable: true, composed: true, inputType: 'insertText', data: ''}));
            lastInput.dispatchEvent(new Event('change', { bubbles: true }));
            lastInput.blur();

            // Click check button
            const checkBtn = document.querySelector('button[title="Shortcut: Enter"], a[title="Shortcut: Enter"]');
            if (checkBtn && !checkBtn.disabled) {
                checkBtn.click();
                if (autoAnswerEnabled) {
                    setTimeout(() => {
                        const contBtn = document.querySelector('#quiz-next-round button[title="Shortcut: Enter"]');
                        if (contBtn) contBtn.click();
                    }, 100);
                }
            }
        }

        if (window.wl_auto_mode === 'fast' && !window._fastAutoAnswered) {
            window._fastAutoAnswered = true;
            localStorage.setItem('wl_fast_auto_answered', 'true');
        }
    } finally {
        isFillingDataWord = false;
    }
}

async function fillBlankFromPhrase() {
    // Find the contenteditable input
    const input = document.querySelector('div[contenteditable="true"].js-quiz-answer-input-field, #translator-input');
    if (!input) {
        console.log('No input found');
        return;
    }

    // Try multiple methods to find the answer
    let answer = null;

    // Method 1: Check active word with data-word
    const activeWord = document.querySelector('.clip-preview-term-instance.word[data-word]');
    if (activeWord) {
        answer = activeWord.getAttribute('data-word');
    }

    // Method 2: Check data-studied-phrase elements
    if (!answer) {
        const studiedPhraseEl = document.querySelector('[data-studied-phrase]');
        if (studiedPhraseEl) {
            answer = studiedPhraseEl.getAttribute('data-studied-phrase');
        }
    }

    // Method 3: Check for native phrase
    if (!answer) {
        const nativePhraseEl = document.querySelector('[data-native-phrase]');
        if (nativePhraseEl) {
            answer = nativePhraseEl.getAttribute('data-native-phrase');
        }
    }

    if (!answer) {
        console.log('No answer found');
        return;
    }

    // Normalize the answer text
    answer = normalizeText(answer);
    console.log('Found answer:', answer);

    // Focus and click the input first
    input.focus();
    input.click();

    // Clear existing content
    input.textContent = '';

    // Type each character with proper events
    for (let i = 0; i < answer.length; i++) {
        const char = answer[i];

        // Create keyboard events
        const keyDown = new KeyboardEvent('keydown', {
            key: char,
            code: `Key${char.toUpperCase()}`,
            bubbles: true,
            cancelable: true,
            composed: true,
            keyCode: char.charCodeAt(0),
            which: char.charCodeAt(0)
        });

        const keyPress = new KeyboardEvent('keypress', {
            key: char,
            code: `Key${char.toUpperCase()}`,
            bubbles: true,
            cancelable: true,
            composed: true,
            keyCode: char.charCodeAt(0),
            which: char.charCodeAt(0),
            charCode: char.charCodeAt(0)
        });

        // Dispatch keydown and keypress
        input.dispatchEvent(keyDown);
        input.dispatchEvent(keyPress);

        // Update content
        input.textContent = answer.substring(0, i + 1);

        // Create and dispatch input event
        const inputEvent = new InputEvent('input', {
            inputType: 'insertText',
            data: char,
            bubbles: true,
            cancelable: true,
            composed: true
        });
        input.dispatchEvent(inputEvent);

        // Dispatch keyup
        const keyUp = new KeyboardEvent('keyup', {
            key: char,
            code: `Key${char.toUpperCase()}`,
            bubbles: true,
            cancelable: true,
            composed: true,
            keyCode: char.charCodeAt(0),
            which: char.charCodeAt(0)
        });
        input.dispatchEvent(keyUp);

        // Slightly faster delay between characters
        if (i < answer.length - 1) {
            await new Promise(resolve => setTimeout(resolve, 5));
        }
    }

    // Final change event
    input.dispatchEvent(new Event('change', { bubbles: true }));

    // Small delay before clicking check
    await new Promise(resolve => setTimeout(resolve, 25));

    // Simulate Enter key press
    const enterKeyDown = new KeyboardEvent('keydown', {
        key: 'Enter',
        code: 'Enter',
        keyCode: 13,
        which: 13,
        bubbles: true,
        cancelable: true,
        composed: true
    });
    input.dispatchEvent(enterKeyDown);
    document.dispatchEvent(enterKeyDown);

    // Click check button
    const checkBtn = document.querySelector('button[title="Shortcut: Enter"], a[title="Shortcut: Enter"]');
    if (checkBtn && !checkBtn.disabled) {
        checkBtn.click();
    }

    // If auto-answer is enabled, click continue after a short delay
    if (autoAnswerEnabled) {
        setTimeout(() => {
            const contBtn = document.querySelector('#quiz-next-round button[title="Shortcut: Enter"]');
            if (contBtn) contBtn.click();
        }, 100);
    }

    if (window.wl_auto_mode === 'fast' && !window._fastAutoAnswered) {
        window._fastAutoAnswered = true;
        localStorage.setItem('wl_fast_auto_answered', 'true');
    }
}

function clickCorrectAnswer() {
    const finalMCString = getMCStringForClick();
    if (!finalMCString) return;

    let clicked = false;
    const buttons = Array.from(document.querySelectorAll('button.btn-answer'));
    let idx = 0;
    function clickNext() {
        if (idx >= buttons.length) return;
        const button = buttons[idx];
        const label = button.querySelector('label');
        if (label) {
            const buttonText = normalizeText(label.textContent);
            if (buttonText.includes(finalMCString) || finalMCString.includes(buttonText)) {
                button.click();
                clicked = true;
                // Set fast auto answered flag ONLY after a real answer
                if (window.wl_auto_mode === 'fast' && !window._fastAutoAnswered) {
                    window._fastAutoAnswered = true;
                    localStorage.setItem('wl_fast_auto_answered', 'true');
                }
            }
        }
        idx++;
        setTimeout(clickNext, 100); // 100ms delay between each button
    }
    clickNext();

    if (clicked) {
        setTimeout(() => {
            const checkBtn = document.querySelector('button[title="Shortcut: Enter"], a[title="Shortcut: Enter"]');
            if (checkBtn && !checkBtn.disabled) checkBtn.click();
        }, 150);
    }
}

// Improved helper for the new question type (button order)
async function answerButtonOrderQuestion() {
    // 1. Find all visible, enabled answer buttons with a data-place attribute
    const buttons = Array.from(document.querySelectorAll('button[data-place]')).filter(btn => {
        return btn.offsetParent !== null && !btn.disabled;
    });
    if (buttons.length === 0) {
        return false;
    }

    // 2. Sort buttons by their data-place value (as numbers)
    buttons.sort((a, b) => {
        return parseInt(a.getAttribute('data-place')) - parseInt(b.getAttribute('data-place'));
    });

    // 3. Click each button in order
    for (let i = 0; i < buttons.length; i++) {
        buttons[i].click();
    }
    // Wait for the Check button to appear and be enabled, then simulate Enter on it, or click it as fallback
    function tryCheckButton() {
        const checkBtn = document.querySelector('button[title="Shortcut: Enter"], a[title="Shortcut: Enter"]');
        if (checkBtn && !checkBtn.disabled) {
            checkBtn.focus();
            // Try to simulate Enter keydown on the button
            const enterEvent = new KeyboardEvent('keydown', {
                key: 'Enter',
                code: 'Enter',
                keyCode: 13,
                which: 13,
                bubbles: true,
                cancelable: true,
                composed: true
            });
            checkBtn.dispatchEvent(enterEvent);
            // Fallback: also click the button directly
            setTimeout(() => {
                if (!checkBtn.disabled) checkBtn.click();
            }, 50);
        } else {
            setTimeout(tryCheckButton, 100);
        }
    }
    tryCheckButton();
}

// Helper to check if there are any actionable elements (questions or buttons)
function isActionablePresent() {
    // Check for any question input/button
    if (document.querySelector('.quiz-clip-preview-active-word, div[contenteditable="true"].js-quiz-answer-input-field, #translator-input, button.btn-answer, button[data-place]')) {
        return true;
    }

    // Check for letter inputs
    const letterInputs = document.querySelectorAll('input[id^="word-input-"][class*="letter"], input[id^="word-input-"][class*="space"]');
    if (letterInputs.length > 0) {
        // Only return true if there are unfilled inputs
        return Array.from(letterInputs).some(input => !input.value.trim());
    }

    // Check for multi-word inputs
    const multiWordInputs = document.querySelectorAll('input[id^="word-input-"]:not([class*="letter"])');
    if (multiWordInputs.length > 0) {
        // Only return true if there are unfilled inputs
        return Array.from(multiWordInputs).some(input => !input.value.trim());
    }

    // Check for "Already Know" button
    if (document.querySelector('#quiz-already-know, .js-answer-tip, [title="Already Know"]')) {
        return true;
    }

    // Check for navigation buttons
    const btns = Array.from(document.querySelectorAll('button, a'));
    return btns.some(btn => {
        if (btn.disabled) return false;
        const txt = btn.textContent.trim().toLowerCase();
        return ["continue", "next", "check", "submit"].some(word => txt === word || txt.startsWith(word + ' '));
    });
}

function findContinueButton() {
    // Try common selectors first
    let btn = document.querySelector('button[title="Shortcut: Enter"], a[title="Shortcut: Enter"]');
    if (btn && !btn.disabled) return btn;
    // Fallback: search for button by text
    const btns = Array.from(document.querySelectorAll('button, a'));
    return btns.find(b => {
        if (b.disabled) return false;
        const txt = b.textContent.trim().toLowerCase();
        return ["continue", "next", "check", "submit"].some(word => txt === word || txt.startsWith(word + ' '));
    }) || null;
}

function performAnswerActions(skipContinue) {
    if (!hasValidKey) {
        showLockScreen();
        return;
    }

    // Turn off auto answer if 'Percentage of video learned' or 'Accuracy' is detected
    if (document.body && document.body.innerText && (document.body.innerText.includes('Percentage of video learned') || document.body.innerText.includes('Accuracy'))) {
        autoAnswerEnabled = false;
        updateStatusCircle && updateStatusCircle();
        updateOverlayWithSecretPhrase && updateOverlayWithSecretPhrase();
        console.log('Auto answer disabled: Summary screen detected.');
        return;
    }

    updateOverlayWithSecretPhrase();

    // Fast mode: only answer the first question, then click Already Know for the rest
    if (window.wl_auto_mode === 'fast' && window._fastAutoAnswered) {
        // Click Already Know as fast as possible
        const clickAlreadyKnow = () => {
            const alreadyKnowBtn = document.querySelector('#quiz-already-know, .js-answer-tip, [title="Already Know"]');
            if (alreadyKnowBtn) {
                alreadyKnowBtn.click();
            } else {
                // If not available, try again on next animation frame
                requestAnimationFrame(clickAlreadyKnow);
            }
        };
        clickAlreadyKnow();
        return;
    }

    // PRIORITY: Check for button order question type FIRST
    if (document.querySelectorAll('button[data-place]').length > 0) {
        answerButtonOrderQuestion();
        return;
    }

    // If secret phrase is not found and "Already Know" button exists, click it
    if (overlay && overlay.textContent.includes('Secret phrase not found')) {
        const alreadyKnowBtn = document.querySelector('#quiz-already-know, .js-answer-tip, [title="Already Know"]');
        if (alreadyKnowBtn) {
            alreadyKnowBtn.click();
            return;
        }
    }

    // Check for contenteditable input first
    const contentEditableInput = document.querySelector('div[contenteditable="true"].js-quiz-answer-input-field, #translator-input');
    if (contentEditableInput) {
        fillBlankFromPhrase();
        return;
    }

    // Then check for letter inputs
    const letterInputs = document.querySelectorAll('input[id^="word-input-"][class*="letter"]');
    const multiWordInputs = document.querySelectorAll('input[id^="word-input-"]:not([class*="letter"])');
    if (letterInputs.length > 0) {
        fillBlankFromDataWord();
        return;
    } else if (multiWordInputs.length > 0) {
        fillBlankFromPhrase();
        return;
    }

    // Only run MC logic if no other inputs are present
    if (document.querySelectorAll('button.btn-answer').length > 0) {
        clickCorrectAnswer();
    }

    // Auto-progress: always click Check if autoAnswerEnabled or skipContinue === false
    if (autoAnswerEnabled || skipContinue === false) {
        const checkBtn = document.querySelector('button[title="Shortcut: Enter"], a[title="Shortcut: Enter"]');
        if (checkBtn) {
            checkBtn.click();
        }
    }
    // Only click Continue/Next if autoAnswerEnabled is true
    if (autoAnswerEnabled) {
        setTimeout(() => {
            const contBtn = document.querySelector('#quiz-next-round button[title="Shortcut: Enter"]');
            if (contBtn) contBtn.click();
        }, 100);
    }
}

function initializeScriptFeatures() {
    if (!hasValidKey) return;
    createStatusCircle();
    createOverlay();
    updateStatusCircle();
    updateOverlayWithSecretPhrase();
    setInterval(() => {
        if (!hasValidKey) {
            removeScriptFeatures();
            return;
        }
        if (autoAnswerEnabled) {
            performAnswerActions();
        }
    }, 300);
    addTopRightIcons();
}

// Add settings and changelog icons to the top right
function addTopRightIcons() {
    let iconBar = document.getElementById('wl-icon-bar');
    if (!iconBar) {
        iconBar = document.createElement('div');
        iconBar.id = 'wl-icon-bar';
        iconBar.style = `
            position: fixed;
            top: 18px;
            right: 24px;
            z-index: 100010;
            display: flex;
            gap: 12px;
        `;
        document.body.appendChild(iconBar);
    }
    // Settings (cog)
    let cog = document.getElementById('wl-cog-icon');
    if (!cog) {
        cog = document.createElement('div');
        cog.id = 'wl-cog-icon';
        cog.innerHTML = COG_ICON;
        cog.style = 'width:38px;height:38px;cursor:pointer;user-select:none;display:flex;align-items:center;justify-content:center;background:#fff;border-radius:12px;box-shadow:0 2px 8px rgba(0,0,0,0.08);transition:box-shadow 0.2s;';
        cog.onmousedown = () => { cog.style.boxShadow = '0 1px 4px rgba(0,0,0,0.18)'; cog.style.transform = 'scale(0.96)'; };
        cog.onmouseup = () => { cog.style.boxShadow = '0 2px 8px rgba(0,0,0,0.08)'; cog.style.transform = 'scale(1)'; };
        cog.onclick = () => {
            // If changelog is open, close it and open settings
            if (document.querySelector('.wl-changelog-modal-roblox')) {
                closeChangelogModal();
                setTimeout(() => showSettingsModal(), 10);
            } else if (!document.querySelector('.wl-settings-modal-roblox')) {
                showSettingsModal();
            }
        };
        iconBar.appendChild(cog);
    }
    // Changelog
    let changelog = document.getElementById('wl-changelog-icon');
    if (!changelog) {
        changelog = document.createElement('div');
        changelog.id = 'wl-changelog-icon';
        changelog.innerHTML = CHANGELOG_ICON;
        changelog.style = 'width:38px;height:38px;cursor:pointer;user-select:none;display:flex;align-items:center;justify-content:center;background:#fff;border-radius:12px;box-shadow:0 2px 8px rgba(0,0,0,0.08);transition:box-shadow 0.2s;';
        changelog.onmousedown = () => { changelog.style.boxShadow = '0 1px 4px rgba(0,0,0,0.18)'; changelog.style.transform = 'scale(0.96)'; };
        changelog.onmouseup = () => { changelog.style.boxShadow = '0 2px 8px rgba(0,0,0,0.08)'; changelog.style.transform = 'scale(1)'; };
        changelog.onclick = () => {
            // If settings is open, close it and open changelog
            if (document.querySelector('.wl-settings-modal-roblox')) {
                document.querySelector('.wl-settings-modal-roblox')?.remove();
                document.querySelector('.wl-settings-overlay-roblox')?.remove();
                setTimeout(() => showChangelogModal(), 10);
            } else if (!document.querySelector('.wl-changelog-modal-roblox')) {
                showChangelogModal();
            }
        };
        iconBar.appendChild(changelog);
    }
}

// Settings modal
function showSettingsModal() {
    // If changelog modal is open, close it and open settings after a short delay
    if (document.querySelector('.wl-changelog-modal-roblox')) {
        document.querySelector('.wl-changelog-modal-roblox')?.remove();
        document.querySelector('.wl-changelog-overlay-roblox')?.remove();
        setTimeout(() => showSettingsModal(), 10);
        return;
    }
    closeModals();
    injectCartoonFont();
    injectChangelogStyles();
    let overlay = document.createElement('div');
    overlay.className = 'wl-settings-overlay-roblox';
    overlay.style = 'position: fixed; top: 0; left: 0; width: 100vw; height: 100vh; background: rgba(33,150,243,0.08); z-index: 100011; cursor: pointer;';
    overlay.onclick = () => {
        document.querySelector('.wl-settings-modal-roblox')?.remove();
        overlay.remove();
    };
    document.body.appendChild(overlay);
    let modal = document.createElement('div');
    modal.className = 'wl-settings-modal-roblox';
    modal.innerHTML = `
        <div class="wl-settings-header-roblox"><span>Settings</span><span class="wl-settings-close-roblox" id="wl-settings-close">×</span></div>
        <div class="wl-settings-content-roblox">
            <div style="margin-bottom:8px;display:flex;align-items:center;justify-content:space-between;">
                <span style="font-size:15px;">Auto Answer Mode:</span>
                <select id="wl-auto-mode-select" style="font-size:15px;padding:4px 10px;border-radius:8px;border:1px solid #2196F3;background:#f7faff;color:#222;outline:none;">
                    <option value="auto">Normal</option>
                    <option value="fast">Fast</option>
                </select>
            </div>
            <div style="font-size:12px;color:#888;margin-bottom:10px;">If it says <b>you have no words for review</b> switch from fast mode to normal mode.</div>
            <div style="margin-bottom:8px;display:flex;align-items:center;justify-content:space-between;">
                <span style="font-size:15px;">Dark Mode 🌓</span>
                <label class="wl-switch">
                    <input type="checkbox" id="wl-dark-mode-toggle">
                    <span class="wl-slider"></span>
                </label>
            </div>
        </div>
        <style>
        .wl-switch { position: relative; display: inline-block; width: 46px; height: 24px; }
        .wl-switch input { opacity: 0; width: 0; height: 0; }
        .wl-slider { position: absolute; cursor: pointer; top: 0; left: 0; right: 0; bottom: 0; background: #ccc; transition: .3s; border-radius: 24px; }
        .wl-slider:before { position: absolute; content: ""; height: 18px; width: 18px; left: 3px; bottom: 3px; background: #fff; transition: .3s; border-radius: 50%; box-shadow: 0 1px 4px rgba(0,0,0,0.12); }
        input:checked + .wl-slider { background: #23272a; }
        input:checked + .wl-slider:before { transform: translateX(22px); background: #e0e0e0; }
        </style>
    `;
    document.body.appendChild(modal);
    animateIn(modal);
    document.getElementById('wl-settings-close').onclick = () => {
        animateOut(modal, () => modal.remove());
        overlay.remove();
    };
    // Dropdown logic
    const modeSelect = document.getElementById('wl-auto-mode-select');
    let mode = localStorage.getItem(AUTO_MODE_KEY) || 'auto';
    modeSelect.value = mode;
    modeSelect.onchange = () => {
        localStorage.setItem(AUTO_MODE_KEY, modeSelect.value);
        window.wl_auto_mode = modeSelect.value;
        resetFastAutoAnswered();
    };
    // Dark mode toggle logic
    const darkToggle = document.getElementById('wl-dark-mode-toggle');
    const darkPref = localStorage.getItem(DARK_MODE_KEY) === 'on';
    darkToggle.checked = darkPref;
    darkToggle.onchange = () => {
        if (darkToggle.checked) {
            localStorage.setItem(DARK_MODE_KEY, 'on');
            applyDarkMode(true);
        } else {
            localStorage.setItem(DARK_MODE_KEY, 'off');
            applyDarkMode(false);
        }
    };
}

// --- CHANGELOG MODAL ---
function injectCartoonFont() {
    if (!document.getElementById('wl-cartoon-font')) {
        const link = document.createElement('link');
        link.id = 'wl-cartoon-font';
        link.rel = 'stylesheet';
        link.href = 'https://fonts.googleapis.com/css2?family=Fredoka+One&display=swap';
        document.head.appendChild(link);
    }
}

function injectChangelogStyles() {
    if (document.getElementById('wl-changelog-style')) return;
    const style = document.createElement('style');
    style.id = 'wl-changelog-style';
    style.textContent = `
    .wl-changelog-modal-roblox, .wl-settings-modal-roblox {
        font-family: 'Fredoka One', 'Baloo', Arial, sans-serif !important;
        border-radius: 24px !important;
        box-shadow: 0 6px 24px 0 rgba(0,0,0,0.16) !important;
        min-width: 220px !important;
        max-width: 96vw !important;
        width: 340px !important;
        background: #fff !important;
        border: 3px solid #2196F3 !important;
        overflow: visible !important;
        z-index: 100012 !important;
        position: fixed !important;
        left: 50% !important;
        top: 50% !important;
        transform: translate(-50%, -50%) !important;
        padding: 0 !important;
        margin: 0 !important;
        display: flex;
        flex-direction: column;
        align-items: stretch;
        transition: box-shadow 0.2s;
    }
    .wl-changelog-overlay-roblox {
        position: fixed; top: 0; left: 0; width: 100vw; height: 100vh;
        background: rgba(33,150,243,0.08); z-index: 100011; cursor: pointer;
    }
    .wl-changelog-header-roblox, .wl-settings-header-roblox {
        background: linear-gradient(90deg, #2196F3 80%, #42a5f5 100%);
        color: #fff;
        font-size: 1.25rem;
        font-weight: 600;
        border-radius: 20px 20px 0 0;
        padding: 14px 18px 8px 18px;
        display: flex;
        align-items: center;
        justify-content: space-between;
        letter-spacing: 1px;
        box-shadow: 0 2px 8px rgba(33,150,243,0.08);
        text-shadow: 0 2px 8px rgba(33,150,243,0.18), 0 1px 0 #fff;
    }
    .wl-changelog-header-roblox span:first-child, .wl-settings-header-roblox span:first-child {
        text-shadow: 0 2px 8px rgba(33,150,243,0.18), 0 1px 0 #fff;
    }
    .wl-changelog-close-roblox, .wl-settings-close-roblox {
        width: 32px; height: 32px; border-radius: 50%; background: #fff; color: #2196F3;
        display: flex; align-items: center; justify-content: center;
        font-size: 1.3rem; font-weight: 700; cursor: pointer; transition: background 0.18s, color 0.18s, transform 0.18s;
        box-shadow: 0 2px 8px rgba(33,150,243,0.10);
        border: 2px solid #2196F3;
    }
    .wl-changelog-close-roblox:hover, .wl-settings-close-roblox:hover {
        background: #2196F3; color: #fff; transform: scale(1.08);
    }
    .wl-changelog-content-roblox, .wl-settings-content-roblox {
        padding: 18px 18px 12px 18px;
        max-height: 320px;
        overflow-y: auto;
        font-size: 1.02rem;
        background: #f7faff;
        border-radius: 0 0 20px 20px;
    }
    .wl-changelog-bubble {
        display: flex; align-items: flex-start; gap: 10px;
        background: #fff;
        border-left: 5px solid #2196F3;
        border-radius: 14px;
        box-shadow: 0 2px 8px rgba(33,150,243,0.08);
        margin-bottom: 10px;
        padding: 10px 12px 10px 12px;
        font-size: 0.98rem;
        color: #222;
        position: relative;
        animation: wl-bubble-in 0.5s cubic-bezier(.4,2,.6,1);
    }
    .wl-changelog-bubble-icon {
        font-size: 1.1rem; color: #2196F3; margin-top: 2px;
        flex-shrink: 0;
    }
    @keyframes wl-bubble-in {
        0% { opacity: 0; transform: translateY(20px) scale(0.95); }
        100% { opacity: 1; transform: translateY(0) scale(1); }
    }
    .wl-changelog-date-roblox {
        color: #2196F3; font-weight: 700; margin-bottom: 12px; font-size: 0.98rem;
        text-align: right;
    }
    .wl-changelog-error-roblox {
        color: #e74c3c; font-weight: 700; text-align: center; margin-top: 18px; font-size: 1.02rem;
    }
    .wl-loading-anim-roblox {
        display: flex; gap: 2px; font-family: 'Fredoka One', 'Baloo', Arial, sans-serif; font-size: 1.1rem; margin-top: 12px; justify-content: center;
    }
    .wl-loading-anim-roblox span {
        display: inline-block; transition: transform 0.3s cubic-bezier(.4,2,.6,1);
        animation: wl-bounce 1.1s infinite;
    }
    .wl-loading-anim-roblox span:nth-child(1) { animation-delay: 0s; }
    .wl-loading-anim-roblox span:nth-child(2) { animation-delay: 0.1s; }
    .wl-loading-anim-roblox span:nth-child(3) { animation-delay: 0.2s; }
    .wl-loading-anim-roblox span:nth-child(4) { animation-delay: 0.3s; }
    .wl-loading-anim-roblox span:nth-child(5) { animation-delay: 0.4s; }
    .wl-loading-anim-roblox span:nth-child(6) { animation-delay: 0.5s; }
    .wl-loading-anim-roblox span:nth-child(7) { animation-delay: 0.6s; }
    .wl-loading-anim-roblox span:nth-child(8) { animation-delay: 0.7s; }
    .wl-loading-anim-roblox span:nth-child(9) { animation-delay: 0.8s; }
    .wl-loading-anim-roblox span:nth-child(10) { animation-delay: 0.9s; }
    @keyframes wl-bounce {
        0%, 100% { transform: translateY(0); }
        40% { transform: translateY(-12px); }
        60% { transform: translateY(-7px); }
    }
    `;
    document.head.appendChild(style);
}

let wlChangelogCache = null;
function preloadChangelog() {
    GM_xmlhttpRequest({
        method: 'GET',
        url: CHANGELOG_URL,
        headers: { 'Accept': 'application/json' },
        onload: function(response) {
            try {
                wlChangelogCache = JSON.parse(response.responseText);
            } catch (e) {
                wlChangelogCache = null;
            }
        },
        onerror: function() {
            wlChangelogCache = null;
        }
    });
}

function showChangelogModal(force) {
    // If settings modal is open, close it and open changelog after a short delay
    if (document.querySelector('.wl-settings-modal-roblox')) {
        document.querySelector('.wl-settings-modal-roblox')?.remove();
        document.querySelector('.wl-settings-overlay-roblox')?.remove();
        setTimeout(() => showChangelogModal(force), 10);
        return;
    }
    injectCartoonFont();
    injectChangelogStyles();
    document.querySelector('.wl-changelog-modal-roblox')?.remove();
    document.querySelector('.wl-changelog-overlay-roblox')?.remove();
    let overlay = document.createElement('div');
    overlay.className = 'wl-changelog-overlay-roblox';
    overlay.onclick = () => closeChangelogModal();
    document.body.appendChild(overlay);
    let modal = document.createElement('div');
    modal.className = 'wl-changelog-modal-roblox';
    modal.innerHTML = `
        <div class="wl-changelog-header-roblox"><span>Changelog</span><span class="wl-changelog-close-roblox" id="wl-changelog-close">×</span></div>
        <div class="wl-changelog-content-roblox" id="wl-changelog-content">
            <div class="wl-loading-anim-roblox">
                <span>L</span><span>o</span><span>a</span><span>d</span><span>i</span><span>n</span><span>g</span><span>.</span><span>.</span><span>.</span>
            </div>
        </div>
    `;
    document.body.appendChild(modal);
    animateIn(modal);
    function closeHandler() { closeChangelogModal(); }
    document.getElementById('wl-changelog-close').onclick = closeHandler;
    window.addEventListener('keydown', function escHandler(e) {
        if (e.key === 'Escape') { closeChangelogModal(); window.removeEventListener('keydown', escHandler); }
    });
    // Use cache if available
    if (wlChangelogCache) {
        let html = `<div class='wl-changelog-date-roblox' style='text-align:center;font-size:1.05em;margin-bottom:18px;'>Last updated: ${wlChangelogCache.date}</div>`;
        for (const entry of wlChangelogCache.entries) {
            html += `<div class='wl-changelog-bubble'><span class='wl-changelog-bubble-icon'>⭐</span><span>${entry}</span></div>`;
        }
        document.getElementById('wl-changelog-content').innerHTML = html;
        document.getElementById('wl-changelog-close').onclick = closeHandler;
        if (!force) localStorage.setItem(CHANGELOG_LOCAL_KEY, wlChangelogCache.version);
    } else {
        // Fallback: fetch if not yet loaded
        GM_xmlhttpRequest({
            method: 'GET',
            url: CHANGELOG_URL,
            headers: { 'Accept': 'application/json' },
            onload: function(response) {
                try {
                    const data = JSON.parse(response.responseText);
                    wlChangelogCache = data;
                    let html = `<div class='wl-changelog-date-roblox' style='text-align:center;font-size:1.05em;margin-bottom:18px;'>Last updated: ${data.date}</div>`;
                    for (const entry of data.entries) {
                        html += `<div class='wl-changelog-bubble'><span class='wl-changelog-bubble-icon'>⭐</span><span>${entry}</span></div>`;
                    }
                    document.getElementById('wl-changelog-content').innerHTML = html;
                    document.getElementById('wl-changelog-close').onclick = closeHandler;
                    if (!force) localStorage.setItem(CHANGELOG_LOCAL_KEY, data.version);
                } catch (e) {
                    document.getElementById('wl-changelog-content').innerHTML = '<div class="wl-changelog-error-roblox">Failed to load changelog.</div>';
                    document.getElementById('wl-changelog-close').onclick = closeHandler;
                }
            },
            onerror: function() {
                document.getElementById('wl-changelog-content').innerHTML = '<div class="wl-changelog-error-roblox">Failed to load changelog.</div>';
                document.getElementById('wl-changelog-close').onclick = closeHandler;
            }
        });
    }
}

function closeChangelogModal() {
    document.querySelector('.wl-changelog-modal-roblox')?.remove();
    document.querySelector('.wl-changelog-overlay-roblox')?.remove();
}

// --- MAIN ENTRY ---
(function() {
    if (!window.location.href.startsWith('https://www.fluentu.com/spanish/learning/')) return;
    // Run key check immediately
    let keyChecked = false;
    checkWhitelistKey((isValid) => {
        keyChecked = true;
        if (!isValid) {
            hasValidKey = false;
            removeScriptFeatures();
        } else {
            hasValidKey = true;
            initializeScriptFeatures();
        }
    });
    // Defer event listeners until key is checked
    document.addEventListener('keydown', function(e) {
        if (!window.location.href.startsWith('https://www.fluentu.com/spanish/learning/')) return;
        if (!keyChecked) return; // Ignore until key is checked

        // Handle backtick key - always shows whitelist screen
        if (e.key === '`') {
            e.preventDefault();
            showLockScreen();
            return;
        }

        // Only show prompt for hotkeys if no valid key
        if (!hasValidKey && (e.key === 'Tab' || e.key === 'Control' || e.key === '\\')) {
            e.preventDefault();
            showLockScreen();
            return;
        }

        // Only handle other hotkeys if we have a valid key
        if (hasValidKey) {
            if (e.key === '\\') {
                e.preventDefault();
                isOverlayVisible = !isOverlayVisible;
                if (!overlay) createOverlay();
                overlay.style.display = isOverlayVisible ? 'block' : 'none';
                if (isOverlayVisible) updateOverlayWithSecretPhrase();
            }
            if (e.key === 'Tab') {
                e.preventDefault();
                autoAnswerEnabled = !autoAnswerEnabled;
                // Reset fast mode state when toggling auto answer
                window._fastAutoAnswered = false;
                updateOverlayWithSecretPhrase();
                updateStatusCircle();
            }
            if (e.key === 'Control') {
                e.preventDefault();
                // Enter the correct answer and click check (but do not click continue unless autoAnswerEnabled)
                performAnswerActions(false);
            }
        }
    });
    // Preload the whitelist when the script starts
    loadWhitelist(() => {});
    // Preload changelog as soon as possible
    preloadChangelog();
})();

// --- ANIMATION HELPERS ---
function animateIn(el) {
    el.style.opacity = '0';
    el.style.transform = 'scale(0.95)';
    setTimeout(() => {
        el.style.transition = 'all 0.25s cubic-bezier(.4,2,.6,1)';
        el.style.opacity = '1';
        el.style.transform = 'scale(1)';
    }, 10);
}
function animateOut(el, cb) {
    el.style.transition = 'all 0.2s cubic-bezier(.4,2,.6,1)';
    el.style.opacity = '0';
    el.style.transform = 'scale(0.95)';
    setTimeout(() => { if (cb) cb(); }, 200);
}
function closeModals() {
    document.getElementById('wl-settings-modal')?.remove();
}

// --- Ensure preferred mode is always loaded on script start ---
window.addEventListener('DOMContentLoaded', () => {
    window.wl_auto_mode = localStorage.getItem(AUTO_MODE_KEY) || 'auto';
});
window.wl_auto_mode = localStorage.getItem(AUTO_MODE_KEY) || 'auto';

function resetFastAutoAnswered() {
    window._fastAutoAnswered = false;
    localStorage.setItem('wl_fast_auto_answered', 'false');
}

// Optionally, reset fast mode state when quiz changes (e.g., on navigation)
window.addEventListener('popstate', resetFastAutoAnswered);
window.addEventListener('hashchange', resetFastAutoAnswered);

function applyDarkMode(enabled) {
    let style = document.getElementById('wl-dark-mode-style');
    if (enabled) {
        if (!style) {
            style = document.createElement('style');
            style.id = 'wl-dark-mode-style';
            style.textContent = `
                body, html, #app, .main-content, .content, .container, .card, .panel, .quiz-card, .quiz-panel, .quiz-content, .quiz-question, .trans-content, .vocab-card, .vocab-panel, .vocab-content, .vocab-section, .vocab-list, .vocab-header, .vocab-footer, .vocab-modal, .vocab-overlay, .vocab-popup, .vocab-dialog, .vocab-tooltip, .vocab-dropdown, .vocab-menu,
                .header, .footer, .video-player, .video-container, .sidebar, .nav, .navbar, .menu, .menu-bar, .menu-list, .menu-item, .dropdown, .dropdown-menu, .dropdown-item, .modal, .popup, .dialog, .tooltip, .panel, .card, .list, .list-item, .section, .content, .main, .background, .overlay, .input, .button, .form, .quiz, .quiz-header, .quiz-footer, .quiz-main, .quiz-side, .quiz-row, .quiz-col, .quiz-box, .quiz-container, .quiz-modal, .quiz-overlay, .quiz-popup, .quiz-dialog, .quiz-tooltip, .quiz-dropdown, .quiz-menu, .quiz-list, .quiz-list-item, .quiz-list-header, .quiz-list-footer, .quiz-list-content, .quiz-list-title, .quiz-list-description, .quiz-list-meta, .quiz-list-action, .quiz-list-icon, .quiz-list-avatar, .quiz-list-badge, .quiz-list-tag, .quiz-list-label, .quiz-list-value, .quiz-list-extra, .quiz-list-divider, .quiz-list-separator, .quiz-list-group, .quiz-list-subtitle, .quiz-list-note, .quiz-list-tip, .quiz-list-warning, .quiz-list-error, .quiz-list-success, .quiz-list-info, .quiz-list-empty, .quiz-list-loading, .quiz-list-more, .quiz-list-expand, .quiz-list-collapse, .quiz-list-toggle, .quiz-list-switch, .quiz-list-slider, .quiz-list-progress, .quiz-list-step, .quiz-list-dot, .quiz-list-circle, .quiz-list-bar, .quiz-list-line, .quiz-list-track, .quiz-list-thumb, .quiz-list-rail, .quiz-list-fill, .quiz-list-mask, .quiz-list-shadow, .quiz-list-glow, .quiz-list-blur, .quiz-list-fade, .quiz-list-zoom, .quiz-list-flip, .quiz-list-spin, .quiz-list-bounce, .quiz-list-shake, .quiz-list-wobble, .quiz-list-jump, .quiz-list-swing, .quiz-list-tada, .quiz-list-rubber, .quiz-list-pulse, .quiz-list-heart, .quiz-list-star, .quiz-list-fire, .quiz-list-spark, .quiz-list-flash, .quiz-list-light, .quiz-list-dark, .quiz-list-night, .quiz-list-day, .quiz-list-sun, .quiz-list-moon, .quiz-list-cloud, .quiz-list-rain, .quiz-list-snow, .quiz-list-wind, .quiz-list-thunder, .quiz-list-lightning, .quiz-list-storm, .quiz-list-fog, .quiz-list-mist, .quiz-list-haze, .quiz-list-smoke, .quiz-list-dust, .quiz-list-sand, .quiz-list-mud, .quiz-list-dirt, .quiz-list-grass, .quiz-list-leaf, .quiz-list-flower, .quiz-list-tree, .quiz-list-plant, .quiz-list-fruit, .quiz-list-vegetable, .quiz-list-meat, .quiz-list-fish, .quiz-list-egg, .quiz-list-milk, .quiz-list-bread, .quiz-list-cake, .quiz-list-cookie, .quiz-list-candy, .quiz-list-ice, .quiz-list-cream, .quiz-list-chocolate, .quiz-list-coffee, .quiz-list-tea, .quiz-list-juice, .quiz-list-water, .quiz-list-soda, .quiz-list-beer, .quiz-list-wine, .quiz-list-cocktail, .quiz-list-drink, .quiz-list-cup, .quiz-list-glass, .quiz-list-bottle, .quiz-list-can, .quiz-list-jar, .quiz-list-box, .quiz-list-bag, .quiz-list-basket {
                    background: #23272a !important;
                    color: #e0e0e0 !important;
                    border-color: #444 !important;
                }
            `;
            document.head.appendChild(style);
        }
        document.body.classList.add('wl-dark-mode');
    } else {
        if (style) style.remove();
        document.body.classList.remove('wl-dark-mode');
    }
}