/**
 * jscolor - JavaScript Color Picker
 *
 * @link    http://jscolor.com
 * @license For open source use: GPLv3
 *          For commercial use: JSColor Commercial License
 * @author  Jan Odvarko - East Desire
 * @version 2.4.6
 *
 * See usage examples at http://jscolor.com/examples/
 */


(function (global, factory) {

    'use strict';

    if (typeof module === 'object' && typeof module.exports === 'object') {
        // Export jscolor as a module
        module.exports = global.document ?
            factory (global) :
            function (win) {
                if (!win.document) {
                    throw new Error('jscolor needs a window with document');
                }
                return factory(win);
            }
        return;
    }

    // Default use (no module export)
    factory(global);

})(typeof window !== 'undefined' ? window : this, function (window) { // BEGIN factory

// BEGIN jscolor code


'use strict';


var jscolor = (function () { // BEGIN jscolor

var jsc = {


    initialized : false,

    instances : [], // created instances of jscolor

    readyQueue : [], // functions waiting to be called after init


    register : function () {
        if (typeof window !== 'undefined' && window.document) {
            window.document.addEventListener('DOMContentLoaded', jsc.pub.init, false);
        }
    },


    installBySelector : function (selector, rootNode) {
        rootNode = rootNode ? jsc.node(rootNode) : window.document;
        if (!rootNode) {
            throw new Error('Missing root node');
        }

        var elms = rootNode.querySelectorAll(selector);

        // for backward compatibility with DEPRECATED installation/configuration using className
        var matchClass = new RegExp('(^|\\s)(' + jsc.pub.lookupClass + ')(\\s*(\\{[^}]*\\})|\\s|$)', 'i');

        for (var i = 0; i < elms.length; i += 1) {

            if (elms[i].jscolor && elms[i].jscolor instanceof jsc.pub) {
                continue; // jscolor already installed on this element
            }

            if (elms[i].type !== undefined && elms[i].type.toLowerCase() == 'color' && jsc.isColorAttrSupported) {
                continue; // skips inputs of type 'color' if supported by the browser
            }

            var dataOpts, m;

            if (
                (dataOpts = jsc.getDataAttr(elms[i], 'jscolor')) !== null ||
                (elms[i].className && (m = elms[i].className.match(matchClass))) // installation using className (DEPRECATED)
            ) {
                var targetElm = elms[i];

                var optsStr = '';
                if (dataOpts !== null) {
                    optsStr = dataOpts;

                } else if (m) { // installation using className (DEPRECATED)
                    console.warn('Installation using class name is DEPRECATED. Use data-jscolor="" attribute instead.' + jsc.docsRef);
                    if (m[4]) {
                        optsStr = m[4];
                    }
                }

                var opts = null;
                if (optsStr.trim()) {
                    try {
                        opts = jsc.parseOptionsStr(optsStr);
                    } catch (e) {
                        console.warn(e + '\n' + optsStr);
                    }
                }

                try {
                    new jsc.pub(targetElm, opts);
                } catch (e) {
                    console.warn(e);
                }
            }
        }
    },


    parseOptionsStr : function (str) {
        var opts = null;

        try {
            opts = JSON.parse(str);

        } catch (eParse) {
            if (!jsc.pub.looseJSON) {
                throw new Error('Could not parse jscolor options as JSON: ' + eParse);
            } else {
                // loose JSON syntax is enabled -> try to evaluate the options string as JavaScript object
                try {
                    opts = (new Function ('var opts = (' + str + '); return typeof opts === "object" ? opts : {};'))();
                } catch (eEval) {
                    throw new Error('Could not evaluate jscolor options: ' + eEval);
                }
            }
        }
        return opts;
    },


    getInstances : function () {
        var inst = [];
        for (var i = 0; i < jsc.instances.length; i += 1) {
            // if the targetElement still exists, the instance is considered "alive"
            if (jsc.instances[i] && jsc.instances[i].targetElement) {
                inst.push(jsc.instances[i]);
            }
        }
        return inst;
    },


    createEl : function (tagName) {
        var el = window.document.createElement(tagName);
        jsc.setData(el, 'gui', true);
        return el;
    },


    node : function (nodeOrSelector) {
        if (!nodeOrSelector) {
            return null;
        }

        if (typeof nodeOrSelector === 'string') {
            // query selector
            var sel = nodeOrSelector;
            var el = null;
            try {
                el = window.document.querySelector(sel);
            } catch (e) {
                console.warn(e);
                return null;
            }
            if (!el) {
                console.warn('No element matches the selector: %s', sel);
            }
            return el;
        }

        if (jsc.isNode(nodeOrSelector)) {
            // DOM node
            return nodeOrSelector;
        }

        console.warn('Invalid node of type %s: %s', typeof nodeOrSelector, nodeOrSelector);
        return null;
    },


    // See https://stackoverflow.com/questions/384286/
    isNode : function (val) {
        if (typeof Node === 'object') {
            return val instanceof Node;
        }
        return val && typeof val === 'object' && typeof val.nodeType === 'number' && typeof val.nodeName === 'string';
    },


    nodeName : function (node) {
        if (node && node.nodeName) {
            return node.nodeName.toLowerCase();
        }
        return false;
    },


    removeChildren : function (node) {
        while (node.firstChild) {
            node.removeChild(node.firstChild);
        }
    },


    isTextInput : function (el) {
        return el && jsc.nodeName(el) === 'input' && el.type.toLowerCase() === 'text';
    },


    isButton : function (el) {
        if (!el) {
            return false;
        }
        var n = jsc.nodeName(el);
        return (
            (n === 'button') ||
            (n === 'input' && ['button', 'submit', 'reset'].indexOf(el.type.toLowerCase()) > -1)
        );
    },


    isButtonEmpty : function (el) {
        switch (jsc.nodeName(el)) {
            case 'input': return (!el.value || el.value.trim() === '');
            case 'button': return (el.textContent.trim() === '');
        }
        return null; // could not determine element's text
    },


    // See https://github.com/WICG/EventListenerOptions/blob/gh-pages/explainer.md
    isPassiveEventSupported : (function () {
        var supported = false;

        try {
            var opts = Object.defineProperty({}, 'passive', {
                get: function () { supported = true; }
            });
            window.addEventListener('testPassive', null, opts);
            window.removeEventListener('testPassive', null, opts);
        } catch (e) {}

        return supported;
    })(),


    isColorAttrSupported : (function () {
        var elm = window.document.createElement('input');
        if (elm.setAttribute) {
            elm.setAttribute('type', 'color');
            if (elm.type.toLowerCase() == 'color') {
                return true;
            }
        }
        return false;
    })(),


    dataProp : '_data_jscolor',


    // usage:
    //   setData(obj, prop, value)
    //   setData(obj, {prop:value, ...})
    //
    setData : function () {
        var obj = arguments[0];

        if (arguments.length === 3) {
            // setting a single property
            var data = obj.hasOwnProperty(jsc.dataProp) ? obj[jsc.dataProp] : (obj[jsc.dataProp] = {});
            var prop = arguments[1];
            var value = arguments[2];

            data[prop] = value;
            return true;

        } else if (arguments.length === 2 && typeof arguments[1] === 'object') {
            // setting multiple properties
            var data = obj.hasOwnProperty(jsc.dataProp) ? obj[jsc.dataProp] : (obj[jsc.dataProp] = {});
            var map = arguments[1];

            for (var prop in map) {
                if (map.hasOwnProperty(prop)) {
                    data[prop] = map[prop];
                }
            }
            return true;
        }

        throw new Error('Invalid arguments');
    },


    // usage:
    //   removeData(obj, prop, [prop...])
    //
    removeData : function () {
        var obj = arguments[0];
        if (!obj.hasOwnProperty(jsc.dataProp)) {
            return true; // data object does not exist
        }
        for (var i = 1; i < arguments.length; i += 1) {
            var prop = arguments[i];
            delete obj[jsc.dataProp][prop];
        }
        return true;
    },


    getData : function (obj, prop, setDefault) {
        if (!obj.hasOwnProperty(jsc.dataProp)) {
            // data object does not exist
            if (setDefault !== undefined) {
                obj[jsc.dataProp] = {}; // create data object
            } else {
                return undefined; // no value to return
            }
        }
        var data = obj[jsc.dataProp];

        if (!data.hasOwnProperty(prop) && setDefault !== undefined) {
            data[prop] = setDefault;
        }
        return data[prop];
    },


    getDataAttr : function (el, name) {
        var attrName = 'data-' + name;
        var attrValue = el.getAttribute(attrName);
        return attrValue;
    },


    setDataAttr : function (el, name, value) {
        var attrName = 'data-' + name;
        el.setAttribute(attrName, value);
    },


    _attachedGroupEvents : {},


    attachGroupEvent : function (groupName, el, evnt, func) {
        if (!jsc._attachedGroupEvents.hasOwnProperty(groupName)) {
            jsc._attachedGroupEvents[groupName] = [];
        }
        jsc._attachedGroupEvents[groupName].push([el, evnt, func]);
        el.addEventListener(evnt, func, false);
    },


    detachGroupEvents : function (groupName) {
        if (jsc._attachedGroupEvents.hasOwnProperty(groupName)) {
            for (var i = 0; i < jsc._attachedGroupEvents[groupName].length; i += 1) {
                var evt = jsc._attachedGroupEvents[groupName][i];
                evt[0].removeEventListener(evt[1], evt[2], false);
            }
            delete jsc._attachedGroupEvents[groupName];
        }
    },


    preventDefault : function (e) {
        if (e.preventDefault) { e.preventDefault(); }
        e.returnValue = false;
    },


    captureTarget : function (target) {
        // IE
        if (target.setCapture) {
            jsc._capturedTarget = target;
            jsc._capturedTarget.setCapture();
        }
    },


    releaseTarget : function () {
        // IE
        if (jsc._capturedTarget) {
            jsc._capturedTarget.releaseCapture();
            jsc._capturedTarget = null;
        }
    },


    triggerEvent : function (el, eventName, bubbles, cancelable) {
        if (!el) {
            return;
        }

        var ev = null;

        if (typeof Event === 'function') {
            ev = new Event(eventName, {
                bubbles: bubbles,
                cancelable: cancelable
            });
        } else {
            // IE
            ev = window.document.createEvent('Event');
            ev.initEvent(eventName, bubbles, cancelable);
        }

        if (!ev) {
            return false;
        }

        // so that we know that the event was triggered internally
        jsc.setData(ev, 'internal', true);

        el.dispatchEvent(ev);
        return true;
    },


    triggerInputEvent : function (el, eventName, bubbles, cancelable) {
        if (!el) {
            return;
        }
        if (jsc.isTextInput(el)) {
            jsc.triggerEvent(el, eventName, bubbles, cancelable);
        }
    },


    eventKey : function (ev) {
        var keys = {
            9: 'Tab',
            13: 'Enter',
            27: 'Escape',
        };
        if (typeof ev.code === 'string') {
            return ev.code;
        } else if (ev.keyCode !== undefined && keys.hasOwnProperty(ev.keyCode)) {
            return keys[ev.keyCode];
        }
        return null;
    },


    strList : function (str) {
        if (!str) {
            return [];
        }
        return str.replace(/^\s+|\s+$/g, '').split(/\s+/);
    },


    // The className parameter (str) can only contain a single class name
    hasClass : function (elm, className) {
        if (!className) {
            return false;
        }
        if (elm.classList !== undefined) {
            return elm.classList.contains(className);
        }
        // polyfill
        return -1 != (' ' + elm.className.replace(/\s+/g, ' ') + ' ').indexOf(' ' + className + ' ');
    },


    // The className parameter (str) can contain multiple class names separated by whitespace
    addClass : function (elm, className) {
        var classNames = jsc.strList(className);

        if (elm.classList !== undefined) {
            for (var i = 0; i < classNames.length; i += 1) {
                elm.classList.add(classNames[i]);
            }
            return;
        }
        // polyfill
        for (var i = 0; i < classNames.length; i += 1) {
            if (!jsc.hasClass(elm, classNames[i])) {
                elm.className += (elm.className ? ' ' : '') + classNames[i];
            }
        }
    },


    // The className parameter (str) can contain multiple class names separated by whitespace
    removeClass : function (elm, className) {
        var classNames = jsc.strList(className);

        if (elm.classList !== undefined) {
            for (var i = 0; i < classNames.length; i += 1) {
                elm.classList.remove(classNames[i]);
            }
            return;
        }
        // polyfill
        for (var i = 0; i < classNames.length; i += 1) {
            var repl = new RegExp(
                '^\\s*' + classNames[i] + '\\s*|' +
                '\\s*' + classNames[i] + '\\s*$|' +
                '\\s+' + classNames[i] + '(\\s+)',
                'g'
            );
            elm.className = elm.className.replace(repl, '$1');
        }
    },


    getCompStyle : function (elm) {
        var compStyle = window.getComputedStyle ? window.getComputedStyle(elm) : elm.currentStyle;

        // Note: In Firefox, getComputedStyle returns null in a hidden iframe,
        // that's why we need to check if the returned value is non-empty
        if (!compStyle) {
            return {};
        }
        return compStyle;
    },


    // Note:
    //   Setting a property to NULL reverts it to the state before it was first set
    //   with the 'reversible' flag enabled
    //
    setStyle : function (elm, styles, important, reversible) {
        // using '' for standard priority (IE10 apparently doesn't like value undefined)
        var priority = important ? 'important' : '';
        var origStyle = null;

        for (var prop in styles) {
            if (styles.hasOwnProperty(prop)) {
                var setVal = null;

                if (styles[prop] === null) {
                    // reverting a property value

                    if (!origStyle) {
                        // get the original style object, but dont't try to create it if it doesn't exist
                        origStyle = jsc.getData(elm, 'origStyle');
                    }
                    if (origStyle && origStyle.hasOwnProperty(prop)) {
                        // we have property's original value -> use it
                        setVal = origStyle[prop];
                    }

                } else {
                    // setting a property value

                    if (reversible) {
                        if (!origStyle) {
                            // get the original style object and if it doesn't exist, create it
                            origStyle = jsc.getData(elm, 'origStyle', {});
                        }
                        if (!origStyle.hasOwnProperty(prop)) {
                            // original property value not yet stored -> store it
                            origStyle[prop] = elm.style[prop];
                        }
                    }
                    setVal = styles[prop];
                }

                if (setVal !== null) {
                    elm.style.setProperty(prop, setVal, priority);
                }
            }
        }
    },


    hexColor : function (r, g, b) {
        return '#' + (
            ('0' + Math.round(r).toString(16)).substr(-2) +
            ('0' + Math.round(g).toString(16)).substr(-2) +
            ('0' + Math.round(b).toString(16)).substr(-2)
        ).toUpperCase();
    },


    hexaColor : function (r, g, b, a) {
        return '#' + (
            ('0' + Math.round(r).toString(16)).substr(-2) +
            ('0' + Math.round(g).toString(16)).substr(-2) +
            ('0' + Math.round(b).toString(16)).substr(-2) +
            ('0' + Math.round(a * 255).toString(16)).substr(-2)
        ).toUpperCase();
    },


    rgbColor : function (r, g, b) {
        return 'rgb(' +
            Math.round(r) + ',' +
            Math.round(g) + ',' +
            Math.round(b) +
        ')';
    },


    rgbaColor : function (r, g, b, a) {
        return 'rgba(' +
            Math.round(r) + ',' +
            Math.round(g) + ',' +
            Math.round(b) + ',' +
            (Math.round((a===undefined || a===null ? 1 : a) * 100) / 100) +
        ')';
    },


    linearGradient : (function () {

        function getFuncName () {
            var stdName = 'linear-gradient';
            var prefixes = ['', '-webkit-', '-moz-', '-o-', '-ms-'];
            var helper = window.document.createElement('div');

            for (var i = 0; i < prefixes.length; i += 1) {
                var tryFunc = prefixes[i] + stdName;
                var tryVal = tryFunc + '(to right, rgba(0,0,0,0), rgba(0,0,0,0))';

                helper.style.background = tryVal;
                if (helper.style.background) { // CSS background successfully set -> function name is supported
                    return tryFunc;
                }
            }
            return stdName; // fallback to standard 'linear-gradient' without vendor prefix
        }

        var funcName = getFuncName();

        return function () {
            return funcName + '(' + Array.prototype.join.call(arguments, ', ') + ')';
        };

    })(),


    setBorderRadius : function (elm, value) {
        jsc.setStyle(elm, {'border-radius' : value || '0'});
    },


    setBoxShadow : function (elm, value) {
        jsc.setStyle(elm, {'box-shadow': value || 'none'});
    },


    getElementPos : function (e, relativeToViewport) {
        var x=0, y=0;
        var rect = e.getBoundingClientRect();
        x = rect.left;
        y = rect.top;
        if (!relativeToViewport) {
            var viewPos = jsc.getViewPos();
            x += viewPos[0];
            y += viewPos[1];
        }
        return [x, y];
    },


    getElementSize : function (e) {
        return [e.offsetWidth, e.offsetHeight];
    },


    // get pointer's X/Y coordinates relative to viewport
    getAbsPointerPos : function (e) {
        var x = 0, y = 0;
        if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) {
            // touch devices
            x = e.changedTouches[0].clientX;
            y = e.changedTouches[0].clientY;
        } else if (typeof e.clientX === 'number') {
            x = e.clientX;
            y = e.clientY;
        }
        return { x: x, y: y };
    },


    // get pointer's X/Y coordinates relative to target element
    getRelPointerPos : function (e) {
        var target = e.target || e.srcElement;
        var targetRect = target.getBoundingClientRect();

        var x = 0, y = 0;

        var clientX = 0, clientY = 0;
        if (typeof e.changedTouches !== 'undefined' && e.changedTouches.length) {
            // touch devices
            clientX = e.changedTouches[0].clientX;
            clientY = e.changedTouches[0].clientY;
        } else if (typeof e.clientX === 'number') {
            clientX = e.clientX;
            clientY = e.clientY;
        }

        x = clientX - targetRect.left;
        y = clientY - targetRect.top;
        return { x: x, y: y };
    },


    getViewPos : function () {
        var doc = window.document.documentElement;
        return [
            (window.pageXOffset || doc.scrollLeft) - (doc.clientLeft || 0),
            (window.pageYOffset || doc.scrollTop) - (doc.clientTop || 0)
        ];
    },


    getViewSize : function () {
        var doc = window.document.documentElement;
        return [
            (window.innerWidth || doc.clientWidth),
            (window.innerHeight || doc.clientHeight),
        ];
    },


    // r: 0-255
    // g: 0-255
    // b: 0-255
    //
    // returns: [ 0-360, 0-100, 0-100 ]
    //
    RGB_HSV : function (r, g, b) {
        r /= 255;
        g /= 255;
        b /= 255;
        var n = Math.min(Math.min(r,g),b);
        var v = Math.max(Math.max(r,g),b);
        var m = v - n;
        if (m === 0) { return [ null, 0, 100 * v ]; }
        var h = r===n ? 3+(b-g)/m : (g===n ? 5+(r-b)/m : 1+(g-r)/m);
        return [
            60 * (h===6?0:h),
            100 * (m/v),
            100 * v
        ];
    },


    // h: 0-360
    // s: 0-100
    // v: 0-100
    //
    // returns: [ 0-255, 0-255, 0-255 ]
    //
    HSV_RGB : function (h, s, v) {
        var u = 255 * (v / 100);

        if (h === null) {
            return [ u, u, u ];
        }

        h /= 60;
        s /= 100;

        var i = Math.floor(h);
        var f = i%2 ? h-i : 1-(h-i);
        var m = u * (1 - s);
        var n = u * (1 - s * f);
        switch (i) {
            case 6:
            case 0: return [u,n,m];
            case 1: return [n,u,m];
            case 2: return [m,u,n];
            case 3: return [m,n,u];
            case 4: return [n,m,u];
            case 5: return [u,m,n];
        }
    },


    parseColorString : function (str) {
        var ret = {
            rgba: null,
            format: null // 'hex' | 'hexa' | 'rgb' | 'rgba'
        };

        var m;

        if (m = str.match(/^\W*([0-9A-F]{3,8})\W*$/i)) {
            // HEX notation

            if (m[1].length === 8) {
                // 8-char notation (= with alpha)
                ret.format = 'hexa';
                ret.rgba = [
                    parseInt(m[1].substr(0,2),16),
                    parseInt(m[1].substr(2,2),16),
                    parseInt(m[1].substr(4,2),16),
                    parseInt(m[1].substr(6,2),16) / 255
                ];

            } else if (m[1].length === 6) {
                // 6-char notation
                ret.format = 'hex';
                ret.rgba = [
                    parseInt(m[1].substr(0,2),16),
                    parseInt(m[1].substr(2,2),16),
                    parseInt(m[1].substr(4,2),16),
                    null
                ];

            } else if (m[1].length === 3) {
                // 3-char notation
                ret.format = 'hex';
                ret.rgba = [
                    parseInt(m[1].charAt(0) + m[1].charAt(0),16),
                    parseInt(m[1].charAt(1) + m[1].charAt(1),16),
                    parseInt(m[1].charAt(2) + m[1].charAt(2),16),
                    null
                ];

            } else {
                return false;
            }

            return ret;
        }

        if (m = str.match(/^\W*rgba?\(([^)]*)\)\W*$/i)) {
            // rgb(...) or rgba(...) notation

            var par = m[1].split(',');
            var re = /^\s*(\d+|\d*\.\d+|\d+\.\d*)\s*$/;
            var mR, mG, mB, mA;
            if (
                par.length >= 3 &&
                (mR = par[0].match(re)) &&
                (mG = par[1].match(re)) &&
                (mB = par[2].match(re))
            ) {
                ret.format = 'rgb';
                ret.rgba = [
                    parseFloat(mR[1]) || 0,
                    parseFloat(mG[1]) || 0,
                    parseFloat(mB[1]) || 0,
                    null
                ];

                if (
                    par.length >= 4 &&
                    (mA = par[3].match(re))
                ) {
                    ret.format = 'rgba';
                    ret.rgba[3] = parseFloat(mA[1]) || 0;
                }
                return ret;
            }
        }

        return false;
    },


    parsePaletteValue : function (mixed) {
        var vals = [];

        if (typeof mixed === 'string') { // input is a string of space separated color values
            // rgb() and rgba() may contain spaces too, so let's find all color values by regex
            mixed.replace(/#[0-9A-F]{3}([0-9A-F]{3})?|rgba?\(([^)]*)\)/ig, function (val) {
                vals.push(val);
            });
        } else if (Array.isArray(mixed)) { // input is an array of color values
            vals = mixed;
        }

        // convert all values into uniform color format

        var colors = [];

        for (var i = 0; i < vals.length; i++) {
            var color = jsc.parseColorString(vals[i]);
            if (color) {
                colors.push(color);
            }
        }

        return colors;
    },


    containsTranparentColor : function (colors) {
        for (var i = 0; i < colors.length; i++) {
            var a = colors[i].rgba[3];
            if (a !== null && a < 1.0) {
                return true;
            }
        }
        return false;
    },


    isAlphaFormat : function (format) {
        switch (format.toLowerCase()) {
        case 'hexa':
        case 'rgba':
            return true;
        }
        return false;
    },


    // Canvas scaling for retina displays
    //
    // adapted from https://www.html5rocks.com/en/tutorials/canvas/hidpi/
    //
    scaleCanvasForHighDPR : function (canvas) {
        var dpr = window.devicePixelRatio || 1;
        canvas.width *= dpr;
        canvas.height *= dpr;
        var ctx = canvas.getContext('2d');
        ctx.scale(dpr, dpr);
    },


    genColorPreviewCanvas : function (color, separatorPos, specWidth, scaleForHighDPR) {

        var sepW = Math.round(jsc.pub.previewSeparator.length);
        var sqSize = jsc.pub.chessboardSize;
        var sqColor1 = jsc.pub.chessboardColor1;
        var sqColor2 = jsc.pub.chessboardColor2;

        var cWidth = specWidth ? specWidth : sqSize * 2;
        var cHeight = sqSize * 2;

        var canvas = jsc.createEl('canvas');
        var ctx = canvas.getContext('2d');

        canvas.width = cWidth;
        canvas.height = cHeight;
        if (scaleForHighDPR) {
            jsc.scaleCanvasForHighDPR(canvas);
        }

        // transparency chessboard - background
        ctx.fillStyle = sqColor1;
        ctx.fillRect(0, 0, cWidth, cHeight);

        // transparency chessboard - squares
        ctx.fillStyle = sqColor2;
        for (var x = 0; x < cWidth; x += sqSize * 2) {
            ctx.fillRect(x, 0, sqSize, sqSize);
            ctx.fillRect(x + sqSize, sqSize, sqSize, sqSize);
        }

        if (color) {
            // actual color in foreground
            ctx.fillStyle = color;
            ctx.fillRect(0, 0, cWidth, cHeight);
        }

        var start = null;
        switch (separatorPos) {
            case 'left':
                start = 0;
                ctx.clearRect(0, 0, sepW/2, cHeight);
                break;
            case 'right':
                start = cWidth - sepW;
                ctx.clearRect(cWidth - (sepW/2), 0, sepW/2, cHeight);
                break;
        }
        if (start !== null) {
            ctx.lineWidth = 1;
            for (var i = 0; i < jsc.pub.previewSeparator.length; i += 1) {
                ctx.beginPath();
                ctx.strokeStyle = jsc.pub.previewSeparator[i];
                ctx.moveTo(0.5 + start + i, 0);
                ctx.lineTo(0.5 + start + i, cHeight);
                ctx.stroke();
            }
        }

        return {
            canvas: canvas,
            width: cWidth,
            height: cHeight,
        };
    },


    // if position or width is not set => fill the entire element (0%-100%)
    genColorPreviewGradient : function (color, position, width) {
        var params = [];

        if (position && width) {
            params = [
                'to ' + {'left':'right', 'right':'left'}[position],
                color + ' 0%',
                color + ' ' + width + 'px',
                'rgba(0,0,0,0) ' + (width + 1) + 'px',
                'rgba(0,0,0,0) 100%',
            ];
        } else {
            params = [
                'to right',
                color + ' 0%',
                color + ' 100%',
            ];
        }

        return jsc.linearGradient.apply(this, params);
    },


    redrawPosition : function () {

        if (!jsc.picker || !jsc.picker.owner) {
            return; // picker is not shown
        }

        var thisObj = jsc.picker.owner;

        var tp, vp;

        if (thisObj.fixed) {
            // Fixed elements are positioned relative to viewport,
            // therefore we can ignore the scroll offset
            tp = jsc.getElementPos(thisObj.targetElement, true); // target pos
            vp = [0, 0]; // view pos
        } else {
            tp = jsc.getElementPos(thisObj.targetElement); // target pos
            vp = jsc.getViewPos(); // view pos
        }

        var ts = jsc.getElementSize(thisObj.targetElement); // target size
        var vs = jsc.getViewSize(); // view size
        var pd = jsc.getPickerDims(thisObj);
        var ps = [pd.borderW, pd.borderH]; // picker outer size
        var a, b, c;
        switch (thisObj.position.toLowerCase()) {
            case 'left': a=1; b=0; c=-1; break;
            case 'right':a=1; b=0; c=1; break;
            case 'top':  a=0; b=1; c=-1; break;
            default:     a=0; b=1; c=1; break;
        }
        var l = (ts[b]+ps[b])/2;

        // compute picker position
        if (!thisObj.smartPosition) {
            var pp = [
                tp[a],
                tp[b]+ts[b]-l+l*c
            ];
        } else {
            var pp = [
                -vp[a]+tp[a]+ps[a] > vs[a] ?
                    (-vp[a]+tp[a]+ts[a]/2 > vs[a]/2 && tp[a]+ts[a]-ps[a] >= 0 ? tp[a]+ts[a]-ps[a] : tp[a]) :
                    tp[a],
                -vp[b]+tp[b]+ts[b]+ps[b]-l+l*c > vs[b] ?
                    (-vp[b]+tp[b]+ts[b]/2 > vs[b]/2 && tp[b]+ts[b]-l-l*c >= 0 ? tp[b]+ts[b]-l-l*c : tp[b]+ts[b]-l+l*c) :
                    (tp[b]+ts[b]-l+l*c >= 0 ? tp[b]+ts[b]-l+l*c : tp[b]+ts[b]-l-l*c)
            ];
        }

        var x = pp[a];
        var y = pp[b];
        var positionValue = thisObj.fixed ? 'fixed' : 'absolute';
        var contractShadow =
            (pp[0] + ps[0] > tp[0] || pp[0] < tp[0] + ts[0]) &&
            (pp[1] + ps[1] < tp[1] + ts[1]);

        jsc._drawPosition(thisObj, x, y, positionValue, contractShadow);
    },


    _drawPosition : function (thisObj, x, y, positionValue, contractShadow) {
        var vShadow = contractShadow ? 0 : thisObj.shadowBlur; // px

        jsc.picker.wrap.style.position = positionValue;
        jsc.picker.wrap.style.left = x + 'px';
        jsc.picker.wrap.style.top = y + 'px';

        jsc.setBoxShadow(
            jsc.picker.boxS,
            thisObj.shadow ?
                new jsc.BoxShadow(0, vShadow, thisObj.shadowBlur, 0, thisObj.shadowColor) :
                null);
    },


    getPickerDims : function (thisObj) {
        var w = 2 * thisObj.controlBorderWidth + thisObj.width;
        var h = 2 * thisObj.controlBorderWidth + thisObj.height;

        var sliderSpace = 2 * thisObj.controlBorderWidth + 2 * jsc.getControlPadding(thisObj) + thisObj.sliderSize;

        if (jsc.getSliderChannel(thisObj)) {
            w += sliderSpace;
        }
        if (thisObj.hasAlphaChannel()) {
            w += sliderSpace;
        }

        var pal = jsc.getPaletteDims(thisObj, w);

        if (pal.height) {
            h += pal.height + thisObj.padding;
        }
        if (thisObj.closeButton) {
            h += 2 * thisObj.controlBorderWidth + thisObj.padding + thisObj.buttonHeight;
        }

        var pW = w + (2 * thisObj.padding);
        var pH = h + (2 * thisObj.padding);

        return {
            contentW: w,
            contentH: h,
            paddedW: pW,
            paddedH: pH,
            borderW: pW + (2 * thisObj.borderWidth),
            borderH: pH + (2 * thisObj.borderWidth),
            palette: pal,
        };
    },


    getPaletteDims : function (thisObj, width) {
        var cols = 0, rows = 0, cellW = 0, cellH = 0, height = 0;
        var sampleCount = thisObj._palette ? thisObj._palette.length : 0;

        if (sampleCount) {
            cols = thisObj.paletteCols;
            rows = cols > 0 ? Math.ceil(sampleCount / cols) : 0;

            // color sample's dimensions (includes border)
            cellW = Math.max(1, Math.floor((width - ((cols - 1) * thisObj.paletteSpacing)) / cols));
            cellH = thisObj.paletteHeight ? Math.min(thisObj.paletteHeight, cellW) : cellW;
        }

        if (rows) {
            height =
                rows * cellH +
                (rows - 1) * thisObj.paletteSpacing;
        }

        return {
            cols: cols,
            rows: rows,
            cellW: cellW,
            cellH: cellH,
            width: width,
            height: height,
        };
    },


    getControlPadding : function (thisObj) {
        return Math.max(
            thisObj.padding / 2,
            (2 * thisObj.pointerBorderWidth + thisObj.pointerThickness) - thisObj.controlBorderWidth
        );
    },


    getPadYChannel : function (thisObj) {
        switch (thisObj.mode.charAt(1).toLowerCase()) {
            case 'v': return 'v'; break;
        }
        return 's';
    },


    getSliderChannel : function (thisObj) {
        if (thisObj.mode.length > 2) {
            switch (thisObj.mode.charAt(2).toLowerCase()) {
                case 's': return 's'; break;
                case 'v': return 'v'; break;
            }
        }
        return null;
    },


    // calls function specified in picker's property
    triggerCallback : function (thisObj, prop) {
        if (!thisObj[prop]) {
            return; // callback func not specified
        }
        var callback = null;

        if (typeof thisObj[prop] === 'string') {
            // string with code
            try {
                callback = new Function (thisObj[prop]);
            } catch (e) {
                console.error(e);
            }
        } else {
            // function
            callback = thisObj[prop];
        }

        if (callback) {
            callback.call(thisObj);
        }
    },


    // Triggers a color change related event(s) on all picker instances.
    // It is possible to specify multiple events separated with a space.
    triggerGlobal : function (eventNames) {
        var inst = jsc.getInstances();
        for (var i = 0; i < inst.length; i += 1) {
            inst[i].trigger(eventNames);
        }
    },


    _pointerMoveEvent : {
        mouse: 'mousemove',
        touch: 'touchmove'
    },
    _pointerEndEvent : {
        mouse: 'mouseup',
        touch: 'touchend'
    },


    _pointerOrigin : null,
    _capturedTarget : null,


    onDocumentKeyUp : function (e) {
        if (['Tab', 'Escape'].indexOf(jsc.eventKey(e)) !== -1) {
            if (jsc.picker && jsc.picker.owner) {
                jsc.picker.owner.tryHide();
            }
        }
    },


    onWindowResize : function (e) {
        jsc.redrawPosition();
    },


    onWindowScroll : function (e) {
        jsc.redrawPosition();
    },


    onParentScroll : function (e) {
        // hide the picker when one of the parent elements is scrolled
        if (jsc.picker && jsc.picker.owner) {
            jsc.picker.owner.tryHide();
        }
    },


    onDocumentMouseDown : function (e) {
        var target = e.target || e.srcElement;

        if (target.jscolor && target.jscolor instanceof jsc.pub) { // clicked targetElement -> show picker
            if (target.jscolor.showOnClick && !target.disabled) {
                target.jscolor.show();
            }
        } else if (jsc.getData(target, 'gui')) { // clicked jscolor's GUI element
            var control = jsc.getData(target, 'control');
            if (control) {
                // jscolor's control
                jsc.onControlPointerStart(e, target, jsc.getData(target, 'control'), 'mouse');
            }
        } else {
            // mouse is outside the picker's controls -> hide the color picker!
            if (jsc.picker && jsc.picker.owner) {
                jsc.picker.owner.tryHide();
            }
        }
    },


    onPickerTouchStart : function (e) {
        var target = e.target || e.srcElement;

        if (jsc.getData(target, 'control')) {
            jsc.onControlPointerStart(e, target, jsc.getData(target, 'control'), 'touch');
        }
    },


    onControlPointerStart : function (e, target, controlName, pointerType) {
        var thisObj = jsc.getData(target, 'instance');

        jsc.preventDefault(e);
        jsc.captureTarget(target);

        var registerDragEvents = function (doc, offset) {
            jsc.attachGroupEvent('drag', doc, jsc._pointerMoveEvent[pointerType],
                jsc.onDocumentPointerMove(e, target, controlName, pointerType, offset));
            jsc.attachGroupEvent('drag', doc, jsc._pointerEndEvent[pointerType],
                jsc.onDocumentPointerEnd(e, target, controlName, pointerType));
        };

        registerDragEvents(window.document, [0, 0]);

        if (window.parent && window.frameElement) {
            var rect = window.frameElement.getBoundingClientRect();
            var ofs = [-rect.left, -rect.top];
            registerDragEvents(window.parent.window.document, ofs);
        }

        var abs = jsc.getAbsPointerPos(e);
        var rel = jsc.getRelPointerPos(e);
        jsc._pointerOrigin = {
            x: abs.x - rel.x,
            y: abs.y - rel.y
        };

        switch (controlName) {
        case 'pad':
            // if the value slider is at the bottom, move it up
            if (jsc.getSliderChannel(thisObj) === 'v' && thisObj.channels.v === 0) {
                thisObj.fromHSVA(null, null, 100, null);
            }
            jsc.setPad(thisObj, e, 0, 0);
            break;

        case 'sld':
            jsc.setSld(thisObj, e, 0);
            break;

        case 'asld':
            jsc.setASld(thisObj, e, 0);
            break;
        }
        thisObj.trigger('input');
    },


    onDocumentPointerMove : function (e, target, controlName, pointerType, offset) {
        return function (e) {
            var thisObj = jsc.getData(target, 'instance');
            switch (controlName) {
            case 'pad':
                jsc.setPad(thisObj, e, offset[0], offset[1]);
                break;

            case 'sld':
                jsc.setSld(thisObj, e, offset[1]);
                break;

            case 'asld':
                jsc.setASld(thisObj, e, offset[1]);
                break;
            }
            thisObj.trigger('input');
        }
    },


    onDocumentPointerEnd : function (e, target, controlName, pointerType) {
        return function (e) {
            var thisObj = jsc.getData(target, 'instance');
            jsc.detachGroupEvents('drag');
            jsc.releaseTarget();

            // Always trigger changes AFTER detaching outstanding mouse handlers,
            // in case some color change that occured in user-defined onChange/onInput handler
            // intruded into current mouse events
            thisObj.trigger('input');
            thisObj.trigger('change');
        };
    },


    onPaletteSampleClick : function (e) {
        var target = e.currentTarget;
        var thisObj = jsc.getData(target, 'instance');
        var color = jsc.getData(target, 'color');

        // when format is flexible, use the original format of this color sample
        if (thisObj.format.toLowerCase() === 'any') {
            thisObj._setFormat(color.format); // adapt format
            if (!jsc.isAlphaFormat(thisObj.getFormat())) {
                color.rgba[3] = 1.0; // when switching to a format that doesn't support alpha, set full opacity
            }
        }

        // if this color doesn't specify alpha, use alpha of 1.0 (if applicable)
        if (color.rgba[3] === null) {
            if (thisObj.paletteSetsAlpha === true || (thisObj.paletteSetsAlpha === 'auto' && thisObj._paletteHasTransparency)) {
                color.rgba[3] = 1.0;
            }
        }

        thisObj.fromRGBA.apply(thisObj, color.rgba);

        thisObj.trigger('input');
        thisObj.trigger('change');

        if (thisObj.hideOnPaletteClick) {
            thisObj.hide();
        }
    },


    setPad : function (thisObj, e, ofsX, ofsY) {
        var pointerAbs = jsc.getAbsPointerPos(e);
        var x = ofsX + pointerAbs.x - jsc._pointerOrigin.x - thisObj.padding - thisObj.controlBorderWidth;
        var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.controlBorderWidth;

        var xVal = x * (360 / (thisObj.width - 1));
        var yVal = 100 - (y * (100 / (thisObj.height - 1)));

        switch (jsc.getPadYChannel(thisObj)) {
        case 's': thisObj.fromHSVA(xVal, yVal, null, null); break;
        case 'v': thisObj.fromHSVA(xVal, null, yVal, null); break;
        }
    },


    setSld : function (thisObj, e, ofsY) {
        var pointerAbs = jsc.getAbsPointerPos(e);
        var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.controlBorderWidth;
        var yVal = 100 - (y * (100 / (thisObj.height - 1)));

        switch (jsc.getSliderChannel(thisObj)) {
        case 's': thisObj.fromHSVA(null, yVal, null, null); break;
        case 'v': thisObj.fromHSVA(null, null, yVal, null); break;
        }
    },


    setASld : function (thisObj, e, ofsY) {
        var pointerAbs = jsc.getAbsPointerPos(e);
        var y = ofsY + pointerAbs.y - jsc._pointerOrigin.y - thisObj.padding - thisObj.controlBorderWidth;
        var yVal = 1.0 - (y * (1.0 / (thisObj.height - 1)));

        if (yVal < 1.0) {
            // if format is flexible and the current format doesn't support alpha, switch to a suitable one
            var fmt = thisObj.getFormat();
            if (thisObj.format.toLowerCase() === 'any' && !jsc.isAlphaFormat(fmt)) {
                thisObj._setFormat(fmt === 'hex' ? 'hexa' : 'rgba');
            }
        }

        thisObj.fromHSVA(null, null, null, yVal);
    },


    createPadCanvas : function () {

        var ret = {
            elm: null,
            draw: null
        };

        var canvas = jsc.createEl('canvas');
        var ctx = canvas.getContext('2d');

        var drawFunc = function (width, height, type) {
            canvas.width = width;
            canvas.height = height;

            ctx.clearRect(0, 0, canvas.width, canvas.height);

            var hGrad = ctx.createLinearGradient(0, 0, canvas.width, 0);
            hGrad.addColorStop(0 / 6, '#F00');
            hGrad.addColorStop(1 / 6, '#FF0');
            hGrad.addColorStop(2 / 6, '#0F0');
            hGrad.addColorStop(3 / 6, '#0FF');
            hGrad.addColorStop(4 / 6, '#00F');
            hGrad.addColorStop(5 / 6, '#F0F');
            hGrad.addColorStop(6 / 6, '#F00');

            ctx.fillStyle = hGrad;
            ctx.fillRect(0, 0, canvas.width, canvas.height);

            var vGrad = ctx.createLinearGradient(0, 0, 0, canvas.height);
            switch (type.toLowerCase()) {
            case 's':
                vGrad.addColorStop(0, 'rgba(255,255,255,0)');
                vGrad.addColorStop(1, 'rgba(255,255,255,1)');
                break;
            case 'v':
                vGrad.addColorStop(0, 'rgba(0,0,0,0)');
                vGrad.addColorStop(1, 'rgba(0,0,0,1)');
                break;
            }
            ctx.fillStyle = vGrad;
            ctx.fillRect(0, 0, canvas.width, canvas.height);
        };

        ret.elm = canvas;
        ret.draw = drawFunc;

        return ret;
    },


    createSliderGradient : function () {

        var ret = {
            elm: null,
            draw: null
        };

        var canvas = jsc.createEl('canvas');
        var ctx = canvas.getContext('2d');

        var drawFunc = function (width, height, color1, color2) {
            canvas.width = width;
            canvas.height = height;

            ctx.clearRect(0, 0, canvas.width, canvas.height);

            var grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
            grad.addColorStop(0, color1);
            grad.addColorStop(1, color2);

            ctx.fillStyle = grad;
            ctx.fillRect(0, 0, canvas.width, canvas.height);
        };

        ret.elm = canvas;
        ret.draw = drawFunc;

        return ret;
    },


    createASliderGradient : function () {

        var ret = {
            elm: null,
            draw: null
        };

        var canvas = jsc.createEl('canvas');
        var ctx = canvas.getContext('2d');

        var drawFunc = function (width, height, color) {
            canvas.width = width;
            canvas.height = height;

            ctx.clearRect(0, 0, canvas.width, canvas.height);

            var sqSize = canvas.width / 2;
            var sqColor1 = jsc.pub.chessboardColor1;
            var sqColor2 = jsc.pub.chessboardColor2;

            // dark gray background
            ctx.fillStyle = sqColor1;
            ctx.fillRect(0, 0, canvas.width, canvas.height);

            if (sqSize > 0) { // to avoid infinite loop
                for (var y = 0; y < canvas.height; y += sqSize * 2) {
                    // light gray squares
                    ctx.fillStyle = sqColor2;
                    ctx.fillRect(0, y, sqSize, sqSize);
                    ctx.fillRect(sqSize, y + sqSize, sqSize, sqSize);
                }
            }

            var grad = ctx.createLinearGradient(0, 0, 0, canvas.height);
            grad.addColorStop(0, color);
            grad.addColorStop(1, 'rgba(0,0,0,0)');

            ctx.fillStyle = grad;
            ctx.fillRect(0, 0, canvas.width, canvas.height);
        };

        ret.elm = canvas;
        ret.draw = drawFunc;

        return ret;
    },


    BoxShadow : (function () {
        var BoxShadow = function (hShadow, vShadow, blur, spread, color, inset) {
            this.hShadow = hShadow;
            this.vShadow = vShadow;
            this.blur = blur;
            this.spread = spread;
            this.color = color;
            this.inset = !!inset;
        };

        BoxShadow.prototype.toString = function () {
            var vals = [
                Math.round(this.hShadow) + 'px',
                Math.round(this.vShadow) + 'px',
                Math.round(this.blur) + 'px',
                Math.round(this.spread) + 'px',
                this.color
            ];
            if (this.inset) {
                vals.push('inset');
            }
            return vals.join(' ');
        };

        return BoxShadow;
    })(),


    flags : {
        leaveValue : 1 << 0,
        leaveAlpha : 1 << 1,
        leavePreview : 1 << 2,
    },


    enumOpts : {
        format: ['auto', 'any', 'hex', 'hexa', 'rgb', 'rgba'],
        previewPosition: ['left', 'right'],
        mode: ['hsv', 'hvs', 'hs', 'hv'],
        position: ['left', 'right', 'top', 'bottom'],
        alphaChannel: ['auto', true, false],
        paletteSetsAlpha: ['auto', true, false],
    },


    deprecatedOpts : {
        // <old_option>: <new_option>  (<new_option> can be null)
        'styleElement': 'previewElement',
        'onFineChange': 'onInput',
        'overwriteImportant': 'forceStyle',
        'closable': 'closeButton',
        'insetWidth': 'controlBorderWidth',
        'insetColor': 'controlBorderColor',
        'refine': null,
    },


    docsRef : ' ' + 'See https://jscolor.com/docs/',


    //
    // Usage:
    // var myPicker = new JSColor(<targetElement> [, <options>])
    //
    // (constructor is accessible via both 'jscolor' and 'JSColor' name)
    //

    pub : function (targetElement, opts) {

        var THIS = this;

        if (!opts) {
            opts = {};
        }

        this.channels = {
            r: 255, // red [0-255]
            g: 255, // green [0-255]
            b: 255, // blue [0-255]
            h: 0, // hue [0-360]
            s: 0, // saturation [0-100]
            v: 100, // value (brightness) [0-100]
            a: 1.0, // alpha (opacity) [0.0 - 1.0]
        };

        // General options
        //
        this.format = 'auto'; // 'auto' | 'any' | 'hex' | 'hexa' | 'rgb' | 'rgba' - Format of the input/output value
        this.value = undefined; // INITIAL color value in any supported format. To change it later, use method fromString(), fromHSVA(), fromRGBA() or channel()
        this.alpha = undefined; // INITIAL alpha value. To change it later, call method channel('A', <value>)
        this.random = false; // whether to randomize the initial color. Either true | false, or an array of ranges: [minV, maxV, minS, maxS, minH, maxH, minA, maxA]
        this.onChange = undefined; // called when color changes. Value can be either a function or a string with JS code.
        this.onInput = undefined; // called repeatedly as the color is being changed, e.g. while dragging a slider. Value can be either a function or a string with JS code.
        this.valueElement = undefined; // element that will be used to display and input the color value
        this.alphaElement = undefined; // element that will be used to display and input the alpha (opacity) value
        this.previewElement = undefined; // element that will preview the picked color using CSS background
        this.previewPosition = 'left'; // 'left' | 'right' - position of the color preview in previewElement
        this.previewSize = 32; // (px) width of the color preview displayed in previewElement
        this.previewPadding = 8; // (px) space between color preview and content of the previewElement
        this.required = true; // whether the associated text input must always contain a color value. If false, the input can be left empty.
        this.hash = true; // whether to prefix the HEX color code with # symbol (only applicable for HEX format)
        this.uppercase = true; // whether to show the HEX color code in upper case (only applicable for HEX format)
        this.forceStyle = true; // whether to overwrite CSS style of the previewElement using !important flag

        // Color Picker options
        //
        this.width = 181; // width of the color spectrum (in px)
        this.height = 101; // height of the color spectrum (in px)
        this.mode = 'HSV'; // 'HSV' | 'HVS' | 'HS' | 'HV' - layout of the color picker controls
        this.alphaChannel = 'auto'; // 'auto' | true | false - if alpha channel is enabled, the alpha slider will be visible. If 'auto', it will be determined according to color format
        this.position = 'bottom'; // 'left' | 'right' | 'top' | 'bottom' - position relative to the target element
        this.smartPosition = true; // automatically change picker position when there is not enough space for it
        this.showOnClick = true; // whether to show the picker when user clicks its target element
        this.hideOnLeave = true; // whether to automatically hide the picker when user leaves its target element (e.g. upon clicking the document)
        this.palette = []; // colors to be displayed in the palette, specified as an array or a string of space separated color values (in any supported format)
        this.paletteCols = 10; // number of columns in the palette
        this.paletteSetsAlpha = 'auto'; // 'auto' | true | false - if true, palette colors that don't specify alpha will set alpha to 1.0
        this.paletteHeight = 16; // maximum height (px) of a row in the palette
        this.paletteSpacing = 4; // distance (px) between color samples in the palette
        this.hideOnPaletteClick = false; // when set to true, clicking the palette will also hide the color picker
        this.sliderSize = 16; // px
        this.crossSize = 8; // px
        this.closeButton = false; // whether to display the Close button
        this.closeText = 'Close';
        this.buttonColor = 'rgba(0,0,0,1)'; // CSS color
        this.buttonHeight = 18; // px
        this.padding = 12; // px
        this.backgroundColor = 'rgba(255,255,255,1)'; // CSS color
        this.borderWidth = 1; // px
        this.borderColor = 'rgba(187,187,187,1)'; // CSS color
        this.borderRadius = 8; // px
        this.controlBorderWidth = 1; // px
        this.controlBorderColor = 'rgba(187,187,187,1)'; // CSS color
        this.shadow = true; // whether to display a shadow
        this.shadowBlur = 15; // px
        this.shadowColor = 'rgba(0,0,0,0.2)'; // CSS color
        this.pointerColor = 'rgba(76,76,76,1)'; // CSS color
        this.pointerBorderWidth = 1; // px
        this.pointerBorderColor = 'rgba(255,255,255,1)'; // CSS color
        this.pointerThickness = 2; // px
        this.zIndex = 5000;
        this.container = undefined; // where to append the color picker (BODY element by default)

        // Experimental
        //
        this.minS = 0; // min allowed saturation (0 - 100)
        this.maxS = 100; // max allowed saturation (0 - 100)
        this.minV = 0; // min allowed value (brightness) (0 - 100)
        this.maxV = 100; // max allowed value (brightness) (0 - 100)
        this.minA = 0.0; // min allowed alpha (opacity) (0.0 - 1.0)
        this.maxA = 1.0; // max allowed alpha (opacity) (0.0 - 1.0)


        // Getter: option(name)
        // Setter: option(name, value)
        //         option({name:value, ...})
        //
        this.option = function () {
            if (!arguments.length) {
                throw new Error('No option specified');
            }

            if (arguments.length === 1 && typeof arguments[0] === 'string') {
                // getting a single option
                try {
                    return getOption(arguments[0]);
                } catch (e) {
                    console.warn(e);
                }
                return false;

            } else if (arguments.length >= 2 && typeof arguments[0] === 'string') {
                // setting a single option
                try {
                    if (!setOption(arguments[0], arguments[1])) {
                        return false;
                    }
                } catch (e) {
                    console.warn(e);
                    return false;
                }
                this.redraw(); // immediately redraws the picker, if it's displayed
                this.exposeColor(); // in case some preview-related or format-related option was changed
                return true;

            } else if (arguments.length === 1 && typeof arguments[0] === 'object') {
                // setting multiple options
                var opts = arguments[0];
                var success = true;
                for (var opt in opts) {
                    if (opts.hasOwnProperty(opt)) {
                        try {
                            if (!setOption(opt, opts[opt])) {
                                success = false;
                            }
                        } catch (e) {
                            console.warn(e);
                            success = false;
                        }
                    }
                }
                this.redraw(); // immediately redraws the picker, if it's displayed
                this.exposeColor(); // in case some preview-related or format-related option was changed
                return success;
            }

            throw new Error('Invalid arguments');
        }


        // Getter: channel(name)
        // Setter: channel(name, value)
        //
        this.channel = function (name, value) {
            if (typeof name !== 'string') {
                throw new Error('Invalid value for channel name: ' + name);
            }

            if (value === undefined) {
                // getting channel value
                if (!this.channels.hasOwnProperty(name.toLowerCase())) {
                    console.warn('Getting unknown channel: ' + name);
                    return false;
                }
                return this.channels[name.toLowerCase()];

            } else {
                // setting channel value
                var res = false;
                switch (name.toLowerCase()) {
                    case 'r': res = this.fromRGBA(value, null, null, null); break;
                    case 'g': res = this.fromRGBA(null, value, null, null); break;
                    case 'b': res = this.fromRGBA(null, null, value, null); break;
                    case 'h': res = this.fromHSVA(value, null, null, null); break;
                    case 's': res = this.fromHSVA(null, value, null, null); break;
                    case 'v': res = this.fromHSVA(null, null, value, null); break;
                    case 'a': res = this.fromHSVA(null, null, null, value); break;
                    default:
                        console.warn('Setting unknown channel: ' + name);
                        return false;
                }
                if (res) {
                    this.redraw(); // immediately redraws the picker, if it's displayed
                    return true;
                }
            }

            return false;
        }


        // Triggers given input event(s) by:
        // - executing on<Event> callback specified as picker's option
        // - triggering standard DOM event listeners attached to the value element
        //
        // It is possible to specify multiple events separated with a space.
        //
        this.trigger = function (eventNames) {
            var evs = jsc.strList(eventNames);
            for (var i = 0; i < evs.length; i += 1) {
                var ev = evs[i].toLowerCase();

                // trigger a callback
                var callbackProp = null;
                switch (ev) {
                    case 'input': callbackProp = 'onInput'; break;
                    case 'change': callbackProp = 'onChange'; break;
                }
                if (callbackProp) {
                    jsc.triggerCallback(this, callbackProp);
                }

                // trigger standard DOM event listeners on the value element
                jsc.triggerInputEvent(this.valueElement, ev, true, true);
            }
        };


        // h: 0-360
        // s: 0-100
        // v: 0-100
        // a: 0.0-1.0
        //
        this.fromHSVA = function (h, s, v, a, flags) { // null = don't change
            if (h === undefined) { h = null; }
            if (s === undefined) { s = null; }
            if (v === undefined) { v = null; }
            if (a === undefined) { a = null; }

            if (h !== null) {
                if (isNaN(h)) { return false; }
                this.channels.h = Math.max(0, Math.min(360, h));
            }
            if (s !== null) {
                if (isNaN(s)) { return false; }
                this.channels.s = Math.max(0, Math.min(100, this.maxS, s), this.minS);
            }
            if (v !== null) {
                if (isNaN(v)) { return false; }
                this.channels.v = Math.max(0, Math.min(100, this.maxV, v), this.minV);
            }
            if (a !== null) {
                if (isNaN(a)) { return false; }
                this.channels.a = this.hasAlphaChannel() ?
                    Math.max(0, Math.min(1, this.maxA, a), this.minA) :
                    1.0; // if alpha channel is disabled, the color should stay 100% opaque
            }

            var rgb = jsc.HSV_RGB(
                this.channels.h,
                this.channels.s,
                this.channels.v
            );
            this.channels.r = rgb[0];
            this.channels.g = rgb[1];
            this.channels.b = rgb[2];

            this.exposeColor(flags);
            return true;
        };


        // r: 0-255
        // g: 0-255
        // b: 0-255
        // a: 0.0-1.0
        //
        this.fromRGBA = function (r, g, b, a, flags) { // null = don't change
            if (r === undefined) { r = null; }
            if (g === undefined) { g = null; }
            if (b === undefined) { b = null; }
            if (a === undefined) { a = null; }

            if (r !== null) {
                if (isNaN(r)) { return false; }
                r = Math.max(0, Math.min(255, r));
            }
            if (g !== null) {
                if (isNaN(g)) { return false; }
                g = Math.max(0, Math.min(255, g));
            }
            if (b !== null) {
                if (isNaN(b)) { return false; }
                b = Math.max(0, Math.min(255, b));
            }
            if (a !== null) {
                if (isNaN(a)) { return false; }
                this.channels.a = this.hasAlphaChannel() ?
                    Math.max(0, Math.min(1, this.maxA, a), this.minA) :
                    1.0; // if alpha channel is disabled, the color should stay 100% opaque
            }

            var hsv = jsc.RGB_HSV(
                r===null ? this.channels.r : r,
                g===null ? this.channels.g : g,
                b===null ? this.channels.b : b
            );
            if (hsv[0] !== null) {
                this.channels.h = Math.max(0, Math.min(360, hsv[0]));
            }
            if (hsv[2] !== 0) { // fully black color stays black through entire saturation range, so let's not change saturation
                this.channels.s = Math.max(0, this.minS, Math.min(100, this.maxS, hsv[1]));
            }
            this.channels.v = Math.max(0, this.minV, Math.min(100, this.maxV, hsv[2]));

            // update RGB according to final HSV, as some values might be trimmed
            var rgb = jsc.HSV_RGB(this.channels.h, this.channels.s, this.channels.v);
            this.channels.r = rgb[0];
            this.channels.g = rgb[1];
            this.channels.b = rgb[2];

            this.exposeColor(flags);
            return true;
        };


        // DEPRECATED. Use .fromHSVA() instead
        //
        this.fromHSV = function (h, s, v, flags) {
            console.warn('fromHSV() method is DEPRECATED. Using fromHSVA() instead.' + jsc.docsRef);
            return this.fromHSVA(h, s, v, null, flags);
        };


        // DEPRECATED. Use .fromRGBA() instead
        //
        this.fromRGB = function (r, g, b, flags) {
            console.warn('fromRGB() method is DEPRECATED. Using fromRGBA() instead.' + jsc.docsRef);
            return this.fromRGBA(r, g, b, null, flags);
        };


        this.fromString = function (str, flags) {
            if (!this.required && str.trim() === '') {
                // setting empty string to an optional color input
                this.setPreviewElementBg(null);
                this.setValueElementValue('');
                return true;
            }

            var color = jsc.parseColorString(str);
            if (!color) {
                return false; // could not parse
            }
            if (this.format.toLowerCase() === 'any') {
                this._setFormat(color.format); // adapt format
                if (!jsc.isAlphaFormat(this.getFormat())) {
                    color.rgba[3] = 1.0; // when switching to a format that doesn't support alpha, set full opacity
                }
            }
            this.fromRGBA(
                color.rgba[0],
                color.rgba[1],
                color.rgba[2],
                color.rgba[3],
                flags
            );
            return true;
        };


        this.randomize = function (minV, maxV, minS, maxS, minH, maxH, minA, maxA) {
            if (minV === undefined) { minV = 0; }
            if (maxV === undefined) { maxV = 100; }
            if (minS === undefined) { minS = 0; }
            if (maxS === undefined) { maxS = 100; }
            if (minH === undefined) { minH = 0; }
            if (maxH === undefined) { maxH = 359; }
            if (minA === undefined) { minA = 1; }
            if (maxA === undefined) { maxA = 1; }

            this.fromHSVA(
                minH + Math.floor(Math.random() * (maxH - minH + 1)),
                minS + Math.floor(Math.random() * (maxS - minS + 1)),
                minV + Math.floor(Math.random() * (maxV - minV + 1)),
                ((100 * minA) + Math.floor(Math.random() * (100 * (maxA - minA) + 1))) / 100
            );
        };


        this.toString = function (format) {
            if (format === undefined) {
                format = this.getFormat(); // format not specified -> use the current format
            }
            switch (format.toLowerCase()) {
                case 'hex': return this.toHEXString(); break;
                case 'hexa': return this.toHEXAString(); break;
                case 'rgb': return this.toRGBString(); break;
                case 'rgba': return this.toRGBAString(); break;
            }
            return false;
        };


        this.toHEXString = function () {
            return jsc.hexColor(
                this.channels.r,
                this.channels.g,
                this.channels.b
            );
        };


        this.toHEXAString = function () {
            return jsc.hexaColor(
                this.channels.r,
                this.channels.g,
                this.channels.b,
                this.channels.a
            );
        };


        this.toRGBString = function () {
            return jsc.rgbColor(
                this.channels.r,
                this.channels.g,
                this.channels.b
            );
        };


        this.toRGBAString = function () {
            return jsc.rgbaColor(
                this.channels.r,
                this.channels.g,
                this.channels.b,
                this.channels.a
            );
        };


        this.toGrayscale = function () {
            return (
                0.213 * this.channels.r +
                0.715 * this.channels.g +
                0.072 * this.channels.b
            );
        };


        this.toCanvas = function () {
            return jsc.genColorPreviewCanvas(this.toRGBAString()).canvas;
        };


        this.toDataURL = function () {
            return this.toCanvas().toDataURL();
        };


        this.toBackground = function () {
            return jsc.pub.background(this.toRGBAString());
        };


        this.isLight = function () {
            return this.toGrayscale() > 255 / 2;
        };


        this.hide = function () {
            if (isPickerOwner()) {
                detachPicker();
            }
        };


        this.show = function () {
            drawPicker();
        };


        this.redraw = function () {
            if (isPickerOwner()) {
                drawPicker();
            }
        };


        this.getFormat = function () {
            return this._currentFormat;
        };


        this._setFormat = function (format) {
            this._currentFormat = format.toLowerCase();
        };


        this.hasAlphaChannel = function () {
            if (this.alphaChannel === 'auto') {
                return (
                    this.format.toLowerCase() === 'any' || // format can change on the fly (e.g. from hex to rgba), so let's consider the alpha channel enabled
                    jsc.isAlphaFormat(this.getFormat()) || // the current format supports alpha channel
                    this.alpha !== undefined || // initial alpha value is set, so we're working with alpha channel
                    this.alphaElement !== undefined // the alpha value is redirected, so we're working with alpha channel
                );
            }

            return this.alphaChannel; // the alpha channel is explicitly set
        };


        this.processValueInput = function (str) {
            if (!this.fromString(str)) {
                // could not parse the color value - let's just expose the current color
                this.exposeColor();
            }
        };


        this.processAlphaInput = function (str) {
            if (!this.fromHSVA(null, null, null, parseFloat(str))) {
                // could not parse the alpha value - let's just expose the current color
                this.exposeColor();
            }
        };


        this.exposeColor = function (flags) {
            var colorStr = this.toString();
            var fmt = this.getFormat();

            // reflect current color in data- attribute
            jsc.setDataAttr(this.targetElement, 'current-color', colorStr);

            if (!(flags & jsc.flags.leaveValue) && this.valueElement) {
                if (fmt === 'hex' || fmt === 'hexa') {
                    if (!this.uppercase) { colorStr = colorStr.toLowerCase(); }
                    if (!this.hash) { colorStr = colorStr.replace(/^#/, ''); }
                }
                this.setValueElementValue(colorStr);
            }

            if (!(flags & jsc.flags.leaveAlpha) && this.alphaElement) {
                var alphaVal = Math.round(this.channels.a * 100) / 100;
                this.setAlphaElementValue(alphaVal);
            }

            if (!(flags & jsc.flags.leavePreview) && this.previewElement) {
                var previewPos = null; // 'left' | 'right' (null -> fill the entire element)

                if (
                    jsc.isTextInput(this.previewElement) || // text input
                    (jsc.isButton(this.previewElement) && !jsc.isButtonEmpty(this.previewElement)) // button with text
                ) {
                    previewPos = this.previewPosition;
                }

                this.setPreviewElementBg(this.toRGBAString());
            }

            if (isPickerOwner()) {
                redrawPad();
                redrawSld();
                redrawASld();
            }
        };


        this.setPreviewElementBg = function (color) {
            if (!this.previewElement) {
                return;
            }

            var position = null; // color preview position:  null | 'left' | 'right'
            var width = null; // color preview width:  px | null = fill the entire element
            if (
                jsc.isTextInput(this.previewElement) || // text input
                (jsc.isButton(this.previewElement) && !jsc.isButtonEmpty(this.previewElement)) // button with text
            ) {
                position = this.previewPosition;
                width = this.previewSize;
            }

            var backgrounds = [];

            if (!color) {
                // there is no color preview to display -> let's remove any previous background image
                backgrounds.push({
                    image: 'none',
                    position: 'left top',
                    size: 'auto',
                    repeat: 'no-repeat',
                    origin: 'padding-box',
                });
            } else {
                // CSS gradient for background color preview
                backgrounds.push({
                    image: jsc.genColorPreviewGradient(
                        color,
                        position,
                        width ? width - jsc.pub.previewSeparator.length : null
                    ),
                    position: 'left top',
                    size: 'auto',
                    repeat: position ? 'repeat-y' : 'repeat',
                    origin: 'padding-box',
                });

                // data URL of generated PNG image with a gray transparency chessboard
                var preview = jsc.genColorPreviewCanvas(
                    'rgba(0,0,0,0)',
                    position ? {'left':'right', 'right':'left'}[position] : null,
                    width,
                    true
                );
                backgrounds.push({
                    image: 'url(\'' + preview.canvas.toDataURL() + '\')',
                    position: (position || 'left') + ' top',
                    size: preview.width + 'px ' + preview.height + 'px',
                    repeat: position ? 'repeat-y' : 'repeat',
                    origin: 'padding-box',
                });
            }

            var bg = {
                image: [],
                position: [],
                size: [],
                repeat: [],
                origin: [],
            };
            for (var i = 0; i < backgrounds.length; i += 1) {
                bg.image.push(backgrounds[i].image);
                bg.position.push(backgrounds[i].position);
                bg.size.push(backgrounds[i].size);
                bg.repeat.push(backgrounds[i].repeat);
                bg.origin.push(backgrounds[i].origin);
            }

            // set previewElement's background-images
            var sty = {
                'background-image': bg.image.join(', '),
                'background-position': bg.position.join(', '),
                'background-size': bg.size.join(', '),
                'background-repeat': bg.repeat.join(', '),
                'background-origin': bg.origin.join(', '),
            };
            jsc.setStyle(this.previewElement, sty, this.forceStyle);


            // set/restore previewElement's padding
            var padding = {
                left: null,
                right: null,
            };
            if (position) {
                padding[position] = (this.previewSize + this.previewPadding) + 'px';
            }

            var sty = {
                'padding-left': padding.left,
                'padding-right': padding.right,
            };
            jsc.setStyle(this.previewElement, sty, this.forceStyle, true);
        };


        this.setValueElementValue = function (str) {
            if (this.valueElement) {
                if (jsc.nodeName(this.valueElement) === 'input') {
                    this.valueElement.value = str;
                } else {
                    this.valueElement.innerHTML = str;
                }
            }
        };


        this.setAlphaElementValue = function (str) {
            if (this.alphaElement) {
                if (jsc.nodeName(this.alphaElement) === 'input') {
                    this.alphaElement.value = str;
                } else {
                    this.alphaElement.innerHTML = str;
                }
            }
        };


        this._processParentElementsInDOM = function () {
            if (this._parentElementsProcessed) { return; }
            this._parentElementsProcessed = true;

            var elm = this.targetElement;
            do {
                // If the target element or one of its parent nodes has fixed position,
                // then use fixed positioning instead
                var compStyle = jsc.getCompStyle(elm);
                if (compStyle.position && compStyle.position.toLowerCase() === 'fixed') {
                    this.fixed = true;
                }

                if (elm !== this.targetElement) {
                    // Ensure to attach onParentScroll only once to each parent element
                    // (multiple targetElements can share the same parent nodes)
                    //
                    // Note: It's not just offsetParents that can be scrollable,
                    // that's why we loop through all parent nodes
                    if (!jsc.getData(elm, 'hasScrollListener')) {
                        elm.addEventListener('scroll', jsc.onParentScroll, false);
                        jsc.setData(elm, 'hasScrollListener', true);
                    }
                }
            } while ((elm = elm.parentNode) && jsc.nodeName(elm) !== 'body');
        };


        this.tryHide = function () {
            if (this.hideOnLeave) {
                this.hide();
            }
        };


        this.set__palette = function (val) {
            this.palette = val;
            this._palette = jsc.parsePaletteValue(val);
            this._paletteHasTransparency = jsc.containsTranparentColor(this._palette);
        };


        function setOption (option, value) {
            if (typeof option !== 'string') {
                throw new Error('Invalid value for option name: ' + option);
            }

            // enum option
            if (jsc.enumOpts.hasOwnProperty(option)) {
                if (typeof value === 'string') { // enum string values are case insensitive
                    value = value.toLowerCase();
                }
                if (jsc.enumOpts[option].indexOf(value) === -1) {
                    throw new Error('Option \'' + option + '\' has invalid value: ' + value);
                }
            }

            // deprecated option
            if (jsc.deprecatedOpts.hasOwnProperty(option)) {
                var oldOpt = option;
                var newOpt = jsc.deprecatedOpts[option];
                if (newOpt) {
                    // if we have a new name for this option, let's log a warning and use the new name
                    console.warn('Option \'%s\' is DEPRECATED, using \'%s\' instead.' + jsc.docsRef, oldOpt, newOpt);
                    option = newOpt;
                } else {
                    // new name not available for the option
                    throw new Error('Option \'' + option + '\' is DEPRECATED');
                }
            }

            var setter = 'set__' + option;

            if (typeof THIS[setter] === 'function') { // a setter exists for this option
                THIS[setter](value);
                return true;

            } else if (option in THIS) { // option exists as a property
                THIS[option] = value;
                return true;
            }

            throw new Error('Unrecognized configuration option: ' + option);
        }


        function getOption (option) {
            if (typeof option !== 'string') {
                throw new Error('Invalid value for option name: ' + option);
            }

            // deprecated option
            if (jsc.deprecatedOpts.hasOwnProperty(option)) {
                var oldOpt = option;
                var newOpt = jsc.deprecatedOpts[option];
                if (newOpt) {
                    // if we have a new name for this option, let's log a warning and use the new name
                    console.warn('Option \'%s\' is DEPRECATED, using \'%s\' instead.' + jsc.docsRef, oldOpt, newOpt);
                    option = newOpt;
                } else {
                    // new name not available for the option
                    throw new Error('Option \'' + option + '\' is DEPRECATED');
                }
            }

            var getter = 'get__' + option;

            if (typeof THIS[getter] === 'function') { // a getter exists for this option
                return THIS[getter](value);

            } else if (option in THIS) { // option exists as a property
                return THIS[option];
            }

            throw new Error('Unrecognized configuration option: ' + option);
        }


        function detachPicker () {
            jsc.removeClass(THIS.targetElement, jsc.pub.activeClassName);
            jsc.picker.wrap.parentNode.removeChild(jsc.picker.wrap);
            delete jsc.picker.owner;
        }


        function drawPicker () {

            // At this point, when drawing the picker, we know what the parent elements are
            // and we can do all related DOM operations, such as registering events on them
            // or checking their positioning
            THIS._processParentElementsInDOM();

            if (!jsc.picker) {
                jsc.picker = {
                    owner: null, // owner picker instance
                    wrap : jsc.createEl('div'),
                    box : jsc.createEl('div'),
                    boxS : jsc.createEl('div'), // shadow area
                    boxB : jsc.createEl('div'), // border
                    pad : jsc.createEl('div'),
                    padB : jsc.createEl('div'), // border
                    padM : jsc.createEl('div'), // mouse/touch area
                    padCanvas : jsc.createPadCanvas(),
                    cross : jsc.createEl('div'),
                    crossBY : jsc.createEl('div'), // border Y
                    crossBX : jsc.createEl('div'), // border X
                    crossLY : jsc.createEl('div'), // line Y
                    crossLX : jsc.createEl('div'), // line X
                    sld : jsc.createEl('div'), // slider
                    sldB : jsc.createEl('div'), // border
                    sldM : jsc.createEl('div'), // mouse/touch area
                    sldGrad : jsc.createSliderGradient(),
                    sldPtrS : jsc.createEl('div'), // slider pointer spacer
                    sldPtrIB : jsc.createEl('div'), // slider pointer inner border
                    sldPtrMB : jsc.createEl('div'), // slider pointer middle border
                    sldPtrOB : jsc.createEl('div'), // slider pointer outer border
                    asld : jsc.createEl('div'), // alpha slider
                    asldB : jsc.createEl('div'), // border
                    asldM : jsc.createEl('div'), // mouse/touch area
                    asldGrad : jsc.createASliderGradient(),
                    asldPtrS : jsc.createEl('div'), // slider pointer spacer
                    asldPtrIB : jsc.createEl('div'), // slider pointer inner border
                    asldPtrMB : jsc.createEl('div'), // slider pointer middle border
                    asldPtrOB : jsc.createEl('div'), // slider pointer outer border
                    pal : jsc.createEl('div'), // palette
                    btn : jsc.createEl('div'),
                    btnT : jsc.createEl('span'), // text
                };

                jsc.picker.pad.appendChild(jsc.picker.padCanvas.elm);
                jsc.picker.padB.appendChild(jsc.picker.pad);
                jsc.picker.cross.appendChild(jsc.picker.crossBY);
                jsc.picker.cross.appendChild(jsc.picker.crossBX);
                jsc.picker.cross.appendChild(jsc.picker.crossLY);
                jsc.picker.cross.appendChild(jsc.picker.crossLX);
                jsc.picker.padB.appendChild(jsc.picker.cross);
                jsc.picker.box.appendChild(jsc.picker.padB);
                jsc.picker.box.appendChild(jsc.picker.padM);

                jsc.picker.sld.appendChild(jsc.picker.sldGrad.elm);
                jsc.picker.sldB.appendChild(jsc.picker.sld);
                jsc.picker.sldB.appendChild(jsc.picker.sldPtrOB);
                jsc.picker.sldPtrOB.appendChild(jsc.picker.sldPtrMB);
                jsc.picker.sldPtrMB.appendChild(jsc.picker.sldPtrIB);
                jsc.picker.sldPtrIB.appendChild(jsc.picker.sldPtrS);
                jsc.picker.box.appendChild(jsc.picker.sldB);
                jsc.picker.box.appendChild(jsc.picker.sldM);

                jsc.picker.asld.appendChild(jsc.picker.asldGrad.elm);
                jsc.picker.asldB.appendChild(jsc.picker.asld);
                jsc.picker.asldB.appendChild(jsc.picker.asldPtrOB);
                jsc.picker.asldPtrOB.appendChild(jsc.picker.asldPtrMB);
                jsc.picker.asldPtrMB.appendChild(jsc.picker.asldPtrIB);
                jsc.picker.asldPtrIB.appendChild(jsc.picker.asldPtrS);
                jsc.picker.box.appendChild(jsc.picker.asldB);
                jsc.picker.box.appendChild(jsc.picker.asldM);

                jsc.picker.box.appendChild(jsc.picker.pal);

                jsc.picker.btn.appendChild(jsc.picker.btnT);
                jsc.picker.box.appendChild(jsc.picker.btn);

                jsc.picker.boxB.appendChild(jsc.picker.box);
                jsc.picker.wrap.appendChild(jsc.picker.boxS);
                jsc.picker.wrap.appendChild(jsc.picker.boxB);

                jsc.picker.wrap.addEventListener('touchstart', jsc.onPickerTouchStart,
                    jsc.isPassiveEventSupported ? {passive: false} : false);
            }

            var p = jsc.picker;

            var displaySlider = !!jsc.getSliderChannel(THIS);
            var displayAlphaSlider = THIS.hasAlphaChannel();
            var pickerDims = jsc.getPickerDims(THIS);
            var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize);
            var controlPadding = jsc.getControlPadding(THIS);
            var borderRadius = Math.min(
                THIS.borderRadius,
                Math.round(THIS.padding * Math.PI)); // px
            var padCursor = 'crosshair';

            // wrap
            p.wrap.className = 'jscolor-picker-wrap';
            p.wrap.style.clear = 'both';
            p.wrap.style.width = pickerDims.borderW + 'px';
            p.wrap.style.height = pickerDims.borderH + 'px';
            p.wrap.style.zIndex = THIS.zIndex;

            // picker
            p.box.className = 'jscolor-picker';
            p.box.style.width = pickerDims.paddedW + 'px';
            p.box.style.height = pickerDims.paddedH + 'px';
            p.box.style.position = 'relative';

            // picker shadow
            p.boxS.className = 'jscolor-picker-shadow';
            p.boxS.style.position = 'absolute';
            p.boxS.style.left = '0';
            p.boxS.style.top = '0';
            p.boxS.style.width = '100%';
            p.boxS.style.height = '100%';
            jsc.setBorderRadius(p.boxS, borderRadius + 'px');

            // picker border
            p.boxB.className = 'jscolor-picker-border';
            p.boxB.style.position = 'relative';
            p.boxB.style.border = THIS.borderWidth + 'px solid';
            p.boxB.style.borderColor = THIS.borderColor;
            p.boxB.style.background = THIS.backgroundColor;
            jsc.setBorderRadius(p.boxB, borderRadius + 'px');

            // IE hack:
            // If the element is transparent, IE will trigger the event on the elements under it,
            // e.g. on Canvas or on elements with border
            p.padM.style.background = 'rgba(255,0,0,.2)';
            p.sldM.style.background = 'rgba(0,255,0,.2)';
            p.asldM.style.background = 'rgba(0,0,255,.2)';

            p.padM.style.opacity =
            p.sldM.style.opacity =
            p.asldM.style.opacity =
                '0';

            // pad
            p.pad.style.position = 'relative';
            p.pad.style.width = THIS.width + 'px';
            p.pad.style.height = THIS.height + 'px';

            // pad - color spectrum (HSV and HVS)
            p.padCanvas.draw(THIS.width, THIS.height, jsc.getPadYChannel(THIS));

            // pad border
            p.padB.style.position = 'absolute';
            p.padB.style.left = THIS.padding + 'px';
            p.padB.style.top = THIS.padding + 'px';
            p.padB.style.border = THIS.controlBorderWidth + 'px solid';
            p.padB.style.borderColor = THIS.controlBorderColor;

            // pad mouse area
            p.padM.style.position = 'absolute';
            p.padM.style.left = 0 + 'px';
            p.padM.style.top = 0 + 'px';
            p.padM.style.width = (THIS.padding + 2 * THIS.controlBorderWidth + THIS.width + controlPadding) + 'px';
            p.padM.style.height = (2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height) + 'px';
            p.padM.style.cursor = padCursor;
            jsc.setData(p.padM, {
                instance: THIS,
                control: 'pad',
            })

            // pad cross
            p.cross.style.position = 'absolute';
            p.cross.style.left =
            p.cross.style.top =
                '0';
            p.cross.style.width =
            p.cross.style.height =
                crossOuterSize + 'px';

            // pad cross border Y and X
            p.crossBY.style.position =
            p.crossBX.style.position =
                'absolute';
            p.crossBY.style.background =
            p.crossBX.style.background =
                THIS.pointerBorderColor;
            p.crossBY.style.width =
            p.crossBX.style.height =
                (2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px';
            p.crossBY.style.height =
            p.crossBX.style.width =
                crossOuterSize + 'px';
            p.crossBY.style.left =
            p.crossBX.style.top =
                (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2) - THIS.pointerBorderWidth) + 'px';
            p.crossBY.style.top =
            p.crossBX.style.left =
                '0';

            // pad cross line Y and X
            p.crossLY.style.position =
            p.crossLX.style.position =
                'absolute';
            p.crossLY.style.background =
            p.crossLX.style.background =
                THIS.pointerColor;
            p.crossLY.style.height =
            p.crossLX.style.width =
                (crossOuterSize - 2 * THIS.pointerBorderWidth) + 'px';
            p.crossLY.style.width =
            p.crossLX.style.height =
                THIS.pointerThickness + 'px';
            p.crossLY.style.left =
            p.crossLX.style.top =
                (Math.floor(crossOuterSize / 2) - Math.floor(THIS.pointerThickness / 2)) + 'px';
            p.crossLY.style.top =
            p.crossLX.style.left =
                THIS.pointerBorderWidth + 'px';


            // slider
            p.sld.style.overflow = 'hidden';
            p.sld.style.width = THIS.sliderSize + 'px';
            p.sld.style.height = THIS.height + 'px';

            // slider gradient
            p.sldGrad.draw(THIS.sliderSize, THIS.height, '#000', '#000');

            // slider border
            p.sldB.style.display = displaySlider ? 'block' : 'none';
            p.sldB.style.position = 'absolute';
            p.sldB.style.left = (THIS.padding + THIS.width + 2 * THIS.controlBorderWidth + 2 * controlPadding) + 'px';
            p.sldB.style.top = THIS.padding + 'px';
            p.sldB.style.border = THIS.controlBorderWidth + 'px solid';
            p.sldB.style.borderColor = THIS.controlBorderColor;

            // slider mouse area
            p.sldM.style.display = displaySlider ? 'block' : 'none';
            p.sldM.style.position = 'absolute';
            p.sldM.style.left = (THIS.padding + THIS.width + 2 * THIS.controlBorderWidth + controlPadding) + 'px';
            p.sldM.style.top = 0 + 'px';
            p.sldM.style.width = (
                    (THIS.sliderSize + 2 * controlPadding + 2 * THIS.controlBorderWidth) +
                    (displayAlphaSlider ? 0 : Math.max(0, THIS.padding - controlPadding)) // remaining padding to the right edge
                ) + 'px';
            p.sldM.style.height = (2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height) + 'px';
            p.sldM.style.cursor = 'default';
            jsc.setData(p.sldM, {
                instance: THIS,
                control: 'sld',
            });

            // slider pointer inner and outer border
            p.sldPtrIB.style.border =
            p.sldPtrOB.style.border =
                THIS.pointerBorderWidth + 'px solid ' + THIS.pointerBorderColor;

            // slider pointer outer border
            p.sldPtrOB.style.position = 'absolute';
            p.sldPtrOB.style.left = -(2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px';
            p.sldPtrOB.style.top = '0';

            // slider pointer middle border
            p.sldPtrMB.style.border = THIS.pointerThickness + 'px solid ' + THIS.pointerColor;

            // slider pointer spacer
            p.sldPtrS.style.width = THIS.sliderSize + 'px';
            p.sldPtrS.style.height = jsc.pub.sliderInnerSpace + 'px';


            // alpha slider
            p.asld.style.overflow = 'hidden';
            p.asld.style.width = THIS.sliderSize + 'px';
            p.asld.style.height = THIS.height + 'px';

            // alpha slider gradient
            p.asldGrad.draw(THIS.sliderSize, THIS.height, '#000');

            // alpha slider border
            p.asldB.style.display = displayAlphaSlider ? 'block' : 'none';
            p.asldB.style.position = 'absolute';
            p.asldB.style.left = (
                    (THIS.padding + THIS.width + 2 * THIS.controlBorderWidth + controlPadding) +
                    (displaySlider ? (THIS.sliderSize + 3 * controlPadding + 2 * THIS.controlBorderWidth) : 0)
                ) + 'px';
            p.asldB.style.top = THIS.padding + 'px';
            p.asldB.style.border = THIS.controlBorderWidth + 'px solid';
            p.asldB.style.borderColor = THIS.controlBorderColor;

            // alpha slider mouse area
            p.asldM.style.display = displayAlphaSlider ? 'block' : 'none';
            p.asldM.style.position = 'absolute';
            p.asldM.style.left = (
                    (THIS.padding + THIS.width + 2 * THIS.controlBorderWidth + controlPadding) +
                    (displaySlider ? (THIS.sliderSize + 2 * controlPadding + 2 * THIS.controlBorderWidth) : 0)
                ) + 'px';
            p.asldM.style.top = 0 + 'px';
            p.asldM.style.width = (
                    (THIS.sliderSize + 2 * controlPadding + 2 * THIS.controlBorderWidth) +
                    Math.max(0, THIS.padding - controlPadding) // remaining padding to the right edge
                ) + 'px';
            p.asldM.style.height = (2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height) + 'px';
            p.asldM.style.cursor = 'default';
            jsc.setData(p.asldM, {
                instance: THIS,
                control: 'asld',
            })

            // alpha slider pointer inner and outer border
            p.asldPtrIB.style.border =
            p.asldPtrOB.style.border =
                THIS.pointerBorderWidth + 'px solid ' + THIS.pointerBorderColor;

            // alpha slider pointer outer border
            p.asldPtrOB.style.position = 'absolute';
            p.asldPtrOB.style.left = -(2 * THIS.pointerBorderWidth + THIS.pointerThickness) + 'px';
            p.asldPtrOB.style.top = '0';

            // alpha slider pointer middle border
            p.asldPtrMB.style.border = THIS.pointerThickness + 'px solid ' + THIS.pointerColor;

            // alpha slider pointer spacer
            p.asldPtrS.style.width = THIS.sliderSize + 'px';
            p.asldPtrS.style.height = jsc.pub.sliderInnerSpace + 'px';


            // palette
            p.pal.className = 'jscolor-palette';
            p.pal.style.display = pickerDims.palette.rows ? 'block' : 'none';
            p.pal.style.position = 'absolute';
            p.pal.style.left = THIS.padding + 'px';
            p.pal.style.top = (2 * THIS.controlBorderWidth + 2 * THIS.padding + THIS.height) + 'px';

            // palette's color samples

            p.pal.innerHTML = '';

            var chessboard = jsc.genColorPreviewCanvas('rgba(0,0,0,0)');

            var si = 0; // color sample's index
            for (var r = 0; r < pickerDims.palette.rows; r++) {
                for (var c = 0; c < pickerDims.palette.cols && si < THIS._palette.length; c++, si++) {
                    var sampleColor = THIS._palette[si];
                    var sampleCssColor = jsc.rgbaColor.apply(null, sampleColor.rgba);

                    var sc = jsc.createEl('div'); // color sample's color
                    sc.style.width = (pickerDims.palette.cellW - 2 * THIS.controlBorderWidth) + 'px';
                    sc.style.height = (pickerDims.palette.cellH - 2 * THIS.controlBorderWidth) + 'px';
                    sc.style.backgroundColor = sampleCssColor;

                    var sw = jsc.createEl('div'); // color sample's wrap
                    sw.className = 'jscolor-palette-sample';
                    sw.style.display = 'block';
                    sw.style.position = 'absolute';
                    sw.style.left = (
                            pickerDims.palette.cols <= 1 ? 0 :
                            Math.round(10 * (c * ((pickerDims.contentW - pickerDims.palette.cellW) / (pickerDims.palette.cols - 1)))) / 10
                        ) + 'px';
                    sw.style.top = (r * (pickerDims.palette.cellH + THIS.paletteSpacing)) + 'px';
                    sw.style.border = THIS.controlBorderWidth + 'px solid';
                    sw.style.borderColor = THIS.controlBorderColor;
                    sw.style.cursor = 'pointer';
                    if (sampleColor.rgba[3] !== null && sampleColor.rgba[3] < 1.0) { // only create chessboard background if the sample has transparency
                        sw.style.backgroundImage = 'url(\'' + chessboard.canvas.toDataURL() + '\')';
                        sw.style.backgroundRepeat = 'repeat';
                        sw.style.backgroundPosition = 'center center';
                    }
                    jsc.setData(sw, {
                        instance: THIS,
                        control: 'palette-sample',
                        color: sampleColor,
                    })
                    sw.addEventListener('click', jsc.onPaletteSampleClick, false);
                    sw.appendChild(sc);
                    p.pal.appendChild(sw);
                }
            }


            // the Close button
            function setBtnBorder () {
                var insetColors = THIS.controlBorderColor.split(/\s+/);
                var outsetColor = insetColors.length < 2 ? insetColors[0] : insetColors[1] + ' ' + insetColors[0] + ' ' + insetColors[0] + ' ' + insetColors[1];
                p.btn.style.borderColor = outsetColor;
            }
            var btnPadding = 15; // px
            p.btn.className = 'jscolor-btn-close';
            p.btn.style.display = THIS.closeButton ? 'block' : 'none';
            p.btn.style.position = 'absolute';
            p.btn.style.left = THIS.padding + 'px';
            p.btn.style.bottom = THIS.padding + 'px';
            p.btn.style.padding = '0 ' + btnPadding + 'px';
            p.btn.style.maxWidth = (pickerDims.contentW - 2 * THIS.controlBorderWidth - 2 * btnPadding) + 'px';
            p.btn.style.overflow = 'hidden';
            p.btn.style.height = THIS.buttonHeight + 'px';
            p.btn.style.whiteSpace = 'nowrap';
            p.btn.style.border = THIS.controlBorderWidth + 'px solid';
            setBtnBorder();
            p.btn.style.color = THIS.buttonColor;
            p.btn.style.font = '12px sans-serif';
            p.btn.style.textAlign = 'center';
            p.btn.style.cursor = 'pointer';
            p.btn.onmousedown = function () {
                THIS.hide();
            };
            p.btnT.style.lineHeight = THIS.buttonHeight + 'px';
            p.btnT.innerHTML = '';
            p.btnT.appendChild(window.document.createTextNode(THIS.closeText));

            // reposition the pointers
            redrawPad();
            redrawSld();
            redrawASld();

            // If we are changing the owner without first closing the picker,
            // make sure to first deal with the old owner
            if (jsc.picker.owner && jsc.picker.owner !== THIS) {
                jsc.removeClass(jsc.picker.owner.targetElement, jsc.pub.activeClassName);
            }

            // Set a new picker owner
            jsc.picker.owner = THIS;

            // The redrawPosition() method needs picker.owner to be set, that's why we call it here,
            // after setting the owner
            if (THIS.container === window.document.body) {
                jsc.redrawPosition();
            } else {
                jsc._drawPosition(THIS, 0, 0, 'relative', false);
            }

            if (p.wrap.parentNode !== THIS.container) {
                THIS.container.appendChild(p.wrap);
            }

            jsc.addClass(THIS.targetElement, jsc.pub.activeClassName);
        }


        function redrawPad () {
            // redraw the pad pointer
            var yChannel = jsc.getPadYChannel(THIS);
            var x = Math.round((THIS.channels.h / 360) * (THIS.width - 1));
            var y = Math.round((1 - THIS.channels[yChannel] / 100) * (THIS.height - 1));
            var crossOuterSize = (2 * THIS.pointerBorderWidth + THIS.pointerThickness + 2 * THIS.crossSize);
            var ofs = -Math.floor(crossOuterSize / 2);
            jsc.picker.cross.style.left = (x + ofs) + 'px';
            jsc.picker.cross.style.top = (y + ofs) + 'px';

            // redraw the slider
            switch (jsc.getSliderChannel(THIS)) {
            case 's':
                var rgb1 = jsc.HSV_RGB(THIS.channels.h, 100, THIS.channels.v);
                var rgb2 = jsc.HSV_RGB(THIS.channels.h, 0, THIS.channels.v);
                var color1 = 'rgb(' +
                    Math.round(rgb1[0]) + ',' +
                    Math.round(rgb1[1]) + ',' +
                    Math.round(rgb1[2]) + ')';
                var color2 = 'rgb(' +
                    Math.round(rgb2[0]) + ',' +
                    Math.round(rgb2[1]) + ',' +
                    Math.round(rgb2[2]) + ')';
                jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2);
                break;
            case 'v':
                var rgb = jsc.HSV_RGB(THIS.channels.h, THIS.channels.s, 100);
                var color1 = 'rgb(' +
                    Math.round(rgb[0]) + ',' +
                    Math.round(rgb[1]) + ',' +
                    Math.round(rgb[2]) + ')';
                var color2 = '#000';
                jsc.picker.sldGrad.draw(THIS.sliderSize, THIS.height, color1, color2);
                break;
            }

            // redraw the alpha slider
            jsc.picker.asldGrad.draw(THIS.sliderSize, THIS.height, THIS.toHEXString());
        }


        function redrawSld () {
            var sldChannel = jsc.getSliderChannel(THIS);
            if (sldChannel) {
                // redraw the slider pointer
                var y = Math.round((1 - THIS.channels[sldChannel] / 100) * (THIS.height - 1));
                jsc.picker.sldPtrOB.style.top = (y - (2 * THIS.pointerBorderWidth + THIS.pointerThickness) - Math.floor(jsc.pub.sliderInnerSpace / 2)) + 'px';
            }

            // redraw the alpha slider
            jsc.picker.asldGrad.draw(THIS.sliderSize, THIS.height, THIS.toHEXString());
        }


        function redrawASld () {
            var y = Math.round((1 - THIS.channels.a) * (THIS.height - 1));
            jsc.picker.asldPtrOB.style.top = (y - (2 * THIS.pointerBorderWidth + THIS.pointerThickness) - Math.floor(jsc.pub.sliderInnerSpace / 2)) + 'px';
        }


        function isPickerOwner () {
            return jsc.picker && jsc.picker.owner === THIS;
        }


        function onValueKeyDown (ev) {
            if (jsc.eventKey(ev) === 'Enter') {
                if (THIS.valueElement) {
                    THIS.processValueInput(THIS.valueElement.value);
                }
                THIS.tryHide();
            }
        }


        function onAlphaKeyDown (ev) {
            if (jsc.eventKey(ev) === 'Enter') {
                if (THIS.alphaElement) {
                    THIS.processAlphaInput(THIS.alphaElement.value);
                }
                THIS.tryHide();
            }
        }


        function onValueChange (ev) {
            if (jsc.getData(ev, 'internal')) {
                return; // skip if the event was internally triggered by jscolor
            }

            var oldVal = THIS.valueElement.value;

            THIS.processValueInput(THIS.valueElement.value); // this might change the value

            jsc.triggerCallback(THIS, 'onChange');

            if (THIS.valueElement.value !== oldVal) {
                // value was additionally changed -> let's trigger the change event again, even though it was natively dispatched
                jsc.triggerInputEvent(THIS.valueElement, 'change', true, true);
            }
        }


        function onAlphaChange (ev) {
            if (jsc.getData(ev, 'internal')) {
                return; // skip if the event was internally triggered by jscolor
            }

            var oldVal = THIS.alphaElement.value;

            THIS.processAlphaInput(THIS.alphaElement.value); // this might change the value

            jsc.triggerCallback(THIS, 'onChange');

            // triggering valueElement's onChange (because changing alpha changes the entire color, e.g. with rgba format)
            jsc.triggerInputEvent(THIS.valueElement, 'change', true, true);

            if (THIS.alphaElement.value !== oldVal) {
                // value was additionally changed -> let's trigger the change event again, even though it was natively dispatched
                jsc.triggerInputEvent(THIS.alphaElement, 'change', true, true);
            }
        }


        function onValueInput (ev) {
            if (jsc.getData(ev, 'internal')) {
                return; // skip if the event was internally triggered by jscolor
            }

            if (THIS.valueElement) {
                THIS.fromString(THIS.valueElement.value, jsc.flags.leaveValue);
            }

            jsc.triggerCallback(THIS, 'onInput');

            // triggering valueElement's onInput
            // (not needed, it was dispatched normally by the browser)
        }


        function onAlphaInput (ev) {
            if (jsc.getData(ev, 'internal')) {
                return; // skip if the event was internally triggered by jscolor
            }

            if (THIS.alphaElement) {
                THIS.fromHSVA(null, null, null, parseFloat(THIS.alphaElement.value), jsc.flags.leaveAlpha);
            }

            jsc.triggerCallback(THIS, 'onInput');

            // triggering valueElement's onInput (because changing alpha changes the entire color, e.g. with rgba format)
            jsc.triggerInputEvent(THIS.valueElement, 'input', true, true);
        }


        // let's process the DEPRECATED 'options' property (this will be later removed)
        if (jsc.pub.options) {
            // let's set custom default options, if specified
            for (var opt in jsc.pub.options) {
                if (jsc.pub.options.hasOwnProperty(opt)) {
                    try {
                        setOption(opt, jsc.pub.options[opt]);
                    } catch (e) {
                        console.warn(e);
                    }
                }
            }
        }


        // let's apply configuration presets
        //
        var presetsArr = [];

        if (opts.preset) {
            if (typeof opts.preset === 'string') {
                presetsArr = opts.preset.split(/\s+/);
            } else if (Array.isArray(opts.preset)) {
                presetsArr = opts.preset.slice(); // slice() to clone
            } else {
                console.warn('Unrecognized preset value');
            }
        }

        // always use the 'default' preset. If it's not listed, append it to the end.
        if (presetsArr.indexOf('default') === -1) {
            presetsArr.push('default');
        }

        // let's apply the presets in reverse order, so that should there be any overlapping options,
        // the formerly listed preset will override the latter
        for (var i = presetsArr.length - 1; i >= 0; i -= 1) {
            var pres = presetsArr[i];
            if (!pres) {
                continue; // preset is empty string
            }
            if (!jsc.pub.presets.hasOwnProperty(pres)) {
                console.warn('Unknown preset: %s', pres);
                continue;
            }
            for (var opt in jsc.pub.presets[pres]) {
                if (jsc.pub.presets[pres].hasOwnProperty(opt)) {
                    try {
                        setOption(opt, jsc.pub.presets[pres][opt]);
                    } catch (e) {
                        console.warn(e);
                    }
                }
            }
        }


        // let's set specific options for this color picker
        var nonProperties = [
            // these options won't be set as instance properties
            'preset',
        ];
        for (var opt in opts) {
            if (opts.hasOwnProperty(opt)) {
                if (nonProperties.indexOf(opt) === -1) {
                    try {
                        setOption(opt, opts[opt]);
                    } catch (e) {
                        console.warn(e);
                    }
                }
            }
        }


        //
        // Install the color picker on chosen element(s)
        //


        // Determine picker's container element
        if (this.container === undefined) {
            this.container = window.document.body; // default container is BODY element

        } else { // explicitly set to custom element
            this.container = jsc.node(this.container);
        }

        if (!this.container) {
            throw new Error('Cannot instantiate color picker without a container element');
        }


        // Fetch the target element
        this.targetElement = jsc.node(targetElement);

        if (!this.targetElement) {
            // temporarily customized error message to help with migrating from versions prior to 2.2
            if (typeof targetElement === 'string' && /^[a-zA-Z][\w:.-]*$/.test(targetElement)) {
                // targetElement looks like valid ID
                var possiblyId = targetElement;
                throw new Error('If \'' + possiblyId + '\' is supposed to be an ID, please use \'#' + possiblyId + '\' or any valid CSS selector.');
            }

            throw new Error('Cannot instantiate color picker without a target element');
        }

        if (this.targetElement.jscolor && this.targetElement.jscolor instanceof jsc.pub) {
            throw new Error('Color picker already installed on this element');
        }


        // link this instance with the target element
        this.targetElement.jscolor = this;
        jsc.addClass(this.targetElement, jsc.pub.className);

        // register this instance
        jsc.instances.push(this);


        // if target is BUTTON
        if (jsc.isButton(this.targetElement)) {

            if (this.targetElement.type.toLowerCase() !== 'button') {
                // on buttons, always force type to be 'button', e.g. in situations the target <button> has no type
                // and thus defaults to 'submit' and would submit the form when clicked
                this.targetElement.type = 'button';
            }

            if (jsc.isButtonEmpty(this.targetElement)) { // empty button
                // it is important to clear element's contents first.
                // if we're re-instantiating color pickers on DOM that has been modified by changing page's innerHTML,
                // we would keep adding more non-breaking spaces to element's content (because element's contents survive
                // innerHTML changes, but picker instances don't)
                jsc.removeChildren(this.targetElement);

                // let's insert a non-breaking space
                this.targetElement.appendChild(window.document.createTextNode('\xa0'));

                // set min-width = previewSize, if not already greater
                var compStyle = jsc.getCompStyle(this.targetElement);
                var currMinWidth = parseFloat(compStyle['min-width']) || 0;
                if (currMinWidth < this.previewSize) {
                    jsc.setStyle(this.targetElement, {
                        'min-width': this.previewSize + 'px',
                    }, this.forceStyle);
                }
            }
        }

        // Determine the value element
        if (this.valueElement === undefined) {
            if (jsc.isTextInput(this.targetElement)) {
                // for text inputs, default valueElement is targetElement
                this.valueElement = this.targetElement;
            } else {
                // leave it undefined
            }

        } else if (this.valueElement === null) { // explicitly set to null
            // leave it null

        } else { // explicitly set to custom element
            this.valueElement = jsc.node(this.valueElement);
        }

        // Determine the alpha element
        if (this.alphaElement) {
            this.alphaElement = jsc.node(this.alphaElement);
        }

        // Determine the preview element
        if (this.previewElement === undefined) {
            this.previewElement = this.targetElement; // default previewElement is targetElement

        } else if (this.previewElement === null) { // explicitly set to null
            // leave it null

        } else { // explicitly set to custom element
            this.previewElement = jsc.node(this.previewElement);
        }

        // valueElement
        if (this.valueElement && jsc.isTextInput(this.valueElement)) {

            // If the value element has onInput event already set, we need to detach it and attach AFTER our listener.
            // otherwise the picker instance would still contain the old color when accessed from the onInput handler.
            var valueElementOrigEvents = {
                onInput: this.valueElement.oninput
            };
            this.valueElement.oninput = null;

            this.valueElement.addEventListener('keydown', onValueKeyDown, false);
            this.valueElement.addEventListener('change', onValueChange, false);
            this.valueElement.addEventListener('input', onValueInput, false);
            // the original event listener must be attached AFTER our handler (to let it first set picker's color)
            if (valueElementOrigEvents.onInput) {
                this.valueElement.addEventListener('input', valueElementOrigEvents.onInput, false);
            }

            this.valueElement.setAttribute('autocomplete', 'off');
            this.valueElement.setAttribute('autocorrect', 'off');
            this.valueElement.setAttribute('autocapitalize', 'off');
            this.valueElement.setAttribute('spellcheck', false);
        }

        // alphaElement
        if (this.alphaElement && jsc.isTextInput(this.alphaElement)) {
            this.alphaElement.addEventListener('keydown', onAlphaKeyDown, false);
            this.alphaElement.addEventListener('change', onAlphaChange, false);
            this.alphaElement.addEventListener('input', onAlphaInput, false);

            this.alphaElement.setAttribute('autocomplete', 'off');
            this.alphaElement.setAttribute('autocorrect', 'off');
            this.alphaElement.setAttribute('autocapitalize', 'off');
            this.alphaElement.setAttribute('spellcheck', false);
        }

        // determine initial color value
        //
        var initValue = 'FFFFFF';

        if (this.value !== undefined) {
            initValue = this.value; // get initial color from the 'value' property
        } else if (this.valueElement && this.valueElement.value !== undefined) {
            initValue = this.valueElement.value; // get initial color from valueElement's value
        }

        // determine initial alpha value
        //
        var initAlpha = undefined;

        if (this.alpha !== undefined) {
            initAlpha = (''+this.alpha); // get initial alpha value from the 'alpha' property
        } else if (this.alphaElement && this.alphaElement.value !== undefined) {
            initAlpha = this.alphaElement.value; // get initial color from alphaElement's value
        }

        // determine current format based on the initial color value
        //
        this._currentFormat = null;

        if (['auto', 'any'].indexOf(this.format.toLowerCase()) > -1) {
            // format is 'auto' or 'any' -> let's auto-detect current format
            var color = jsc.parseColorString(initValue);
            this._currentFormat = color ? color.format : 'hex';
        } else {
            // format is specified
            this._currentFormat = this.format.toLowerCase();
        }


        // let's parse the initial color value and expose color's preview
        this.processValueInput(initValue);

        // let's also parse and expose the initial alpha value, if any
        //
        // Note: If the initial color value contains alpha value in it (e.g. in rgba format),
        // this will overwrite it. So we should only process alpha input if there was initial
        // alpha explicitly set, otherwise we could needlessly lose initial value's alpha
        if (initAlpha !== undefined) {
            this.processAlphaInput(initAlpha);
        }

        if (this.random) {
            // randomize the initial color value
            this.randomize.apply(this, Array.isArray(this.random) ? this.random : []);
        }

    }

};


//================================
// Public properties and methods
//================================

//
// These will be publicly available via jscolor.<name> and JSColor.<name>
//


// class that will be set to elements having jscolor installed on them
jsc.pub.className = 'jscolor';


// class that will be set to elements having jscolor active on them
jsc.pub.activeClassName = 'jscolor-active';


// whether to try to parse the options string by evaluating it using 'new Function()'
// in case it could not be parsed with JSON.parse()
jsc.pub.looseJSON = true;


// presets
jsc.pub.presets = {};

// built-in presets
jsc.pub.presets['default'] = {}; // baseline for customization

jsc.pub.presets['light'] = { // default color scheme
    backgroundColor: 'rgba(255,255,255,1)',
    controlBorderColor: 'rgba(187,187,187,1)',
    buttonColor: 'rgba(0,0,0,1)',
};
jsc.pub.presets['dark'] = {
    backgroundColor: 'rgba(51,51,51,1)',
    controlBorderColor: 'rgba(153,153,153,1)',
    buttonColor: 'rgba(240,240,240,1)',
};

jsc.pub.presets['small'] = { width:101, height:101, padding:10, sliderSize:14, paletteCols:8 };
jsc.pub.presets['medium'] = { width:181, height:101, padding:12, sliderSize:16, paletteCols:10 }; // default size
jsc.pub.presets['large'] = { width:271, height:151, padding:12, sliderSize:24, paletteCols:15 };

jsc.pub.presets['thin'] = { borderWidth:1, controlBorderWidth:1, pointerBorderWidth:1 }; // default thickness
jsc.pub.presets['thick'] = { borderWidth:2, controlBorderWidth:2, pointerBorderWidth:2 };


// size of space in the sliders
jsc.pub.sliderInnerSpace = 3; // px

// transparency chessboard
jsc.pub.chessboardSize = 8; // px
jsc.pub.chessboardColor1 = '#666666';
jsc.pub.chessboardColor2 = '#999999';

// preview separator
jsc.pub.previewSeparator = ['rgba(255,255,255,.65)', 'rgba(128,128,128,.65)'];


// Initializes jscolor
jsc.pub.init = function () {
    if (jsc.initialized) {
        return;
    }

    // attach some necessary handlers
    window.document.addEventListener('mousedown', jsc.onDocumentMouseDown, false);
    window.document.addEventListener('keyup', jsc.onDocumentKeyUp, false);
    window.addEventListener('resize', jsc.onWindowResize, false);
    window.addEventListener('scroll', jsc.onWindowScroll, false);

    // install jscolor on current DOM
    jsc.pub.install();

    jsc.initialized = true;

    // call functions waiting in the queue
    while (jsc.readyQueue.length) {
        var func = jsc.readyQueue.shift();
        func();
    }
};


// Installs jscolor on current DOM tree
jsc.pub.install = function (rootNode) {
    var success = true;

    try {
        jsc.installBySelector('[data-jscolor]', rootNode);
    } catch (e) {
        success = false;
        console.warn(e);
    }

    // for backward compatibility with DEPRECATED installation using class name
    if (jsc.pub.lookupClass) {
        try {
            jsc.installBySelector(
                (
                    'input.' + jsc.pub.lookupClass + ', ' +
                    'button.' + jsc.pub.lookupClass
                ),
                rootNode
            );
        } catch (e) {}
    }

    return success;
};


// Registers function to be called as soon as jscolor is initialized (or immediately, if it already is).
//
jsc.pub.ready = function (func) {
    if (typeof func !== 'function') {
        console.warn('Passed value is not a function');
        return false;
    }

    if (jsc.initialized) {
        func();
    } else {
        jsc.readyQueue.push(func);
    }
    return true;
};


// Triggers given input event(s) (e.g. 'input' or 'change') on all color pickers.
//
// It is possible to specify multiple events separated with a space.
// If called before jscolor is initialized, then the events will be triggered after initialization.
//
jsc.pub.trigger = function (eventNames) {
    var triggerNow = function () {
        jsc.triggerGlobal(eventNames);
    };

    if (jsc.initialized) {
        triggerNow();
    } else {
        jsc.pub.ready(triggerNow);
    }
};


// Hides current color picker box
jsc.pub.hide = function () {
    if (jsc.picker && jsc.picker.owner) {
        jsc.picker.owner.hide();
    }
};


// Returns a data URL of a gray chessboard image that indicates transparency
jsc.pub.chessboard = function (color) {
    if (!color) {
        color = 'rgba(0,0,0,0)';
    }
    var preview = jsc.genColorPreviewCanvas(color);
    return preview.canvas.toDataURL();
};


// Returns a data URL of a gray chessboard image that indicates transparency
jsc.pub.background = function (color) {
    var backgrounds = [];

    // CSS gradient for background color preview
    backgrounds.push(jsc.genColorPreviewGradient(color));

    // data URL of generated PNG image with a gray transparency chessboard
    var preview = jsc.genColorPreviewCanvas();
    backgrounds.push([
        'url(\'' + preview.canvas.toDataURL() + '\')',
        'left top',
        'repeat',
    ].join(' '));

    return backgrounds.join(', ');
};


//
// DEPRECATED properties and methods
//


// DEPRECATED. Use jscolor.presets.default instead.
//
// Custom default options for all color pickers, e.g. { hash: true, width: 300 }
jsc.pub.options = {};


// DEPRECATED. Use data-jscolor attribute instead, which installs jscolor on given element.
//
// By default, we'll search for all elements with class="jscolor" and install a color picker on them.
//
// You can change what class name will be looked for by setting the property jscolor.lookupClass
// anywhere in your HTML document. To completely disable the automatic lookup, set it to null.
//
jsc.pub.lookupClass = 'jscolor';


// DEPRECATED. Use data-jscolor attribute instead, which installs jscolor on given element.
//
// Install jscolor on all elements that have the specified class name
jsc.pub.installByClassName = function () {
    console.error('jscolor.installByClassName() is DEPRECATED. Use data-jscolor="" attribute instead of a class name.' + jsc.docsRef);
    return false;
};


jsc.register();


return jsc.pub;


})(); // END jscolor


if (typeof window.jscolor === 'undefined') {
    window.jscolor = window.JSColor = jscolor;
}


// END jscolor code

return jscolor;

}); // END factory