Skip to content

Instantly share code, notes, and snippets.

@clort81
Created January 22, 2026 21:09
Show Gist options
  • Select an option

  • Save clort81/2d6ffd10ceff428ce080b5f3e89b423e to your computer and use it in GitHub Desktop.

Select an option

Save clort81/2d6ffd10ceff428ce080b5f3e89b423e to your computer and use it in GitHub Desktop.
Dark Background and Light Text Userscript for Min Browser
// ==UserScript==
// @name Smart HSL Dark Mode
// @namespace http://tampermonkey.net/
// @version 4.2
// @description Attempts hue-preserving intensity inversion with reasonable priorities
// @author clort81 + z.AI
// @match *://*/*
// @grant none
// @run-at document-start
// ==/UserScript==
/* ---------------------------------------------------------------------------
UTILITIES
--------------------------------------------------------------------------- */
function getRelativeLuminance(r, g, b) {
const [rsRGB, gsRGB, bsRGB] = [r, g, b].map(c => {
c = c / 255;
return c <= 0.03928 ? c / 12.92 : Math.pow((c + 0.055) / 1.055, 2.4);
});
return 0.2126 * rsRGB + 0.7152 * gsRGB + 0.0722 * bsRGB;
}
function rgbToHsl(r, g, b) {
r /= 255, g /= 255, b /= 255;
const max = Math.max(r, g, b), min = Math.min(r, g, b);
let h, s, l = (max + min) / 2;
if (max === min) {
h = s = 0;
} else {
const d = max - min;
s = l > 0.5 ? d / (2 - max - min) : d / (max + min);
switch (max) {
case r: h = (g - b) / d + (g < b ? 6 : 0); break;
case g: h = (b - r) / d + 2; break;
case b: h = (r - g) / d + 4; break;
}
h /= 6;
}
return [h, s, l];
}
function hslToRgb(h, s, l) {
let r, g, b;
if (s === 0) {
r = g = b = l;
} else {
const hue2rgb = (p, q, t) => {
if (t < 0) t += 1;
if (t > 1) t -= 1;
if (t < 1/6) return p + (q - p) * 6 * t;
if (t < 1/2) return q;
if (t < 2/3) return p + (q - p) * (2/3 - t) * 6;
return p;
};
const q = l < 0.5 ? l * (1 + s) : l + s - l * s;
const p = 2 * l - q;
r = hue2rgb(p, q, h + 1/3);
g = hue2rgb(p, q, h);
b = hue2rgb(p, q, h - 1/3);
}
return { r: Math.round(r * 255), g: Math.round(g * 255), b: Math.round(b * 255) };
}
function parseColor(colorStr) {
if (colorStr === 'transparent') {
return { r: 0, g: 0, b: 0, a: 0 };
}
const rgbaMatch = colorStr.match(/^rgba\((\d+),\s*(\d+),\s*(\d+),\s*([\d.]+)\)$/);
if (rgbaMatch) {
const alpha = parseFloat(rgbaMatch[4]);
if (alpha === 0) return { r: 0, g: 0, b: 0, a: 0 };
return {
r: parseInt(rgbaMatch[1], 10),
g: parseInt(rgbaMatch[2], 10),
b: parseInt(rgbaMatch[3], 10),
a: alpha
};
}
const rgbMatch = colorStr.match(/^rgb\((\d+),\s*(\d+),\s*(\d+)\)$/);
if (rgbMatch) {
return {
r: parseInt(rgbMatch[1], 10),
g: parseInt(rgbMatch[2], 10),
b: parseInt(rgbMatch[3], 10)
};
}
const hexMatch = colorStr.match(/^#?([a-f\d]{1,2})([a-f\d]{1,2})([a-f\d]{1,2})$/i);
if (hexMatch) {
const expand = (hex) => hex.length === 1 ? hex + hex : hex;
return {
r: parseInt(expand(hexMatch[1]), 16),
g: parseInt(expand(hexMatch[2]), 16),
b: parseInt(expand(hexMatch[3]), 16)
};
}
const canvas = document.createElement('canvas');
canvas.width = 1;
canvas.height = 1;
const ctx = canvas.getContext('2d');
ctx.fillStyle = colorStr;
ctx.fillRect(0, 0, 1, 1);
const [r, g, b, a] = ctx.getImageData(0, 0, 1, 1).data;
return { r, g, b, a: a / 255 };
}
function isFullyTransparent(parsedColor) {
return parsedColor.a !== undefined && parsedColor.a === 0;
}
/* ---------------------------------------------------------------------------
CORE LOGIC
--------------------------------------------------------------------------- */
function processElement(element) {
if (!element.tagName) return;
if (['IMG', 'VIDEO', 'CANVAS', 'SVG', 'A'].includes(element.tagName)) return;
const computedStyle = window.getComputedStyle(element);
const bgColorStr = computedStyle.backgroundColor;
const textColorStr = computedStyle.color;
// Handle transparent backgrounds
if (bgColorStr === 'transparent' ||
bgColorStr.startsWith('rgba') && /,\s*0+(\.0*)?\)$/.test(bgColorStr)) {
const textRgb = parseColor(textColorStr);
if (textRgb && !isFullyTransparent(textRgb)) {
const textL = getRelativeLuminance(textRgb.r, textRgb.g, textRgb.b);
if (textL < 0.5) {
element.style.setProperty('color', '#e0e0e0', 'important');
}
}
return;
}
if (['INPUT', 'TEXTAREA', 'SELECT'].includes(element.tagName)) {
element.style.setProperty('background-color', '#121212', 'important');
const textRgb = parseColor(textColorStr);
if (textRgb) {
const textL = getRelativeLuminance(textRgb.r, textRgb.g, textRgb.b);
if (textL < 0.5) {
element.style.setProperty('color', '#e0e0e0', 'important');
}
}
return;
}
const bgRgb = parseColor(bgColorStr);
if (!bgRgb || isFullyTransparent(bgRgb)) return;
const [h, s, l] = rgbToHsl(bgRgb.r, bgRgb.g, bgRgb.b);
const bgL = getRelativeLuminance(bgRgb.r, bgRgb.g, bgRgb.b);
if (bgL > 0.4) {
const newRgb = hslToRgb(h, s, 0.15);
element.style.setProperty('background-color', `rgb(${newRgb.r}, ${newRgb.g}, ${newRgb.b})`, 'important');
}
const textRgb = parseColor(textColorStr);
if (textRgb && !isFullyTransparent(textRgb)) {
const textL = getRelativeLuminance(textRgb.r, textRgb.g, textRgb.b);
if (textL < 0.5) {
element.style.setProperty('color', '#e0e0e0', 'important');
}
}
}
function processPseudoElements() {
const style = document.createElement('style');
style.textContent = `
html { background-color: #121212 !important; }
a { color: #62a0ea !important; }
a:visited { color: #c061cb !important; }
img, video, canvas, svg {
filter: brightness(0.8) contrast(1.1) !important;
-webkit-filter: brightness(0.8) contrast(1.1) !important;
}
iframe {
filter: invert(90%) hue-rotate(180deg) brightness(1.1) !important;
}
`;
document.head.appendChild(style);
}
function processIframes() {
document.querySelectorAll('iframe').forEach(iframe => {
try {
const iframeDoc = iframe.contentDocument || iframe.contentWindow.document;
if (iframeDoc) {
const style = iframeDoc.createElement('style');
style.textContent = `
html { background-color: #121212 !important; }
* { background-color: inherit !important; color: inherit !important; border-color: #555 !important; }
`;
iframeDoc.head.appendChild(style);
}
} catch (e) {
iframe.style.filter = 'invert(90%) hue-rotate(180deg) brightness(1.1)';
}
});
}
/* ---------------------------------------------------------------------------
INITIALIZATION
--------------------------------------------------------------------------- */
const darkModeEnabled = localStorage.getItem('smartDarkModeEnabled') === 'true' ||
window.matchMedia('(prefers-color-scheme: dark)').matches;
if (darkModeEnabled) {
processPseudoElements();
const processAll = () => {
document.querySelectorAll('*').forEach(processElement);
processIframes();
};
if (document.readyState === 'loading') {
document.addEventListener('DOMContentLoaded', processAll);
} else {
processAll();
}
const observer = new MutationObserver(mutations => {
clearTimeout(observer.timer);
observer.timer = setTimeout(() => {
mutations.forEach(mutation => {
mutation.addedNodes.forEach(node => {
if (node.nodeType === 1) {
processElement(node);
node.querySelectorAll('*').forEach(processElement);
}
});
});
processIframes();
}, 100);
});
observer.observe(document.documentElement, {
childList: true,
subtree: true
});
}
document.addEventListener('keydown', e => {
if (e.ctrlKey && e.shiftKey && e.key === 'D') {
e.preventDefault();
const enabled = localStorage.getItem('smartDarkModeEnabled') === 'true';
localStorage.setItem('smartDarkModeEnabled', !enabled);
location.reload();
}
});
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment