Skip to content

Instantly share code, notes, and snippets.

@jmstacey
Created September 22, 2025 04:14
Show Gist options
  • Select an option

  • Save jmstacey/0114d9c3cd46b931117c08a3bd33f8d6 to your computer and use it in GitHub Desktop.

Select an option

Save jmstacey/0114d9c3cd46b931117c08a3bd33f8d6 to your computer and use it in GitHub Desktop.
Darkmode for wBlock
// ==UserScript==
// @name Dark Mode
// @version 1
// @description Add a Dark Mode / Night Mode to your website in a few seconds
// @author theabbie
// @match *://*/*
// @namespace https://theabbie.github.io
// @license MIT
// @downloadURL https://update.greasyfork.org/scripts/435749/Dark%20Mode.user.js
// @updateURL https://update.greasyfork.org/scripts/435749/Dark%20Mode.meta.js
// ==/UserScript==
const IS_BROWSER = true;
class Darkmode {
constructor(options) {
if (!IS_BROWSER) return;
const defaultOptions = {
bottom: '32px',
right: '32px',
left: 'unset',
time: '0.3s',
mixColor: '#fff',
backgroundColor: '#fff',
buttonColorDark: '#100f2c',
buttonColorLight: '#fff',
label: '',
saveInCookies: true,
autoMatchOsTheme: true
};
options = Object.assign({}, defaultOptions, options);
const css = `
.darkmode-layer {
position: fixed;
pointer-events: none;
background: ${options.mixColor};
transition: all ${options.time} ease;
mix-blend-mode: difference;
}
.darkmode-layer--button {
width: 2.9rem;
height: 2.9rem;
border-radius: 50%;
right: ${options.right};
bottom: ${options.bottom};
left: ${options.left};
}
.darkmode-layer--simple {
width: 100%;
height: 100%;
top: 0;
left: 0;
transform: scale(1) !important;
}
.darkmode-layer--expanded {
transform: scale(100);
border-radius: 0;
}
.darkmode-layer--no-transition {
transition: none;
}
.darkmode-toggle {
background: ${options.buttonColorDark};
width: 3rem;
height: 3rem;
position: fixed;
border-radius: 50%;
border:none;
right: ${options.right};
bottom: ${options.bottom};
left: ${options.left};
cursor: pointer;
transition: all 0.5s ease;
display: flex;
justify-content: center;
align-items: center;
}
.darkmode-toggle--white {
background: ${options.buttonColorLight};
}
.darkmode-toggle--inactive {
display: none;
}
.darkmode-background {
background: ${options.backgroundColor};
position: fixed;
pointer-events: none;
z-index: -10;
width: 100%;
height: 100%;
top: 0;
left: 0;
}
img, .darkmode-ignore {
isolation: isolate;
display: inline-block;
}
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) {
.darkmode-toggle {display: none !important}
}
@supports (-ms-ime-align:auto), (-ms-accelerator:true) {
.darkmode-toggle {display: none !important}
}
`;
// ---- DOM elements ----------------------------------------------------
const layer = document.createElement('div');
const button = document.createElement('button');
const background = document.createElement('div');
button.innerHTML = options.label;
button.classList.add('darkmode-toggle--inactive');
layer.classList.add('darkmode-layer');
background.classList.add('darkmode-background');
// ---- Restore previous state -----------------------------------------
const darkmodeActivated = window.localStorage.getItem('darkmode') === 'true';
const preferedThemeOs =
options.autoMatchOsTheme && window.matchMedia('(prefers-color-scheme: dark)').matches;
const darkmodeNeverActivatedByAction = window.localStorage.getItem('darkmode') === null;
if (
(darkmodeActivated && options.saveInCookies) ||
(darkmodeNeverActivatedByAction && preferedThemeOs)
) {
layer.classList.add(
'darkmode-layer--expanded',
'darkmode-layer--simple',
'darkmode-layer--no-transition'
);
button.classList.add('darkmode-toggle--white');
document.body.classList.add('darkmode--activated');
}
// ---- Insert elements --------------------------------------------------
document.body.insertBefore(button, document.body.firstChild);
document.body.insertBefore(layer, document.body.firstChild);
document.body.insertBefore(background, document.body.firstChild);
// ---- Inject the CSS --------------------------------------------------
this.addStyle(css);
// ---- Save references -------------------------------------------------
this.button = button;
this.layer = layer;
this.saveInCookies = options.saveInCookies;
this.time = options.time;
}
// -----------------------------------------------------------------------
// CSP‑friendly injection
addStyle(css) {
// 1️⃣ If the userscript manager provides GM_addStyle, use it – it bypasses CSP.
if (typeof GM_addStyle === 'function') {
GM_addStyle(css);
return;
}
// 2️⃣ Fallback: normal <style> element (works only when the page allows inline styles)
const styleEl = document.createElement('style');
styleEl.type = 'text/css';
styleEl.textContent = css; // no URI‑encoding needed
document.head.appendChild(styleEl);
}
// -----------------------------------------------------------------------
showWidget() {
if (!IS_BROWSER) return;
const button = this.button;
const layer = this.layer;
const time = parseFloat(this.time) * 1000;
button.classList.add('darkmode-toggle');
button.classList.remove('darkmode-toggle--inactive');
button.setAttribute('aria-label', 'Activate dark mode');
button.setAttribute('aria-checked', 'false');
button.setAttribute('role', 'checkbox');
layer.classList.add('darkmode-layer--button');
button.addEventListener('click', () => {
const isDarkmode = this.isActivated();
if (!isDarkmode) {
layer.classList.add('darkmode-layer--expanded');
button.setAttribute('disabled', true);
setTimeout(() => {
layer.classList.add('darkmode-layer--no-transition');
layer.classList.add('darkmode-layer--simple');
button.removeAttribute('disabled');
}, time);
} else {
layer.classList.remove('darkmode-layer--simple');
button.setAttribute('disabled', true);
setTimeout(() => {
layer.classList.remove('darkmode-layer--no-transition');
layer.classList.remove('darkmode-layer--expanded');
button.removeAttribute('disabled');
}, 1);
}
button.classList.toggle('darkmode-toggle--white');
document.body.classList.toggle('darkmode--activated');
window.localStorage.setItem('darkmode', !isDarkmode);
});
}
// -----------------------------------------------------------------------
toggle() {
if (!IS_BROWSER) return;
const layer = this.layer;
const isDarkmode = this.isActivated();
const button = this.button;
layer.classList.toggle('darkmode-layer--simple');
document.body.classList.toggle('darkmode--activated');
window.localStorage.setItem('darkmode', !isDarkmode);
button.setAttribute('aria-label', 'De-activate dark mode');
button.setAttribute('aria-checked', 'true');
}
// -----------------------------------------------------------------------
isActivated() {
if (!IS_BROWSER) return null;
return document.body.classList.contains('darkmode--activated');
}
}
// ---------------------------------------------------------------------------
new Darkmode().showWidget();
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment