So it was my free time and some of depression from manually re-adjusting dark themes of some sites when they're constantly updating their layout. So I wrote script that runs on node.js framework with js-beautify module to prepare basic dark theme for any website (for MAL for example).
Feel free to use it.
// ==UserScript==
// @description Automatically darkifies CSS
// @example-command "node colors.js > new.css"
// @include http://localhost/*
// ==/UserScript==
(function(){
var filename = 'c:\\temp\\original_mal_css-s.css'; // path to a combined CSS file
var white = {r:170,g:170,b:170}; // color to replace pure black
var black = {r:33,g:33,b:33}; // color to replace pure white
// V is a Value from HSV color scheme (https://en.wikipedia.org/wiki/HSL_and_HSV)
var v_trigger_high = .6; // Value triger for bright colors
var v_trigger_low = .2;// Value triger for dark colors
var v_replace_high = .23;// Value replacement for bright colors
var v_replace_low = .8;// Value replacement for dark colors
var pretext = 'body, textarea, input {\n background-color: #222 !important;\n color: #aaa !important;\n}\n\n';
function cleanupCSS(text){
text = text.replace(/;{2,}/g,';');
text = text.replace(/ {4,}color/gi,'====color');
text = text.replace(/ {4,}background/gi,'====background');
text = text.replace(/ {4,}border/gi,'====border');
text = text.replace(/ {4,}[^\n]+\n/gi, '');
text = text.replace(/\n[^\{\}]+\{\s*\}/gmi,''); // remove empty definitions
text = text.replace(/====/gi,' ');
text = text.replace(/([^; ])(\n})/gi,'$1;$2');
text = text.replace(/ {4,}(border-width: 0(px)? 0(px)? 0(px)? 0(px)?.+\n)/gi,'');
text = text.replace(/ {4,}(background-position.+)\n/gi,'/*$1*/\n'); // keep the data for later use
text = text.replace(/ {4,}(background-image.+)\n/gi,'/*$1*/\n');
text = text.replace(/ {4,}(background-repeat.+)\n/gi,'/*$1*/\n');
text = text.replace(/ {4,}(background-size.+)\n/gi,'/*$1*/\n');
text = text.replace(/ {4,}(border-radius.+)\n/gi,'/*$1*/\n');
text = text.replace(/( {4,}[^\n]+)white([^\n]+\n)/gi,'$1#FFF$2');
text = text.replace(/( {4,}[^\n]+)black([^\n]+\n)/gi,'$1#000$2');
text = text.replace(/( {4,}[^\n]+)red([^\n]+\n)/gi,'$1#F00$2');
text = text.replace(/( {4,}[^\n]+)green([^\n]+\n)/gi,'$1#0F0$2');
text = text.replace(/( {4,}[^\n]+)blue([^\n]+\n)/gi,'$1#00F$2');
text = text.replace(/(\ba\b[^{]+\{[^\}]+)#(\d+[^\}]+\})/gi,'$1dont-parse-me-placeholder$2'); // mark active links because their color often neutral to a dark theme
return text;
}
function removeComments(text) {
var uid = '_' + Date.now(),
e_strings = [],
index = 0;
return (
text.replace(/(['"])(\\\1|.)+?\1/g, function(match){
e_strings[index] = match;
return (uid + '') + index++;
})
.replace(/\/\/.*?\/?\*.+?(?=\n|\r|$)|\/\*[\s\S]*?\/\/[\s\S]*?\*\//g, '')
.replace(/\/\/.+?(?=\n|\r|$)|\/\*[\s\S]+?\*\//g, '')
.replace(new RegExp('\\/\\*[\\s\\S]+' + uid + '\\d+', 'g'), '')
.replace(new RegExp(uid + '(\\d+)', 'g'), function(match, n){
return e_strings[n];
})
);
}
function restoreSpecial(text){
return text.replace(/dont-parse-me-placeholder/gi,'#');
}
function makeImportant(text){
text = text.replace(/;\n/gi,' !important;\n')
text = text.replace(/\s*!important !important/gi,' !important');
text = text.replace(/\*\/ !important;/gi,'*/');
return text;
}
function RGBtoHSV(r, g, b) {
if (arguments.length === 1)
g = r.g, b = r.b, r = r.r;
var max = Math.max(r, g, b), min = Math.min(r, g, b),
d = max - min,
h,
s = (max === 0 ? 0 : d / max),
v = max / 255;
switch (max) {
case min: h = 0; break;
case r: h = (g - b) + d * (g < b ? 6: 0); h /= 6 * d; break;
case g: h = (b - r) + d * 2; h /= 6 * d; break;
case b: h = (r - g) + d * 4; h /= 6 * d; break;
}
return {
h: h,
s: s,
v: v
};
}
function HSVtoRGB(h, s, v) {
var r, g, b, i, f, p, q, t;
if (arguments.length === 1)
s = h.s, v = h.v, h = h.h;
i = Math.floor(h * 6);
f = h * 6 - i;
p = v * (1 - s);
q = v * (1 - f * s);
t = v * (1 - (1 - f) * s);
switch (i % 6) {
case 0: r = v, g = t, b = p; break;
case 1: r = q, g = v, b = p; break;
case 2: r = p, g = v, b = t; break;
case 3: r = p, g = q, b = v; break;
case 4: r = t, g = p, b = v; break;
case 5: r = v, g = p, b = q; break;
}
return {
r: Math.round(r * 255),
g: Math.round(g * 255),
b: Math.round(b * 255)
};
}
function format(num, places) {
var s = "00" + num.toString(16);
return s.substr(s.length-places);
}
function removeDuplicates(arr) {
var i, len = arr.length,
out = [],
obj = {};
for (i = 0; i < len; i++)
obj[arr[i]] = 0;
for (i in obj)
out.push(i);
return out;
}
function replaceColorScheme(text) {
var col_array = text.match(/ {4,}.+\#[a-f0-9]+\b/gi);
if (col_array) {
col_array = removeDuplicates(col_array);
//console.log(JSON.stringify(col_array, null, 4));
for (var r,g,b,h,s,v,p,i = col_array.length - 1; i >= 0; i--) {
// gradients will break as we process only first color on a line so skipping them
if (col_array[i].match(/gradient/i))
continue;
col_str = col_array[i].match(/\#[a-f0-9]+\b/i);
if (col_str)
col_str = col_str[0].substr(1);
else
continue;
//console.log(col_str);
if (col_str.length === 3) {
r = parseInt(col_str.substring(0, 1),16) * 15;
g = parseInt(col_str.substring(1, 2),16) * 15;
b = parseInt(col_str.substring(2, 3),16) * 15;
} else if (col_str.length === 6) {
r = parseInt(col_str.substring(0, 2),16);
g = parseInt(col_str.substring(2, 4),16);
b = parseInt(col_str.substring(4, 6),16);
} else
continue;
var col = RGBtoHSV(r, g, b);
if (r === 0 && g === 0 && b === 0) col = white;
else if (r === 255 && g === 255 && b === 255) col = black;
else {
if (col.v > v_trigger_high) col.v = v_replace_high;
else if (col.v < v_trigger_low) col.v = v_replace_low ;
col = HSVtoRGB(col);
}
color = '===='+format(col.r, 2)+format(col.g, 2)+format(col.b, 2); // ==== to prevent recursion
// to prevent messing with overlapping id selectors
text = text.replace(new RegExp('('+col_array[i].substr(0, col_array[i].indexOf('#')+1)+')'+col_str+'\\b','ig'), '$1'+color);
//console.log('('+col_array[i].substr(0, col_array[i].indexOf('#')+1)+')'+col_str+'\\b' + ' :: ' + '$1'+color);
} //for
text = text.replace(/\#====/gi, '#');
}
col_array = text.match(/rgba\(\d+, *\d+, *\d+,/gi);
if (col_array) {
col_array = removeDuplicates(col_array);
for (var r,g,b,h,s,v, i = col_array.length - 1; i >= 0; i--) {
r = parseInt(col_array[i].match(/\d+/g)[0],16);
g = parseInt(col_array[i].match(/\d+/g)[1],16);
b = parseInt(col_array[i].match(/\d+/g)[2],16);
var col = RGBtoHSV(r, g, b);
if (r === 0 && g === 0 && b === 0) col = white;
else if (r === 255 && g === 255 && b === 255) col = black;
else {
if (col.v > v_trigger_high) col.v = v_replace_high;
else if (col.v < v_trigger_low) col.v = v_replace_low ;
col = HSVtoRGB(col);
}
color = 'rgba('+col.r+', '+col.g+', '+col.b+', ';
text = text.replace(new RegExp(col_array[i].replace('(','\\('),'ig'), color);
} //for
}
return text;
}
if (typeof require !== 'undefined') {
beautify = require('js-beautify').css;
if (!beautify) throw 'js-beautify module not installed...';
fs = require('fs');
fs.readFile(filename, 'utf8', function (err,data) {
if (err) return console.log(err);
data = removeComments(data);
data = beautify(data, { indent_size: 4, selector_separator_newline: false });
data = cleanupCSS(data);
data = makeImportant(data);
data = replaceColorScheme(data);
data = restoreSpecial(data);
data = pretext + data;
console.log(data);
});
}
}());
ErgoSisJun 5, 2016 2:59 PM
A story has no beginning or end; arbitrarily one chooses that moment of experience from which to look back or from which to look ahead.