Universal Dark Mode

An incredibly crappy implementation of universal dark mode

You will need to install an extension such as Tampermonkey, Greasemonkey or Violentmonkey to install this script.

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

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

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

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

You will need to install a user script manager extension to install this script.

(I already have a user script manager, let me install it!)

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install an extension such as Stylus to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

You will need to install a user style manager extension to install this style.

(I already have a user style manager, let me install it!)

// ==UserScript==
// @name         Universal Dark Mode
// @namespace    https://github.com/colorBanded
// @version      1.0
// @description  An incredibly crappy implementation of universal dark mode
// @author       Sam Padilla
// @match        *://*/*
// @grant        none
// @run-at       document-start
// @license      MIT
// ==/UserScript==

(function() {
    'use strict';
    
    // Configuration
    const config = {
        // preserve
        preserveColors: [
            'red', 'green', 'blue', 'yellow', 'orange', 'purple', 'pink',
            '#ff0000', '#00ff00', '#0000ff', '#ffff00', '#ffa500', '#800080', '#ffc0cb'
        ],
        
        // exclude from dark mode
        excludeSelectors: [
            'img', 'video', 'canvas', 'svg', 'iframe',
            '.preserve-colors', '[data-preserve-colors]'
        ],
        
        // Background color
        backgroundColor: '#000000',
        
        // Default text color
        textColor: '#ffffff',
        
        // Border color for elements
        borderColor: '#333333'
    };
    
    // Utility functions
    function isColorPreserved(color) {
        if (!color || color === 'inherit' || color === 'transparent') return false;
        
        // Convert rgb/rgba to hex for comparison
        const rgbMatch = color.match(/rgb\((\d+),\s*(\d+),\s*(\d+)\)/);
        if (rgbMatch) {
            const hex = '#' + [rgbMatch[1], rgbMatch[2], rgbMatch[3]]
                .map(x => parseInt(x).toString(16).padStart(2, '0')).join('');
            color = hex;
        }
        
        return config.preserveColors.some(preservedColor => 
            color.toLowerCase().includes(preservedColor.toLowerCase())
        );
    }
    
    function shouldExcludeElement(element) {
        return config.excludeSelectors.some(selector => {
            try {
                return element.matches && element.matches(selector);
            } catch (e) {
                return false;
            }
        });
    }
    
    function getComputedStyleProperty(element, property) {
        try {
            return window.getComputedStyle(element).getPropertyValue(property);
        } catch (e) {
            return '';
        }
    }
    
    // Main dark mode application function
    function applyDarkMode(element = document.documentElement) {
        // Apply to the element itself
        if (element.nodeType === Node.ELEMENT_NODE && !shouldExcludeElement(element)) {
            const computedStyle = window.getComputedStyle(element);
            
            // Handle background
            const bgColor = computedStyle.backgroundColor;
            if (bgColor && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent') {
                if (!isColorPreserved(bgColor)) {
                    element.style.backgroundColor = config.backgroundColor;
                }
            }
            
            // Handle text color
            const textColor = computedStyle.color;
            if (textColor && !isColorPreserved(textColor)) {
                element.style.color = config.textColor;
            }
            
            // borders restaurant and cafe
            ['border-color', 'border-top-color', 'border-right-color', 
             'border-bottom-color', 'border-left-color'].forEach(prop => {
                const borderColor = computedStyle.getPropertyValue(prop);
                if (borderColor && borderColor !== 'rgba(0, 0, 0, 0)' && 
                    borderColor !== 'transparent' && !isColorPreserved(borderColor)) {
                    element.style.setProperty(prop, config.borderColor);
                }
            });
        }
        
        // Recursively apply to children
        // "how to kill a child (process)"
        if (element.children) {
            Array.from(element.children).forEach(child => {
                applyDarkMode(child);
            });
        }
    }
    
    // not mysql injection (CSS Injection)
    function injectGlobalCSS() {
        const style = document.createElement('style');
        style.id = 'dark-mode-userscript';
        style.textContent = `
            /* Global dark mode styles */
            html, body {
                background-color: ${config.backgroundColor} !important;
                color: ${config.textColor} !important;
            }
            
            /* Common elements */
            div, span, p, h1, h2, h3, h4, h5, h6, li, td, th, 
            article, section, header, footer, nav, aside {
                background-color: ${config.backgroundColor} !important;
                color: ${config.textColor} !important;
                border-color: ${config.borderColor} !important;
            }
            
            /* Form elements */
            input, textarea, select, button {
                background-color: #222222 !important;
                color: ${config.textColor} !important;
                border-color: ${config.borderColor} !important;
            }
            
            /* Links */
            a {
                color: #66b3ff !important;
            }
            
            a:visited {
                color: #cc99ff !important;
            }
            
            /* Preserve specific colors */
            ${config.preserveColors.map(color => `
                [style*="color: ${color}"],
                [style*="color:${color}"],
                [style*="background-color: ${color}"],
                [style*="background-color:${color}"] {
                    color: ${color} !important;
                }
            `).join('')}
            
            /* Exclude preserved elements */
            ${config.excludeSelectors.join(', ')} {
                filter: none !important;
            }
        `;
        
        // Insert at the beginning of head to allow overrides
        if (document.head) {
            document.head.insertBefore(style, document.head.firstChild);
        } else {
            document.documentElement.appendChild(style);
        }
    }
    
    // Observer for dynamically added content
    function setupMutationObserver() {
        const observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                mutation.addedNodes.forEach((node) => {
                    if (node.nodeType === Node.ELEMENT_NODE) {
                        applyDarkMode(node);
                    }
                });
            });
        });
        
        observer.observe(document.body || document.documentElement, {
            childList: true,
            subtree: true
        });
        
        return observer;
    }
    
    // Initialize dark mode :yippee:
    function initDarkMode() {
        // Inject global CSS immediately
        injectGlobalCSS();
        
        // Apply dark mode to existing elements
        if (document.readyState === 'loading') {
            document.addEventListener('DOMContentLoaded', () => {
                applyDarkMode();
                setupMutationObserver();
            });
        } else {
        applyDarkMode();
        setupMutationObserver();
        }
        
        // Handle dynamically loaded content
        window.addEventListener('load', () => {
            setTimeout(() => applyDarkMode(), 1000);
        });
    }
    
    // Public API for customization
    window.DarkModeUserscript = {
        config: config,
        applyDarkMode: applyDarkMode,
        addPreservedColor: (color) => {
            config.preserveColors.push(color);
        },
        removePreservedColor: (color) => {
            const index = config.preserveColors.indexOf(color);
            if (index > -1) config.preserveColors.splice(index, 1);
        },
        addExcludeSelector: (selector) => {
            config.excludeSelectors.push(selector);
        },
        refresh: () => {
            document.getElementById('dark-mode-userscript')?.remove();
            initDarkMode();
        }
    };
    
    // Start the script
    initDarkMode();
    
    // Console info
    console.log('Velkommen to the Dark Side. Access customization via window.DarkModeUserscript');
    
})();