SubDL Enhanced - Dark Theme + Image Previews

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

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Violentmonkey 暴力猴,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Userscripts ,才能安装此脚本。

您需要先安装一款用户脚本管理器扩展,例如 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();
})();