WebMD Retro Theme

Customizable theme for Web MiniDisc Pro, configuration can be found at the bottom of the settings dialog

// ==UserScript==
// @name         WebMD Retro Theme
// @namespace    http://tampermonkey.net/
// @version      2024-11-15
// @license MIT
// @description  Customizable theme for Web MiniDisc Pro, configuration can be found at the bottom of the settings dialog
// @author       Pablo Maciá
// @match        https://web.minidisc.wiki/*
// @grant        none
// ==/UserScript==

(function() {
    'use strict';

    const MIN_BRIGHTNESS = 2;
    const MAX_BRIGHTNESS = 10;

    const themes = {
        white:   ['#F0F0F0', '#AAAAAA', '#555555', '#000000'],
        gray:    ['#7F7F7F', '#555555', '#2A2A2A', '#000000'],
        red:     ['#FF0000', '#AA0000', '#550000', '#000000'],
        orange:  ['#FF7F00', '#AA5500', '#552A00', '#000000'],
        yellow:  ['#FFFF00', '#AAAA00', '#555500', '#000000'],
        leaf:    ['#7FFF00', '#55AA00', '#2A5500', '#000000'],
        green:   ['#00FF00', '#00AA00', '#005500', '#000000'],
        aqua:    ['#00FF7F', '#00AA55', '#00552A', '#000000'],
        cyan:    ['#00FFFF', '#00AAAA', '#005555', '#000000'],
        azure:   ['#007FFF', '#0055AA', '#002A55', '#000000'],
        blue:    ['#0000FF', '#0000AA', '#000055', '#000000'],
        purple:  ['#7F00FF', '#5500AA', '#2A0055', '#000000'],
        pink:    ['#FF007F', '#AA0055', '#55002A', '#000000'],
        magenta: ['#FF00FF', '#AA00AA', '#550055', '#000000'],
        DMG:     ['#8cad28', '#6c9421', '#426b29', '#214231'],
    };

    const filters = {
        none: 'none',
        crt1: 'crt1',
        crt2: 'crt2',
        lcd: 'lcd'
    };

    const settings = {
        enabled: true,
        darkMode: true,
        theme: 'white',
        filter: filters.none,
        brightness: 10,
    };

    let themeLoaded = false;
    let activeTheme;
    let themeStyle;
    let pageStyle;
    let screenFilter;
    let screenBrightnessFilter;

    function readSetting(key, defaultValue) {
        return localStorage[key] === undefined ? defaultValue : localStorage[key];
    }

    function loadSettings() {
        settings.enabled = readSetting('rt_enabled', settings.enabled);
        settings.darkMode = readSetting('rt_dark_mode', settings.darkMode);
        settings.theme = readSetting('rt_theme', settings.theme);
        settings.filter = readSetting('rt_filter', settings.filter);
        settings.brightness = readSetting('rt_brightness', settings.brightness);
        saveSettings();
    }

    function saveSettings() {
        localStorage.rt_enabled = settings.enabled;
        localStorage.rt_dark_mode = settings.darkMode;
        localStorage.rt_theme = settings.theme;
        localStorage.rt_filter = settings.filter;
        localStorage.rt_brightness = settings.brightness;
    }

    function setEnabledSetting(value) {
        settings.enabled = value;
        saveSettings();
    }

    function setDarkModeSetting(value) {
        settings.darkMode = value;
        saveSettings();
    }

    function setThemeSetting(value) {
        settings.theme = value;
        saveSettings();
    }

    function setFilterSetting(value) {
        settings.filter = value;
        saveSettings();
    }

    function setBrightnessSetting(value) {
        settings.brightness = value;
        saveSettings();
        setBrightness(settings.brightness);
    }

    function init() {
        loadSettings();
        initEvents();
        if (settings.enabled === 'true') {
            insertStyles();
            insertScreenModifiers();
            themeLoaded = true;
        }
    }

    function initEvents() {
        initOnSettingsDialogOpenEvent();
    }

    function initOnSettingsDialogOpenEvent() {
        const body = document.querySelector('body');
        const config = { childList: true };
        const callback = (mutationsList, observer) => {
            let settingsDialog = null;

            for(const mutation of mutationsList) {
                if (mutation.type === 'childList') {
                    for (let i = 0; i < mutation.addedNodes.length; i++) {
                        const node = mutation.addedNodes[i];
                        if (node.classList.contains('MuiDialog-root') && node.querySelector('h2').innerText == 'Settings') {
                            settingsDialog = node;
                            break;
                        }
                    }
                }
            }

            if (settingsDialog) {
                insertThemeSettingsToDialog(settingsDialog);
            }
        };
        const observer = new MutationObserver(callback);
        observer.observe(body, config);
    }

    function insertThemeSettingsToDialog(dialog) {
        const content = dialog.querySelector('.MuiDialogContent-root');
        console.log(content);

        const sectionLabel = document.createElement('p');
        sectionLabel.classList.add('MuiTypography-root', 'MuiDialogContentText-root', 'MuiTypography-body1', 'css-1rddy68-header');
        sectionLabel.innerText = 'WebMD Retro Theme (Reload to apply changes)';
        content.appendChild(sectionLabel);

        const enabledLabel = document.createElement('label');
        enabledLabel.classList.add('MuiFormControlLabel-root', 'MuiFormControlLabel-labelPlacementStart', 'css-1q5bp8s-propertyBox');
        content.appendChild(enabledLabel);

        const enabledCb = document.createElement('input');
        enabledCb.id = 'rt_enabled';
        enabledCb.checked = settings.enabled === 'true';
        enabledCb.type = 'checkbox';
        enabledCb.addEventListener('change', () => { setEnabledSetting(enabledCb.checked); });
        enabledLabel.appendChild(enabledCb);

        const enabledLabelText = document.createElement('span');
        enabledLabelText.classList.add('MuiTypography-root', 'MuiTypography-body1', 'MuiFormControlLabel-label', 'css-1fmsrhg-spread');
        enabledLabelText.innerText = 'Enable retro theme';
        enabledLabel.appendChild(enabledLabelText);

        const darkModeLabel = document.createElement('label');
        darkModeLabel.classList.add('MuiFormControlLabel-root', 'MuiFormControlLabel-labelPlacementStart', 'css-1q5bp8s-propertyBox');
        content.appendChild(darkModeLabel);

        const darkModeCb = document.createElement('input');
        darkModeCb.id = 'rt_dark_mode';
        darkModeCb.type = 'checkbox';
        darkModeCb.checked = settings.darkMode === 'true';
        darkModeCb.addEventListener('change', () => { setDarkModeSetting(darkModeCb.checked); });
        darkModeLabel.appendChild(darkModeCb);

        const darkModeLabelText = document.createElement('span');
        darkModeLabelText.classList.add('MuiTypography-root', 'MuiTypography-body1', 'MuiFormControlLabel-label', 'css-1fmsrhg-spread');
        darkModeLabelText.innerText = 'Dark mode';
        darkModeLabel.appendChild(darkModeLabelText);

        const themeLabel = document.createElement('label');
        themeLabel.classList.add('MuiFormControlLabel-root', 'MuiFormControlLabel-labelPlacementStart', 'css-1q5bp8s-propertyBox');
        content.appendChild(themeLabel);

        const themeSelect = document.createElement('select');
        themeSelect.id = 'rt_theme';
        themeSelect.addEventListener('change', () => { setThemeSetting(themeSelect.value); });
        themeLabel.appendChild(themeSelect);

        Object.keys(themes).forEach(themeName => {
            const themeOption = document.createElement('option');
            themeOption.innerText = themeName;
            themeOption.value = themeName;
            if (themeName === settings.theme) {
                themeOption.selected = true;
            }
            themeSelect.appendChild(themeOption);
        });

        const themeLabelText = document.createElement('span');
        themeLabelText.classList.add('MuiTypography-root', 'MuiTypography-body1', 'MuiFormControlLabel-label', 'css-1fmsrhg-spread');
        themeLabelText.innerText = 'Theme';
        themeLabel.appendChild(themeLabelText);

        const filterLabel = document.createElement('label');
        filterLabel.classList.add('MuiFormControlLabel-root', 'MuiFormControlLabel-labelPlacementStart', 'css-1q5bp8s-propertyBox');
        content.appendChild(filterLabel);

        const filterSelect = document.createElement('select');
        filterSelect.id = 'rt_filter';
        filterSelect.addEventListener('change', () => { setFilterSetting(filterSelect.value); });
        filterLabel.appendChild(filterSelect);

        Object.keys(filters).forEach(filterName => {
            const filterOption = document.createElement('option');
            filterOption.innerText = filterName;
            filterOption.value = filterName;
            if (filterName === settings.filter) {
                filterOption.selected = true;
            }
            filterSelect.appendChild(filterOption);
        });

        const filterLabelText = document.createElement('span');
        filterLabelText.classList.add('MuiTypography-root', 'MuiTypography-body1', 'MuiFormControlLabel-label', 'css-1fmsrhg-spread');
        filterLabelText.innerText = 'Filter';
        filterLabel.appendChild(filterLabelText);

        const brightnessLabel = document.createElement('label');
        brightnessLabel.classList.add('MuiFormControlLabel-root', 'MuiFormControlLabel-labelPlacementStart', 'css-1q5bp8s-propertyBox');
        content.appendChild(brightnessLabel);

        const brightnessRange = document.createElement('input');
        brightnessRange.id = 'rt_brightness';
        brightnessRange.type = 'range';
        brightnessRange.min = MIN_BRIGHTNESS;
        brightnessRange.max = MAX_BRIGHTNESS;
        brightnessRange.value = settings.brightness;
        brightnessRange.addEventListener('change', () => { setBrightnessSetting(brightnessRange.value); });
        brightnessLabel.appendChild(brightnessRange);

        const brightnessLabelText = document.createElement('span');
        brightnessLabelText.classList.add('MuiTypography-root', 'MuiTypography-body1', 'MuiFormControlLabel-label', 'css-1fmsrhg-spread');
        brightnessLabelText.innerText = 'Brightness';
        brightnessLabel.appendChild(brightnessLabelText);
    }

    function getThemeColor(colorIndex) {
        return activeTheme[settings.darkMode === 'true' ? 3 - colorIndex : colorIndex];
    }

    function generateThemeStyles() {
        return ':root{--color-0: ' + getThemeColor(0) + '; --color-1: ' + getThemeColor(1) + '; --color-2: ' + getThemeColor(2) + '; --color-3: ' + getThemeColor(3) + '}';
    }

    function setActiveTheme(newTheme) {
        activeTheme = newTheme;

        if (!themeStyle) {
            themeStyle = document.createElement('style');
            document.getElementsByTagName('body')[0].appendChild(themeStyle);
        }

        themeStyle.innerText = generateThemeStyles();
    }

    function setBrightness(value) {
        if (!screenBrightnessFilter) { return; }
        value = value < MIN_BRIGHTNESS ? MIN_BRIGHTNESS : value;
        value = value > MAX_BRIGHTNESS ? MAX_BRIGHTNESS : value;
        const filterOpacity = (MAX_BRIGHTNESS - value) / 10;
        screenBrightnessFilter.style.opacity = filterOpacity;
    }

    function insertScreenModifiers() {
        screenFilter = document.createElement('div');
        screenFilter.id = 'rt-screen-filter';
        screenFilter.classList.add('rt-screen-filter', settings.filter);
        document.getElementsByTagName('body')[0].appendChild(screenFilter);

        screenBrightnessFilter = document.createElement('div');
        screenBrightnessFilter.id = 'rt-screen-brightness';
        screenBrightnessFilter.classList.add('rt-screen-filter');
        document.getElementsByTagName('body')[0].appendChild(screenBrightnessFilter);
        setBrightness(settings.brightness);
    }

    function insertStyles() {
        setActiveTheme(themes[settings.theme]);

        // GLOBALS
        let css = 'svg, h1, h2, h3, h4, p, i, b, li, .MuiTypography-root, .MuiAlert-message, .MuiTooltip-tooltip{color: var(--color-3); font-family: Courier !important;}';
        css += '.Mui-error, .Mui-focused{color: var(--color-3) !important; font-family: Courier !important;}';

        // SCREEN FILTERS
        css += '.rt-screen-filter{z-index: 1000000001; position: fixed; top: 0; left: 0; width: 100%; height: 100vh; pointer-events: none;}';

        // CRT filter
        css += '.rt-screen-filter.crt1{background: repeating-linear-gradient(0deg, #111, #111 1px, transparent 3px, transparent 4px); opacity: 0.2}';
        css += '.rt-screen-filter.crt2{background: repeating-linear-gradient(0deg, #111, #111 1px, transparent 3px, transparent 4px), radial-gradient(white, black 65%); opacity: 0.2}';

        // LCD filter
        css += '.rt-screen-filter.lcd{background: repeating-linear-gradient(0deg, #11111133, #11111133 1px, transparent 2px, transparent 5px), repeating-linear-gradient(90deg, #11111133, #11111133 1px, transparent 2px, transparent 5px); opacity: 0.2}';

        // Brightness filter
        css += '#rt-screen-brightness{background: #000000; opacity: 0.0}';

        // CONTAINERS
        // Body
        css += 'body{background: var(--color-0);}';

        // Card container
        css += '.MuiPaper-root.MuiPaper-elevation{background: var(--color-0); border: solid 1px var(--color-3); border-radius: 0px !important;}';

        // Tooltip
        css += '.MuiTooltip-tooltip{background: var(--color-1); border: solid 1px var(--color-3); border-radius: 0px !important;}';

        // Menu box
        css += '[class*="-menuContainer"].MuiBox-root{background: var(--color-1); border: solid 1px var(--color-3); border-radius: 0px !important;}';

        // COMPONENTS
        // Scrollbar
        css += '::-webkit-scrollbar{width: 20px;}';
        css += '::-webkit-scrollbar-track {background: var(--color-1);}';
        css += '::-webkit-scrollbar-thumb {background: var(--color-2);}';
        css += '::-webkit-scrollbar-thumb:hover, ::-webkit-scrollbar-thumb:active {background: var(--color-3);}';

        // Button
        css += 'button{background: var(--color-2) !important; color: var(--color-0) !important; border-radius: 0px !important; font-family: Courier !important; font-weight: bold !important;}';
        css += 'button:hover, button:active{background: var(--color-3) !important}';
        css += 'button.MuiButtonGroup-firstButton, button.MuiButtonGroup-middleButton{border-right: 1px solid var(--color-0) !important; border-color: var(--color-0) !important;}';
        css += 'button svg{color: var(--color-0) !important;}';

        // List
        css += '.MuiList-root .MuiMenuItem-root{color: var(--color-3)}';
        css += '.MuiList-root .MuiMenuItem-root:hover, .MuiList-root .MuiMenuItem-root:hover .MuiTypography-root, .MuiList-root .MuiMenuItem-root:hover svg{background: var(--color-2); color: var(--color-0)}';
        css += '.MuiList-root .MuiMenuItem-root.Mui-selected{background: var(--color-3); color: var(--color-0);}';
        css += '.MuiList-root .MuiMenuItem-root button{background: transparent !important;}';
        css += '.MuiList-root .MuiMenuItem-root button svg{color: var(--color-3) !important;}';
        css += '.MuiList-root .MuiMenuItem-root.Mui-selected button svg, .MuiList-root .MuiMenuItem-root:hover button svg{color: var(--color-0) !important;}';

        // Input
        css += '.MuiFormLabel-root, .MuiInputLabel-standard, .MuiInputBase-input{color: var(--color-2)}';
        css += '.Mui-focused .MuiInputBase-input{color: var(--color-3)}';
        css += '.MuiInputBase-root.MuiInput-underline::before, .MuiInputBase-root.MuiInput-underline::after{border-color: var(--color-1) !important;}';
        css += '.MuiInputBase-root svg{color: var(--color-3) !important;}';

        // Select
        css += '.MuiSelect-select{color: var(--color-3)}';

        // Checkbox
        css += '.MuiSwitch-root .MuiButtonBase-root{color: var(--color-2);}';
        css += '.MuiSwitch-root .MuiSwitch-track{background: var(--color-1); opacity: 1;}';
        css += '.MuiSwitch-root .Mui-checked{color: var(--color-3);}';
        css += '.MuiSwitch-root .Mui-checked + .MuiSwitch-track{background: var(--color-2); opacity: 1;}';

        // Progress bar
        css += '.MuiLinearProgress-root{background: var(--color-1) !important;}';
        css += '.MuiLinearProgress-root .MuiLinearProgress-bar{background: var(--color-3) !important;}';

        // Toolbar
        css += '[class*="-toolbarHighlight"].MuiToolbar-regular{background: var(--color-2) !important;}';

        // Playlist table
        css += '.MuiTableCell-head, .MuiTableCell-body{border-color: var(--color-1);}';
        css += '.MuiTableCell-body svg{color: var(--color-3)}';
        css += '.MuiTableCell-head, .MuiTableCell-body, .MuiTableCell-body span{color: var(--color-2); font-family: Courier !important;}';
        css += '.MuiTableRow-hover:hover{background: var(--color-2) !important;}';
        css += '.MuiTableRow-hover:hover .MuiTableCell-head, .MuiTableRow-hover:hover .MuiTableCell-body, .MuiTableRow-hover:hover .MuiTableCell-body span{color: var(--color-0);}';
        css += '[class*="-currentTrackRow"] .MuiTableCell-head, [class*="-currentTrackRow"] .MuiTableCell-body, [class*="-currentTrackRow"] .MuiTableCell-body span{color: var(--color-3)}';
        css += '[class*="-rowClass-trackRow"].Mui-selected{background: var(--color-1) !important;}';
        css += '[class*="-rowClass-trackRow"].Mui-selected:hover{background: var(--color-2) !important;}';

        // Format badge
        css += '[class*="-formatBadge"]{background: var(--color-3) !important; color: var(--color-0) !important; border: none;}';

        // LCD
        css += '[class*="-lcd"]{background: var(--color-0); border-radius: 0; border: solid 1px var(--color-3)}';
        css += '[class*="-lcdText"]{color: var(--color-3) !important; background: transparent; border: none;}';
        css += '[class*="-lcdDisc"]{background: transparent; border: none;}';
        css += '[class*="-lcdDisc"] svg, [class*="-lcdDisc"] g{color: var(--color-3) !important; fill: var(--color-3) !important;}';

        // Duration bar
        css += '.css-biokn-duration{background: var(--color-3); filter: none;}';

        pageStyle = document.createElement('style');
        pageStyle.innerText = css;
        document.getElementsByTagName('body')[0].appendChild(pageStyle);
    }

    init();
})();