Created
September 22, 2025 04:14
-
-
Save jmstacey/0114d9c3cd46b931117c08a3bd33f8d6 to your computer and use it in GitHub Desktop.
Darkmode for wBlock
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| // ==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