SubDL Enhanced - Dark Theme + Image Previews

Combined script: Forces dark theme, displays image previews on hover and beside titles with configuration options

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

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         SubDL Enhanced - Dark Theme + Image Previews
// @namespace    http://tampermonkey.net/
// @version      2.0
// @description  Combined script: Forces dark theme, displays image previews on hover and beside titles with configuration options
// @author       dr.bobo0
// @license      MIT
// @match        https://subdl.com/*
// @match        https://*.subdl.com/*
// @icon        data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAB8AAAAgCAMAAADdXFNzAAAAXVBMVEVHcEz/7ir/7ir/7ir/7ir/7ir/7ir/7in/7ir/7ir/7ir/7ir/7Sr/7ir/7ir/7ir/9Sn/8iopKjMxMTMdIDMKEDSDfDBkXzH/+inBtS3bzSzSxC1GRDLs3SuZkC8BXe1rAAAAD3RSTlMAmpG5qyvRFvBGY9wGycP/EwDKAAABG0lEQVQokX2Ti5KDIAxFUSmgtg1P6/v/P3NFB0y03TvjjPFAIjeEsSzZ8FaBankj2V21gFOivtDnG6jeT4wfcNcD5f6CAer/duMMPzDAgXmOtYnSJsWcZl982DXmBbFClXd31lm7PR+dPlW4euRd12G+/UGDue11PwfEG1Zg7hdjpumsDwUTmLvZwHaC84SCqfxuxrDV7okDCpujh+D8ShcQ8/rVuzBowhWO9Me6sKD6ir1IOpidcyh8sZJgs3jiT4l7r7UGM3nMa+qv1aafqb9neyMfpxHz2GCJuPPeO3fy/aKnDpgh+KiwJl4cFyhZYKZhV8IqDU4+3SGS/fcFRgMg1Y0qOoTFBRfX+ZQcUf5tglldVqIVVYmH9w/WzDC9Fj6LqQAAAABJRU5ErkJggg==
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @run-at       document-start
// ==/UserScript==

(function() {
    'use strict';

    // ===== DARK THEME SECTION =====
    // Force dark theme immediately
    function initDarkTheme() {
        try {
            localStorage.setItem('theme', 'dark');
        } catch (e) {
            console.error("SubDL Enhanced: Failed to set localStorage", e);
        }

        const applyDarkStyles = () => {
            if (document.documentElement) {
                document.documentElement.classList.add('dark');
                document.documentElement.classList.remove('light');
                document.documentElement.style.colorScheme = 'dark';
            }
        };

        applyDarkStyles();

        if (!document.documentElement || !document.documentElement.classList.contains('dark')) {
            const observer = new MutationObserver((mutationsList, obs) => {
                if (document.documentElement) {
                    applyDarkStyles();
                    obs.disconnect();
                }
            });
            observer.observe(document, { childList: true, subtree: true });
        }
    }

    // Initialize dark theme immediately
    initDarkTheme();

    // ===== IMAGE PREVIEW CONFIGURATION =====
    const DEFAULT_CONFIG = {
        imageWidth: 75,
        imageHeight: 112,
        isSquare: false,
        hideDownloadButton: false,
        enableHoverPreview: true,
        enableBesidePreview: true
    };

    function getSettings() {
        return {
            imageWidth: GM_getValue('imageWidth', DEFAULT_CONFIG.imageWidth),
            imageHeight: GM_getValue('imageHeight', DEFAULT_CONFIG.imageHeight),
            isSquare: GM_getValue('isSquare', DEFAULT_CONFIG.isSquare),
            hideDownloadButton: GM_getValue('hideDownloadButton', DEFAULT_CONFIG.hideDownloadButton),
            enableHoverPreview: GM_getValue('enableHoverPreview', DEFAULT_CONFIG.enableHoverPreview),
            enableBesidePreview: GM_getValue('enableBesidePreview', DEFAULT_CONFIG.enableBesidePreview)
        };
    }

    function saveSettings(settings) {
        Object.keys(settings).forEach(key => {
            GM_setValue(key, settings[key]);
        });
    }

    // ===== SHARED UTILITIES =====
    const storagePrefix = "subdl_image_cache_";
    const maxCacheAge = 7 * 24 * 60 * 60 * 1000; // 7 days

    const exclusionList = [
        '/', '/panel', '/panel/my-subtitles', '/panel/account', '/panel/api',
        '/latest', '/popular', 'https://t.me/subdl_com', '/ads', '/api-doc',
        '/panel/logout', '/login', '#', '/signup'
    ];

    function safeJSONParse(key) {
        try {
            const item = localStorage.getItem(key);
            return item ? JSON.parse(item) : null;
        } catch (e) {
            localStorage.removeItem(key);
            return null;
        }
    }

    function clearOldCache() {
        const now = Date.now();
        Object.keys(localStorage)
            .filter(key => key.startsWith(storagePrefix))
            .forEach(key => {
                const item = safeJSONParse(key);
                if (!item || now - item.timestamp > maxCacheAge) {
                    localStorage.removeItem(key);
                }
            });
    }

    function getImageConfig() {
        const settings = getSettings();
        return {
            IMAGE_STYLES: {
                width: `${settings.imageWidth}px`,
                height: `${settings.imageHeight}px`,
                objectFit: settings.isSquare ? 'cover' : 'contain',
                borderRadius: '4px',
                boxShadow: '0 2px 6px rgba(0, 0, 0, 0.15)',
                marginRight: '8px'
            },
            LINK_STYLES: {
                display: 'flex',
                alignItems: 'center',
                gap: '8px'
            }
        };
    }

    function applyStyles(element, styles) {
        Object.entries(styles).forEach(([key, value]) => {
            element.style[key] = value;
        });
    }

    function createElement(tag, styles = {}, attributes = {}) {
        const element = document.createElement(tag);
        applyStyles(element, styles);
        Object.entries(attributes).forEach(([key, value]) => {
            element[key] = value;
        });
        return element;
    }

    // ===== HOVER PREVIEW SECTION =====
    function shouldAddPreview(link) {
        const href = link.href;
        return !exclusionList.some(exclusion => href.endsWith(exclusion)) && /subdl.com/.test(href);
    }

    function createPreviewContainer() {
        const previewContainer = document.createElement("div");
        Object.assign(previewContainer.style, {
            position: "fixed",
            display: "none",
            transition: "opacity 0.1s ease-in-out",
            opacity: 0,
            width: "154px",
            height: "231px",
            overflow: "hidden",
            zIndex: 1000,
            borderRadius: "8px",
            boxShadow: "0 4px 8px rgba(0,0,0,0.2)",
            backgroundColor: "#21293b"
        });
        return previewContainer;
    }

    function showLoadingSpinner(previewContainer) {
        previewContainer.innerHTML = `
            <div style="display: flex; justify-content: center; align-items: center; width: 100%; height: 100%; background-color: #2d3748;">
                <div style="width: 40px; height: 40px; border: 4px solid #4a5568; border-top: 4px solid #718096; border-radius: 50%; animation: spin 1s linear infinite;"></div>
            </div>
            <style>
                @keyframes spin {
                    0% { transform: rotate(0deg); }
                    100% { transform: rotate(360deg); }
                }
            </style>
        `;
        previewContainer.style.display = "block";
        previewContainer.style.opacity = 1;
    }

    function fetchImageForHover(url, previewContainer) {
        const cacheKey = storagePrefix + url;
        const cachedImage = safeJSONParse(cacheKey);

        if (cachedImage && Date.now() - cachedImage.timestamp < maxCacheAge) {
            setImage(previewContainer, cachedImage.src);
            return;
        }

        fetch(url)
            .then(response => response.text())
            .then(html => {
                const doc = new DOMParser().parseFromString(html, 'text/html');
                const preview = doc.querySelector("div.select-none img");
                if (preview) {
                    const src = preview.getAttribute("src");
                    setImage(previewContainer, src);
                    try {
                        localStorage.setItem(cacheKey, JSON.stringify({ src, timestamp: Date.now() }));
                    } catch (e) {
                        clearOldCache();
                    }
                } else {
                    setError(previewContainer, "Image not found.");
                }
            })
            .catch(() => setError(previewContainer, "Failed to load image."));
    }

    function setImage(previewContainer, src) {
        previewContainer.innerHTML = `<img style="width: 100%; height: 100%; object-fit: cover;" src="${src}"/>`;
        previewContainer.style.display = "block";
        previewContainer.style.opacity = 1;
    }

    function setError(previewContainer, message) {
        previewContainer.innerHTML = `
            <div style="display: flex; justify-content: center; align-items: center; width: 100%; height: 100%; color: #fc8181; font-weight: bold; text-align: center; background-color: #2d3748;">
                ${message}
            </div>
        `;
        previewContainer.style.display = "block";
        previewContainer.style.opacity = 1;
    }

    function addMousemoveListener(previewContainer) {
        function movePreview(event) {
            previewContainer.style.top = event.clientY + 20 + "px";
            previewContainer.style.left = event.clientX + 20 + "px";

            if (event.clientX + previewContainer.offsetWidth + 20 > window.innerWidth) {
                previewContainer.style.left = window.innerWidth - previewContainer.offsetWidth - 20 + "px";
            }
            if (event.clientY + previewContainer.offsetHeight + 20 > window.innerHeight) {
                previewContainer.style.top = window.innerHeight - previewContainer.offsetHeight - 20 + "px";
            }
        }

        document.addEventListener("mousemove", movePreview);
        return () => document.removeEventListener("mousemove", movePreview);
    }

    function cleanupPreview(previewContainer, removeMousemoveListener) {
        previewContainer.style.opacity = 0;
        setTimeout(() => {
            if (previewContainer.parentNode) {
                previewContainer.remove();
            }
            removeMousemoveListener();
        }, 200);
    }

    function addHoverPreviewToLinks() {
        if (!getSettings().enableHoverPreview) return;

        const links = document.querySelectorAll('a[href*="/s/info/"]:not([data-hover-preview])');

        links.forEach(link => {
            if (shouldAddPreview(link)) {
                link.setAttribute('data-hover-preview', 'true');
                link.addEventListener("mouseover", function () {
                    const previewContainer = createPreviewContainer();
                    document.body.appendChild(previewContainer);
                    showLoadingSpinner(previewContainer);
                    fetchImageForHover(this.href, previewContainer);

                    const removeMousemoveListener = addMousemoveListener(previewContainer);

                    const handleMouseout = () => cleanupPreview(previewContainer, removeMousemoveListener);
                    const handleClick = () => cleanupPreview(previewContainer, removeMousemoveListener);

                    link.addEventListener("mouseout", handleMouseout, { once: true });
                    link.addEventListener("click", handleClick, { once: true });
                });
            }
        });
    }

    // ===== BESIDE PREVIEW SECTION =====
    async function fetchImageForBeside(url, container) {
        const fullUrl = new URL(url, window.location.origin).href;
        const cacheKey = storagePrefix + fullUrl;

        const cachedImage = safeJSONParse(cacheKey);
        if (cachedImage && Date.now() - cachedImage.timestamp < maxCacheAge) {
            displayImageBeside(cachedImage.src, container);
            return;
        }

        try {
            const response = await fetch(fullUrl);
            const html = await response.text();
            const parser = new DOMParser();
            const doc = parser.parseFromString(html, 'text/html');
            const preview = doc.querySelector("div.select-none img");

            if (preview) {
                const src = preview.getAttribute("src");
                if (src) {
                    displayImageBeside(src, container);
                    localStorage.setItem(cacheKey, JSON.stringify({
                        src: src,
                        timestamp: Date.now()
                    }));
                }
            }
        } catch (error) {
            console.error(`Failed to fetch image for ${fullUrl}:`, error);
        }
    }

    function displayImageBeside(src, container) {
        const CONFIG = getImageConfig();
        const img = document.createElement("img");
        img.src = src;
        img.alt = "Preview";
        img.setAttribute('data-subdl-preview', 'true');

        Object.entries(CONFIG.IMAGE_STYLES).forEach(([key, value]) => {
            img.style[key] = value;
        });

        img.onerror = () => {
            img.src = 'https://subdl.com/images/poster.jpeg';
        };

        container.parentElement.insertBefore(img, container);
    }

    function addBesideImagePreviews() {
        if (!getSettings().enableBesidePreview) return;

        const links = document.querySelectorAll('a[href^="/s/info/"]:not([data-beside-preview])');
        const CONFIG = getImageConfig();

        links.forEach(link => {
            link.setAttribute('data-beside-preview', 'true');

            const container = link.querySelector('h3');
            if (!container) return;

            applyStyles(link, CONFIG.LINK_STYLES);
            const parentDiv = link.closest('.flex');
            if (parentDiv) {
                applyStyles(parentDiv, {
                    display: 'flex',
                    alignItems: 'center',
                    gap: '12px'
                });
            }

            const svgIcon = link.querySelector('svg');
            if (svgIcon) {
                applyStyles(svgIcon, {
                    width: '20px',
                    height: '20px',
                    marginRight: '8px',
                    verticalAlign: 'middle'
                });
            }

            fetchImageForBeside(link.href, container);
        });
    }

    // ===== SETTINGS MODAL =====
    const COLORS = {
        background: '#1a202c',
        modalBg: '#2d3748',
        primary: '#4299e1',
        secondary: '#718096',
        accent: '#48bb78',
        textPrimary: '#e2e8f0',
        textSecondary: '#a0aec0',
        borderColor: '#4a5568'
    };

    function createSettingsModal() {
        const backdrop = createElement('div', {
            position: 'fixed',
            top: '0',
            left: '0',
            width: '100%',
            height: '100%',
            background: 'rgba(0,0,0,0.5)',
            display: 'flex',
            justifyContent: 'center',
            alignItems: 'center',
            zIndex: '10000',
            fontFamily: '-apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, Helvetica, Arial, sans-serif'
        });

        const modal = createElement('div', {
            background: COLORS.modalBg,
            padding: '30px',
            borderRadius: '16px',
            width: '500px',
            boxShadow: '0 25px 50px -12px rgba(0,0,0,0.15)',
            position: 'relative',
            maxHeight: '80vh',
            overflowY: 'auto'
        });

        const title = createElement('h2', {
            color: COLORS.primary,
            marginBottom: '25px',
            textAlign: 'center',
            fontWeight: '700',
            fontSize: '1.5rem'
        }, { textContent: 'SubDL Enhancement Settings' });
        modal.appendChild(title);

        const settings = getSettings();

        // Preview Container
        const previewContainer = createElement('div', {
            display: 'flex',
            justifyContent: 'center',
            alignItems: 'center',
            marginBottom: '25px',
            background: COLORS.background,
            padding: '25px',
            borderRadius: '12px'
        });
        modal.appendChild(previewContainer);

        const previewImg = createElement('img', {
            transition: 'all 0.3s ease',
            borderRadius: '12px',
            boxShadow: '0 10px 25px rgba(0,0,0,0.1)'
        }, { src: 'https://via.placeholder.com/150x225?text=Preview' });
        previewContainer.appendChild(previewImg);

        const settingsContainer = createElement('div', {
            display: 'flex',
            flexDirection: 'column',
            gap: '20px'
        });
        modal.appendChild(settingsContainer);

        // Preview Mode Toggles
        const previewModesContainer = createElement('div', {
            display: 'flex',
            flexDirection: 'column',
            gap: '12px'
        });

        const hoverToggleContainer = createElement('div', {
            display: 'flex',
            alignItems: 'center',
            gap: '12px'
        });

        const hoverToggle = createElement('input', {
            accentColor: COLORS.primary
        }, {
            type: 'checkbox',
            id: 'hoverPreviewToggle',
            checked: settings.enableHoverPreview
        });

        const hoverToggleLabel = createElement('label', {
            color: COLORS.textSecondary
        }, {
            htmlFor: 'hoverPreviewToggle',
            textContent: 'Enable Hover Previews'
        });

        hoverToggleContainer.appendChild(hoverToggle);
        hoverToggleContainer.appendChild(hoverToggleLabel);

        const besideToggleContainer = createElement('div', {
            display: 'flex',
            alignItems: 'center',
            gap: '12px'
        });

        const besideToggle = createElement('input', {
            accentColor: COLORS.primary
        }, {
            type: 'checkbox',
            id: 'besidePreviewToggle',
            checked: settings.enableBesidePreview
        });

        const besideToggleLabel = createElement('label', {
            color: COLORS.textSecondary
        }, {
            htmlFor: 'besidePreviewToggle',
            textContent: 'Enable Beside Title Previews'
        });

        besideToggleContainer.appendChild(besideToggle);
        besideToggleContainer.appendChild(besideToggleLabel);

        previewModesContainer.appendChild(hoverToggleContainer);
        previewModesContainer.appendChild(besideToggleContainer);
        settingsContainer.appendChild(previewModesContainer);

        // Image Width Slider
        const widthContainer = createElement('div');
        const widthLabel = createElement('label', {
            display: 'flex',
            justifyContent: 'space-between',
            color: COLORS.textPrimary,
            fontWeight: '600'
        });

        const widthLabelText = createElement('span', {}, { textContent: 'Image Width' });
        const widthValue = createElement('span', {}, { textContent: `${settings.imageWidth}px` });
        widthLabel.appendChild(widthLabelText);
        widthLabel.appendChild(widthValue);

        const widthSlider = createElement('input', {
            width: '100%',
            accentColor: COLORS.primary
        }, {
            type: 'range',
            min: '50',
            max: '200',
            value: settings.imageWidth
        });

        function updatePreview() {
            const width = widthSlider.value;
            const height = width * (squareToggle.checked ? 1 : 1.5);

            widthValue.textContent = `${width}px`;
            previewImg.style.width = `${width}px`;
            previewImg.style.height = `${height}px`;

            document.querySelectorAll('img[data-subdl-preview]').forEach(img => {
                img.style.width = `${width}px`;
                img.style.height = `${height}px`;
            });
        }

        widthSlider.oninput = updatePreview;

        widthContainer.appendChild(widthLabel);
        widthContainer.appendChild(widthSlider);
        settingsContainer.appendChild(widthContainer);

        // Square Images Toggle
        const squareToggleContainer = createElement('div', {
            display: 'flex',
            alignItems: 'center',
            gap: '12px'
        });

        const squareToggle = createElement('input', {
            accentColor: COLORS.primary
        }, {
            type: 'checkbox',
            id: 'squareImagesToggle',
            checked: settings.isSquare
        });

        const squareToggleLabel = createElement('label', {
            color: COLORS.textSecondary
        }, {
            htmlFor: 'squareImagesToggle',
            textContent: 'Square Images'
        });

        squareToggle.oninput = () => {
            const width = widthSlider.value;
            const height = width * (squareToggle.checked ? 1 : 1.5);

            previewImg.style.height = `${height}px`;

            document.querySelectorAll('img[data-subdl-preview]').forEach(img => {
                img.style.height = `${height}px`;
                img.style.objectFit = squareToggle.checked ? 'cover' : 'contain';
            });
        };

        squareToggleContainer.appendChild(squareToggle);
        squareToggleContainer.appendChild(squareToggleLabel);
        settingsContainer.appendChild(squareToggleContainer);

        // Download Button Toggle
        const downloadToggleContainer = createElement('div', {
            display: 'flex',
            alignItems: 'center',
            gap: '12px'
        });

        const downloadToggle = createElement('input', {
            accentColor: COLORS.primary
        }, {
            type: 'checkbox',
            id: 'hideDownloadToggle',
            checked: settings.hideDownloadButton
        });

        const downloadToggleLabel = createElement('label', {
            color: COLORS.textSecondary
        }, {
            htmlFor: 'hideDownloadToggle',
            textContent: 'Hide Download Buttons'
        });

        downloadToggle.oninput = () => {
            const downloadButtons = document.querySelectorAll('a[href^="https://dl.subdl.com/subtitle/"]');
            downloadButtons.forEach(button => {
                button.style.display = downloadToggle.checked ? 'none' : '';
            });
        };

        downloadToggleContainer.appendChild(downloadToggle);
        downloadToggleContainer.appendChild(downloadToggleLabel);
        settingsContainer.appendChild(downloadToggleContainer);

        // Buttons
        const buttonContainer = createElement('div', {
            display: 'flex',
            justifyContent: 'space-between',
            marginTop: '25px',
            gap: '15px'
        });

        const saveButton = createElement('button', {
            backgroundColor: COLORS.accent,
            color: 'white',
            border: 'none',
            padding: '12px 20px',
            borderRadius: '8px',
            cursor: 'pointer',
            flexGrow: '1',
            fontWeight: '600'
        }, { textContent: 'Save Settings' });

        saveButton.onclick = () => {
            const newSettings = {
                imageWidth: parseInt(widthSlider.value),
                imageHeight: parseInt(widthSlider.value) * (squareToggle.checked ? 1 : 1.5),
                isSquare: squareToggle.checked,
                hideDownloadButton: downloadToggle.checked,
                enableHoverPreview: hoverToggle.checked,
                enableBesidePreview: besideToggle.checked
            };
            saveSettings(newSettings);
            document.body.removeChild(backdrop);

            // Apply settings immediately
            applyDownloadButtonVisibility();
            refreshPreviews();
        };

        const cancelButton = createElement('button', {
            backgroundColor: COLORS.background,
            color: COLORS.textSecondary,
            border: `2px solid ${COLORS.background}`,
            padding: '10px 20px',
            borderRadius: '8px',
            cursor: 'pointer',
            flexGrow: '1',
            fontWeight: '600'
        }, { textContent: 'Cancel' });

        cancelButton.onclick = () => {
            document.body.removeChild(backdrop);
        };

        buttonContainer.appendChild(saveButton);
        buttonContainer.appendChild(cancelButton);
        settingsContainer.appendChild(buttonContainer);

        backdrop.appendChild(modal);
        document.body.appendChild(backdrop);

        updatePreview();
    }

    // ===== UTILITY FUNCTIONS =====
    function applyDownloadButtonVisibility() {
        const settings = getSettings();
        const downloadButtons = document.querySelectorAll('a[href^="https://dl.subdl.com/subtitle/"]');
        downloadButtons.forEach(button => {
            button.style.display = settings.hideDownloadButton ? 'none' : '';
        });
    }

    function refreshPreviews() {
        // Remove existing previews
        document.querySelectorAll('img[data-subdl-preview]').forEach(img => img.remove());
        document.querySelectorAll('a[data-beside-preview]').forEach(link => link.removeAttribute('data-beside-preview'));
        document.querySelectorAll('a[data-hover-preview]').forEach(link => link.removeAttribute('data-hover-preview'));

        // Re-add previews
        addHoverPreviewToLinks();
        addBesideImagePreviews();
    }

    function throttle(func, limit) {
        let lastFunc;
        let lastRan;
        return function() {
            const context = this;
            const args = arguments;
            if (!lastRan) {
                func.apply(context, args);
                lastRan = Date.now();
            } else {
                clearTimeout(lastFunc);
                lastFunc = setTimeout(function() {
                    if (Date.now() - lastRan >= limit) {
                        func.apply(context, args);
                        lastRan = Date.now();
                    }
                }, limit - (Date.now() - lastRan));
            }
        };
    }

    // ===== INITIALIZATION =====
    function init() {
        // Wait for DOM to be ready
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', initFeatures);
        } else {
            initFeatures();
        }
    }

    function initFeatures() {
        clearOldCache();
        addHoverPreviewToLinks();
        addBesideImagePreviews();
        applyDownloadButtonVisibility();

        const throttledUpdate = throttle(() => {
            addHoverPreviewToLinks();
            addBesideImagePreviews();
            applyDownloadButtonVisibility();
        }, 500);

        const observer = new MutationObserver(() => {
            throttledUpdate();
        });

        observer.observe(document.body, {
            childList: true,
            subtree: true
        });
    }

    // Register menu command
    GM_registerMenuCommand('Configure SubDL Enhancements', createSettingsModal);

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