ALMOST proper dark mode for the fritz.box web UI
// ==UserScript==
// @Author iSlippedOnThinAir
// @name FritzBox Darkmode
// @namespace iSlippedOnThinAir
// @version 1.3
// @description ALMOST proper dark mode for the fritz.box web UI
// @license MIT
// @match http://fritz.box/*
// @match https://fritz.box/*
// @match http://192.168.178.1/*
// @match https://192.168.178.1/*
// @match http://169.254.1.1/*
// @match https://169.254.1.1/*
// @grant none
// @run-at document-start
// ==/UserScript==
(function() {
'use strict';
//Dark mode variable values
const MAIN_BG = 'rgb(25,25,25)';
const VARS = {
'--white-100': 'rgb(40,40,40)',
'--charcoal-gray-100': 'rgb(70,70,70)',
'--color-text': 'rgb(200,200,200)',
'--color-text-bright': 'rgb(230,230,230)',
'--rice-gray-100': 'rgb(30,30,30)'
};
//CSS injected into open shadow roots and document
const cssForOpenShadows = `
:host, * {
color: ${VARS['--color-text']} !important;
-webkit-text-fill-color: ${VARS['--color-text']} !important;
}
/*Apply .borderColorFix*/
.borderColorFix {
background-color: rgb(40,40,40) !important;
--charcoal-gray-tints-3-tint: rgb(40,40,40) !important;
}
/*Fix white bar behind main buttons*/
.main-buttons,
.main-buttons--visible {
background-color: ${MAIN_BG} !important;
--white-90: ${MAIN_BG} !important;
}
/*Fix light background behind content (ground)*/
.ground,
.ground[data-v-4020b471] {
background-color: ${MAIN_BG} !important;
--rice-gray-100: ${MAIN_BG} !important;
}
`;
function applyRootVars() {
const docEl = document.documentElement;
if (!docEl) return;
docEl.style.setProperty('background-color', MAIN_BG);
Object.entries(VARS).forEach(([k,v]) => docEl.style.setProperty(k,v));
}
function injectStyleIntoRoot(root) {
if (!root || !root.querySelector) return;
if (root.querySelector('#dark-fritz-style')) return;
try {
const style = document.createElement('style');
style.id = 'dark-fritz-style';
style.textContent = cssForOpenShadows;
if (root === document) {
(document.head || document.documentElement).prepend(style);
} else {
root.prepend(style);
}
} catch (e) {}
}
function setVarsOnHost(el){
if (!el || !el.style) return;
Object.entries(VARS).forEach(([k,v]) => el.style.setProperty(k,v,'important'));
el.style.setProperty('color', VARS['--color-text'],'important');
el.style.setProperty('-webkit-text-fill-color', VARS['--color-text'],'important');
}
function crawlAndPatch(root) {
try {
applyRootVars();
injectStyleIntoRoot(document);
const all = Array.from((root.querySelectorAll && root.querySelectorAll('*')) || []);
all.forEach(el => {
const tag = (el.tagName || '').toLowerCase();
if (tag.includes('-')) setVarsOnHost(el);
if (el.shadowRoot) {
injectStyleIntoRoot(el.shadowRoot);
setVarsOnHost(el);
crawlAndPatch(el.shadowRoot);
}
});
} catch(e) {}
}
//Not sure if this is actually needed but i was to lazy to test
//Initial run
crawlAndPatch(document);
//Observe for dynamic elements
new MutationObserver(() => crawlAndPatch(document))
.observe(document.documentElement || document, { childList: true, subtree: true });
//Manual re-run helper
window.__fritz_darkmode_rerun = () => crawlAndPatch(document);
})();