Universal Dark Mode

An incredibly crappy implementation of universal dark mode

// ==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');
    
})();