Universal Dark Mode

An incredibly crappy implementation of universal dark mode

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey, Greasemonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Violentmonkey.

Voor het installeren van scripts heb je een extensie nodig, zoals Tampermonkey of Userscripts.

Voor het installeren van scripts heb je een extensie nodig, zoals {tampermonkey_link:Tampermonkey}.

Voor het installeren van scripts heb je een gebruikersscriptbeheerder nodig.

(Ik heb al een user script manager, laat me het downloaden!)

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een extensie nodig, zoals {stylus_link:Stylus}.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

Voor het installeren van gebruikersstijlen heb je een gebruikersstijlbeheerder nodig.

(Ik heb al een beheerder - laat me doorgaan met de installatie!)

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