Mark Dark Text Light in Dark Mode v1.2.0
← Back to User Scripts
Script Content
// ==UserScript==
// @name Zendesk: Mark Dark Text Light in Dark Mode
// @namespace https://www.timhilton.xyz/user-scripts
// @version 1.2.0
// @description Adjusts dark text to light colour in Zendesk tickets when dark mode is active.
// @author Tim Hilton using GitHub Copilot
// @match https://*.zendesk.com/agent/tickets/*
// @grant none
// ==/UserScript==
(function() {
'use strict';
const LOG_PREFIX = '[Zendesk: Mark Dark Text Light in Dark Mode]';
const LIGHT_TEXT_COLOR = 'rgb(216, 220, 222)';
const DARK_TEXT_CLASS = 'zendesk-dark-text-adjusted';
const LIGHT_BG_CLASS = 'zendesk-light-bg-adjusted';
console.debug(`${LOG_PREFIX} Initializing script`);
// Inject CSS that only applies in dark mode
const style = document.createElement('style');
style.textContent = `
section[data-theme="dark"] .${DARK_TEXT_CLASS} {
color: ${LIGHT_TEXT_COLOR} !important;
}
section[data-theme="dark"] .${LIGHT_BG_CLASS} {
background-color: transparent !important;
}
section[data-theme="dark"] .${DARK_TEXT_CLASS} code,
section[data-theme="dark"] .${DARK_TEXT_CLASS} pre {
color: rgb(0, 0, 0) !important;
}
`;
document.head.appendChild(style);
console.debug(`${LOG_PREFIX} Styles injected`);
function isTextDark(element) {
const color = window.getComputedStyle(element).color;
// Parse RGB values
const rgbMatch = color.match(/\d+/g);
if (!rgbMatch) {
return false; // If colour can't be parsed (e.g., transparent, named colours), assume not dark
}
const rgb = rgbMatch.map(Number);
// Calculate relative luminance (WCAG formula)
const [r, g, b] = rgb.map(val => {
val = val / 255;
return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4);
});
const luminance = 0.2126 * r + 0.7152 * g + 0.0722 * b;
// Luminance threshold - adjust as needed (0.5 is middle grey)
return luminance < 0.5;
}
function isBackgroundLight(element) {
const bgColor = window.getComputedStyle(element).backgroundColor;
// rgba(0, 0, 0, 0) is transparent — no explicit background set
if (!bgColor || bgColor === 'transparent' || bgColor === 'rgba(0, 0, 0, 0)') {
return false;
}
// Parse RGB values
const rgbMatch = bgColor.match(/\d+(\.\d+)?/g);
if (!rgbMatch || rgbMatch.length < 3) {
return false;
}
// If alpha is 0, treat as transparent
if (rgbMatch.length >= 4 && parseFloat(rgbMatch[3]) === 0) {
return false;
}
const [r, g, b] = rgbMatch.slice(0, 3).map(Number);
// Calculate relative luminance (WCAG formula)
const [rL, gL, bL] = [r, g, b].map(val => {
val = val / 255;
return val <= 0.03928 ? val / 12.92 : Math.pow((val + 0.055) / 1.055, 2.4);
});
const luminance = 0.2126 * rL + 0.7152 * gL + 0.0722 * bL;
// Luminance threshold — >0.5 is considered a light background
return luminance > 0.5;
}
function adjustDarkText() {
console.debug(`${LOG_PREFIX} Adjusting dark text`);
// Find all comment elements (in any theme)
const commentElements = document.querySelectorAll('[data-test-id="omni-log-omni-to-ag-comment"]');
console.debug(`${LOG_PREFIX} Found ${commentElements.length} comment elements`);
let adjustedCount = 0;
commentElements.forEach(commentElement => {
// Get text-containing elements within the comment
const textElements = commentElement.querySelectorAll('p, span, div, h1, h2, h3, h4, h5, h6, li, td, th, table, tr, tbody, thead, tfoot, strong, em, b, i, font');
textElements.forEach(element => {
// Only process elements that have text content and are visible
if (element.textContent.trim() && element.offsetParent !== null) {
// Check if element already has the class to avoid redundant checks
if (!element.classList.contains(DARK_TEXT_CLASS)) {
if (isTextDark(element)) {
element.classList.add(DARK_TEXT_CLASS);
adjustedCount++;
}
}
if (!element.classList.contains(LIGHT_BG_CLASS)) {
if (isBackgroundLight(element)) {
element.classList.add(LIGHT_BG_CLASS);
adjustedCount++;
}
}
}
});
});
if (adjustedCount > 0) {
console.log(`${LOG_PREFIX} ✅ Adjusted ${adjustedCount} elements (text colour and/or background)`);
}
}
// Use MutationObserver to handle dynamic content and navigation
let timeoutId = null;
let observerFireCount = 0;
const observer = new MutationObserver(() => {
observerFireCount++;
console.debug(`${LOG_PREFIX} MutationObserver fired (count: ${observerFireCount})`);
// Debounce to avoid excessive executions
if (timeoutId) {
clearTimeout(timeoutId);
}
timeoutId = setTimeout(() => {
adjustDarkText();
}, 100); // Wait 100ms after last mutation before processing
});
console.debug(`${LOG_PREFIX} Setting up MutationObserver`);
observer.observe(document.body, {
childList: true,
subtree: true
});
// Initial run
console.debug(`${LOG_PREFIX} Running initial adjustment`);
adjustDarkText();
console.log(`${LOG_PREFIX} 🎉 Script initialized successfully`);
})();