LingQ Addon

Provides custom LingQ layouts

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

(function () {
    "use strict";

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

    // Default values
    const defaults = {
        styleType: "video",
        colorMode: "dark",
        fontSize: 1.1,
        lineHeight: 1.7,
        heightBig: 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"
        }
    };

    // Load stored settings
    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)
    };

    // Helper function to get color settings based on mode
    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)
        };
    }

    // Get current color settings
    const colorSettings = getColorSettings(settings.colorMode);

    // UI Creation
    function createUI() {
        // Create settings button
        const settingsButton = document.createElement("button");
        settingsButton.id = "lingqAddonSettings";
        settingsButton.textContent = "⚙️";
        settingsButton.title = "LingQ Addon Settings";
        settingsButton.style.cssText = `
            background: none;
            border: none;
            cursor: pointer;
            font-size: 1.2rem;
            margin-left: 10px;
            padding: 5px;
        `;

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

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

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

        // Add event listeners
        setupEventListeners(settingsButton, settingsPopup);
    }

    // Create settings popup with all controls
    function createSettingsPopup() {
        const popup = document.createElement("div");
        popup.id = "lingqAddonSettingsPopup";
        popup.style.cssText = `
            position: fixed;
            top: 40%;
            left: 40%;
            transform: translate(-40%, -40%);
            background-color: var(--background-color, #2a2c2e);
            color: var(--font_color, #e0e0e0);
            border: 1px solid s;
            border-radius: 8px;
            box-shadow: 8px 8px 8px rgba(0, 0, 0, 0.2);
            z-index: 10000;
            display: none;
            width: 400px;
            max-height: 80vh;
            overflow-y: auto;
        `;

        // popup content
        const content = document.createElement("div");
        content.style.cssText = `padding: 20px; `;
        content.innerHTML = generatePopupContent();

        // drag handle
        const dragHandle = document.createElement("div");
        dragHandle.id = "lingqAddonSettingsDragHandle";
        dragHandle.style.cssText = `
            cursor: move;
            background-color: rgba(128, 128, 128, 0.2);
            padding: 8px;
            border-radius: 8px 8px 0 0;
            text-align: center;
            user-select: none;
        `;
        dragHandle.innerHTML = `<h3 style="margin: 0; user-select: none;">LingQ Addon Settings</h3>`;

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

        return popup;
    }

    // Generate HTML content for the popup
    function generatePopupContent() {
        return `
            <div style="margin-bottom: 15px;">
                <label for="styleTypeSelector">Layout Style:</label>
                <select id="styleTypeSelector" style="width: 100%; margin-top: 5px; padding: 5px;">
                    <option value="video" ${settings.styleType === "video" ? "selected" : ""}>Video</option>
                    <option value="video2" ${settings.styleType === "video2" ? "selected" : ""}>Video2</option>
                    <option value="audio" ${settings.styleType === "audio" ? "selected" : ""}>Audio</option>
                    <option value="off" ${settings.styleType === "off" ? "selected" : ""}>Off</option>
                </select>
            </div>

            <div id="videoSettings" style="margin-bottom: 15px; ${settings.styleType === "video" ? "" : "display: none"}">
                <label for="heightBigSlider">Video Height: <span id="heightBigValue">${settings.heightBig}</span>px</label>
                <input type="range" id="heightBigSlider" min="300" max="800" step="10" value="${settings.heightBig}" style="width: 100%;">
            </div>

            <div style="margin-bottom: 15px;">
                <label for="fontSizeSlider">Font Size: <span id="fontSizeValue">${settings.fontSize}</span>rem</label>
                <input type="range" id="fontSizeSlider" min="0.8" max="1.8" step="0.05" value="${settings.fontSize}" style="width: 100%;">
            </div>

            <div style="margin-bottom: 15px;">
                <label for="lineHeightSlider">Line Height: <span id="lineHeightValue">${settings.lineHeight}</span></label>
                <input type="range" id="lineHeightSlider" min="1.2" max="3.0" step="0.1" value="${settings.lineHeight}" style="width: 100%;">
            </div>

            <div style="margin-bottom: 15px; border: 1px solid var(--font_color, #e0e0e0); padding: 10px; border-radius: 5px;">
                <label for="colorModeSelector">Color Mode:</label>
                <select id="colorModeSelector" style="width: 100%; margin-top: 5px; padding: 5px;">
                    <option value="dark" ${settings.colorMode === "dark" ? "selected" : ""}>Dark</option>
                    <option value="white" ${settings.colorMode === "white" ? "selected" : ""}>White</option>
                </select>

                <div style="margin-top: 10px;">
                    <label for="fontColorText">Font Color:</label>
                    <div style="display: flex; align-items: center;">
                        <div id="fontColorPicker" class="color-picker"></div>
                        <input type="text" id="fontColorText" value="${colorSettings.fontColor}" style="flex-grow: 1; padding: 5px; margin-left: 10px;">
                    </div>
                </div>

                <div style="margin-top: 10px;">
                    <label for="lingqBackgroundText">LingQ Background:</label>
                    <div style="display: flex; align-items: center;">
                        <div id="lingqBackgroundPicker" class="color-picker"></div>
                        <input type="text" id="lingqBackgroundText" value="${colorSettings.lingqBackground}" style="flex-grow: 1; padding: 5px; margin-left: 10px;">
                    </div>
                </div>

                <div style="margin-top: 10px;">
                    <label for="lingqBorderText">LingQ Border:</label>
                    <div style="display: flex; align-items: center;">
                        <div id="lingqBorderPicker" class="color-picker"></div>
                        <input type="text" id="lingqBorderText" value="${colorSettings.lingqBorder}" style="flex-grow: 1; padding: 5px; margin-left: 10px;">
                    </div>
                </div>

                <div style="margin-top: 10px;">
                    <label for="lingqBorderLearnedText">LingQ Border Learned:</label>
                    <div style="display: flex; align-items: center;">
                        <div id="lingqBorderLearnedPicker" class="color-picker"></div>
                        <input type="text" id="lingqBorderLearnedText" value="${colorSettings.lingqBorderLearned}" style="flex-grow: 1; padding: 5px; margin-left: 10px;">
                    </div>
                </div>

                <div style="margin-top: 10px;">
                    <label for="blueBorderText">Blue Border:</label>
                    <div style="display: flex; align-items: center;">
                        <div id="blueBorderPicker" class="color-picker"></div>
                        <input type="text" id="blueBorderText" value="${colorSettings.blueBorder}" style="flex-grow: 1; padding: 5px; margin-left: 10px;">
                    </div>
                </div>

                 <div style="margin-top: 10px;">
                    <label for="playingUnderlineText">Playing Underline:</label>
                    <div style="display: flex; align-items: center;">
                        <div id="playingUnderlinePicker" class="color-picker"></div>
                        <input type="text" id="playingUnderlineText" value="${colorSettings.playingUnderline}" style="flex-grow: 1; padding: 5px; margin-left: 10px;">
                    </div>
                </div>
            </div>

            <div style="display: flex; justify-content: space-between; margin-top: 20px;">
                <button id="resetSettingsBtn" style="padding: 5px 10px; margin-right: 10px; cursor: pointer;">Reset</button>
                <button id="closeSettingsBtn" style="padding: 5px 10px; cursor: pointer;">Close</button>
            </div>
        `;
    }

    // Add Pickr CSS to the document
    function addPickrStyles() {
        const pickrCSS = document.createElement('style');
        pickrCSS.textContent = `
            .color-picker {
                width: 30px;
                height: 30px;
                border-radius: 4px;
                cursor: pointer;
                position: relative;
                overflow: hidden;
                box-shadow: 0 2px 4px rgba(0,0,0,0.2);
            }

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

            .pcr-app .pcr-interaction .pcr-result {
                color: var(--font_color) !important;
            }
        `;
        document.head.appendChild(pickrCSS);
    }

    // Initialize Pickr color pickers
    function initializePickrs() {
        // Load Pickr library dynamically
        return new Promise((resolve) => {
            // Add Pickr CSS
            const pickrCss = document.createElement('link');
            pickrCss.rel = 'stylesheet';
            pickrCss.href = 'https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/themes/nano.min.css';
            document.head.appendChild(pickrCss);

            // Add Pickr JS
            const pickrScript = document.createElement('script');
            pickrScript.src = 'https://cdn.jsdelivr.net/npm/@simonwep/pickr/dist/pickr.min.js';
            pickrScript.onload = () => resolve();
            document.head.appendChild(pickrScript);
        }).then(() => {
            // Add custom styles for Pickr
            addPickrStyles();

            // Setup each color picker
            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');
        });
    }

    // Setup RGBA color picker with Pickr
    function setupRGBAPickr(pickerId, textId, settingKey, cssVar) {
        const pickerElement = document.getElementById(pickerId);
        const textElement = document.getElementById(textId);

        if (!pickerElement || !textElement) return;

        // Set initial color for the picker element
        pickerElement.style.backgroundColor = textElement.value;

        // Create Pickr instance
        const pickr = Pickr.create({
            el: pickerElement,
            theme: 'nano',
            useAsButton: true,
            default: textElement.value,
            components: {
                preview: true,
                opacity: true,
                hue: true,
            }
        });

        // Handle color change
        pickr.on('change', (color) => {
            const rgbaColor = color.toRGBA();

            // Round the RGBA values
            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; // Update picker background
            document.documentElement.style.setProperty(cssVar, roundedRGBA);

            saveColorSetting(settingKey, roundedRGBA);
        });

        // Update picker when text input changes
        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;
        });

        // Update picker background color when color changes
        pickr.on('hide', () => {
            const rgbaColor = pickr.getColor().toRGBA().toString();
            pickerElement.style.backgroundColor = rgbaColor;
        });
    }

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

    // Set up all event listeners for the settings UI
    function setupEventListeners(settingsButton, settingsPopup) {
        // Initialize Pickr after popup is displayed
        settingsButton.addEventListener("click", () => {
            setTimeout(() => {
                initializePickrs();
            }, 100);
        });

        // Drag popup
        settingsButton.addEventListener("click", () => {
            settingsPopup.style.display = "block";

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

        // Toggle popup visibility
        settingsButton.addEventListener("click", () => {
            settingsPopup.style.display = "block";
        });

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

        // Reset button
        document.getElementById("resetSettingsBtn").addEventListener("click", resetSettings);

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

        // Color mode selector
        document.getElementById("colorModeSelector").addEventListener("change", updateColorMode);

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

    // Helper function to set up slider controls
    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);
        });
    }

    // Function to save color setting with appropriate prefix
    function saveColorSetting(key, value) {
        const currentColorMode = document.getElementById("colorModeSelector").value;
        const prefix = currentColorMode === "dark" ? "dark_" : "white_";
        storage.set(prefix + key, value);
    }

    // Update color mode and related settings
    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);

        // Load color settings for the selected mode
        const colorSettings = getColorSettings(selectedColorMode);

        // Update all color inputs
        updateColorInputs(colorSettings);

        // Update CSS variables
        document.documentElement.style.setProperty(
            "--background-color",
            selectedColorMode === "dark" ? "#2a2c2e" : "#ffffff"
        );
        updateCssColorVariables(colorSettings);

        applyStyles(document.getElementById("styleTypeSelector").value, selectedColorMode);

        // Update color picker backgrounds
        updateColorPickerBackgrounds(colorSettings);
    }

    // Update all color input fields with new settings
    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;

        //Update Pickr color pickers with new values
        const fontColorPicker = document.getElementById("fontColorPicker");
        if (fontColorPicker) fontColorPicker.style.backgroundColor = colorSettings.fontColor;

        const playingUnderlinePicker = document.getElementById("playingUnderlinePicker");
        if(playingUnderlinePicker) playingUnderlinePicker.style.backgroundColor = colorSettings.playingUnderline;
    }

    // Update color picker backgrounds
    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;
            }
        });
    }

    // Update CSS color variables
    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);
    }

    // Reset settings to defaults
    function resetSettings() {
        if (!confirm("Reset all settings to default?")) return;

        const currentColorMode = document.getElementById("colorModeSelector").value;

        // Default values
        const defaultSettings = {
            styleType: "video",
            colorMode: currentColorMode,
            fontSize: 1.1,
            lineHeight: 1.7,
            heightBig: 400,
        };

        // Default color settings for current mode
        const defaultColorSettings = currentColorMode === "dark"
            ? defaults.darkColors
            : defaults.whiteColors;

        // Update all inputs
        document.getElementById("styleTypeSelector").value = defaultSettings.styleType;
        document.getElementById("fontSizeSlider").value = defaultSettings.fontSize;
        document.getElementById("fontSizeValue").textContent = defaultSettings.fontSize;
        document.getElementById("lineHeightSlider").value = defaultSettings.lineHeight;
        document.getElementById("lineHeightValue").textContent = defaultSettings.lineHeight;
        document.getElementById("heightBigSlider").value = defaultSettings.heightBig;
        document.getElementById("heightBigValue").textContent = defaultSettings.heightBig;

        // Update color inputs
        updateColorInputs(defaultColorSettings);

        // Update color picker backgrounds
        updateColorPickerBackgrounds(defaultColorSettings);

        // Save general settings
        for (const [key, value] of Object.entries(defaultSettings)) {
            storage.set(key, value);
        }

        // Save color settings with prefix
        const prefix = currentColorMode === "dark" ? "dark_" : "white_";
        for (const [key, value] of Object.entries(defaultColorSettings)) {
            storage.set(prefix + key, value);
        }

        // Apply styles
        applyStyles(defaultSettings.styleType, currentColorMode);

        // Show/hide video settings
        document.getElementById("videoSettings").style.display =
            defaultSettings.styleType === "video" ? "block" : "none";

        // Update CSS variables directly
        document.documentElement.style.setProperty("--font_size", `${defaultSettings.fontSize}rem`);
        document.documentElement.style.setProperty("--line_height", defaultSettings.lineHeight);
        document.documentElement.style.setProperty("--height_big", `${defaultSettings.heightBig}px`);
        updateCssColorVariables(defaultColorSettings);
    }

    // CSS Management
    let styleElement = null;

    // Apply styles based on current settings
    function applyStyles(styleType, colorMode) {
        // Load color settings for the current mode
        const colorSettings = getColorSettings(colorMode);

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

        // Apply color mode CSS
        switch (colorMode) {
            case "dark":
                theme_btn = document.querySelector(".reader-themes-component > button:nth-child(5)");
                break;
            case "white":
                theme_btn = document.querySelector(".reader-themes-component > button:nth-child(1)");
                break;
        }

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

        // Append the style-specific CSS
        css += specificCSS;

        // Remove the old style tag
        if (styleElement) {
            styleElement.remove();
            styleElement = null;
        }

        // Create & append the new style tag
        if (css) {
            styleElement = document.createElement("style");
            styleElement.textContent = css;
            document.querySelector("head").appendChild(styleElement);
        }

        if (theme_btn) {
            theme_btn.click();
        }
    }

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

            --article_height: calc(var(--app-height) - var(--height_big) - 50px);
            --grid-layout: calc(var(--article_height) - 10px) 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"}
          }

        .color-picker {
            height: 15px !important;
        }

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

        #lingqAddonSettingsPopup {
            background-color: var(--background-color);
            color: var(--font_color);
        }

        .main-wrapper {
          padding-top: calc(var(--spacing) * 12) !important;
        }

        #main-nav .navbar,
        #main-nav .navbar-brand {
          min-height: 2.75rem !important;
        }

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

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

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

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

        /*video viewer*/

        .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;
        }

        .main-content > .main-header {
          margin-top: 30px;
        }

        .modal-container .modls {
          pointer-events: none;
          justify-content: end !important;
          align-items: flex-start;
        }

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

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

        .video-player .video-wrapper,
        .sent-video-player .video-wrapper {
          height: var(--height_big);
          overflow: hidden;
          pointer-events: auto;
        }

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

        /*make prev/next page buttons compact*/

        .reader-component {
          grid-template-columns: 0.5rem 1fr 0rem !important;
        }

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

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

        /*font settings*/

        .reader-container {
          margin: 0 !important;
          float: left !important;
          line-height: var(--line_height) !important;
          padding: 0 0 100px 0 !important;
          font-size: var(--font_size) !important;
          columns: unset !important;
          overflow-y: scroll !important;
          max-width: unset !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;
        }

        /*LingQ 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;
        }
        `;
    }

    // Generate Video mode CSS
    function generateVideoCSS() {
        return `
        :root {
            --width_big: calc(100vw - 424px - 10px);
            --height_big: 400px;
            --video_margin: 0 0 10px 5px !important;
        }

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

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

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

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

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

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

            --grid-layout: 1fr 80px;
            --video_margin: 0 10px 20px 10px !important;
            --article_height: calc(var(--app-height) - 265px);
        }

        .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 / -1 / 1 !important;
        }

        .widget-area {
            grid-area: 1 / 2 / -1 / 2 !important;
        }
        .main-footer {
            grid-area: 2 / 1 / 2 / 1 !important;
            margin: 10px 0;
        }

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

    // Generate Audio mode CSS
    function generateAudioCSS() {
        return `
        :root {
            --height_big: 80px;
        }

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

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

    // Generate Off mode CSS
    function generateOffModeCSS(colorSettings) {
        return `
        :root {
            --width_small: 440px;
            --height_small: 260px;
            --right_pos: 0.5%;
            --bottom_pos: 5.5%;
        }

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

    // Keyboard shortcuts and other functionality
    function setupKeyboardShortcuts() {
        document.addEventListener("keydown", function(event) {
            const targetElement = event.target;
            const isTextInput = targetElement.type === "text" || targetElement.type === "textarea";
            if (isTextInput) return;

            const shortcuts = {
                'q': () => clickElement(".modal-section > div > button:nth-child(2)"), // video full screen toggle
                '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
                '`': () => 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
                't': () => clickElement(".dictionary-resources > a:nth-last-child(1)"), // Open Translator
                'c': () => copySelectedText() // Copy selected text
            };

            if (shortcuts[event.key]) {
                event.preventDefault();
                event.stopPropagation();
                shortcuts[event.key]();
            }
        }, true);
    }

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

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

    // Helper function to copy selected text
    function copySelectedText() {
        const selected_text = document.querySelector(".reference-word");
        if (selected_text) {
            navigator.clipboard.writeText(selected_text.textContent);
        }
    }

    // Custom embedded player
    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);
                }
            });
        }

        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"]');
                        }
                    });
                }
            }
        });

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

    // Scroll customization
    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);
    }

    // Focus on playing sentence
    function setupSentenceFocus() {
        function focusPlayingSentence() {
            const playingSentence = document.querySelector(".sentence.is-playing");
            if (playingSentence) {
                playingSentence.scrollIntoView({
                    behavior: "smooth",
                    block: "center"
                });
            }
        }

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

    // Initialize everything
    function init() {
        createUI();
        applyStyles(settings.styleType, settings.colorMode);
        setupKeyboardShortcuts();
        setupYoutubePlayerCustomization();
        setupScrollCustomization();
        setupSentenceFocus();
    }

    // Start the script
    init();
})();