LingQ Addon

Provides custom LingQ layouts

目前為 2025-05-11 提交的版本,檢視 最新版本

您需要先安裝使用者腳本管理器擴展,如 TampermonkeyGreasemonkeyViolentmonkey 之後才能安裝該腳本。

You will need to install an extension such as Tampermonkey to install this script.

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyViolentmonkey 後才能安裝該腳本。

您需要先安裝使用者腳本管理器擴充功能,如 TampermonkeyUserscripts 後才能安裝該腳本。

你需要先安裝一款使用者腳本管理器擴展,比如 Tampermonkey,才能安裝此腳本

您需要先安裝使用者腳本管理器擴充功能後才能安裝該腳本。

(我已經安裝了使用者腳本管理器,讓我安裝!)

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展,比如 Stylus,才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

你需要先安裝一款使用者樣式管理器擴展後才能安裝此樣式

(我已經安裝了使用者樣式管理器,讓我安裝!)

// ==UserScript==
// @name         LingQ Addon
// @description  Provides custom LingQ layouts
// @match        https://www.lingq.com/*/learn/*/web/reader/*
// @match        https://www.lingq.com/*/learn/*/web/library/course/*
// @exclude      https://www.lingq.com/*/learn/*/web/editor/*
// @version      4.5
// @grant       GM_setValue
// @grant       GM_getValue
// @namespace https://greasyfork.org/users/1458847
// ==/UserScript==

(function () {
    "use strict";

    const storage = {
        get: (key, defaultValue) => {
            const value = GM_getValue(key);
            return value === undefined ? defaultValue : value;
        },
        set: (key, value) => GM_setValue(key, value)
    };

    const defaults = {
        styleType: "video",
        colorMode: "dark",
        fontSize: 1.1,
        lineHeight: 1.7,
        heightBig: 400,
        sentenceHeight: 400,
        darkColors: {
            fontColor: "#e0e0e0",
            lingqBackground: "rgba(109, 89, 44, 0.7)",
            lingqBorder: "rgba(254, 203, 72, 0.3)",
            lingqBorderLearned: "rgba(254, 203, 72, 0.5)",
            blueBorder: "rgba(72, 154, 254, 0.5)",
            playingUnderline: "#ffffff"
        },
        whiteColors: {
            fontColor: "#000000",
            lingqBackground: "rgba(255, 200, 0, 0.4)",
            lingqBorder: "rgba(255, 200, 0, 0.3)",
            lingqBorderLearned: "rgba(255, 200, 0, 1)",
            blueBorder: "rgba(0, 111, 255, 0.3)",
            playingUnderline: "#000000"
        },
        librarySortOption: 0,
        autoFinishing: false
    };

    const settings = {
        styleType: storage.get("styleType", defaults.styleType),
        colorMode: storage.get("colorMode", defaults.colorMode),
        fontSize: storage.get("fontSize", defaults.fontSize),
        lineHeight: storage.get("lineHeight", defaults.lineHeight),
        heightBig: storage.get("heightBig", defaults.heightBig),
        sentenceHeight: storage.get("sentenceHeight", defaults.sentenceHeight),
        librarySortOption: storage.get("librarySortOption", defaults.librarySortOption),
        autoFinishing: storage.get("autoFinishing", defaults.autoFinishing)
    };

    function getColorSettings(colorMode) {
        const prefix = colorMode === "dark" ? "dark_" : "white_";
        const defaultColors = colorMode === "dark" ? defaults.darkColors : defaults.whiteColors;

        return {
            fontColor: storage.get(prefix + "fontColor", defaultColors.fontColor),
            lingqBackground: storage.get(prefix + "lingqBackground", defaultColors.lingqBackground),
            lingqBorder: storage.get(prefix + "lingqBorder", defaultColors.lingqBorder),
            lingqBorderLearned: storage.get(prefix + "lingqBorderLearned", defaultColors.lingqBorderLearned),
            blueBorder: storage.get(prefix + "blueBorder", defaultColors.blueBorder),
            playingUnderline: storage.get(prefix + "playingUnderline", defaultColors.playingUnderline)
        };
    }

    const colorSettings = getColorSettings(settings.colorMode);

    function createElement(tag, props = {}) {
        const element = document.createElement(tag);
        Object.entries(props).forEach(([key, value]) => {
            if (key === "style" && typeof value === "string") {
                element.style.cssText = value;
            } else if (key === "textContent") {
                element.textContent = value;
            } else {
                element[key] = value;
            }
        });
        return element;
    }

    function createSettingsPopup() {
        const popup = createElement("div", {id: "lingqAddonSettingsPopup"});

        // drag handle
        const dragHandle = createElement("div", {id: "lingqAddonSettingsDragHandle"});

        const dragHandleTitle = createElement("h3", {textContent: "LingQ Addon Settings"});
        dragHandle.appendChild(dragHandleTitle);

        // popup content
        const content = createElement("div", {style: `padding: 0 10px;`});
        const popupContentElement = generatePopupContent();
        content.appendChild(popupContentElement);

        popup.appendChild(dragHandle);
        popup.appendChild(content);

        return popup;
    }

    function generatePopupContent() {
        function addSelect(parent, id, labelText, options, selectedValue) {
            const container = createElement("div", {className: "popup-row"});
            container.appendChild(createElement("label", {htmlFor: id, textContent: labelText}));
    
            const select = createElement("select", {id, style: "width: 100%; margin-top: 5px; padding: 5px;"});
            options.forEach(option => {
                select.appendChild(createElement("option", {value: option.value, textContent: option.text, selected: selectedValue === option.value}));
            });
    
            container.appendChild(select);
            parent.appendChild(container);
            return container;
        }
    
        function addSlider(parent, id, labelText, valueId, value, unit, min, max, step) {
            const container = createElement("div", {className: "popup-row"});
    
            const label = createElement("label", { htmlFor: id });
            label.appendChild(document.createTextNode(labelText + " "));
            label.appendChild(createElement("span", { id: valueId, textContent: value }));
            if (unit) label.appendChild(document.createTextNode(unit));
    
            container.appendChild(label);
            container.appendChild(createElement("input", {type: "range", id, min, max, step, value, style: "width: 100%;"}));
    
            parent.appendChild(container);
            return container;
        }
    
        function addColorPicker(parent, id, labelText, value) {
            const container = createElement("div", {className: "popup-row"});
            container.appendChild(createElement("label", {htmlFor: id + "Text", textContent: labelText}));
    
            const flexContainer = createElement("div", {style: "display: flex; align-items: center;"});
            flexContainer.appendChild(createElement("div", {id: id + "Picker", className: "color-picker" }));
            flexContainer.appendChild(createElement("input", {type: "text", id: id + "Text", value, style: "flex-grow: 1; margin-left: 10px;"}));
    
            container.appendChild(flexContainer);
            parent.appendChild(container);
            return container;
        }
    
        function addCheckbox(parent, id, labelText, checked) {
            const container = createElement("div", {className: "popup-row"});
            const label = createElement("label", {htmlFor: id, textContent: labelText});
            const checkbox = createElement("input", {type: "checkbox", id, checked, style: "margin-left: 10px;"});
    
            label.style.display = "flex";
            label.style.alignItems = "center";
            container.appendChild(label);
            label.appendChild(checkbox);
            parent.appendChild(container);
    
            return container;
        }
        
        const container = createElement("div");

        addSelect(container, "styleTypeSelector", "Layout Style:", [
            { value: "video", text: "Video" },
            { value: "video2", text: "Video2" },
            { value: "audio", text: "Audio" },
            { value: "off", text: "Off" }
        ], settings.styleType);

        const videoSettings = createElement("div", {
            id: "videoSettings",
            style: `${settings.styleType === "video" ? "" : "display: none"}`
        });
        addSlider(videoSettings, "heightBigSlider", "Video Height:", "heightBigValue", settings.heightBig, "px", 300, 800, 10);
        container.appendChild(videoSettings);

        const sentenceVideoSettings = createElement("div", {
            id: "sentenceVideoSettings",
            style: `${settings.styleType === "off" ? "" : "display: none"}`
        });
        addSlider(sentenceVideoSettings, "sentenceHeightSlider", "Sentence Video Height:", "sentenceHeightValue", settings.heightBig, "px", 300, 600, 10);
        container.appendChild(sentenceVideoSettings);

        addSlider(container, "fontSizeSlider", "Font Size:", "fontSizeValue", settings.fontSize, "rem", 0.8, 1.8, 0.05);
        addSlider(container, "lineHeightSlider", "Line Height:", "lineHeightValue", settings.lineHeight, "", 1.2, 3.0, 0.1);

        const colorSection = createElement("div", {
            style: "border: 1px solid var(--font_color, #e0e0e0); padding: 0 10px; border-radius: 5px;"
        });

        addSelect(colorSection, "colorModeSelector", "Color Mode:", [
            { value: "dark", text: "Dark" },
            { value: "white", text: "White" }
        ], settings.colorMode);

        [
            { id: "fontColor", label: "Font Color:", value: colorSettings.fontColor },
            { id: "lingqBackground", label: "LingQ Background:", value: colorSettings.lingqBackground },
            { id: "lingqBorder", label: "LingQ Border:", value: colorSettings.lingqBorder },
            { id: "lingqBorderLearned", label: "LingQ Border Learned:", value: colorSettings.lingqBorderLearned },
            { id: "blueBorder", label: "Blue Border:", value: colorSettings.blueBorder },
            { id: "playingUnderline", label: "Playing Underline:", value: colorSettings.playingUnderline }
        ].forEach(config => addColorPicker(colorSection, config.id, config.label, config.value));

        container.appendChild(colorSection);

        addCheckbox(container, "autoFinishingCheckbox", "Finish Lesson Automatically", settings.autoFinishing);

        const buttonContainer = createElement("div", {style: "display: flex; justify-content: space-between;", className: "popup-row"});
        [
            {id: "resetSettingsBtn", textContent: "Reset", className: "popup-button"},
            {id: "closeSettingsBtn", textContent: "Close", className: "popup-button"}
        ].forEach((prop) => {
            buttonContainer.appendChild(createElement("button", prop));
        });

        container.appendChild(buttonContainer);
        return container;
    }

    function createDownloadWordsPopup() {
        const popup = createElement("div", {id: "lingqDownloadWordsPopup"});

        // drag handle
        const dragHandle = createElement("div", {id: "lingqDownloadWordsDragHandle"});

        const dragHandleTitle = createElement("h3", {textContent: "Download Words"});
        dragHandle.appendChild(dragHandleTitle);

        const content = createElement("div", {style: `padding: 0 10px;`});

        [
            {id: "downloadUnknownLingqsBtn", textContent: "Download Unknown LingQs (words + phrases)", className: "popup-button"},
            {id: "downloadUnknownLingqWordsBtn", textContent: "Download Unknown LingQ Words (1, 2, 3, 4)", className: "popup-button"},
            {id: "downloadUnknownLingqPhrasesBtn", textContent: "Download Unknown LingQ Phrases (1, 2, 3, 4)", className: "popup-button"},
            {id: "downloadKnownLingqsBtn", textContent: "Download Known LingQs (✓)", className: "popup-button"},
            {id: "downloadKnownWordsBtn", textContent: "Download Known Words ", className: "popup-button"}
        ].forEach((prop) => {
            let rowContainer = createElement("div", {className: "popup-row"});
            rowContainer.appendChild(createElement("button", prop))
            content.appendChild(rowContainer);
        });

        // Progress Bar Elements
        const progressContainer = createElement("div", {id: "downloadProgressContainer", className: "popup-row"});
        const progressText = createElement("div", {id: "downloadProgressText"});
        const progressBar = createElement("progress", {id: "downloadProgressBar", value: "0", max: "100"});

        progressContainer.appendChild(progressText);
        progressContainer.appendChild(progressBar);
        content.appendChild(progressContainer);

        const buttonContainer = createElement("div", {style: "display: flex; justify-content: flex-end;", className: "popup-row"});
        const closeButton = createElement("button", {id: "closeDownloadWordsBtn", textContent: "Close", className: "popup-button"});
        buttonContainer.appendChild(closeButton);
        content.appendChild(buttonContainer);

        popup.appendChild(dragHandle);
        popup.appendChild(content);

        return popup;
    }

    function createUI() {
        // Create settings button
        const settingsButton = createElement("button", {
            id: "lingqAddonSettings",
            textContent: "⚙️",
            title: "LingQ Addon Settings",
            className: "nav-button"
        });

        // Create lesson complete button
        const completeLessonButton = createElement("button", {
            id: "lingqLessonComplete",
            textContent: "✔",
            title: "Complete Lesson Button",
            className: "nav-button"
        });

        // Create download words button
        const downloadWordsButton = createElement("button", {
            id: "lingqDownloadWords",
            textContent: "💾",
            title: "Download Words",
            className: "nav-button"
        });

        // Find the #main-nav element
        let mainNav = document.querySelector("#main-nav > nav > div:nth-child(2) > div:nth-child(1)");

        if (mainNav) {
            mainNav.appendChild(settingsButton);
            mainNav.appendChild(downloadWordsButton);
            mainNav.appendChild(completeLessonButton);
        } else {
            console.error("#main-nav element not found. Buttons not inserted.");
        }

        // Create settings popup
        const settingsPopup = createSettingsPopup();
        document.body.appendChild(settingsPopup);

        // Create download words popup
        const downloadWordsPopup = createDownloadWordsPopup();
        document.body.appendChild(downloadWordsPopup);

        // Add event listeners
        setupSettingEventListeners(settingsButton, settingsPopup);
        setupDownloadWordsEventListeners(downloadWordsButton, downloadWordsPopup);
        setupEventListeners()
    }

    function makeDraggable(element, handle) {
        let pos1 = 0, pos2 = 0, pos3 = 0, pos4 = 0;

        if (handle) {
            handle.onmousedown = dragMouseDown;
        } else {
            element.onmousedown = dragMouseDown;
        }

        function dragMouseDown(e) {
            e = e || window.event;
            e.preventDefault();

            if (element.style.transform && element.style.transform.includes('translate')) {
                const rect = element.getBoundingClientRect();

                element.style.transform = 'none';
                element.style.top = rect.top + 'px';
                element.style.left = rect.left + 'px';
            }

            pos3 = e.clientX;
            pos4 = e.clientY;
            document.onmouseup = closeDragElement;
            document.onmousemove = elementDrag;
        }

        function elementDrag(e) {
            e = e || window.event;
            e.preventDefault();

            pos1 = pos3 - e.clientX;
            pos2 = pos4 - e.clientY;
            pos3 = e.clientX;
            pos4 = e.clientY;

            element.style.top = (element.offsetTop - pos2) + "px";
            element.style.left = (element.offsetLeft - pos1) + "px";
        }

        function closeDragElement() {
            document.onmouseup = null;
            document.onmousemove = null;
        }
    }

    function setupSettingEventListeners(settingsButton, settingsPopup) {
        function initializePickrs() {
            function setupRGBAPickr(pickerId, textId, settingKey, cssVar) {
                function saveColorSetting(key, value) {
                    const currentColorMode = document.getElementById("colorModeSelector").value;
                    const prefix = currentColorMode === "dark" ? "dark_" : "white_";
                    storage.set(prefix + key, value);
                }
                
                const pickerElement = document.getElementById(pickerId);
                const textElement = document.getElementById(textId);
        
                if (!pickerElement || !textElement) return;
        
                pickerElement.style.backgroundColor = textElement.value;
        
                const pickr = Pickr.create({
                    el: pickerElement,
                    theme: 'nano',
                    useAsButton: true,
                    default: textElement.value,
                    components: {preview: true, opacity: true, hue: true}
                });
        
                pickr.on('change', (color) => {
                    const rgbaColor = color.toRGBA();
        
                    const r = Math.round(rgbaColor[0]);
                    const g = Math.round(rgbaColor[1]);
                    const b = Math.round(rgbaColor[2]);
                    const a = rgbaColor[3];
        
                    const roundedRGBA = `rgba(${r}, ${g}, ${b}, ${a})`;
        
                    textElement.value = roundedRGBA;
                    pickerElement.style.backgroundColor = roundedRGBA;
                    document.documentElement.style.setProperty(cssVar, roundedRGBA);
        
                    saveColorSetting(settingKey, roundedRGBA);
                });
        
                textElement.addEventListener('change', function () {
                    const rgbaColor = this.value;
        
                    pickr.setColor(this.value);
                    saveColorSetting(settingKey, rgbaColor);
                    document.documentElement.style.setProperty(cssVar, rgbaColor);
                    pickerElement.style.backgroundColor = rgbaColor;
                });
        
                pickr.on('hide', () => {
                    const rgbaColor = pickr.getColor().toRGBA().toString();
                    pickerElement.style.backgroundColor = rgbaColor;
                });
            }
            
            return new Promise((resolve) => {
                const pickrCss = createElement('link', {
                    rel: 'stylesheet',
                    href: 'https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/themes/nano.min.css'
                });
                document.head.appendChild(pickrCss);
    
                const pickrScript = createElement('script', {
                    src: 'https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/pickr.min.js',
                    onload: () => resolve() // Pass function reference directly
                });
                document.head.appendChild(pickrScript);
            }).then(() => {
                setupRGBAPickr('lingqBackgroundPicker', 'lingqBackgroundText', 'lingqBackground', '--lingq_background');
                setupRGBAPickr('lingqBorderPicker', 'lingqBorderText', 'lingqBorder', '--lingq_border');
                setupRGBAPickr('lingqBorderLearnedPicker', 'lingqBorderLearnedText', 'lingqBorderLearned', '--lingq_border_learned');
                setupRGBAPickr('blueBorderPicker', 'blueBorderText', 'blueBorder', '--blue_border');
                setupRGBAPickr('fontColorPicker', 'fontColorText', 'fontColor', '--font_color');
                setupRGBAPickr('playingUnderlinePicker', 'playingUnderlineText', 'playingUnderline', '--is_playing_underline');
            });
        }
        
        settingsButton.addEventListener("click", () => {
            settingsPopup.style.display = "block";
            initializePickrs();

            const dragHandle = document.getElementById("lingqAddonSettingsDragHandle");
            makeDraggable(settingsPopup, dragHandle);
        });

        // Style type selector
        const styleTypeSelector = document.getElementById("styleTypeSelector");
        styleTypeSelector.addEventListener("change", (event) => {
            const selectedStyleType = event.target.value;
            storage.set("styleType", selectedStyleType);
            document.getElementById("videoSettings").style.display = selectedStyleType === "video" ? "block" : "none";
            document.getElementById("sentenceVideoSettings").style.display = selectedStyleType === "off" ? "block" : "none";
            applyStyles(selectedStyleType, document.getElementById("colorModeSelector").value);
        });

        function updateColorInputs(colorSettings) {
            document.getElementById("fontColorText").value = colorSettings.fontColor;
            document.getElementById("lingqBackgroundText").value = colorSettings.lingqBackground;
            document.getElementById("lingqBorderText").value = colorSettings.lingqBorder;
            document.getElementById("lingqBorderLearnedText").value = colorSettings.lingqBorderLearned;
            document.getElementById("blueBorderText").value = colorSettings.blueBorder;
            document.getElementById("playingUnderlineText").value = colorSettings.playingUnderline;
    
            const fontColorPicker = document.getElementById("fontColorPicker");
            if (fontColorPicker) fontColorPicker.style.backgroundColor = colorSettings.fontColor;
    
            const playingUnderlinePicker = document.getElementById("playingUnderlinePicker");
            if (playingUnderlinePicker) playingUnderlinePicker.style.backgroundColor = colorSettings.playingUnderline;
        }

        function updateColorPickerBackgrounds(colorSettings) {
            const pickerIds = [
                { id: "lingqBackgroundPicker", color: colorSettings.lingqBackground },
                { id: "lingqBorderPicker", color: colorSettings.lingqBorder },
                { id: "lingqBorderLearnedPicker", color: colorSettings.lingqBorderLearned },
                { id: "blueBorderPicker", color: colorSettings.blueBorder },
                { id: "fontColorPicker", color: colorSettings.fontColor },
                { id: "playingUnderlinePicker", color: colorSettings.playingUnderline }
            ];
    
            pickerIds.forEach(item => {
                const picker = document.getElementById(item.id);
                if (picker) {
                    picker.style.backgroundColor = item.color;
                }
            });
        }

        function updateCssColorVariables(colorSettings) {
            document.documentElement.style.setProperty("--font_color", colorSettings.fontColor);
            document.documentElement.style.setProperty("--lingq_background", colorSettings.lingqBackground);
            document.documentElement.style.setProperty("--lingq_border", colorSettings.lingqBorder);
            document.documentElement.style.setProperty("--lingq_border_learned", colorSettings.lingqBorderLearned);
            document.documentElement.style.setProperty("--blue_border", colorSettings.blueBorder);
            document.documentElement.style.setProperty("--is_playing_underline", colorSettings.playingUnderline);
        }

        function updateColorMode(event) {
            event.stopPropagation();
    
            const selectedColorMode = this.value;
            const settingsPopup = document.getElementById("lingqAddonSettingsPopup");
            settingsPopup.style.backgroundColor = selectedColorMode === "dark" ? "#2a2c2e" : "#ffffff";
    
            storage.set("colorMode", selectedColorMode);
    
            const colorSettings = getColorSettings(selectedColorMode);
    
            updateColorInputs(colorSettings);
    
            document.documentElement.style.setProperty(
                "--background-color",
                selectedColorMode === "dark" ? "#2a2c2e" : "#ffffff"
            );
            updateCssColorVariables(colorSettings);
    
            applyStyles(document.getElementById("styleTypeSelector").value, selectedColorMode);
    
            updateColorPickerBackgrounds(colorSettings);
        }

        document.getElementById("colorModeSelector").addEventListener("change", updateColorMode);

        function setupSlider(sliderId, valueId, settingKey, unit, cssVar, valueTransform) {
            const slider = document.getElementById(sliderId);
            const valueDisplay = document.getElementById(valueId);
    
            slider.addEventListener("input", function () {
                const value = parseFloat(this.value);
                const transformedValue = valueTransform(value);
    
                valueDisplay.textContent = transformedValue.toString().replace(unit, '');
                storage.set(settingKey, value);
                document.documentElement.style.setProperty(cssVar, transformedValue);
            });
        }
        
        setupSlider("fontSizeSlider", "fontSizeValue", "fontSize", "rem", "--font_size", (val) => `${val}rem`);
        setupSlider("lineHeightSlider", "lineHeightValue", "lineHeight", "", "--line_height", (val) => val);
        setupSlider("heightBigSlider", "heightBigValue", "heightBig", "px", "--height_big", (val) => `${val}px`);
        setupSlider("sentenceHeightSlider", "sentenceHeightValue", "sentenceHeight", "px", "--sentence_height", (val) => `${val}px`);

        const autoFinishingCheckbox = document.getElementById("autoFinishingCheckbox");
        autoFinishingCheckbox.addEventListener('change', (event) => {
            const checked = event.target.checked;
            storage.set("autoFinishing", checked);
            settings.autoFinishing = checked;
        });

        document.getElementById("closeSettingsBtn").addEventListener("click", () => {
            settingsPopup.style.display = "none";
        });

        function resetSettings() {
            if (!confirm("Reset all settings to default?")) return;
    
            const currentColorMode = document.getElementById("colorModeSelector").value;
            const defaultColorSettings = currentColorMode === "dark" ? defaults.darkColors : defaults.whiteColors;
    
            document.getElementById("styleTypeSelector").value = defaults.styleType;
            document.getElementById("fontSizeSlider").value = defaults.fontSize;
            document.getElementById("fontSizeValue").textContent = defaults.fontSize;
            document.getElementById("lineHeightSlider").value = defaults.lineHeight;
            document.getElementById("lineHeightValue").textContent = defaults.lineHeight;
            document.getElementById("heightBigSlider").value = defaults.heightBig;
            document.getElementById("heightBigValue").textContent = defaults.heightBig;
            document.getElementById("sentenceHeightSlider").value = defaults.sentenceHeight;
            document.getElementById("sentenceHeightValue").textContent = defaults.sentenceHeight;
    
            updateColorInputs(defaultColorSettings);
            updateColorPickerBackgrounds(defaultColorSettings);
    
            for (const [key, value] of Object.entries(defaults)) {
                storage.set(key, value);
            }
    
            const prefix = currentColorMode === "dark" ? "dark_" : "white_";
            for (const [key, value] of Object.entries(defaultColorSettings)) {
                storage.set(prefix + key, value);
            }
    
            applyStyles(defaults.styleType, currentColorMode);
    
            document.getElementById("videoSettings").style.display = defaults.styleType === "video" ? "block" : "none";
            document.getElementById("sentenceVideoSettings").style.display = defaults.styleType === "off" ? "block" : "none";
    
            document.documentElement.style.setProperty("--font_size", `${defaults.fontSize}rem`);
            document.documentElement.style.setProperty("--line_height", defaults.lineHeight);
            document.documentElement.style.setProperty("--height_big", `${defaults.heightBig}px`);
            document.documentElement.style.setProperty("--sentence_height", `${defaults.sentenceHeight}px`);
            updateCssColorVariables(defaultColorSettings);
    
            document.getElementById("autoFinishingCheckbox").checked = defaults.autoFinishing;
        }

        document.getElementById("resetSettingsBtn").addEventListener("click", resetSettings);
    }

    async function setupDownloadWordsEventListeners(downloadWordsButton, downloadWordsPopup) {
        async function getAllWords(baseUrl, pageSize, apiType, additionalParams="", progressCallback = () => {}) {
            let allResults = [];
            let nextUrl = `${baseUrl}?page_size=${pageSize}&page=1${additionalParams}`;
            let currentPage = 0;
            let totalPages = 0;
            let isFirstCall = true;
    
            while (nextUrl) {
                try {
                    const response = await fetch(nextUrl);
    
                    if (!response.ok) {
                        throw new Error(`HTTP error! Status: ${response.status}`);
                    }
    
                    const data = await response.json();
                    currentPage++;
    
                    if (isFirstCall) {
                        isFirstCall = false;
                        totalPages = Math.ceil(data.count / pageSize);
                        console.log(`total pages: ${totalPages}`);
                    }
    
                    progressCallback(currentPage, totalPages, false, null, data.count);
    
                    if (apiType === 'lingq') {
                        const filteredResults = data.results.map(item => ({
                            pk: item.pk,
                            term: item.term,
                            fragment: item.fragment,
                            status: item.status,
                            hint: item.hints && item.hints[0] ? item.hints[0].text : null
                        }));
                        allResults = allResults.concat(filteredResults);
                    } else if (apiType === 'known') {
                        allResults = allResults.concat(data.results);
                    }
    
                    nextUrl = data.next;
    
                    if (nextUrl) {
                        console.log("Fetched page. Next URL:", nextUrl);
                    } else {
                        console.log("Finished fetching all pages");
                        progressCallback(currentPage, totalPages, true, null, data.count);
                    }
                } catch (error) {
                    console.error('Error fetching data:', error);
                    progressCallback(currentPage, totalPages, true, error, 0);
                    break;
                }
            }
    
            return allResults;
        }
    
        async function downloadWords(baseUrl, pageSize, fileName, apiType, additionalParams="") {
            const progressContainer = document.getElementById("downloadProgressContainer");
            const progressBar = document.getElementById("downloadProgressBar");
            const progressText = document.getElementById("downloadProgressText");
    
            if (progressContainer && progressBar && progressText) {
                progressBar.value = 0;
                progressBar.max = 100;
                progressText.textContent = "Initializing download...";
                progressContainer.style.display = "block";
            }
    
            const progressCallback = (currentPage, totalPages,_isDone, error_isErrorEncountered, totalCount) => {
                if (progressBar && progressText) {
                    if (error_isErrorEncountered) {
                        progressText.textContent = `Error fetching page ${currentPage}: ${error_isErrorEncountered.message}`;
                        progressBar.style.backgroundColor = 'red';
                        return;
                    }
    
                    progressBar.max = totalPages;
                    progressBar.value = currentPage;
                    progressText.textContent = `Fetching data... Page ${currentPage} of ${totalPages} (Total items: ${totalCount || 'N/A'})`;
    
                    if (_isDone) {
                        progressText.textContent = error_isErrorEncountered ? `Export failed: ${error_isErrorEncountered.message}` : `${totalCount} items exported`;
                    }
                }
            };
    
            try {
                const allWords = await getAllWords(baseUrl, pageSize, apiType, additionalParams, progressCallback);
    
                if (!allWords || allWords.length === 0) {
                    console.warn("No words found or an error occurred.");
                    return;
                }
    
                let blob;
                const fileType = fileName.split(".")[1];
    
                if (fileType === 'json') {
                    const dataString = JSON.stringify(allWords, null, 2);
                    blob = new Blob([dataString], { type: 'application/json' });
                } else if (fileType === 'csv') {
                    const headers = Object.keys(allWords[0]).join(',');
                    const rows = allWords.map(item => {
                        return Object.values(item).map(value => {
                            if (typeof value === 'string') {
                                return `"${value.replace(/"/g, '""')}"`;
                            }
                            return value;
                        }).join(',');
                    }).join('\n');
    
                    const dataString = headers + '\n' + rows;
                    blob = new Blob([dataString], { type: 'text/csv' });
                }
    
                downloadBlob(blob, fileName);
                console.log("Export completed.");
            } catch (error) {
                console.error('Error:', error);
            }
        }
    
        function downloadBlob(blob, fileName) {
            const url = URL.createObjectURL(blob);
            const a = createElement("a", {href: url, download: fileName});
            document.body.appendChild(a);
            a.click();
            document.body.removeChild(a);
            URL.revokeObjectURL(url);
        }
        
        downloadWordsButton.addEventListener("click", () => {
            downloadWordsPopup.style.display = "block";

            const progressContainer = document.getElementById("downloadProgressContainer");
            if (progressContainer) progressContainer.style.display = "none";

            const dragHandle = document.getElementById("lingqDownloadWordsDragHandle");
            if (dragHandle) {
                makeDraggable(downloadWordsPopup, dragHandle);
            }
        });

        const languageCode = await getLanguageCode();
        const pageSize = 1000;

        const setButtonsDisabled = (disabled) => {
            const buttons = downloadWordsPopup.querySelectorAll('.popup-button');
            buttons.forEach(button => {
                button.disabled = disabled;
            });
        };

        const handleDownloadButtonClick = async (url, filename, type, params = '') => {
            setButtonsDisabled(true);
            try {
                await downloadWords(url, pageSize, filename, type, params);
            } finally {
                setButtonsDisabled(false);
            }
        };

        // Download Unknown LingQs button
        document.getElementById("downloadUnknownLingqsBtn").addEventListener("click", async () => {
            await handleDownloadButtonClick(`https://www.lingq.com/api/v3/${languageCode}/cards/`, "unknown_lingqs.csv", 'lingq', '&status=0&status=1&status=2&status=3');
        });

        // Download Unknown LingQ Words button
        document.getElementById("downloadUnknownLingqWordsBtn").addEventListener("click", async () => {
            await handleDownloadButtonClick(`https://www.lingq.com/api/v3/${languageCode}/cards/`, "unknown_lingq_words.csv", 'lingq', '&status=0&status=1&status=2&status=3&phrases=false');
        });

        // Download Unknown LingQ phrases button
        document.getElementById("downloadUnknownLingqPhrasesBtn").addEventListener("click", async () => {
            await handleDownloadButtonClick(`https://www.lingq.com/api/v3/${languageCode}/cards/`, "unknown_lingq_phrases.csv", 'lingq', '&status=0&status=1&status=2&status=3&phrases=True');
        });

        // Download Known LingQs button
        document.getElementById("downloadKnownLingqsBtn").addEventListener("click", async () => {
            await handleDownloadButtonClick(`https://www.lingq.com/api/v3/${languageCode}/cards/`, "known_lingqs.csv", 'lingq', '&status=4');
        });

        // Download known words button
        document.getElementById("downloadKnownWordsBtn").addEventListener("click", async () => {
            await handleDownloadButtonClick(`https://www.lingq.com/api/v2/${languageCode}/known-words/`, "known_words.csv", "known");
        });

        // Close button
        document.getElementById("closeDownloadWordsBtn").addEventListener("click", () => {
            downloadWordsPopup.style.display = "none";
        });
    }

    function setupEventListeners() {
        document.getElementById("lingqLessonComplete").addEventListener("click", finishLesson);
    }

    let styleElement = null;

    function applyStyles(styleType, colorMode) {
        const colorSettings = getColorSettings(colorMode);

        let css = generateBaseCSS(colorSettings, colorMode);
        let uiCSS = generateUICSS();
        let specificCSS = "";

        switch (colorMode) {
            case "dark":
                console.log('dark clicked');
                clickElement(".reader-themes-component > button:nth-child(5)");
                break;
            case "white":
                console.log('white clicked');
                clickElement(".reader-themes-component > button:nth-child(1)");
                break;
        }

        switch (styleType) {
            case "video":
                specificCSS = generateVideoCSS();
                break;
            case "video2":
                specificCSS = generateVideo2CSS();
                break;
            case "audio":
                specificCSS = generateAudioCSS();
                break;
            case "off":
                css = generateOffModeCSS(colorSettings);
                break;
        }

        css += specificCSS;
        css += uiCSS;

        if (styleElement) {
            styleElement.remove();
            styleElement = null;
        }

        styleElement = createElement("style", {textContent: css});
        document.querySelector("head").appendChild(styleElement);
    }

    function generateUICSS() {
        return`
        /*Color picker*/

        .color-picker {
            width: 30px;
            height: 15px;
            border-radius: 4px;
            cursor: pointer;
        }

        .pcr-app {
            z-index: 10001 !important;
        }

        .pcr-app .pcr-interaction .pcr-result {
            color: var(--font_color) !important;
        }

        /*Popup settings*/

        #lingqAddonSettings {
            color: var(--font_color);
        }

        #lingqAddonSettingsPopup, #lingqDownloadWordsPopup {
            position: fixed;
            top: 40%;
            left: 40%;
            transform: translate(-40%, -40%);
            background-color: var(--background-color, #2a2c2e);
            color: var(--font_color, #e0e0e0);
            border: 1px solid grey;
            border-radius: 8px;
            box-shadow: 8px 8px 8px rgba(0, 0, 0, 0.2);
            z-index: 10000;
            display: none;
            width: 400px;
            max-height: 90vh;
            overflow-y: auto;
        }

        #lingqAddonSettingsDragHandle, #lingqDownloadWordsDragHandle {
            cursor: move;
            background-color: rgba(128, 128, 128, 0.2);
            padding: 8px;
            border-radius: 8px 8px 0 0;
            text-align: center;
            user-select: none;
        }

        .popup-row {
            margin: 5px 0;
        }

        .nav-button {
            background: none;
            border: none;
            cursor: pointer;
            font-size: 1.2rem;
            margin-left: 10px;
            padding: 5px;
        }

        .popup-button {
            padding: 5px 10px;
            border: 1px solid;
            border-radius: 5px;
            margin: 5px 0;
        }

        #downloadProgressContainer {
            display: none;
        }

        #downloadProgressText {
            text-align: center;
            margin-bottom: 5px;
            font-size: 0.9em;
        }

        #downloadProgressBar {
            width: 100%;
            height: 20px;
        }

        progress[value]::-webkit-progress-bar {
            border-radius: 5px;
        }

        progress[value]::-webkit-progress-value {
            border-radius: 5px;
        }
        `;
    }

    function generateBaseCSS(colorSettings, colorMode) {
        return `
        :root {
            --font_size: ${settings.fontSize}rem;
            --line_height: ${settings.lineHeight};

            --article_height: calc(var(--app-height) - var(--height_big) - 10px);
            --grid-layout: var(--article_height) calc(var(--height_big) - 80px) 90px;

            --font_color: ${colorSettings.fontColor};
            --lingq_background: ${colorSettings.lingqBackground};
            --lingq_border: ${colorSettings.lingqBorder};
            --lingq_border_learned: ${colorSettings.lingqBorderLearned};
            --blue_border: ${colorSettings.blueBorder};
            --is_playing_underline: ${colorSettings.playingUnderline};

            --background-color: ${colorMode === "dark" ? "#2a2c2e" : "#ffffff"}
        }

        /*header settings*/

        .main-wrapper {
            padding: 0 !important;
        }

        #main-nav {
            z-index: 1;
        }

        #main-nav > nav {
            height: 50px;
        }

        #main-nav > nav > div:nth-child(1) {
            height: 50px;
        }

        .main-header {
            pointer-events: none;
        }

        .main-header > div {
            grid-template-columns: 1fr 150px !important;
            padding-left: 400px !important;
        }

        .main-header section:nth-child(1) {
            display: none;
        }

        .main-header section {
            pointer-events: auto;
            z-index: 1;
        }

        .main-header svg {
            width: 20px !important;
            height: 20px !important;
        }

        .lesson-progress-section {
            grid-template-rows: unset !important;
            grid-template-columns: unset !important;
            grid-column: 1 !important;
            pointer-events: auto;
        }

        .lesson-progress-section .rc-slider{
            grid-row: unset !important;
            grid-column: unset !important;
            width: 50% !important;
        }

        /*layout*/

        #lesson-reader {
            grid-template-rows: var(--grid-layout);
            overflow-y: hidden;
            height: auto !important;
        }

        .sentence-text {
            height: calc(var(--article_height) - 70px) !important;
        }

        .reader-container-wrapper {
            height: 100% !important;
        }

        .widget-area {
            padding-top: 50px !important;
            height: 100% !important;
        }

        .main-footer {
            grid-area: 3 / 1 / 3 / 1 !important;
            align-self: end;
            margin: 10px 0;
        }

        .main-content {
            grid-template-rows: 45px 1fr !important;
            overflow: hidden;
            align-items: anchor-center;
        }

        /*make prev/next page buttons compact*/

        .reader-component {
            grid-template-columns: 0rem 1fr 0rem !important;
            align-items: baseline;
            margin-top: 10px;
        }

        .reader-component > div > a.button > span {
            width: 0.5rem !important;
        }

        .reader-component > div > a.button > span > svg {
            width: 15px !important;
            height: 15px !important;
        }

        .loadedContent {
            padding: 0 0 5px 15px !important;;
        }

        /*video viewer*/

        .video-player {
            display: flex !important;
            justify-content: flex-end !important;
            align-items: flex-start !important;
            pointer-events: none;
        }

        .video-player > .modal-background {
            background-color: rgb(26 28 30 / 0%) !important;
        }

        .video-player > .modal-content {
            max-width: var(--width_big) !important;
            margin: var(--video_margin) !important;
            border-radius: 0.75rem !important;
        }

        .video-player .modal-section {
            display: none !important;
        }

        .video-wrapper {
            height: var(--height_big) !important;
            overflow: hidden;
            pointer-events: auto;
        }

        /*video controller*/

        .rc-slider-rail {
            background-color: dimgrey !important;
        }

        .lingq-audio-player {
            margin-left: 10px;
        }

        .section--player.is-expanded {
            padding: 5px 0px !important;
            width: 390px !important;
            margin-left: 10px !important;
        }

        .sentence-mode-button {
            margin: 0 0 10px 0;
        }

        .player-wrapper {
            grid-template-columns: 1fr 40px !important;
            padding: 0 !important;
        }

        .audio-player {
            padding: 0 0.5rem !important;
        }

        /*font settings*/

        .reader-container {
            margin: 0 !important;
            float: left !important;
            line-height: var(--line_height) !important;
            font-size: var(--font_size) !important;
            columns: unset !important;
            overflow-y: scroll !important;
            max-width: unset !important;
        }

        .sentence-text-head {
            min-height: 4.5rem !important;
        }

        .reader-container p {
            margin-top: 0 !important;
        }

        .reader-container p span.sentence-item,
        .reader-container p .sentence {
            color: var(--font_color) !important;
        }

        .sentence.is-playing,
        .sentence.is-playing span {
            text-underline-offset: .2em !important;
            text-decoration-color: var(--is_playing_underline) !important;
        }

        /*highlightings*/

        .phrase-item {
            padding: 0 !important;
        }

        .phrase-item:not(.phrase-item-status--4, .phrase-item-status--4x2)) {
            background-color: var(--lingq_background) !important;
        }

        .phrase-item.phrase-item-status--4,
        .phrase-item.phrase-item-status--4x2 {
            background-color: rgba(0, 0, 0, 0) !important;
        }

        .phrase-cluster:not(:has(.phrase-item-status--4, .phrase-item-status--4x2)) {
            border: 1px solid var(--lingq_border) !important;
            border-radius: .25rem;
        }

        .phrase-cluster:has(.phrase-item-status--4, .phrase-item-status--4x2) {
            border: 1px solid var(--lingq_border_learned) !important;
            border-radius: .25rem;
        }

        .reader-container .sentence .lingq-word:not(.is-learned) {
            border: 1px solid var(--lingq_border) !important;
            background-color: var(--lingq_background) !important;
        }

        .reader-container .sentence .lingq-word.is-learned {
            border: 1px solid var(--lingq_border_learned) !important;
        }

        .reader-container .sentence .blue-word {
            border: 1px solid var(--blue_border) !important;
        }

        .phrase-cluster:hover,
        .phrase-created:hover {
            padding: 0 !important;
        }

        .phrase-cluster:hover .phrase-item,
        .phrase-created .phrase-item {
            padding: 0 !important;
        }

        .reader-container .sentence .selected-text {
            padding: 0 !important;
        }
        `;
    }

    function generateVideoCSS() {
        return `
        :root {
            --width_big: calc(100vw - 424px - 10px);
            --height_big: ${settings.heightBig}px;
            --video_margin: 0 0 10px 10px !important;
        }

        .main-content {
            grid-area: 1 / 1 / 2 / 2 !important;
        }

        .widget-area {
            grid-area: 1 / 2 / 3 / 2 !important;
        }

        .main-footer {
            grid-area: 3 / 2 / 4 / 3 !important;
            align-self: end;
        }
        `;
    }

    function generateVideo2CSS() {
        return `
        :root {
            --width_big: calc(50vw - 217px);
            --height_big: calc(100vh - 80px);

            --grid-layout: var(--article_height) 90px;
            --video_margin: 0 10px 10px 10px !important;
            --article_height: calc(var(--app-height) - 85px);
        }

        .page.reader-page.has-widget-fixed:not(.is-edit-mode):not(.workspace-sentence-reviewer) {
            grid-template-columns: 1fr 424px 1fr;
        }

        .main-content {
            grid-area: 1 / 1 / 2 / 2 !important;
        }

        .widget-area {
            grid-area: 1 / 2 / 2 / 3 !important;
        }

        .main-footer {
            grid-area: 2 / 2 / 3 / 3 !important;
            align-self: end;
        }

        .modal-container .modls {
            align-items: end;
        }
        `;
    }

    function generateAudioCSS() {
        return `
        :root {
            --height_big: 60px;
        }

        .main-content {
            grid-area: 1 / 1 / 2 / 2 !important;
        }

        .widget-area {
            grid-area: 1 / 2 / 2 / 2 !important;
        }
        `;
    }

    function generateOffModeCSS(colorSettings) {
        return `
        :root {
            --width_small: 440px;
            --height_small: 260px;
            --sentence_height: ${settings.sentenceHeight}px;
            --right_pos: 0.5%;
            --bottom_pos: 5.5%;
        }

        /*video player*/

        .video-player.is-minimized .video-wrapper,
        .sent-video-player.is-minimized .video-wrapper {
            height: var(--height_small);
            width: var(--width_small);
            overflow: auto;
            resize: both;
        }

        .video-player.is-minimized .modal-content,
        .sent-video-player.is-minimized .modal-content {
            max-width: calc(var(--width_small)* 3);
            margin-bottom: 0;
        }

        .video-player.is-minimized,
        .sent-video-player.is-minimized {
            left: auto;
            top: auto;
            right: var(--right_pos);
            bottom: var(--bottom_pos);
            z-index: 99999999;
            overflow: visible
        }

        /*sentence mode video player*/
        .loadedContent:has(#sentence-video-player-portal) {
            grid-template-rows: var(--sentence_height) auto auto 1fr !important;
        }

        #sentence-video-player-portal .video-section {
            width: 100% !important;
            max-width: none !important;
        }

        #sentence-video-player-portal .video-wrapper {
            height: 100% !important;
            max-height: none !important;
        }

        #sentence-video-player-portal div:has(> iframe) {
            height: 100% !important;
        }
        `;
    }

    function clickElement(selector) {
        const element = document.querySelector(selector);
        if (element) element.click();
    }

    function focusElement(selector) {
        const element = document.querySelector(selector);
        if (element) {
            element.focus();
            element.setSelectionRange(element.value.length, element.value.length);
        }
    }

    function copySelectedText() {
        const selected_text = document.querySelector(".reference-word");
        if (selected_text) {
            navigator.clipboard.writeText(selected_text.textContent);
        }
    }

    function finishLesson(){
        clickElement(".reader-component > .nav--right > a");
    }

    function setupKeyboardShortcuts() {
        function preventPropagation(event){
            event.preventDefault();
            event.stopPropagation();
        }

        document.addEventListener("keydown", function (event) {
            const targetElement = event.target;
            const isTextInput = targetElement.type === "text" || targetElement.type === "textarea";
            const withoutModifierKeys = !event.ctrlKey && !event.shiftKey && !event.altKey;
            const eventKey = event.key.toLowerCase();
            if (isTextInput) {
                if ((eventKey == 'enter' || eventKey == 'esc') && withoutModifierKeys) {
                    preventPropagation(event);
                    event.target.blur();
                } else {
                    return;
                }
            }

            const shortcuts = {
                'q': () => clickElement(".modal-section > div > button:nth-child(2)"), // video full screen toggle
                'w': () => clickElement(".audio-player--controllers > div:nth-child(1) > a"), // 5 sec Backward
                'e': () => clickElement(".audio-player--controllers > div:nth-child(2) > a"), // 5 sec Forward
                'r': () => document.dispatchEvent(new KeyboardEvent("keydown", { key: "k" })), // Make word Known
                't': () => clickElement(".dictionary-resources > a:nth-last-child(1)"), // Open Translator
                '`': () => focusElement(".reference-input-text"), // Move cursor to reference input
                'd': () => clickElement(".dictionary-resources > a:nth-child(1)"), // Open Dictionary
                'f': () => clickElement(".dictionary-resources > a:nth-child(1)"), // Open Dictionary
                'c': () => copySelectedText() // Copy selected text
            };

            if (shortcuts[eventKey] && withoutModifierKeys) {
                preventPropagation(event);
                shortcuts[eventKey]();
            }
        }, true);
    }

    async function getLanguageCode() {
        const url = `https://www.lingq.com/api/v3/profiles/`;
        
        const response = await fetch(url);
        const data = await response.json();
        
        return data.results[0].active_language;
    }

    function getLessonId() {
        const url = document.URL;
        const regex = /https*:\/\/www\.lingq\.com\/\w+\/learn\/\w+\/web\/reader\/(\d+)/;
        const match = url.match(regex);

        return match[1];
    }

    async function getCollectionId() {
        const url = document.URL;
        const regex = /https*:\/\/www\.lingq\.com\/\w+\/learn\/\w+\/web\/library\/course\/(\d+)/;
        const match = url.match(regex);

        return match[1];
    }

    async function getLessonInfo(lessonId) {
        const languageCode = await getLanguageCode();
        const url = `https://www.lingq.com/api/v3/${languageCode}/lessons/counters/?lesson=${lessonId}`;
        
        const response = await fetch(url);
        const data = await response.json();
        
        return data[lessonId];
    }

    async function getAllLessons(languageCode, collectionId) {
        let allLessons = [];
        let nextUrl = `https://www.lingq.com/api/v3/${languageCode}/search/?page=1&page_size=1000&collection=${collectionId}`;

        while (nextUrl) {
            try {
                const response = await fetch(nextUrl);
                if (!response.ok) {
                    throw new Error(`HTTP error! Status: ${response.status}`);
                }

                const data = await response.json();
                allLessons = allLessons.concat(data.results);
                nextUrl = data.next;
            } catch (error) {
                console.error('Error fetching lessons:', error);
                break;
            }
        }

        return allLessons;
    }

    async function setLessonProgress(lessonId, wordIndex) {
        const languageCode = await getLanguageCode();
        const url = `https://www.lingq.com/api/v3/${languageCode}/lessons/${lessonId}/bookmark/`;
        const payload = { wordIndex: wordIndex, completedWordIndex: wordIndex, client: 'web' };

        fetch(url, {
            method: 'POST',
            headers: { 'Content-Type': 'application/json' },
            body: JSON.stringify(payload)
        });
    }

    function setupYoutubePlayerCustomization() {
        function replaceNoCookie() {
            document.querySelectorAll("iframe").forEach(function (iframe) {
                let src = iframe.getAttribute("src");
                if (src && src.includes("disablekb=1")) {
                    src = src.replace("disablekb=1", "disablekb=0"); // keyboard controls are enabled
                    src = src + "&cc_load_policy=1"; // caption is shown by default
                    src = src + "&controls=0"; // player controls do not display in the player
                    iframe.setAttribute("src", src);
                }
            });
        }

        async function setupSliderObserver() {
            const lessonId = getLessonId();
            const lessonInfo = await getLessonInfo(lessonId);
            let lastCompletedPercentage = lessonInfo["progress"];
            console.log(`last progress: ${lastCompletedPercentage}`);

            const sliderTrack = document.querySelector('.audio-player--progress .rc-slider-track');
            
            const sliderContainer = createSliderElements();
            const videoContainer = document.querySelector(".modal-content > div");
            videoContainer.appendChild(sliderContainer);
            const videoSliderTrack = sliderContainer.querySelector(".rc-slider-track");

            const syncVideoSliderTrack = (videoSliderTrack, sliderTrack) => {
                videoSliderTrack.style.cssText = sliderTrack.style.cssText;
            };

            const updateLessonProgress = (lessonId, lessonInfo, progressPercentage, lastCompletedPercentage) => {
                const progressUpdatePeriod = 5;
                const flooredProgressPercentage = Math.floor(progressPercentage / progressUpdatePeriod) * progressUpdatePeriod;
        
                if (flooredProgressPercentage > lastCompletedPercentage) {
                    console.log(`progress percentage: ${flooredProgressPercentage}. Updated`);
                    const wordIndex = Math.floor(lessonInfo["totalWordsCount"] * (flooredProgressPercentage / 100));
                    setLessonProgress(lessonId, wordIndex);
                    return flooredProgressPercentage;
                }
                return lastCompletedPercentage;
            };

            const sliderObserver = new MutationObserver(function (mutationsList) {
                for (const mutation of mutationsList) {
                    if (mutation.type === 'attributes' && mutation.attributeName === 'style') {
                        syncVideoSliderTrack(videoSliderTrack, sliderTrack);
                        
                        const progressPercentageString = sliderTrack.style.width;
                        const progressPercentage = parseFloat(progressPercentageString);

                        lastCompletedPercentage = updateLessonProgress(lessonId, lessonInfo, progressPercentage, lastCompletedPercentage);
                        const isLessonFinished = progressPercentageString === "100%";
                        if (isLessonFinished && settings.autoFinishing) {
                            finishLesson();
                        }
                    }
                }
            });

            sliderObserver.observe(sliderTrack, {attributes: true, attributeFilter: ['style']});
            console.log('Observer started for rc-slider-track');
        }

        function createSliderElements() {
            const sliderContainer = createElement("div", {className: "rc-slider rc-slider-horizontal"});
            const sliderRail = createElement("div", {className: "rc-slider-rail"});
            const sliderTrack = createElement("div", {className: "rc-slider-track"});
            sliderContainer.appendChild(sliderRail);
            sliderContainer.appendChild(sliderTrack);
            return sliderContainer;
        }

        const iframeObserver = new MutationObserver(function (mutationsList) {
            for (const mutation of mutationsList) {
                if (mutation.type === "childList" && mutation.addedNodes.length > 0) {
                    mutation.addedNodes.forEach((node) => {
                        if (node.nodeName === "IFRAME") {
                            replaceNoCookie();
                            clickElement('.modal-section.modal-section--head button[title="Expand"]');
                            setupSliderObserver();
                        }
                    });
                }
            }
        });

        iframeObserver.observe(document.body, {childList: true, subtree: true, attributes: true, attributeFilter: ["src"]});
    }

    function setupScrollCustomization() {
        setTimeout(() => {
            const readerContainer = document.querySelector(".reader-container");
            if (readerContainer) {
                readerContainer.addEventListener("wheel", (event) => {
                    event.preventDefault();
                    const delta = event.deltaY;
                    const scrollAmount = 0.3;
                    readerContainer.scrollTop += delta * scrollAmount;
                });
            }
        }, 3000);
    }

    function setupSentenceFocus() {
        function focusPlayingSentence() {
            const playingSentence = document.querySelector(".sentence.is-playing");
            if (playingSentence) {
                /*
                playingSentence.scrollIntoView({
                    behavior: "smooth",
                    block: "center"
                });
                */
                const scrolling_div = document.querySelector(".reader-container")
                scrolling_div.scrollTop = playingSentence.offsetTop + Math.floor(playingSentence.offsetHeight / 2) - Math.floor(scrolling_div.offsetHeight / 2);

            }
        }

        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                if (
                    mutation.type === "attributes" &&
                    mutation.attributeName === "class" &&
                    mutation.target.classList.contains("sentence")
                ) {
                    focusPlayingSentence();
                }
            });
        });

        const container = document.querySelector(".sentence-text");
        if (container) {
            observer.observe(container, {
                attributes: true,
                subtree: true
            });
        }
    }

    async function waitForElement(selector) {
        return new Promise(resolve => {
            if (document.querySelector(selector)) {
                return resolve(document.querySelector(selector));
            }
    
            const observer = new MutationObserver(() => {
                if (document.querySelector(selector)) {
                    resolve(document.querySelector(selector));
                    observer.disconnect();
                }
            });
    
            observer.observe(document.documentElement, {
                childList: true,
                subtree: true
            });
        });
    }

    async function setupCourse() {
        function createCourseUI(){
            const resetButton = createElement("button", {
                id: "resetLessonPositions",
                textContent: "⏮️",
                title: "Reset all lessons to the first page",
                className: "nav-button"
            });
    
            let nav = document.querySelector(".library-section > .list-header > .list-header-index");
            nav.appendChild(resetButton);
        }
        
        function setupCourseStyles() {
            const css = `
            .nav-button {
                background: none;
                border: none;
                cursor: pointer;
                font-size: 1.5rem;
            }
    
            .library-section > .list-header > .list-header-index {
                grid-template-columns: auto 1fr auto !important;
            }

            .dynamic--word-progress {
                grid-template-columns: repeat(3, auto) !important;
            }

            .word-indicator--box-green {
                background-color: var(--green-450);
                border-color: var(--green-default);
            }
            `;
            const styleElement = createElement("style", { textContent: css });
            document.querySelector("head").appendChild(styleElement);
        }

        function setupCourseSort() {
            const dropdownItems = document.querySelectorAll('.library-section > .list-header .tw-dropdown--item');
            if (dropdownItems.length) {
                // Setup library sort event listener
                dropdownItems.forEach((item, index) => {
                    item.addEventListener('click', () => {
                        console.log(`Clicked sort option: ${index}`);
                        storage.set('librarySortOption', index);
                        settings.librarySortOption = index;
                    });
                });

                // Change sort by the setting
                dropdownItems[settings.librarySortOption].click();
                return true;
            } else {
                console.warn("Dropdown items not found for library sort.");
                return false;
            }
        }
    
        function setupResetLessonPosition() {
            const resetButton = document.getElementById("resetLessonPositions");
            resetButton.addEventListener("click", async () => {
                const languageCode = await getLanguageCode();
                const collectionId = await getCollectionId();
    
                const allLessons = await getAllLessons(languageCode, collectionId);
                const confirmed = confirm(`Reset all ${allLessons.length} lessons to their starting positions?`);
                if (!confirmed) { return; }
    
                for (const lesson of allLessons) {
                    await setLessonProgress(lesson.id, 0);
                    console.log(`Reset lesson ID: ${lesson.id} to the first page`);
                }
    
                alert(`Successfully reset ${allLessons.length} lessons to their starting positions.`);
            });
        }

        function provideMoreLessonInfo() {
            function addKnownWordsIndicator(lessonElement, lessonInfo) {
                const dynamicWordProgress = lessonElement.querySelector('.dynamic--word-progress');
            
                const knownWordPercentage = Math.round((lessonInfo.knownWordsCount / lessonInfo.uniqueWordsCount) * 100);
            
                const knownWordsItem = createElement('div', {className: 'word-indicator--item grid-layout grid-align--center grid-item is-fluid--left', title: 'Known Words'});
            
                const knownWordsBox = createElement('div', {className: 'word-indicator--box word-indicator--box-green'});
                knownWordsItem.appendChild(knownWordsBox);
            
                const textWrapper = createElement('span', {className: 'text-wrapper is-size-8'});
                textWrapper.appendChild(createElement('span', {textContent: `${lessonInfo.knownWordsCount} (${knownWordPercentage}%)`}));
            
                knownWordsItem.appendChild(textWrapper);
                dynamicWordProgress.appendChild(knownWordsItem);
            }
            
            async function updateWordIndicatorPercentages(lessonElement, lessonId) {
                console.log(`lessonId: ${lessonId}`);
                const lessonInfo = await getLessonInfo(lessonId);
                
                const wordIndicatorItems = lessonElement.querySelector(".word-indicator--item");
                if (!wordIndicatorItems) { return; } 
   
                const lingqsPercentage = Math.round((lessonInfo.cardsCount / lessonInfo.uniqueWordsCount) * 100);
                const lingqsElement = lessonElement.querySelector('.word-indicator--item[title="LingQs"] > span > span');
                lingqsElement.textContent = `${lessonInfo.cardsCount} (${lingqsPercentage}%)`;

                addKnownWordsIndicator(lessonElement, lessonInfo);
            }
    
            const observer = new MutationObserver((mutations) => {
                mutations.forEach((mutation) => {
                    if (mutation.type === 'childList' && mutation.addedNodes.length > 0) {
                        mutation.addedNodes.forEach((node) => {
                            if (node.classList && node.classList.contains('library-item-wrap')) {
                                const lessonId = node.id.split("--")[1].split("-")[0];
                                updateWordIndicatorPercentages(node, lessonId);
                            }
                        });
                    }
                });
            });
    
            const targetNode = document.querySelector('.library-section .library-list');
            console.log(targetNode);
            const config = { childList: true, subtree: true };
            observer.observe(targetNode, config);
        }

        const libraryHeader = await waitForElement('.library-section > .list-header');
        createCourseUI();
        setupCourseStyles();
        
        provideMoreLessonInfo();
        setupCourseSort();
        setupResetLessonPosition();
    }

    function fixBugs() {
        const resizeToast = () => {
            const css = `
            .toasts {
                height: fit-content;
            }
            `;
            const cssElement = createElement("style", {textContent: css});
            document.querySelector("head").appendChild(cssElement);
        }

        resizeToast();
    }

    function init() {
        fixBugs();

        if (document.URL.includes("reader")) {
            createUI();
            applyStyles(settings.styleType, settings.colorMode);
            setupKeyboardShortcuts();
            setupYoutubePlayerCustomization();
            setupScrollCustomization();
            setupSentenceFocus();
        }
        if (document.URL.includes("library")) {
            setupCourse();
        }
    }

    init();
})();