Greasy Fork 还支持 简体中文。

Efficient & Refined Dark Mode

A smarter dark mode script that prevents conflicts with native dark themes and offers user-friendly controls (toggle with Ctrl+D).

// ==UserScript==
// @name         Efficient & Refined Dark Mode
// @namespace    http://tampermonkey.net/
// @version      20250827.1
// @description  A smarter dark mode script that prevents conflicts with native dark themes and offers user-friendly controls (toggle with Ctrl+D).
// @author       Lancelotly (Refined by Gemini)
// @match        *://*/*
// @run-at       document-start
// @grant        GM_addStyle
// @grant        GM_setValue
// @grant        GM_getValue
// @grant        GM_registerMenuCommand
// @icon         data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 24 24"><path d="M12 2C6.477 2 2 6.477 2 12s4.477 10 10 10 10-4.477 10-10S17.523 2 12 2zm-1.5 16.5A7.5 7.5 0 0 1 12 4.5V12a7.5 7.5 0 0 1 7.5 7.5H12a7.5 7.5 0 0 1-1.5-3z" fill="%23ffd900" /></svg>
// ==/UserScript==

(function() {
    'use strict';

    // --- Configuration & State ---

    const STORAGE_KEYS = {
        THEME_STATE: 'darkModeThemeState', // User's preference: 'auto', 'dark', 'light'
        DISABLED_SITES: 'darkModeDisabledSites'
    };

    const state = {
        styleElement: null,
        preemptiveCurtain: null,
        isDarkApplied: false,
        userThemePref: GM_getValue(STORAGE_KEYS.THEME_STATE, 'auto'),
        disabledSites: JSON.parse(GM_getValue(STORAGE_KEYS.DISABLED_SITES, '[]'))
    };

    // --- Core Functions ---

    /**
     * Checks if the user's OS/browser preference is set to dark mode.
     */
    function userPrefersDark() {
        return window.matchMedia && window.matchMedia('(prefers-color-scheme: dark)').matches;
    }

    /**
     * A more robust check to determine if the website already has a dark theme.
     */
    function isSiteAlreadyDark() {
        return new Promise(resolve => {
            const performCheck = () => {
                const htmlElement = document.documentElement;
                const bodyElement = document.body;

                // 1. Check for explicit color-scheme meta tag or CSS property
                const colorSchemeMeta = document.querySelector('meta[name="color-scheme"]');
                if (colorSchemeMeta && colorSchemeMeta.content.includes('dark')) { resolve(true); return; }
                if (getComputedStyle(htmlElement).getPropertyValue('color-scheme').includes('dark')) { resolve(true); return; }

                // 2. Check for common dark theme attributes and classes
                const darkAttributes = ['dark', 'darker-dark-theme', 'darker-dark-theme-deprecate'];
                for (const attr of darkAttributes) { if (htmlElement.hasAttribute(attr)) { resolve(true); return; } }
                if (htmlElement.dataset.theme === 'dark' || htmlElement.dataset.bsTheme === 'dark') { resolve(true); return; }
                if (bodyElement.dataset.cmColorScheme === 'dark' || bodyElement.dataset.cmDarkLaunched === 'true') { resolve(true); return; }
                for (const className of [...htmlElement.classList, ...bodyElement.classList]) {
                    if (className.toLowerCase().includes('dark')) {
                        resolve(true);
                        return;
                    }
                }
                const darkClasses = ['dark', 'dark-mode', 'night', 'night-mode'];
                for (const cls of darkClasses) { if (bodyElement.classList.contains(cls) || htmlElement.classList.contains(cls)) { resolve(true); return; } }

                // 3. Heuristic: Check body background color luminosity
                const bgColor = getComputedStyle(bodyElement).backgroundColor;
                if (bgColor && bgColor !== 'rgba(0, 0, 0, 0)') {
                    const rgb = bgColor.match(/\d+/g);
                    if (rgb) {
                        const luminance = (0.2126 * parseInt(rgb[0]) + 0.7152 * parseInt(rgb[1]) + 0.0722 * parseInt(rgb[2]));
                        if (luminance < 100) {
                            resolve(true);
                            return;
                        }
                    }
                }

                resolve(false);
            };

            if (document.body) {
                performCheck();
            } else {
                new MutationObserver((_, observer) => {
                    if (document.body) {
                        observer.disconnect();
                        performCheck();
                    }
                }).observe(document.documentElement, { childList: true });
            }
        });
    }

    /**
     * Applies the full, filter-based dark mode styles.
     */
    function applyDarkStyles() {
        if (state.isDarkApplied) return;

        const darkCss = `
            :root { --dark-mode-link-color: #6cb6ff; }
            html {
                background-color: #121212;
            }
            body {
                filter: invert(1) hue-rotate(180deg);
                transition: filter 0.2s ease-in-out;
            }
            img, video, iframe, canvas, svg, .icon, [style*="background-image"], .media, .thumb, .avatar, media, path {
                filter: invert(1) hue-rotate(180deg);
            }
            a { color: var(--dark-mode-link-color) !important; }
            button, input[type="button"], input[type="submit"] {
                background-color: #333 !important;
                color: #eee !important;
            }
        `;

        state.styleElement = GM_addStyle(darkCss);
        state.isDarkApplied = true;
        console.log('Efficient Dark Mode: Full styles applied.');
    }

    /**
     * Removes the full dark mode styles.
     */
    function removeDarkStyles() {
        if (!state.isDarkApplied || !state.styleElement) return;
        state.styleElement.remove();
        state.styleElement = null;
        state.isDarkApplied = false;
        console.log('Efficient Dark Mode: Full styles removed.');
    }

    /**
     * Creates a "black curtain" overlay to prevent the initial "white flash".
     */
    function applyPreemptiveCurtain() {
        if (state.preemptiveCurtain) return;
        const curtain = document.createElement('div');
        curtain.id = 'dark-mode-preemptive-curtain';
        curtain.style.cssText = `
            position: fixed;
            top: 0;
            left: 0;
            width: 100vw;
            height: 100vh;
            background-color: rgb(18 18 18 / 95%);
            z-index: 2147483647;
            pointer-events: none;
        `;
        document.documentElement.appendChild(curtain);
        state.preemptiveCurtain = curtain;
    }

    /**
     * Removes the pre-emptive curtain.
     */
    function removePreemptiveCurtain() {
        if (!state.preemptiveCurtain) return;
        state.preemptiveCurtain.remove();
        state.preemptiveCurtain = null;
    }

    /**
     * Toggles the theme on and off for the current session.
     */
    function toggleTheme() {
        if (state.isDarkApplied) {
            removeDarkStyles();
        } else {
            applyDarkStyles();
        }
    }

    /**
     * Registers menu commands for the user script manager.
     */
    function registerMenuCommands() {
        const siteStatus = state.disabledSites.includes(window.location.hostname) ? 'Enable' : 'Disable';
        GM_registerMenuCommand(`${siteStatus} Dark Mode for this site`, () => {
            const hostname = window.location.hostname;
            const index = state.disabledSites.indexOf(hostname);
            if (index > -1) {
                state.disabledSites.splice(index, 1);
            } else {
                state.disabledSites.push(hostname);
            }
            GM_setValue(STORAGE_KEYS.DISABLED_SITES, JSON.stringify(state.disabledSites));
            alert(`Dark mode for ${hostname} has been ${index > -1 ? 'enabled' : 'disabled'}. Please reload the page.`);
        });

        GM_registerMenuCommand(`Set Theme Preference (Current: ${state.userThemePref})`, () => {
            const modes = ['auto', 'dark', 'light'];
            let nextIndex = (modes.indexOf(state.userThemePref) + 1) % modes.length;
            state.userThemePref = modes[nextIndex];
            GM_setValue(STORAGE_KEYS.THEME_STATE, state.userThemePref);
            alert(`Dark mode preference set to "${state.userThemePref}". Please reload the page.`);
        });
    }

    /**
     * The main function to initialize the script logic.
     */
    function main() {
        const shouldRun = (state.userThemePref === 'dark' || (state.userThemePref === 'auto' && userPrefersDark()));
        const isSiteDisabled = state.disabledSites.includes(window.location.hostname);

        if (shouldRun && !isSiteDisabled) {
            applyPreemptiveCurtain();

            let hasChecked = false;
            const verifyAndApplyTheme = async () => {
                if (hasChecked) return;
                hasChecked = true;

                const siteIsNativelyDark = await isSiteAlreadyDark();

                if (siteIsNativelyDark) {
                    removePreemptiveCurtain();
                    console.log("Efficient Dark Mode: Native dark theme detected. No action needed.");
                } else {
                    if (document.body && document.body.style.filter === 'none') {
                        document.body.style.filter = '';
                    }
                    applyDarkStyles();
                    setTimeout(() => {
                        removePreemptiveCurtain();
                    }, 300);
                }
            };

            window.addEventListener('load', verifyAndApplyTheme);
            setTimeout(verifyAndApplyTheme, 3000);
        }
    }

    // --- Event Listeners and Initialization ---
    document.addEventListener('keydown', (e) => {
        if (e.ctrlKey && e.key.toLowerCase() === 'd') {
            e.preventDefault();
            toggleTheme();
        }
    });

    main();
    registerMenuCommands();

})();