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).

当前为 2025-08-24 提交的版本,查看 最新版本

您需要先安装一个扩展,例如 篡改猴Greasemonkey暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴暴力猴,之后才能安装此脚本。

您需要先安装一个扩展,例如 篡改猴Userscripts ,之后才能安装此脚本。

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

您需要先安装用户脚本管理器扩展后才能安装此脚本。

(我已经安装了用户脚本管理器,让我安装!)

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展,比如 Stylus,才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

您需要先安装一款用户样式管理器扩展后才能安装此样式。

(我已经安装了用户样式管理器,让我安装!)

// ==UserScript==
// @name         Efficient & Refined Dark Mode
// @namespace    http://tampermonkey.net/
// @version      20250824.14
// @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 with 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,
        preemptiveStyleElement: 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.
     * @returns {Promise<boolean>} A promise that resolves to true if the site is dark, false otherwise.
     */
    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 on <html> and <body>
                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;
                }
                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)') { // Ignore transparent backgrounds
                    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 {
                // If body doesn't exist, wait for it to be added to the DOM.
                new MutationObserver((mutations, 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 {
                filter: invert(1) hue-rotate(180deg);
                background-color: #121212;
                transition: filter 0.2s ease-in-out;
            }
            img, video, iframe, canvas, svg, .icon, [style*="background-image"] {
                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.');
    }

    /**
     * Applies a simple dark background to prevent the initial "white flash".
     */
    function applyPreemptiveBg() {
        if (state.preemptiveStyleElement) return;
        const preemptiveCss = `
            html, body {
                background-color: #121212 !important;
                color: #eee !important;
            }
        `;
        state.preemptiveStyleElement = GM_addStyle(preemptiveCss);
    }

    /**
     * Removes the pre-emptive dark background.
     */
    function removePreemptiveBg() {
        if (!state.preemptiveStyleElement) return;
        state.preemptiveStyleElement.remove();
        state.preemptiveStyleElement = 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) {
            // Stage 1: Apply a simple dark background immediately to prevent any flash.
            applyPreemptiveBg();

            // Stage 2: Wait for the entire page to load, then check the final theme.
            let hasChecked = false;
            const verifyAndApplyTheme = async () => {
                if (hasChecked) return;
                hasChecked = true;

                removePreemptiveBg();

                // Use a small timeout to allow the browser to render the native theme
                // after we remove our preemptive style, preventing a race condition.
                setTimeout(async () => {
                    if (await isSiteAlreadyDark()) {
                        console.log("Efficient Dark Mode: Native dark theme detected. No action needed.");
                    } else {
                        applyDarkStyles();
                    }
                }, 100);
            };

            window.addEventListener('load', verifyAndApplyTheme);
            document.addEventListener('DOMContentLoaded', () => {
                // This acts as a fallback if the load event is unusually slow.
                setTimeout(verifyAndApplyTheme, 5000);
            });
        }
    }

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

    main();
    registerMenuCommands();

})();