Forum Settings
Forums
New
Jun 4, 2016 11:40 PM
#1

Offline
Jun 2009
29
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.
Reply Disabled for Non-Club Members
Reply Disabled for Non-Club Members

More topics from this board

» Zealotus's MAL Themes

Zealotus - Dec 22, 2008

3 by Zealotus »»
Apr 27, 2021 5:31 PM

» MAL Greasemonkey Userscripts

DeathfireD - Oct 23, 2008

44 by hacker09 »»
Mar 24, 2021 6:10 PM

Sticky: » MAL Themes

5hreddy - Oct 23, 2008

9 by koetemagie »»
Feb 24, 2020 5:28 AM

» Metricx Themes (Userstyles) - Update May 12, 2016 ( 1 2 )

Batmeow - Feb 19, 2013

76 by Batmeow »»
Apr 5, 2017 9:46 PM

» MAL redesign theme

Katgrills - Jul 7, 2016

4 by Katgrills »»
Sep 30, 2016 2:21 AM
It’s time to ditch the text file.
Keep track of your anime easily by creating your own list.
Sign Up Login