Greasy Fork is available in English.

Beyond Dark

A Dark Theme for the DnDBeyond Character Sheet! (WIP)

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         Beyond Dark
// @namespace    http://tampermonkey.net/
// @version      0.7
// @description  A Dark Theme for the DnDBeyond Character Sheet! (WIP)
// @author       You
// @match        https://www.dndbeyond.com/*characters/*
// @grant        GM_addStyle
// ==/UserScript==

//pSBC Color Shade/Blend/Convert tool (V4). Source: https://github.com/PimpTrizkit/PJs/wiki/12.-Shade,-Blend-and-Convert-a-Web-Color-(pSBC.js)
const pSBC = (p, c0, c1, l) => {
    let r, g, b, P, f, t, h, i = parseInt, m = Math.round, a = typeof (c1) == "string";
    if (typeof (p) != "number" || p < -1 || p > 1 || typeof (c0) != "string" || (c0[0] != 'r' && c0[0] != '#') || (c1 && !a)) return null;
    if (!this.pSBCr) this.pSBCr = (d) => {
        let n = d.length, x = {};
        if (n > 9) {
            [r, g, b, a] = d = d.split(","), n = d.length;
            if (n < 3 || n > 4) return null;
            x.r = i(r[3] == "a" ? r.slice(5) : r.slice(4)), x.g = i(g), x.b = i(b), x.a = a ? parseFloat(a) : -1
        } else {
            if (n == 8 || n == 6 || n < 4) return null;
            if (n < 6) d = "#" + d[1] + d[1] + d[2] + d[2] + d[3] + d[3] + (n > 4 ? d[4] + d[4] : "");
            d = i(d.slice(1), 16);
            if (n == 9 || n == 5) x.r = d >> 24 & 255, x.g = d >> 16 & 255, x.b = d >> 8 & 255, x.a = m((d & 255) / 0.255) / 1000;
            else x.r = d >> 16, x.g = d >> 8 & 255, x.b = d & 255, x.a = -1
        } return x
    };
    h = c0.length > 9, h = a ? c1.length > 9 ? true : c1 == "c" ? !h : false : h, f = this.pSBCr(c0), P = p < 0, t = c1 && c1 != "c" ? this.pSBCr(c1) : P ? { r: 0, g: 0, b: 0, a: -1 } : { r: 255, g: 255, b: 255, a: -1 }, p = P ? p * -1 : p, P = 1 - p;
    if (!f || !t) return null;
    if (l) r = m(P * f.r + p * t.r), g = m(P * f.g + p * t.g), b = m(P * f.b + p * t.b);
    else r = m((P * f.r ** 2 + p * t.r ** 2) ** 0.5), g = m((P * f.g ** 2 + p * t.g ** 2) ** 0.5), b = m((P * f.b ** 2 + p * t.b ** 2) ** 0.5);
    a = f.a, t = t.a, f = a >= 0 || t >= 0, a = f ? a < 0 ? t : t < 0 ? a : a * P + t * p : 0;
    if (h) return "rgb" + (f ? "a(" : "(") + r + "," + g + "," + b + (f ? "," + m(a * 1000) / 1000 : "") + ")";
    else return "#" + (4294967296 + r * 16777216 + g * 65536 + b * 256 + (f ? m(a * 255) : 0)).toString(16).slice(1, f ? undefined : -2)
}
(function () {
    'use strict';
    // Userscript Globals
    var accentColor;
    var accentColorMuted;
    /**
     * @type {CSSStyleSheet}
     */
    var sheet;

    // // Change BgColor
    // var bodyElems = document.getElementsByTagName('body');
    // for (let i = 0; i < bodyElems.length; i++) {
    //     bodyElems[i].style.background = "#222222";
    //     console.log("Changing BG Color. " + i);
    // }

    const colorReplacements = {
        "#ffffff": "#353535",
        "#ededed": "#282828",
        "#e0e3e3": "#323232",
        "#838383": "#ababab",
        "#faf8f7": "#3a3d3e",
    }
    initStyleSheet();
    // Called ONCE after DOMContentLoaded
    function onLoad() {
        var startTime = performance.now();
        console.info("Starting Userscript.");

        accentColor = getCharColor();
        accentColorMuted = pSBC(-0.8, accentColor, "#383838");

        restyleSvgs();

        sheet.insertRule(
            `.ddbc-tab-options--layout-pill div.ddbc-tab-options__header-heading--is-active {
                background-color: ${accentColor} !important;
            }`);


        restyleQuickRollAreas();

        fixSlotManagerColors();

        // restyleImgIcons();

        console.log(`DarkMode main exec time: ${performance.now() - startTime} ms`);
    }

    function getCharColor() {
        return window.getComputedStyle(document.querySelectorAll(".body-rpgcharacter-sheet .site-bar")[0]).getPropertyValue('border-color');;
    }

    function initStyleSheet() {
        // Create Stylesheet
        sheet = createStyleSheet('dark-mode-userscript');

        // Background
        sheet.insertRule(`body {
            background: #222222 !important;
        }`);

        // Text Color
        const content_container = document.getElementById('content');
        content_container.style.color = '#d4d5d7';//'#e4e5e7';

        // Initiative
        // document.getElementsByClassName('ct-combat__summary-label--outline')[0].style.textShadow = 'none';
        // Inspiration
        // document.getElementsByClassName('ct-inspiration__label')[0].style.textShadow = 'none';
        sheet.insertRule(`div.ct-combat__summary-label--outline, div.ct-inspiration__label {
            text-shadow: none;
        }`)
        // Sidebar Recolor
        sheet.insertRule(`div.ct-sidebar__pane-gap, div.ct-sidebar__pane-content {
            background-color: #303030;
        }`);
        // Ability Scores Recolor
        sheet.insertRule(
            `.ddbc-ability-summary__primary,
        .ct-proficiency-bonus-box__value .ddbc-signed-number.ddbc-signed-number--large,
        .ct-proficiency-bonus-box .ct-proficiency-bonus-box__heading,
        .ddbc-item-name--rarity-common,
        .ddbc-tab-list__nav-item:hover,
        .ddbc-tab-list__nav-item--is-active,
        .ddbc-spell-name,
        .ddbc-checkbox__label,
        .ct-spell-manager__filter-heading,
        .ct-class-spell-manager__heading,
        .ct-inventory-filter__input,
        .ct-spells-filter__input,
        .ct-feats-manage-pane__feat-heading-name,
        .ct-sidebar__heading,
        .ct-health-summary__adjuster-field-input,
        .collapsible-heading
        {
            color: #e4e5e7 !important;
        }`);
        sheet.insertRule(
            `.ct-class-spell-manager__info,
        .ddbc-html-content p,
        .ct-sidebar__pane-content > *,
        .ct-campaign-pane a {
            color: #d4d5d7 !important;
        }`);

        // Restyling Accent/Element bg colors
        // #f1f1f1 -> #434343
        sheet.insertRule(
            `div.ddbc-collapsible__header {
            background-color: #434343;
        }`);
        // #f2f2f2 -> #424242;
        sheet.insertRule(
            `.ddbc-tab-options--layout-pill div.ddbc-tab-options__header-heading,
        div.ct-spell-detail__tag,
        div.ct-item-detail__tag {
            background-color: #424242;
        }`);

        // Attacks per Action text: convert to secondary color
        sheet.insertRule(
            `span.ct-actions__attacks-per-action {
            color: #838383;
        }`
        );
        // BG #ffffff -> #353535
        sheet.insertRule(
            `.site button.ct-health-summary__adjuster-button,
        input.ct-health-summary__adjuster-field-input {
            background-color: ${colorReplacements["#ffffff"]} !important;
        }`);

        // Tables
        //#fff -> #353535
        sheet.insertRule(
            `.ct-sidebar td {
            background: ${colorReplacements["#ffffff"]} !important;
        }`)
        //#faf8f7 ->
        sheet.insertRule(
            `.ct-sidebar tr:nth-child(odd) td {
            background: ${colorReplacements["#faf8f7"]} !important;
        }`)

        // Collapsible Content Bg
        sheet.insertRule(
            `.ddbc-collapsible__content {
            background-color: #383838 !important;
        }`);
        // SVG Backgrounds
        sheet.insertRule(`
        svg > path,
        .ct-combat__statuses polygon,
        .ct-initiative-box polygon,
        #InspirationBoxSvg-Fill-1,
        .ddbc-svg.ddbc-ability-score-box-svg > path:nth-child(2),
        .ct-proficiency-groups-box polygon,
        .ct-sidebar__pane-top path,
        .ct-sidebar__pane-bottom path,
        svg.ddbc-svg.ddbc-manage-level-svg.ddbc-svg--themed path
        {
            fill: #303030 !important;
        }`);
        sheet.insertRule(`
        .ddbc-svg.ddbc-proficiency-svg.ddbc-proficiency-icon.ddbc-proficiency-level-icon circle
        {
            fill: #cbcbcb !important;
        }`);
        // const no_prof_circles = document.getElementsByClassName('ddbc-no-proficiency-icon');
        // for (let circle of no_prof_circles) {
        //     circle.style.backgroundColor = "inherit";
        // }
        sheet.insertRule(`span.ddbc-no-proficiency-icon {
            background-color: inherit;
        }`)

        sheet.insertRule(`.ct-skills__list {
            overflow-y: auto;
            max-height: min-content !important;
        }`);
    }

    // function recolorElements(elems) {
    //     let matchCount = 0;
    //     for (let i = 0; i < elems.length; i++) {
    //         //console.log("Userscript: test");
    //         let el = elems[i];
    //         let bgColorRGB = getRGBValues(window.getComputedStyle(el, null).getPropertyValue("background-color"));
    //         let bgColor = rgbToHex(bgColorRGB[0], bgColorRGB[1], bgColorRGB[2]);

    //         if (!el.classList.contains('ddbc-no-proficiency-icon')) {
    //             const colorMatch = colorReplacements[bgColor];
    //             if (colorMatch) {
    //                 el.style.backgroundColor = colorMatch;
    //                 matchCount++;
    //                 console.log(el);
    //             }
    //         }

    //     }
    //     console.log(`matchCount: ${matchCount}`);
    // }

    function fixSlotManagerColors() {
        document.querySelectorAll('.ct-slot-manager__slot.ct-slot-manager__slot--interactive').forEach(e => e.style.backgroundColor = "");
    }

    function restyleQuickRollAreas() {
        sheet.insertRule(`.beyond20-quick-roll-area {
            border: 1px solid ${accentColor.toString()};
            border-radius: 6px;
            box-sizing: border-box;
            margin-right: 5px;
        }`);
        sheet.insertRule(`.ct-spells-spell__action.beyond20-quick-roll-area {
            padding-right: 0 !important;
        }`)

        sheet.insertRule(//.ct-skills__col--modifier
            `.beyond20-quick-roll-area:hover {
            background-color: ${accentColorMuted};
        }
        `);
    }

    function restyleSvgs() {
        // const svgs = document.getElementsByClassName('ddbc-svg');
        //console.log(svgs);
        //console.log("I'm Confused");
        // for (let svg of svgs) {

        //     let paths = svg.getElementsByTagName("*");
        //     //paths[0].setAttribute("fill", "#383838");
        //     for (let j = 0; j < paths.length; j++) {
        //         //console.log("elems test" +j);
        //         let c_path_fill = paths[j].getAttribute("fill")
        //         if (c_path_fill === "#FEFEFE") {
        //             paths[j].setAttribute("fill", "#373737");
        //         } else if (c_path_fill === "#383838") {
        //             paths[j].setAttribute("fill", "#cbcbcb");
        //         }
        //     }
        // }

        // Some css selectors grabbed from https://github.com/Azmoria/dndbeyonddark
        // Borders
        sheet.insertRule(`
        svg > path + path,
        .ct-combat__statuses path,
        .ct-initiative-box path,
        .ct-inspiration path,
        .ct-proficiency-groups-box path,
        .ddbc-attunement-slot.ddbc-attunement-slot--empty path
        {
            fill: ${accentColor} !important;
        }`);

    }

    function restyleImgIcons() {
        sheet.insertRule(`.i-hamburger, .i-concentration, .i-homebrew, .i-spell-melee, .i-spell-ranged, .i-favorite, .i-strength-save, .i-dexterity-save, .i-constitution-save, .i-charisma-save, .i-wisdom-save, .i-intelligence-save, .i-type-acid, .i-type-bludgeoning, .i-type-cold, .i-type-fire, .i-type-force, .i-type-lightning, .i-type-necrotic, .i-type-piercing, .i-type-poison, .i-type-psychic, .i-type-radiant, .i-type-slashing, .i-type-thunder, .i-condition-blinded, .i-condition-charmed, .i-condition-deafened, .i-condition-exhaustion, .i-condition-exhausted, .i-condition-frightened, .i-condition-grappled, .i-condition-incapacitated, .i-condition-invisible, .i-condition-paralyzed, .i-condition-petrified, .i-condition-poisoned, .i-condition-prone, .i-condition-restrained, .i-condition-stunned, .i-condition-unconscious, .i-condition-white-blinded, .i-condition-white-charmed, .i-condition-white-deafened, .i-condition-white-exhaustion, .i-condition-white-exhausted, .i-condition-white-frightened, .i-condition-white-grappled, .i-condition-white-incapacitated, .i-condition-white-invisible, .i-condition-white-paralyzed, .i-condition-white-petrified, .i-condition-white-poisoned, .i-condition-white-prone, .i-condition-white-restrained, .i-condition-white-stunned, .i-condition-white-unconscious, .i-aoe-cone, .i-aoe-cube, .i-aoe-cylinder, .i-aoe-line, .i-aoe-sphere, .i-aoe-square, .i-artificer, .i-bard, .i-cleric, .i-druid, .i-paladin, .i-ranger, .i-sorcerer, .i-warlock, .i-wizard, .i-barbarian, .i-fighter, .i-monk, .i-rogue, .i-aberration, .i-beast, .i-celestial, .i-construct, .i-dragon, .i-elemental, .i-fey, .i-fiend, .i-giant, .i-humanoid, .i-monstrosity, .i-ooze, .i-plant, .i-undead, .i-ritual, .i-req-attunement, .i-legendary-monster, .i-checkmark, .i-partnered-content-small, .i-partnered-content-medium, .i-partnered-content-large, .i-copy, .i-download, .i-gift, .i-radio-checked, .i-radio-empty, .i-radio-checked-light
        , .ddbc-healing-icon__icon, .ddbc-combat-attack__icon-img, .ddbc-combat-attack__icon-img--action-attack-weapon-melee
        {
            filter: invert(100%);
        }`)
    }

    function restyleSidebar() {
        let sidebarCapSvgs = document.getElementsByClassName('ddbc-sidebar-cap-svg');
        // let sidebarGap = document.getElementsByClassName('ct-sidebar__pane-gap');
        // let sidebarContent = document.getElementsByClassName('ct-sidebar__pane-content');
        for (let svg of sidebarCapSvgs) {
            svg.children[0].setAttribute("fill", "#303030");
        }

        // for (let el of sidebarGap) {
        //     el.style.backgroundColor = "#383838";
        // }
        // for (let el of sidebarContent) {
        //     el.style.backgroundColor = "#383838";
        // }

    }

    // Bind Event Listeners
    window.addEventListener('load', () => setTimeout(onLoad, 500), false);

    window.addEventListener('resize', () => setTimeout(() => {
        restyleSidebar();
        restyleSvgs();
    }, 0));

    document.body.addEventListener('click', () => setTimeout(() => {
        restyleSidebar();
        // restyleQuickRollAreas();
    }, 0));


    //=========
    // Utility
    //=========
    function componentToHex(c) {
        var hex = c.toString(16);
        return hex.length == 1 ? "0" + hex : hex;
    }

    function rgbToHex(r, g, b) {
        return "#" + componentToHex(r) + componentToHex(g) + componentToHex(b);
    }

    function getRGBValues(rgbString) {
        rgbString = rgbString.substring(rgbString.indexOf('(') + 1, rgbString.indexOf(')'));
        // Notice here that we don't know how many digits are in each value,
        // but we know that every value is separated by a comma.
        // So split the three values using comma as the separator.
        // The split function returns an object.
        let rgbColors = rgbString.split(',', 3);

        // Convert redValue to integer
        rgbColors[0] = parseInt(rgbColors[0]);
        // Convert greenValue to integer
        rgbColors[1] = parseInt(rgbColors[1]);
        // Convert blueValue to integer
        rgbColors[2] = parseInt(rgbColors[2]);

        return rgbColors;
    }

    function createStyleSheet(id, media) {
        var el = document.createElement('style');
        // WebKit hack
        el.appendChild(document.createTextNode(''));
        // el.type  = 'text/css';
        el.rel = 'stylesheet';
        el.media = media || 'screen';
        el.id = id;
        document.head.appendChild(el);
        return el.sheet;
    }

})();