Greasy Fork

Greasy Fork is available in English.

维基百科黑色主题

给予维基百科网页一个黑色主题

当前为 2021-07-05 提交的版本,查看 最新版本

您需要先安装一款用户脚本管理器扩展,例如 Tampermonkey 篡改猴Greasemonkey 油猴子Violentmonkey 暴力猴,才能安装此脚本。

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

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

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

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

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

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

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

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

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

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

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

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

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

// ==UserScript==
// @name         Wikipedia Dark Theme
// @author       Shangru Li
// @version      1.41
// @match        *://*.wikipedia.org/*
// @match        *://*.mediawiki.org/*
// @match        *://*.wikimedia.org/*
// @match        *://*.wikibooks.org/*
// @match        *://*.wikidata.org/*
// @match        *://*.wikinews.org/*
// @match        *://*.wikiquote.org/*
// @match        *://*.wikisource.org/*
// @match        *://*.wikiversity.org/*
// @match        *://*.wikivoyage.org/*
// @match        *://*.wiktionary.org/*
// @namespace    https://github.com/MaxsLi/WikipediaDarkTheme
// @icon         https://www.wikipedia.org/favicon.ico
// @grant        GM_setValue
// @grant        GM_getValue
// @run-at       document-start
// @license      MIT
//######################___Localizations___#####################################
// @name                Wikipedia Dark Theme
// @description         Script gives Wikipedia pages a dark color theme
// @name:ja             Wikipediaダークテーマ
// @description:ja      Wikipediaのサイトのバックグラウンドを黒に変更するスクリプトです
// @name:zh-CN          维基百科黑色主题
// @description:zh-CN   给予维基百科网页一个黑色主题
// @name:zh-TW          维基百科黑色主题
// @description:zh-TW   給予維基百科網頁壹個黑色主題
// ==/UserScript==

'use strict';

//############################################___Default_Colors___######################################################
const DEFAULT_CONTRAST_VALUE = 8;
const DEFAULT_FOREGROUND_COLOR = "rgb(238, 255, 255)";
const DEFAULT_BACKGROUND_COLOR = "rgb(35, 35, 35)";
const DEFAULT_LINK_COLOR = "rgb(249, 186, 82)";
//############################################___One_Could_Alter_If_Desired___##########################################

//############################################___Global_Variables___####################################################

const LOCALE = window.location.href.substring(0, window.location.href.indexOf(".wiki")).slice(-2);

const DEFAULT_BACKGROUND_COLOR_RGB = splitToRGB(DEFAULT_BACKGROUND_COLOR);

// list of tags of the wikipedia logos and symbols to be excluded from setting backgroundColor to white
const EXCLUDE_SRC_TAG = [
    "protection-shackle", "green_check", "symbol_support_vote",
    "edit-clear", "information_icon", "increase2", "decrease_positive",
    "steady2", "decrease2", "increase_negative", "red_question_mark",
    "blue_check", "x_mark", "yes_check", "twemoji", "walnut", "cscr-featured",
    "sound-openclipart", "folder_hexagonal_icon", "symbol_book_class2",
    "question_book-new", "wiktionary-logo", "commons-logo", "wikinews-logo",
    "wikiquote-logo", "wikivoyage-logo", "sound-icon", "wikibooks-logo",
    "wikiversity-logo", "ambox", "system-search", "split-arrows", "wikiversity_logo",
    "wikisource-logo", "wikimedia_community_logo", "wikidata-logo", "mediawiki-logo",
    "wikispecies-logo", "nuvola_apps", "white_flag_icon",
    "wiki_letter_w_cropped", "edit-copy_purple-wikiq", "acap", "portal-puzzle",
    "star_of_life", "disambig-dark", "gnome", "office-book", "audio-input-microphone",
    "hsutvald2", "hspolitic", "hsdagensdatum", "hsvissteduatt", "pl_wiki_aktualnosci_ikona",
    "hsbild", "wiki_aktualnosci_ikona", "hs_vdq", "hssamarbete", "hswpedia", "w-circle",
    "red_pencil_icon", "merge-arrow", "generic_with_pencil", "hsaktuell", "hsearth",
    "wikimedia-logo-circle", "wiktionary_ko_without_text", "mediawiki-notext",
    "wiktprintable_without_text", "dialog-information", "applications-office",
    "celestia", "antistub", "wiki_letter", "kit_body", "ui_icon_edit-ltr-progressive",
    "merge-split-transwiki", "mergefrom", "px-steady", "px-decrease", "px-increase",
    "question_book", "padlock-silver", "incubator-logo", "px-chinese_conversion",
    "px-applications-graphics", "px-pody_candidate", "px-potd-logo", "px-pd-icon",
    "px-dialog-warning", "px-checked_copyright_icon", "px-valued_image_seal",
    "px-cscr-former", "px-red_x", "px-crystal_clear_app_kedit", "px-people_icon",
    "kit_shorts", "kit_socks", "wikipedia-logo-v2", "phacility_phabricator_logo",
    "wikimedia_cloud_services_logo", "lingualibre-logo", "le_dico_des_ados_small_logo",
    "vikidia_v_vectorised", "sciences_humaines", "science-symbol", "history2",
    "vote3_final", "p_religion_world", "tecno-rueda", "notification-icon",
    "countries-vector", "p_history", "social_sciences", "p_culture", "p_religion_world",
    "p_sport", "p_train", "p_parthenon", "wiktionary_small", "wikimedia-logo",
    "crystal_clear_app_xmag", "1rightarrow", "emblem-qual", "hswikimedia", "hsutvald",
    "hstools", "hscontribs", "pl_wiki_czywiesz_ikona", "hs_geo", "p_wwii", "p_literature",
    "p_science", "p_globe", "p_vip", "p_mathematics", "p_chemistry", "p_medicine3",
    "p_technology", "datum17", "hs_liste", "hs_rtl_exclamation", "hsbook", "hshome",
    "hs_korganizer", "hseditor", "84_tematyczny_logo_propozycja", "hsbra",
    "pl_wiki_kalendarium_ikona", "pl_wiki_inm_ikona", "wiktionarypl_nodesc",
    "wikimedia_polska_logo", "icon_of_three_people_in_different_shades_of_grey",
    "wikimania", "hs_skand", "emblem-star-gray", "help-browser-red", "globe-with-clock",
    "records", "office-calendar", "preferences-desktop-locale", "system-users",
    "applications-system", "emblem-earth", "mail-closed", "tango-nosources",
    "emblem-scales", "mediawiki-2020"
];

// list of tags of images to have color inverted, both lists are subjected to amend
const INVERT_SRC_TAG = [
    "loudspeaker", "signature", "signatur", "chinese_characters", "/media/math/render/",
    "translation_to_english_arrow", "disambig_gray", "wikimedia-logo_black", "blue_pencil",
    "latin_alphabet_", "_cursiva", "unbalanced_scales", "question%2c_web_fundamentals",
    "wikipedia-tagline", "wikipedia-wordmark", "copyright/wikipedia", "ui_icon_ellipsis",
    "bluebg_rounded_croped", "blue-bg_rounded_cropped_right", "icon_facebook",
    "youtube_social_dark_circle", "instagram_circle", "icon_twitter",
    "%d8%a8%d8%a7%d9%84%d8%ad%d9%84%d9%8a%d8%a9", "font_awesome_5_solid_feather-alt",
    "font_awesome_5_solid_tree", "font_awesome_5_solid_globe", "font_awesome_5_solid_futbol",
    "font_awesome_5_solid_hourglass", "font_awesome_5_solid_users", "font_awesome_5_solid_palette",
    "font_awesome_5_solid_rocket", "font_awesome_5_solid_bong", "vlad1Trezub", "font_awesome_5_solid_flag",
    "font_awesome_5_solid_university", "wikipedia_wordmark", "wikipedia-logo-textonly"
];

//############################################___Controller___##########################################################

// The main control function is called at every `onreadystatechange`
// Document state will go from `loading` --> `interactive` --> `complete`
// Metadata Block `@run-at document-start` will ensure this script start executing when `loading`
(document.onreadystatechange = function () {
    if (GM_getValue("scriptEnabled")) {
        if ('loading' === document.readyState) {
            setPageVisibility("hidden");
        } else if ('interactive' === document.readyState) {
            applyDarkTheme();
        } else if ('complete' === document.readyState) {
            setPageVisibility("visible");
            initSettingElements();
            invertSpecialElements();
        }
    } else if ('complete' === document.readyState) {
        initSettingElements();
    }
})();

//############################################___Functions___###########################################################

function setPageVisibility(visibility) {
    let entirePage = document.getElementsByTagName('html')[0];
    entirePage.style.backgroundColor = DEFAULT_BACKGROUND_COLOR;
    entirePage.style.visibility = visibility;
}

function applyDarkTheme() {
    // General idea is to put all elements on a wikipedia page to an array `allElements`
    // traverse through this array and reverse the color of each element accordingly
    // running time o(n), where n is the number of elements on a page
    document.querySelectorAll('*').forEach(function (element) {
        try {
            if (!isSpecialElement(element)) {
                changeForegroundColor(element);
                changeBackgroundColor(element);
            }
        } catch (e) {
            console.log(e);
        }
    });
}

function isSpecialElement(e) {
    if (elementIsWikiLogo(e)) {
        invertImage(e, 90);
        return true;
    } else if (elementIsImage(e)) {
        changeImageIfOnLists(e);
        return true;
    } else if (elementIsHyperlink(e)) {
        e.style.color = GM_getValue("linkColor");
        changeBackgroundColor(e);
        return true;
    } else if (elementIsKeyboardKey(e)) {
        e.style.foregroundColor = DEFAULT_BACKGROUND_COLOR;
        return true;
    } else if (elementIsFamilyTree(e)) {
        if (e.style.borderTop) {
            e.style.borderTopColor = "white";
        }
        if (e.style.borderBottom) {
            e.style.borderBottomColor = "white";
        }
        if (e.style.borderLeft) {
            e.style.borderLeftColor = "white";
        }
        if (e.style.borderRight) {
            e.style.borderRightColor = "white";
        }
        if (e.style.border) {
            e.style.borderColor = "white";
        }
        return true;
    } else return (elementIsLegendOrPieChart(e) || elementIsIndicationArrow(e));
}

function elementIsImage(e) {
    return e.nodeName.toLowerCase() === 'img';
}

function changeImageIfOnLists(img) {
    if (!imageInExcludeList(img)) {
        img.style.backgroundColor = "rgb(255, 255, 255)";
    }
    if (imageInInvertList(img)) {
        invertImage(img, 86);
    }
}

function imageInExcludeList(img) {
    for (let i = 0; i < EXCLUDE_SRC_TAG.length; i++) {
        if (img.src.toLowerCase().includes(EXCLUDE_SRC_TAG[i])) {
            return true;
        }
    }
    return false;
}

function imageInInvertList(img) {
    for (let i = 0; i < INVERT_SRC_TAG.length; i++) {
        if (img.src.toLowerCase().includes(INVERT_SRC_TAG[i])) {
            return true;
        }
    }
    return false;
}

function invertImage(img, percent) {
    img.style.filter = "invert(" + percent + "%)";
}

function elementIsHyperlink(e) {
    return e.tagName.toLowerCase() === 'a';
}

function elementIsWikiLogo(e) {
    return e.className.toLowerCase().includes("mw-wiki-logo");
}

function elementIsKeyboardKey(e) {
    return e.className.toLowerCase().includes('keyboard-key');
}

function elementIsLegendOrPieChart(e) {
    return e.className.toLowerCase().includes('legend') ||
        e.style.borderColor.toLowerCase().includes('transparent') ||
        (
            // Pie chart template
            (
                e.style.border.toLowerCase().includes("1px solid rgb(0, 0, 0)") ||
                e.style.border.toLowerCase().includes("1px solid black")
            ) &&
            e.style.height === "200px" &&
            e.style.height === "200px" &&
            e.style.borderRadius === "100px"
        ) ||
        (
            e.nodeName === "SPAN" && e.textContent.replace(/\s/g, '').length === 0
        ) || e.innerHTML === "&nbsp;" || e.innerHTML === "​" || e.innerHTML === "■";
}

function elementIsFamilyTree(e) {
    return e.style.borderTop.toLowerCase().includes("1px solid black") ||
        e.style.borderTop.toLowerCase().includes("1px dashed black") ||
        e.style.borderTop.toLowerCase().includes("1px dotted black") ||
        e.style.borderBottom.toLowerCase().includes("1px solid black") ||
        e.style.borderBottom.toLowerCase().includes("1px dashed black") ||
        e.style.borderBottom.toLowerCase().includes("1px dotted black") ||
        e.style.borderLeft.toLowerCase().includes("1px solid black") ||
        e.style.borderLeft.toLowerCase().includes("1px dashed black") ||
        e.style.borderLeft.toLowerCase().includes("1px dotted black") ||
        e.style.borderRight.toLowerCase().includes("1px solid black") ||
        e.style.borderRight.toLowerCase().includes("1px dashed black") ||
        e.style.borderRight.toLowerCase().includes("1px dotted black") ||
        e.style.border.toLowerCase().includes("2px solid black");
}

function elementIsIndicationArrow(e) {
    return e.innerHTML === "▼" || e.innerHTML === "▲";
}

function changeForegroundColor(e) {
    let foregroundColor = window.getComputedStyle(e, null).getPropertyValue("color");
    if (colorIsRGB(foregroundColor)) {
        foregroundColor = splitToRGB(foregroundColor);
        foregroundColor = inverseRBGColor(foregroundColor);
        foregroundColor = increaseRGBToMatchContrastValue(foregroundColor, DEFAULT_BACKGROUND_COLOR_RGB, GM_getValue("contrastValue"), 30);
        e.style.color = RGBArrayToString(foregroundColor);
    } else {
        e.style.color = DEFAULT_FOREGROUND_COLOR;
    }
}

function changeBackgroundColor(e) {
    const elementComputedStyle = window.getComputedStyle(e, null);
    let backgroundColor = elementComputedStyle.getPropertyValue("background-color");
    if (colorIsRGB(backgroundColor)) {
        backgroundColor = splitToRGB(backgroundColor);
        backgroundColor = inverseRBGColor(backgroundColor);
        if (RGBTooDark(backgroundColor)) {
            backgroundColor = addValueToRGB(backgroundColor, 30);
        }
        backgroundColor = decreaseRGBToMatchContrastValue(backgroundColor, DEFAULT_BACKGROUND_COLOR_RGB, GM_getValue("contrastValue"), -30);
        e.style.backgroundColor = RGBArrayToString(backgroundColor);
    } else if (backgroundColor !== "rgba(0, 0, 0, 0)" || elementToChangeBackground(e)) {
        e.style.backgroundColor = DEFAULT_BACKGROUND_COLOR;
    }
    const backgroundImage = elementComputedStyle.getPropertyValue("background-image");
    if (backgroundImageToRemove(backgroundImage)) {
        e.style.background = DEFAULT_BACKGROUND_COLOR;
    }
}

function backgroundImageToRemove(backgroundImage) {
    // This will remove the white banner on Chinese Wikipedia main page
    return backgroundImage && backgroundImage.includes("Zhwp_blue_banner.png");
}

function colorIsRGB(c) {
    return c.match(/rgb\((\d{1,3}), (\d{1,3}), (\d{1,3})\)/);
}

function splitToRGB(c) {
    const rgb = colorIsRGB(c);
    return [rgb[1], rgb[2], rgb[3]];
}

function inverseRBGColor(c) {
    let r, g, b;
    r = 255 - Number(c[0]);
    g = 255 - Number(c[1]);
    b = 255 - Number(c[2]);
    return [r, g, b];
}

function increaseRGBToMatchContrastValue(colorToChange, colorToMatch, contrastValue, changePerLoop) {
    let result = colorToChange;
    while (contrast(result, colorToMatch) < contrastValue) {
        result = addValueToRGB(result, changePerLoop);
    }
    return result;
}

function decreaseRGBToMatchContrastValue(colorToChange, colorToMatch, contrastValue, changePerLoop) {
    let result = colorToChange;
    while (contrast(result, colorToMatch) > contrastValue) {
        result = addValueToRGB(result, changePerLoop);
    }
    return result;
}

function contrast(rgb1, rgb2) {
    return (luminance(rgb1) + 0.05) / (luminance(rgb2) + 0.05);
}

function luminance(rgb) {
    let result = rgb.map(function (v) {
        v /= 255;
        return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
    });
    return result[0] * 0.2126 + result[1] * 0.7152 + result[2] * 0.0722;
}

function addValueToRGB(rgb, value) {
    return [Number(rgb[0]) + Number(value), Number(rgb[1]) + Number(value), Number(rgb[2]) + Number(value)];
}

function RGBTooDark(rgb) {
    if (Number(rgb[0]) < Number(DEFAULT_BACKGROUND_COLOR_RGB[0]) - 10 && Number(rgb[1]) < Number(DEFAULT_BACKGROUND_COLOR_RGB[1]) - 10 &&
        Number(rgb[2]) < Number(DEFAULT_BACKGROUND_COLOR_RGB[2]) - 10) {
        return true;
    }
}

function RGBArrayToString(rgb) {
    return 'rgb(' + rgb[0] + ', ' + rgb[1] + ', ' + rgb[2] + ')';
}

function RGBToHex(r, g, b) {
    return "#" + ((1 << 24) + (Number(r) << 16) + (Number(g) << 8) + Number(b)).toString(16).slice(1);
}

function elementToChangeBackground(e) {
    return e.id.toLowerCase().includes("mw-head") || e.parentElement.id.includes("ca-");
}

function invertSpecialElements() {
    let sheet = window.document.styleSheets[0];
    if (LOCALE === "zh") {
        // Chinese conversion box
        sheet.insertRule('.vectorTabs li { background-image: none; }', sheet.cssRules.length);
        sheet.insertRule('.vectorTabs li a span { background: ' + DEFAULT_BACKGROUND_COLOR + ' !important; }', sheet.cssRules.length);
        sheet.insertRule('.vectorTabs li a span { color: ' + DEFAULT_FOREGROUND_COLOR + ' !important; }', sheet.cssRules.length);
        // Chinese main page
        sheet.insertRule('@media (min-width: 720px) { .mw-parser-output #mp-2012-column-right-block-b { background: ' + DEFAULT_BACKGROUND_COLOR + ' !important; } }', sheet.cssRules.length);
    } else if (LOCALE === "fr") {
        // French Main page and side bar
        sheet.insertRule('#accueil_2017_en-tete { background: ' + DEFAULT_BACKGROUND_COLOR + ' !important; }', sheet.cssRules.length);
        sheet.insertRule('#mw-panel { background: ' + DEFAULT_BACKGROUND_COLOR + ' !important; }', sheet.cssRules.length);
    } else if (LOCALE === "ja") {
        // Japanese main page headings
        sheet.insertRule('.mw-parser-output .mainpage-heading-title { background: linear-gradient(to right,rgb(74,51,25),rgba(173,171,170,0)) !important; }', sheet.cssRules.length);
    } else if (LOCALE === "es") {
        // Spanish main page headings
        sheet.insertRule('@media (min-width: 1000px) { .mw-parser-output .main-top-left { background-image: linear-gradient(to right, #070605 0%,#070605 70%, rgba(7,6,5,0)100%) !important; } }', sheet.cssRules.length);
    } else if (LOCALE === "pl") {
        // Polish main page headings
        sheet.insertRule('h2 { background-image: none !important; background: linear-gradient(to right,rgb(74,51,25),rgba(173,171,170,0)) !important; }', sheet.cssRules.length);
    } else if (LOCALE === "ru") {
        // Russian main page
        sheet.insertRule('@media (min-width: 1000px) { .main-top-left { background-image: linear-gradient(to right, #070605 0%,#070605 70%, rgba(7,6,5,0)100%) !important; } }', sheet.cssRules.length);
    } else if (LOCALE === "sv") {
        // Swedish main page headings
        sheet.insertRule('.mw-parser-output .frontPageBlock { background: none !important; }', sheet.cssRules.length);
        sheet.insertRule('.frontPageBlockTitle { background: linear-gradient(to right,rgb(74,51,25),rgba(173,171,170,0)) !important; }', sheet.cssRules.length);
    } else if (LOCALE === "uk") {
        // Ukrainian main page
        sheet.insertRule('.mw-parser-output #main-head { background: linear-gradient(rgb(30,30,30),' + DEFAULT_BACKGROUND_COLOR + ' ) !important; }', sheet.cssRules.length);
        sheet.insertRule('.mw-parser-output #main-bottom { background: linear-gradient(' + DEFAULT_BACKGROUND_COLOR + ', rgb(30,30,30)) !important; }', sheet.cssRules.length);
    } else if (LOCALE === "ko") {
        // Korean main page
        sheet.insertRule('.mw-parser-output .main-top-left { background-image: linear-gradient(to right, #070605 0%,#070605 70%, rgba(7,6,5,0)100%) !important; }', sheet.cssRules.length);
    }
}

//############################################___Init_Elements___#######################################################

function initSettingElements() {
    initGMStorage();
    insertSettingsModalStyles();
    createSettingsModal();
    addButtonListeners();
    addSettingsButton();
    updateSettingsModal();
}

function initGMStorage(reset = false) {
    if (!GM_getValue("linkColor") || reset) {
        const split = splitToRGB(DEFAULT_LINK_COLOR);
        GM_setValue("linkColor", RGBToHex(split[0], split[1], split[2]));
    }
    if (!GM_getValue("contrastValue") || reset) {
        GM_setValue("contrastValue", DEFAULT_CONTRAST_VALUE);
    }
}

//############################################___Settings_Button___#####################################################

function addSettingsButton() {
    // Create a list that contains settings button
    let settingsButtonList = document.createElement("li");
    let settingsButton = document.createElement("a");
    settingsButton.id = "settingsButton";
    settingsButton.style.fontWeight = 'bold';
    settingsButton.onclick = function () {
        let settingsModal = document.getElementById("settingsModal")
        settingsModal.style.display = "block";
        return false;
    };
    settingsButtonList.appendChild(settingsButton);
    // Getting the login button and logout button
    const loginLinkElement = document.getElementById("pt-login");
    const logoutLinkElement = document.getElementById("pt-logout");
    // Two cases: user logged-in or not logged-in
    // Get the parent of either element that is defined
    let parentList = (loginLinkElement) ? (loginLinkElement.parentElement) : (logoutLinkElement.parentElement);
    // Adding toggle script button to after the login/logout button
    parentList.appendChild(settingsButtonList);
    setSettingsButton();
}

function setSettingsButton() {
    let settingsButton = document.getElementById("settingsButton");
    let text, title;
    switch (LOCALE) {
        case "zh":
            text = "设置";
            title = "设置维基百科黑色主题。";
            break;
        case "ja":
            text = "設定";
            title = "ウィキペディアダークテーマを設定します。";
            break;
        case "fr":
            text = "Les paramètres";
            title = "Modifiez les paramètres de Wikipédia Dark Theme.";
            break;
        default:
            text = "Settings";
            title = "Change settings for Wikipedia Dark Theme.";
    }
    settingsButton.text = text;
    settingsButton.title = title;
    settingsButton.style.color = GM_getValue("scriptEnabled") ? "white" : "black";
}

//############################################___Settings_Modal___######################################################

function createSettingsModal() {
    let settingsModal = `
        <div id="settingsModal" class="modal">
            <div class="modal-content">
                <div class="modal-header">
                  <h5>Wikipedia Dark Theme Settings</h5>
                  <span id="close" class="close">&times;</span>
                </div>
                <div class="modal-body">
                     <h6>Theme preferences</h6>
                     <div class="form-check form-check-inline">
                      <input class="form-check-input" type="radio" name="theme" id="darkTheme" value="dark">
                      <label class="form-check-label" for="darkTheme">Dark</label>
                    </div>
                    <div class="form-check form-check-inline">
                      <input class="form-check-input" type="radio" name="theme" id="lightTheme" value="light">
                      <label class="form-check-label" for="lightTheme">Light</label>
                    </div>
                    <div class="form-check form-check-inline">
                      <input class="form-check-input" type="radio" name="theme" id="syncTheme" value="sync">
                      <label class="form-check-label" for="syncTheme">Sync with system</label>
                    </div>
                    <h6>Color preferences (in dark theme)</h6>
                    <div class="form-check-inline">
                        <label for="linkColor" id="linkColorLabel">Link color: </label>
                        <input type="color" id="linkColor" title="Choose your link color">
                    </div>
                    <div class="form-check-inline">
                        <label for="contrastValue">Contrast value:</label>
                        <input type="number" id="contrastValue" min="3" max="9" title="Enter a contrast value (between 3 and 9)">
                    </div>
                </div>
                <div class="modal-footer">
                   <button class="btn btn-outline-secondary" id="restoreButton">Restore defaults</button>
                   <button class="btn btn-outline-secondary close">Cancel</button>
                   <button class="btn btn-outline-primary" id="saveButton">Save changes</button>
                </div>
            </div>
        '</div>'
    `;
    document.body.insertAdjacentHTML('afterend', settingsModal)
}

function setSettings() {
    if (document.getElementById("syncTheme").checked) {
        GM_setValue("syncTheme", true);
    } else {
        GM_setValue("syncTheme", false);
    }
    if (document.getElementById("darkTheme").checked) {
        GM_setValue("scriptEnabled", true);
    } else {
        GM_setValue("scriptEnabled", false);
    }
    GM_setValue("linkColor", document.getElementById("linkColor").value);
    const contrastValue = document.getElementById("contrastValue").value;
    if (1 <= contrastValue <= 10) {
        GM_setValue("contrastValue", contrastValue);
    } else {
        GM_setValue("contrastValue", DEFAULT_CONTRAST_VALUE);
    }
}

function updateSettingsModal() {
    updateThemePreferences();
    updateColorPreferences();
}

function updateThemePreferences() {
    if (GM_getValue("syncTheme")) {
        const userPrefersDark = window.matchMedia && window.matchMedia("(prefers-color-scheme: dark)").matches;
        if (userPrefersDark) {
            GM_setValue("scriptEnabled", true);
        } else {
            GM_setValue("scriptEnabled", false);
        }
        document.getElementById("syncTheme").checked = "checked";
    } else {
        if (GM_getValue("scriptEnabled")) {
            document.getElementById("darkTheme").checked = "checked";
        } else {
            document.getElementById("lightTheme").checked = "checked";
        }
    }
}

function updateColorPreferences() {
    document.getElementById("linkColor").value = GM_getValue("linkColor");
    document.getElementById("contrastValue").value = GM_getValue("contrastValue");
}

function dismissSettingsModal() {
    const settingsModal = document.getElementById("settingsModal");
    settingsModal.style.display = "none";
    updateSettingsModal();
}

function addButtonListeners() {
    const closeButtons = document.getElementsByClassName("close");
    for (let i = 0; i < closeButtons.length; i++) {
        closeButtons[i].onclick = function () {
            dismissSettingsModal();
        }
    }
    window.onclick = function (event) {
        if (event.target === document.getElementById("settingsModal")) {
            dismissSettingsModal();
        }
    }
    const saveButton = document.getElementById("saveButton");
    saveButton.onclick = function () {
        setSettings();
        dismissSettingsModal();
        location.reload();
    }
    const restoreButton = document.getElementById("restoreButton");
    restoreButton.onclick = function () {
        initGMStorage(true);
        dismissSettingsModal();
        location.reload();
    }
}

function insertSettingsModalStyles() {
    const style = document.createElement('style');
    style.innerHTML = `
         .modal {
            font-family: var(--bs-font-sans-serif);
            font-size: 1rem;
            font-weight: 400;
            line-height: 1.5;
            color: #212529;
            -webkit-text-size-adjust: 100%;
            display: none;
            position: fixed;
            z-index: 1060;
            padding-top: 100px;
            left: 0;
            top: 0;
            width: 100%;
            height: 100%;
            overflow-x: hidden;
            overflow-y: auto;
            outline: 0;
            background-color: rgb(0,0,0);
            background-color: rgba(0,0,0,0.4);
        }
        .modal-content {
            position: relative;
            display: flex;
            flex-direction: column;
            width: 450px;
            pointer-events: auto;
            background-color: #fff;
            background-clip: padding-box;
            border: 1px solid rgba(0,0,0,.2);
            border-radius: .3rem;
            outline: 0;
            margin: auto;
            -webkit-animation-name: animatetop;
            -webkit-animation-duration: 0.4s;
            animation-name: animatetop;
            animation-duration: 0.4s
        }
        .modal-header {
            display: flex;
            flex-shrink: 0;
            align-items: center;
            justify-content: space-between;
            padding: 1rem 1rem;
            border-bottom: 1px solid #dee2e6;
            border-top-left-radius: calc(.3rem - 1px);
            border-top-right-radius: calc(.3rem - 1px);
        }
        .modal-body {
            position: relative;
            flex: 1 1 auto;
            padding: 1rem;
        }
        .modal-footer {
            display: flex;
            flex-wrap: wrap;
            flex-shrink: 0;
            align-items: center;
            justify-content: flex-end;
            padding: .75rem;
            border-top: 1px solid #dee2e6;
            border-bottom-right-radius: calc(.3rem - 1px);
            border-bottom-left-radius: calc(.3rem - 1px);
        }
        .modal-footer > * {
            margin: .25rem;
        }
        @-webkit-keyframes animatetop {
          from {top:-300px; opacity:0} 
          to {top:0; opacity:1}
        }
        @keyframes animatetop {
          from {top:-300px; opacity:0}
          to {top:0; opacity:1}
        }
        #close {
          color: grey;
          float: right;
          font-size: 28px;
          font-weight: bold;
        }
        #close:hover,
        #close:focus {
            color: black;
            text-decoration: none;
            cursor: pointer;
        }
        #restoreButton {
            margin-right: auto;
        }
        #linkColorLabel {
            margin: .4rem;
        }
        .h1, .h2, .h3, .h4, .h5, .h6, h1, h2, h3, h4, h5, h6 {
            margin-top: 0;
            margin-bottom: .5rem;
            font-weight: 500;
            line-height: 1.2;
        }
        .h5, h5 {
            font-size: 1.25rem;
        }
        .h6, h6 {
            font-size: 1rem;
        }
        .btn {
            display: inline-block;
            font-weight: 400;
            line-height: 1.5;
            color: #212529;
            text-align: center;
            text-decoration: none;
            vertical-align: middle;
            cursor: pointer;
            -webkit-user-select: none;
            -moz-user-select: none;
            user-select: none;
            background-color: transparent;
            border: 1px solid transparent;
            padding: .375rem .75rem;
            font-size: 1rem;
            border-radius: .25rem;
            transition: color .15s ease-in-out,background-color .15s ease-in-out,border-color .15s ease-in-out,box-shadow .15s ease-in-out;
        }
        .btn-outline-primary {
            color: #007bff;
            background-color: transparent;
            background-image: none;
            border-color: #007bff;
        }
        .btn-outline-primary:hover {
            color: #fff;
            background-color: #007bff;
            border-color: #007bff;
        }
        .btn-outline-secondary {
            color: #6c757d;
            background-color: transparent;
            background-image: none;
            border-color: #6c757d;
        }
        .btn-outline-secondary:hover {
            color: #fff;
            background-color: #6c757d;
            border-color: #6c757d;
        }
        .form-check-input:checked[type="radio"] {
            background-image: url("data:image/svg+xml,%3csvg xmlns='http://www.w3.org/2000/svg' viewBox='-4 -4 8 8'%3e%3ccircle r='2' fill='%23fff'/%3e%3c/svg%3e");
        }
        .form-check {
            display: block;
            min-height: 1.5rem;
            padding-left: 1.5em;
            margin-bottom: .125rem;
        }
        .form-check-inline {
            display: inline-block;
            margin-right: 1rem;
        }
        .form-check-input:checked {
            background-color: #0d6efd;
            border-color: #0d6efd;
        }
        .form-check-input[type="radio"] {
            border-radius: 50%;
        }
        .form-check .form-check-input {
            float: left;
            margin-left: -1.5em;
        }
        .form-check-input {
            width: 1em;
            height: 1em;
            margin-top: .25em;
            vertical-align: top;
            background-color: #fff;
            background-repeat: no-repeat;
            background-position: center;
            background-size: contain;
            border: 1px solid rgba(0,0,0,.25);
            -webkit-appearance: none;
            -moz-appearance: none;
            appearance: none;
            -webkit-print-color-adjust: exact;
            color-adjust: exact;
        }
        #contrastValue {
            display: inline-block;
            width: 50px;
            padding: .375rem .75rem;
            font-size: 1rem;
            font-weight: 400;
            line-height: 1.5;
            color: #212529;
            background-color: #fff;
            background-clip: padding-box;
            border: 1px solid #ced4da;
            -webkit-appearance: none;
            -moz-appearance: none;
            appearance: none;
            border-radius: .25rem;
            transition: border-color .15s ease-in-out,box-shadow .15s ease-in-out;
        }
        .label {
            display: inline-block;
        }
    `
    document.head.appendChild(style);
}