Fix sites w/white scroll bars on a light theme, vice versa!
// ==UserScript==
// @name Scrollbar Customizer
// @namespace https://greasyfork.org/en/users/922168-mark-zinzow
// @version 11.1
// @description Fix sites w/white scroll bars on a light theme, vice versa!
// @author Mark Zinzow
// @match *://*/*
// @exclude *://chromewebstore.google.com/*
// @exclude chrome://*
// @grant GM_registerMenuCommand
// @grant GM_setValue
// @grant GM_getValue
// @grant GM_addStyle
// @run-at document-start
// @license MIT
// ==/UserScript==
/* jshint esversion: 9 */
/* eslint-disable no-multi-spaces */
(function() {
'use strict';
// ====================================================================
// 1. GLOBAL SETTINGS & SITE CONFIG
// ====================================================================
const DEFAULT_CONFIG = {
enabled: false, // Default is OFF, so no green bars on fresh sites!
width: 16,
outlineWidth: 2,
outlineColor: '#00FF00',
thumbColor: 'transparent',
trackColor: 'transparent'
};
const currentDomain = window.location.hostname;
let storage = GM_getValue('scrollbarConfig_v11', {});
let siteConfig = storage[currentDomain] || { ...DEFAULT_CONFIG };
let globalTheme = GM_getValue('globalUITheme', 'dark');
// ====================================================================
// 2. CSS ENGINE (Scoped)
// ====================================================================
// FIX: We preface every rule with [data-usb-enabled="true"]
// This guarantees the CSS is dormant until JS activates it.
const cssSkeleton = `
:root {
/* Variables still exist but do nothing unless used */
--usb-width: 16px;
--usb-outline-width: 2px;
--usb-outline-color: #00FF00;
--usb-thumb-color: transparent;
--usb-track-color: transparent;
}
/* FIREFOX & STANDARD: Only apply if enabled */
[data-usb-enabled="true"] * {
scrollbar-width: auto !important;
scrollbar-color: var(--usb-outline-color) var(--usb-track-color) !important;
}
/* WEBKIT: Chrome, Edge, Safari */
/* Main Window */
[data-usb-enabled="true"] ::-webkit-scrollbar {
display: block !important;
width: var(--usb-width) !important;
height: var(--usb-width) !important;
background: var(--usb-track-color) !important;
}
/* Internal Divs */
[data-usb-enabled="true"] *::-webkit-scrollbar {
display: block !important;
width: var(--usb-width) !important;
height: var(--usb-width) !important;
background: var(--usb-track-color) !important;
}
/* Track */
[data-usb-enabled="true"] ::-webkit-scrollbar-track,
[data-usb-enabled="true"] *::-webkit-scrollbar-track {
background: var(--usb-track-color) !important;
}
/* Thumb */
[data-usb-enabled="true"] ::-webkit-scrollbar-thumb,
[data-usb-enabled="true"] *::-webkit-scrollbar-thumb {
background-color: var(--usb-thumb-color) !important;
background-clip: padding-box !important;
border: var(--usb-outline-width) solid var(--usb-outline-color) !important;
border-radius: 0px !important;
}
/* Hover */
[data-usb-enabled="true"] ::-webkit-scrollbar-thumb:hover,
[data-usb-enabled="true"] *::-webkit-scrollbar-thumb:hover {
background-color: var(--usb-outline-color) !important;
border-color: var(--usb-outline-color) !important;
}
/* Corner */
[data-usb-enabled="true"] ::-webkit-scrollbar-corner,
[data-usb-enabled="true"] *::-webkit-scrollbar-corner {
background: var(--usb-track-color) !important;
}
`;
GM_addStyle(cssSkeleton);
function updatePageVariables(config) {
const root = document.documentElement;
// 1. The Master Switch (Fixes the Bug)
// If disabled, we remove the attribute, and the CSS above stops matching.
if (config.enabled) {
root.setAttribute('data-usb-enabled', 'true');
} else {
root.removeAttribute('data-usb-enabled');
return; // Stop here, no need to update variables if disabled
}
// 2. Update Variables
root.style.setProperty('--usb-width', config.width + 'px');
root.style.setProperty('--usb-outline-width', config.outlineWidth + 'px');
root.style.setProperty('--usb-outline-color', config.outlineColor);
root.style.setProperty('--usb-thumb-color', config.thumbColor);
root.style.setProperty('--usb-track-color', config.trackColor);
}
// Apply immediately on load
updatePageVariables(siteConfig);
function saveSiteConfig(newConfig) {
siteConfig = newConfig;
storage[currentDomain] = siteConfig;
GM_setValue('scrollbarConfig_v11', storage);
updatePageVariables(siteConfig);
}
// ====================================================================
// 3. MENU "PANIC BUTTONS"
// ====================================================================
GM_registerMenuCommand("⚡ Force: Black on White", () => {
saveSiteConfig({
enabled: true,
width: 24,
outlineWidth: 0,
outlineColor: '#000000',
thumbColor: '#000000',
trackColor: '#FFFFFF'
});
});
GM_registerMenuCommand("⚡ Force: White on Black", () => {
saveSiteConfig({
enabled: true,
width: 24,
outlineWidth: 0,
outlineColor: '#FFFFFF',
thumbColor: '#FFFFFF',
trackColor: '#000000'
});
});
GM_registerMenuCommand("⚙️ Customize Scrollbars (UI)", createUI);
// ====================================================================
// 4. UI BUILDER
// ====================================================================
function createEl(tag, styles = {}, parent = null) {
const el = document.createElement(tag);
for (const [key, value] of Object.entries(styles)) {
el.style[key] = value;
}
if (parent) parent.appendChild(el);
return el;
}
const PALETTE_COLORS = ['#000000', '#FFFFFF', '#808080', '#C0C0C0', '#FF0000', '#00FF00', '#0000FF', '#FFFF00', '#00FFFF', '#FF00FF'];
function createControlGroup(labelText, value, parent, isColor = false, isTransparentSupported = false) {
const wrapper = createEl('div', { marginBottom: '12px', paddingBottom: '5px', borderBottom: '1px solid var(--border)' }, parent);
const header = createEl('div', { display: 'flex', justifyContent: 'space-between', marginBottom: '5px' }, wrapper);
createEl('span', { fontSize: '12px', color: 'var(--text-dim)' }, header).textContent = labelText;
let input, chkTransparent;
if (isColor) {
const controls = createEl('div', { display: 'flex', gap: '8px', alignItems: 'center' }, header);
input = createEl('input', { width: '40px', height: '25px', border: '1px solid #777', cursor: 'pointer', padding: '0', background: 'none' }, controls);
input.type = 'color';
input.title = "Open System Picker (Eye Dropper)";
input.value = (value === 'transparent') ? '#000000' : value;
if (isTransparentSupported) {
const lbl = createEl('label', { fontSize: '10px', display: 'flex', alignItems: 'center', gap: '3px', color: 'var(--text)' }, controls);
chkTransparent = createEl('input', {}, lbl);
chkTransparent.type = 'checkbox';
chkTransparent.checked = (value === 'transparent');
createEl('span', {}, lbl).textContent = 'None';
const toggle = () => { input.disabled = chkTransparent.checked; input.style.opacity = chkTransparent.checked ? '0.3' : '1'; };
chkTransparent.addEventListener('change', toggle);
toggle();
}
const palContainer = createEl('div', { display: 'flex', gap: '4px', marginBottom: '8px', flexWrap: 'wrap' }, wrapper);
PALETTE_COLORS.forEach(color => {
const swatch = createEl('div', { width: '18px', height: '18px', background: color, border: '1px solid #555', cursor: 'pointer' }, palContainer);
swatch.onclick = () => {
input.value = color;
if (chkTransparent) { chkTransparent.checked = false; input.disabled = false; input.style.opacity = '1'; }
input.dispatchEvent(new Event('input'));
};
});
} else {
input = createEl('input', { width: '50px', background: 'var(--input-bg)', color: 'var(--text)', border: '1px solid #777', padding: '2px' }, header);
input.type = 'number';
input.value = value;
}
return { input, chkTransparent };
}
let uiContainer = null;
function createUI() {
if (uiContainer) { uiContainer.remove(); uiContainer = null; return; }
const host = createEl('div', { position: 'fixed', top: '10px', right: '10px', zIndex: '2147483647', fontFamily: 'sans-serif' });
document.body.appendChild(host);
uiContainer = host;
const shadow = host.attachShadow({ mode: 'open' });
const themeStyles = globalTheme === 'dark'
? { bg: '#1a1a1a', text: '#ffffff', textDim: '#cccccc', inputBg: '#333', border: '#444' }
: { bg: '#f4f4f4', text: '#000000', textDim: '#333333', inputBg: '#ffffff', border: '#cccccc' };
const panel = createEl('div', {
background: themeStyles.bg, color: themeStyles.text,
padding: '15px', border: `2px solid ${themeStyles.border}`, borderRadius: '8px', width: '280px',
boxShadow: '0 10px 40px rgba(0,0,0,0.5)',
'--text': themeStyles.text, '--text-dim': themeStyles.textDim, '--input-bg': themeStyles.inputBg, '--border': themeStyles.border
}, shadow);
const headRow = createEl('div', { display: 'flex', justifyContent: 'space-between', alignItems: 'center', marginBottom: '10px', borderBottom: `1px solid ${themeStyles.border}`, paddingBottom: '5px' }, panel);
createEl('h3', { margin: '0', fontSize: '14px', textTransform: 'uppercase', color: 'var(--text-dim)' }, headRow).textContent = 'Scrollbar Config';
const btnTheme = createEl('button', { background: 'none', border: 'none', cursor: 'pointer', fontSize: '16px' }, headRow);
btnTheme.textContent = globalTheme === 'dark' ? '☀️' : '🌙';
btnTheme.title = "Switch Panel Theme";
btnTheme.onclick = () => {
globalTheme = globalTheme === 'dark' ? 'light' : 'dark';
GM_setValue('globalUITheme', globalTheme);
host.remove(); uiContainer = null; createUI();
};
const presetGrid = createEl('div', { display: 'flex', gap: '8px', marginBottom: '15px', justifyContent: 'space-between' }, panel);
const presets = [
{ name: 'Outline', w: 16, out: 2, outC: '#00FF00', thC: 'transparent', trC: 'transparent' },
{ name: 'Neon', w: 20, out: 0, outC: '#FFFF00', thC: '#FFFF00', trC: '#000000' },
{ name: 'Classic', w: 16, out: 1, outC: '#808080', thC: '#C0C0C0', trC: '#FFFFFF' },
{ name: 'BnW', w: 18, out: 0, outC: '#000000', thC: '#000000', trC: '#FFFFFF' },
{ name: 'WnB', w: 18, out: 0, outC: '#FFFFFF', thC: '#FFFFFF', trC: '#000000' },
];
presets.forEach(p => {
const btn = createEl('button', { flex: '1', height: '35px', padding: '0', background: '#333', border: '1px solid #555', cursor: 'pointer', borderRadius: '4px', position: 'relative', overflow: 'hidden' }, presetGrid);
btn.title = p.name;
createEl('div', { width: '100%', height: '100%', background: '#444' }, btn);
const track = createEl('div', { position: 'absolute', top: '2px', bottom: '2px', right: '4px', width: '10px', background: (p.trC === 'transparent') ? 'none' : p.trC, border: (p.trC === 'transparent') ? '1px dashed #666' : 'none' }, btn);
createEl('div', { position: 'absolute', top: '20%', height: '40%', left: '0', right: '0', backgroundColor: (p.thC === 'transparent') ? 'transparent' : p.thC, border: `${p.out}px solid ${p.outC}`, boxSizing: 'border-box' }, track);
btn.onclick = () => {
inpEnable.checked = true;
ctrlWidth.input.value = p.w;
ctrlOutlineW.input.value = p.out;
ctrlOutlineC.input.value = p.outC;
if (p.thC === 'transparent') { ctrlThumbC.chkTransparent.checked = true; ctrlThumbC.input.disabled = true; }
else { ctrlThumbC.chkTransparent.checked = false; ctrlThumbC.input.disabled = false; ctrlThumbC.input.value = p.thC; }
if (p.trC === 'transparent') { ctrlTrackC.chkTransparent.checked = true; ctrlTrackC.input.disabled = true; }
else { ctrlTrackC.chkTransparent.checked = false; ctrlTrackC.input.disabled = false; ctrlTrackC.input.value = p.trC; }
updateLive();
};
});
const rowEnable = createEl('div', { marginBottom: '15px', display: 'flex', alignItems: 'center', gap: '10px', color: 'var(--text)' }, panel);
const inpEnable = createEl('input', { transform: 'scale(1.2)' }, rowEnable);
inpEnable.type = 'checkbox';
inpEnable.checked = siteConfig.enabled;
createEl('span', { fontWeight: 'bold' }, rowEnable).textContent = 'Enable Custom Scrollbar';
const ctrlWidth = createControlGroup('Total Width (px)', siteConfig.width, panel);
const ctrlOutlineW = createControlGroup('Outline Thickness (px)', siteConfig.outlineWidth, panel);
const ctrlOutlineC = createControlGroup('Outline Color', siteConfig.outlineColor, panel, true, false);
const ctrlThumbC = createControlGroup('Thumb Fill', siteConfig.thumbColor, panel, true, true);
const ctrlTrackC = createControlGroup('Track Background', siteConfig.trackColor, panel, true, true);
const btnSave = createEl('button', { width: '100%', padding: '10px', marginTop: '10px', background: '#00d26a', border: 'none', borderRadius: '4px', fontWeight: 'bold', cursor: 'pointer', color: '#000' }, panel);
btnSave.textContent = 'Save Configuration';
function getValues() {
return {
enabled: inpEnable.checked,
width: ctrlWidth.input.value,
outlineWidth: ctrlOutlineW.input.value,
outlineColor: ctrlOutlineC.input.value,
thumbColor: ctrlThumbC.chkTransparent.checked ? 'transparent' : ctrlThumbC.input.value,
trackColor: ctrlTrackC.chkTransparent.checked ? 'transparent' : ctrlTrackC.input.value
};
}
function updateLive() { updatePageVariables(getValues()); }
panel.addEventListener('input', updateLive);
panel.addEventListener('change', updateLive);
btnSave.addEventListener('click', () => { saveSiteConfig(getValues()); host.remove(); uiContainer = null; });
}
})();