text: /**
   * 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.5
   *
   * 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.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.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 any initial
  		// alpha explicitly set, otherwise we could needlessly lose initial value's alpha
  		if (initAlpha !== undefined) {
  			this.processAlphaInput(initAlpha);
  		}
  
  	}
  
  };
  
  
  //================================
  // 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
  
  ;
  
  export function init(selector, options) {
  
      // options.onChange = function () {
      //     console.log(this);
      //     //this is jscolor instance
      // }
      // options.onInput = function () {
      //     console.log(this);
      //     //this is jscolor instance
      //     // DotNet.invokeMethodAsync("BulmaRazor","JSCallback",datepicker.data.element.id,"onInput",[datepicker.data.value()]);
      // }
      var obj = new JSColor(selector, options);
      return obj;
  }