Skip to content

Instantly share code, notes, and snippets.

@gblazex
Forked from jph00/contrastive.js
Last active July 9, 2024 11:55
Show Gist options
  • Save gblazex/bf2902eeb3be45fae868717f21a6df9a to your computer and use it in GitHub Desktop.
Save gblazex/bf2902eeb3be45fae868717f21a6df9a to your computer and use it in GitHub Desktop.
Chrome tampermonkey helper for the elderly
// ==UserScript==
// @name Keyboard Shortcut Scripts
// @namespace http://tampermonkey.net/
// @version 0.1
// @description Run scripts with keyboard shortcuts
// @match *://*/*
// @grant none
// ==/UserScript==
// Parameters
const targetContrastRatio = 5.0; // WCAG 2.0 AA
const maxTextSaturation = 45; // Limit excessive colors (bad on light bg)
const backgroundColor = [255, 255, 255]; // rgb 0-255
// something with less blue wavelength could be used at night:
// e.g. [255, 243, 220];
// Color conversion functions
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 * 360, s * 100, l * 100];
}
function hslToRgb(h, s, l) {
h /= 360;
s /= 100;
l /= 100;
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 [Math.round(r * 255), Math.round(g * 255), Math.round(b * 255)];
}
function getLuminance(r, g, b) {
const a = [r, g, b].map(v => {
v /= 255;
return v <= 0.03928 ? v / 12.92 : Math.pow((v + 0.055) / 1.055, 2.4);
});
return a[0] * 0.2126 + a[1] * 0.7152 + a[2] * 0.0722;
}
function getContrastRatio(rgb1, rgb2) {
const lum1 = getLuminance(...rgb1);
const lum2 = getLuminance(...rgb2);
const brightest = Math.max(lum1, lum2);
const darkest = Math.min(lum1, lum2);
return (brightest + 0.05) / (darkest + 0.05);
}
function isNearlyGrayscale(rgb) {
const range = Math.max(...rgb) - Math.min(...rgb);
return range <= 13; // 5% of 255
}
// Helper functions
function getRGB(color) {
const rgb = color.match(/\d+/g);
return rgb ? rgb.map(x => parseInt(x)) : [255, 255, 255];
}
function rgbToString(rgb) {
return `rgb(${rgb[0]}, ${rgb[1]}, ${rgb[2]})`;
}
function hasBackgroundColor(element) {
const bgColor = window.getComputedStyle(element).backgroundColor;
return bgColor && bgColor !== 'rgba(0, 0, 0, 0)' && bgColor !== 'transparent';
}
// Adjust color for better contrast with a given background color
// Gray colors target a higher contrast ratio
// Saturation is increased linearly as lightness decreases below 50
function adjustColor(rgb, bgRgb, colorContrastRatio = 4.5, grayContrastRatio = 6.5) {
grayContrastRatio = grayContrastRatio || colorContrastRatio + 2;
let [h, s, l] = rgbToHsl(...rgb);
const isGray = isNearlyGrayscale(rgb);
const adjustmentRatio = isGray ? grayContrastRatio : colorContrastRatio;
const originalS = s;
const originalL = l;
while (l >= 0 && l <= 100) {
if (!isGray) {
// Saturation increases linearly as lightness decreases below 50
// But all saturation is capped at 50% to prevent excessive colors
// (which looks bad on a light background)
s = Math.min(maxTextSaturation, originalS + Math.max(0, 50 - l));
}
const adjustedRgb = hslToRgb(h, s, l);
const ratio = getContrastRatio(adjustedRgb, bgRgb);
if (ratio >= adjustmentRatio) {
return adjustedRgb;
}
l -= 1;
}
return rgb; // Return original if no suitable adjustment found
}
function hasTextNodeChild(el) {
let some = Array.prototype.some;
return el.childNodes.length > 0 &&
some.call(el.childNodes, ch => ch.nodeType === Node.TEXT_NODE);
}
// State variables
let isBackgroundModeActive = false;
let isTextModeActive = false;
let observer = null;
// Main styling functions
function setLightBackground(element) {
if (hasBackgroundColor(element)) {
element.style.setProperty('background-color', rgbToString(backgroundColor), 'important');
}
}
function setDarkTextOnLightBackground(element) {
const color = window.getComputedStyle(element).color;
const rgb = getRGB(color);
const bgRgb = backgroundColor;
const ratio = getContrastRatio(rgb, bgRgb);
if (ratio < targetContrastRatio) {
const adjustedRgb = adjustColor(rgb, bgRgb, targetContrastRatio);
element.style.setProperty('color', rgbToString(adjustedRgb), 'important');
}
}
// Apply styles to all elements
function applyBackgroundToAllElements() {
document.querySelectorAll('*').forEach(setLightBackground);
}
function applyTextColorToAllElements() {
document.querySelectorAll('*').forEach(el =>
hasTextNodeChild(el) && setDarkTextOnLightBackground(el));
}
// Mutation observer
function createObserver() {
if (observer) return; // Observer already exists
observer = new MutationObserver((mutations) => {
mutations.forEach((mutation) => {
if (mutation.type === 'childList') {
mutation.addedNodes.forEach((node) => {
if (node.nodeType === Node.ELEMENT_NODE) {
applyActiveStylesToElement(node);
node.querySelectorAll('*').forEach(applyActiveStylesToElement);
}
});
}
});
});
observer.observe(document.body, { childList: true, subtree: true });
}
function applyActiveStylesToElement(element) {
if (isBackgroundModeActive) {
setLightBackground(element);
}
if (isTextModeActive) {
setDarkTextOnLightBackground(element);
}
}
// Keyboard event listener
document.addEventListener('keydown', function(e) {
if (e.ctrlKey && e.shiftKey) {
if (e.keyCode === 49) { // Key code for '1'
isBackgroundModeActive = true;
applyBackgroundToAllElements();
createObserver();
} else if (e.keyCode === 50) { // Key code for '2'
isTextModeActive = true;
applyTextColorToAllElements();
createObserver();
}
}
});
// Initial state: DOM mutation observer is off, colors are not changed
//console.log('Script loaded. Use Ctrl+Shift+1 to toggle background modifications or Ctrl+Shift+2 to toggle text color modifications.');
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment