;(function(window, undefined){
	"use strict"

	var _data = window.ColorPicker, // will be deleted in buildView() and holds:
		// window.ColorPicker = { // comes from colorPicker.data.js and will be overwritten.
		// 	_html: ..., // holds the HTML markup of colorPicker
		// 	_cssFunc: ..., // CSS for all the sliders
		// 	_cssMain: ..., // CSS of the GUI
		// 	_horizontalPng: ..., // horizontal background images for sliders
		// 	_verticalPng: ..., // vertical background images for sliders
		// 	_patchesPng: ..., // background images for square sliders in RGB mode
		// 	_iconsPng: ..., // some icon sprite images
		// 	_bgsPng: ..., // some more icon sprite images
		// }
		_devMode = !_data, // if no _data we assume that colorPicker.data.js is missing (for development)
		_isIE = document.createStyleSheet !== undefined && document.getElementById || !!window.MSInputMethodContext,
		_doesOpacity = typeof document.body.style.opacity !== 'undefined',
		// _isIE8 = _isIE && document.querySelectorAll,

		_valueRanges = {}, // will be assigned in initInstance() by Colors instance
		// _valueRanges = {
		// 	rgb:   {r: [0, 255], g: [0, 255], b: [0, 255]},
		// 	hsv:   {h: [0, 360], s: [0, 100], v: [0, 100]},
		// 	hsl:   {h: [0, 360], s: [0, 100], l: [0, 100]},
		// 	cmyk:  {c: [0, 100], m: [0, 100], y: [0, 100], k: [0, 100]},
		// 	cmy:   {c: [0, 100], m: [0, 100], y: [0, 100]},
		// 	XYZ:   {X: [0, 100], Y: [0, 100], Z: [0, 100]},
		// 	Lab:   {L: [0, 100], a: [-128, 127], b: [-128, 127]},
		// 	alpha: {alpha: [0, 1]},
		// 	HEX:   {HEX: [0, 16777215]}
		// },
		_bgTypes = {w: 'White', b: 'Black', c: 'Custom'},

		_mouseMoveAction, // current mouseMove handler assigned on mouseDown
		_action = '', // needed for action callback; needed due to minification of javaScript
		_mainTarget, // target on mouseDown, might be parent element though...
		_valueType, // check this variable; gets missused/polutet over time
		_delayState = 1, // mouseMove offset (y-axis) in display elements // same here...
		_startCoords = {},
		_targetOrigin = {},
		_renderTimer, // animationFrame/interval variable
		_newData = true,
		// _txt = {
		// 	selection: document.selection || window.getSelection(),
		// 	range: (document.createRange ? document.createRange() : document.body.createTextRange())
		// },

		_renderVars = {}, // used only in renderAll and convertColors
		_cashedVars = {}, // reset in initSliders

		_colorPicker,
		_previousInstance, // only used for recycling purposes in buildView()
		_colorInstance = {},
		_colors = {},
		_options = {},
		_nodes = {},

		_math = Math,

		animationFrame = 'AnimationFrame', // we also need this later
		requestAnimationFrame = 'request' + animationFrame,
		cancelAnimationFrame = 'cancel' + animationFrame,
		vendors = ['ms', 'moz', 'webkit', 'o'],
		
		ColorPicker = function(options) { // as tiny as possible...
			this.options = {
				color: 'rgba(204, 82, 37, 0.8)',
				mode: 'rgb-b',
				fps: 60, // 1000 / 60 = ~16.7ms
				delayOffset: 8,
				CSSPrefix: 'cp-',
				allMixDetails: true,
				alphaBG: 'w',
				imagePath: ''
				// devPicker: false // uses existing HTML for development...
				// noAlpha: true,
				// customBG: '#808080'
				// size: 0,
				// cmyOnly: false,
				// initStyle: 'display: none',

				// memoryColors: "'rgba(82,80,151,1)','rgba(100,200,10,0.5)','rgba(100,0,0,1)','rgba(0,0,0,1)'"
				// memoryColors: [{r: 100, g: 200, b: 10, a: 0.5}] //  

				// opacityPositionRelative: undefined,
				// customCSS: undefined,
				// appendTo: document.body,
				// noRangeBackground: false,
				// textRight: false, ?????
				// noHexButton: false,
				// noResize: false,

				// noRGBr: false,
				// noRGBg: false,
				// noRGBb: false,

				// ------ CSSStrength: 'div.',
				// XYZMatrix: XYZMatrix,
				// XYZReference: {},
				// grey: grey,
				// luminance: luminance,

				// renderCallback: undefined,
				// actionCallback: undefined,
				// convertCallback: undefined,
			};
			initInstance(this, options || {});
		};

	window.ColorPicker = ColorPicker; // export differently
	ColorPicker.addEvent = addEvent;
	ColorPicker.removeEvent = removeEvent;
	ColorPicker.getOrigin = getOrigin;
	ColorPicker.limitValue = limitValue;
	ColorPicker.changeClass = changeClass;

	// ------------------------------------------------------ //

	ColorPicker.prototype.setColor = function(newCol, type, alpha, forceRender) {
		focusInstance(this);
		_valueType = true; // right cursor...
		// https://github.com/petkaantonov/bluebird/wiki/Optimization-killers
		preRenderAll(_colorInstance.setColor.apply(_colorInstance, arguments));
		if (forceRender) {
			this.startRender(true);
		}
	};

	ColorPicker.prototype.saveAsBackground = function() {
		focusInstance(this);
		return saveAsBackground(true);
	};

	ColorPicker.prototype.setCustomBackground = function(col) {
		focusInstance(this); // needed???
		return _colorInstance.setCustomBackground(col);
	};

	ColorPicker.prototype.startRender = function(oneTime) {
		focusInstance(this);
		if (oneTime) {
			_mouseMoveAction = false; // prevents window[requestAnimationFrame] in renderAll()
			renderAll();
			this.stopRender();
		} else {
			_mouseMoveAction = 1;
			_renderTimer = window[requestAnimationFrame](renderAll);
		}
	};

	ColorPicker.prototype.stopRender = function() {
		focusInstance(this); // check again
		window[cancelAnimationFrame](_renderTimer);
		if (_valueType) {
			// renderAll();
			_mouseMoveAction = 1;
			stopChange(undefined, 'external');
			// _valueType = undefined;
		}
	};

	ColorPicker.prototype.setMode = function(mode) { // check again ... right cursor
		focusInstance(this);
		setMode(mode);
		initSliders();
		renderAll();
	};

	ColorPicker.prototype.destroyAll = function() { // check this again...
		var html = this.nodes.colorPicker,
			destroyReferences = function(nodes) {
			for (var n in nodes) {
				if (nodes[n] && nodes[n].toString() === '[object Object]' || nodes[n] instanceof Array) {
					destroyReferences(nodes[n]);
				}
				nodes[n] = null;
				delete nodes[n];
			}
		};

		this.stopRender();
		installEventListeners(this, true);
		destroyReferences(this);
		html.parentNode.removeChild(html);
		html = null;
	};

	ColorPicker.prototype.renderMemory = function(memory) {
		var memos = this.nodes.memos,
			tmp = [];

		if (typeof memory === 'string') { // revisit!!!
			memory = memory.replace(/^'|'$/g, '').replace(/\s*/, '').split('\',\'');
		}
		for (var n = memos.length; n--; ) { // check again how to handle alpha...
			if (memory && typeof memory[n] === 'string') {
				tmp = memory[n].replace('rgba(', '').replace(')', '').split(',');
				memory[n] = {r: tmp[0], g: tmp[1], b: tmp[2], a: tmp[3]}
			}
			memos[n].style.cssText = 'background-color: ' + (memory && memory[n] !== undefined ?
				color2string(memory[n]) + ';' + getOpacityCSS(memory[n]['a'] || 1) : 'rgb(0,0,0);');
		}
	};

	// ------------------------------------------------------ //

	function initInstance(THIS, options) {
		var exporter, // do something here..
			mode = '',
			CSSPrefix = '',
			optionButtons;

		for (var option in options) { // deep copy ??
			THIS.options[option] = options[option];
		}
		_colorInstance = new Colors(THIS.options);
		// We transfer the responsibility to the instance of Color (to save space and memory)
		delete THIS.options;
		_options = _colorInstance.options;
		_options.scale = 1;
		CSSPrefix = _options.CSSPrefix;

		THIS.color = _colorInstance; // check this again...
		_valueRanges = _options.valueRanges;
		THIS.nodes = _nodes = getInstanceNodes(buildView(THIS), THIS); // ha, ha,... make this different
		setMode(_options.mode);
		focusInstance(THIS);
		saveAsBackground();

		mode = ' ' + _options.mode.type + '-' + _options.mode.z;
		_nodes.slds.className += mode;
		_nodes.panel.className += mode;
		//_nodes.colorPicker.className += ' cmy-' + _options.cmyOnly;

		if (_options.noHexButton) {
			changeClass(_nodes.HEX_butt, CSSPrefix + 'butt', CSSPrefix + 'labl');
		}

		if (_options.size !== undefined) {
			resizeApp(undefined, _options.size);
		}

		optionButtons = {
			alphaBG: _nodes.alpha_labl,
			cmyOnly: _nodes.HEX_labl // test... take out
		};
		for (var n in optionButtons) {
			if (_options[n] !== undefined) {
				buttonActions({target: optionButtons[n], data: _options[n]});
			}
		}
		if (_options.noAlpha) {
			_nodes.colorPicker.className += ' no-alpha'; // IE6 ??? maybe for IE6 on document.body
		}

		THIS.renderMemory(_options.memoryColors);

		installEventListeners(THIS);
		
		_mouseMoveAction = true;
		stopChange(undefined, 'init');

		if (_previousInstance) {
			focusInstance(_previousInstance);
			renderAll();
		}
	}

	function focusInstance(THIS) {
		_newData = true;
		if (_colorPicker !== THIS) {
			_colorPicker = THIS;
			_colors = THIS.color.colors;
			_options = THIS.color.options;
			_nodes = THIS.nodes;
			_colorInstance = THIS.color;

			_cashedVars = {};
			preRenderAll(_colors);
		}
	}

	function getUISizes() {
		var sizes = ['L', 'S', 'XS', 'XXS'];
		_options.sizes = {};
		_nodes.testNode.style.cssText = 'position:absolute;left:-1000px;top:-1000px;';
		document.body.appendChild(_nodes.testNode);
		for (var n = sizes.length; n--; ) {
			_nodes.testNode.className = _options.CSSPrefix + 'app ' + sizes[n];
			_options.sizes[sizes[n]] = [_nodes.testNode.offsetWidth, _nodes.testNode.offsetHeight];
		}
		if (_nodes.testNode.removeNode) { // old IEs
			_nodes.testNode.removeNode(true);
		} else {
			document.body.removeChild(_nodes.testNode);
		}
	}

	function buildView(THIS) {
		var app = document.createElement('div'),
			prefix = _options.CSSPrefix,
			urlData = 'data:image/png;base64,',
			addStyleSheet = function(cssText, id) {
				var style = document.createElement('style');

				style.setAttribute('type', 'text/css');
				if (id) {
					style.setAttribute('id', id);
				}
				if (!style.styleSheet) {
					style.appendChild(document.createTextNode(cssText));
				}
				document.getElementsByTagName('head')[0].appendChild(style);
				if (style.styleSheet) { // IE compatible
					document.styleSheets[document.styleSheets.length-1].cssText = cssText;
				}
			},
			processCSS = function(doesBAS64){
				// CSS - system
				_data._cssFunc = _data._cssFunc.
					replace(/§/g, prefix).
					replace('_patches.png', doesBAS64 ? urlData + _data._patchesPng : _options.imagePath + '_patches.png').
					replace('_vertical.png', doesBAS64 ? urlData + _data._verticalPng : _options.imagePath + '_vertical.png').
					replace('_horizontal.png', doesBAS64 ? urlData + _data._horizontalPng :
						_options.imagePath + '_horizontal.png');
				addStyleSheet(_data._cssFunc, 'colorPickerCSS');
				// CSS - main
				if (!_options.customCSS) {
					_data._cssMain = _data._cssMain.
						replace(/§/g, prefix).
						replace('_bgs.png', doesBAS64 ? urlData + _data._bgsPng : _options.imagePath + '_bgs.png').
						replace('_icons.png', doesBAS64 ? urlData + _data._iconsPng : _options.imagePath + '_icons.png').
						// replace('"Courier New",', !_isIE ? '' : '"Courier New",').
						replace(/opacity:(\d*\.*(\d+))/g, function($1, $2){
							return !_doesOpacity ? '-ms-filter: "progid:DXImageTransform.Microsoft.Alpha(Opacity=' +
							_math.round(+$2 * 100) + ')";filter: alpha(opacity=' + _math.round(+$2 * 100) + ')' :
							'-moz-opacity: ' + $2 + '; -khtml-opacity: ' + $2 + '; opacity: ' + $2;
						});
					// style.appendChild(document.createTextNode(_data._cssFunc));
					addStyleSheet(_data._cssMain);
				}
				// for (var n in _data) { // almost 25k of memory ;o)
				// 	_data[n] = null;
				// }
			},
			test = document.createElement('img');

		// development mode
		if (_devMode) {
			return THIS.color.options.devPicker;
		}

		// CSS
		if (!document.getElementById('colorPickerCSS')) { // only once needed
			test.onload = test.onerror = function(){
				if (_data._cssFunc) {
					processCSS(this.width === 1 && this.height === 1);
				}
			};
			test.src = "data:image/gif;base64,R0lGODlhAQABAIAAAAAAAP///ywAAAAAAQABAAACAUwAOw==";
		}

		// HTML
		if (_previousInstance = _colorPicker) {
			// we need to be careful with recycling HTML as slider calssNames might have been changed...
			initSliders();
		}
		// app.innerHTML = _colorPicker ? _colorPicker.nodes.colorPicker.outerHTML : _data._html.replace(/§/g, prefix);
		// faster ... FF8.0 (2011) though (but IE4)
		// outerHTML ... FF11 (2013)
		app.insertAdjacentHTML('afterbegin',
			_colorPicker ? _colorPicker.nodes.colorPicker.outerHTML ||
				new XMLSerializer().serializeToString(_colorPicker.nodes.colorPicker) : // FF before F11
				_data._html.replace(/§/g, prefix));
			// _colorPicker ? _colorPicker.nodes.colorPicker.parentNode.innerHTML : _data._html.replace(/§/g, prefix));
		// _data._html = null;

		app = app.children[0];
		app.style.cssText = _options.initStyle || ''; // for initial hiding...
		// get a better addClass for this....
		// app.className = app.className.split(' ')[0]; // cleanup for multy instances

		return (_options.appendTo || document.body).appendChild(app);
	}

	function getInstanceNodes(colorPicker, THIS) { // check nodes again... are they all needed?
		var all = colorPicker.getElementsByTagName('*'),
			nodes = {colorPicker: colorPicker}, // length ?? // rename nodes.colorPicker
			node,
			className,
			memoCounter = 0,
			regexp = new RegExp(_options.CSSPrefix);

		// nodes.displayStyles = {}; // not needed ... or change to CSS
		nodes.styles = {};
		// nodes.styles.displays = {};

		nodes.textNodes = {};
		nodes.memos = [];
		nodes.testNode = document.createElement('div');

		for (var n = 0, m = all.length; n < m; n++) {
			node = all[n];
			if ((className = node.className) && regexp.test(className)) {
				className = className.split(' ')[0].replace(_options.CSSPrefix, '').replace(/-/g, '_');
				if (/_disp/.test(className)) {
					className = className.replace('_disp', '');
					// nodes.styles.displays[className] = node.style;
					nodes.styles[className] = node.style;
					nodes.textNodes[className] = node.firstChild;
					node.contentEditable = true; // does this slow down rendering??
				} else {
					if (!(/(?:hs|cmyk|Lab).*?(?:butt|labl)/.test(className))) {
						nodes[className] = node;
					}
					if (/(?:cur|sld[^s]|opacity|cont|col)/.test(className)) {
						nodes.styles[className] = /(?:col\d)/.test(className) ? node.children[0].style : node.style;
					}
				}
			} else if (/memo/.test(node.parentNode.className)) {
				nodes.memos.push(node);
			}
		}

		// Chrome bug: focuses contenteditable on mouse over while dragging
		nodes.panelCover = nodes.panel.appendChild(document.createElement('div'));

		return nodes;
	}

	// ------------------------------------------------------ //
	// ---- Add event listners to colorPicker and window ---- //
	// -------------------------------------------------------//

	function installEventListeners(THIS, off) {
		var onOffEvent = off ? removeEvent : addEvent;

		onOffEvent(_nodes.colorPicker, 'mousedown', function(e) {
			var event = e || window.event,
				page = getPageXY(event),
				target = event.target || event.srcElement,
				className = target.className;
			
			focusInstance(THIS);
			_mainTarget = target;
			stopChange(undefined, 'resetEventListener');
			_action = ''; // needed due to minification of javaScript

			if (target === _nodes.sldl_3 || target === _nodes.curm) {
				_mainTarget = _nodes.sldl_3;
				_mouseMoveAction = changeXYValue;
				_action = 'changeXYValue';
				changeClass(_nodes.slds, 'do-drag');
			} else if (/sldr/.test(className) || target === _nodes.curl || target === _nodes.curr) {
				_mainTarget = _nodes.sldr_4;
				_mouseMoveAction = changeZValue;
				_action = 'changeZValue';
			} else if (target === _nodes.opacity.children[0] || target === _nodes.opacity_slider) {
				_mainTarget = _nodes.opacity;
				_mouseMoveAction = changeOpacityValue;
				_action = 'changeOpacityValue';
			} else if (/-disp/.test(className) && !/HEX-/.test(className)) {
				_mouseMoveAction = changeInputValue;
				_action = 'changeInputValue';
				(target.nextSibling.nodeType === 3 ? target.nextSibling.nextSibling : target.nextSibling).
					appendChild(_nodes.nsarrow); // nextSibling for better text selection
				_valueType = className.split('-disp')[0].split('-');
				_valueType = {type: _valueType[0], z: _valueType[1] || ''};
				changeClass(_nodes.panel, 'start-change');
				_delayState = 0;
			} else if (target === _nodes.resize && !_options.noResize) {
				if (!_options.sizes) {
					getUISizes();
				}
				_mainTarget = _nodes.resizer;
				_mouseMoveAction = resizeApp;
				_action = 'resizeApp';
			} else {
				_mouseMoveAction = undefined;
			}

			if (_mouseMoveAction) {
				_startCoords = {pageX: page.X, pageY: page.Y};
				_mainTarget.style.display = 'block'; // for resizer...
				_targetOrigin = getOrigin(_mainTarget);
				_targetOrigin.width = _nodes.opacity.offsetWidth; // ???????
				_targetOrigin.childWidth = _nodes.opacity_slider.offsetWidth; // ???????
				_mainTarget.style.display = ''; // ??? for resizer...
				_mouseMoveAction(event);
				addEvent(_isIE ? document.body : window, 'mousemove', _mouseMoveAction);
				_renderTimer = window[requestAnimationFrame](renderAll);
			} else {
				// console.log(className)
				// console.log(THIS.nodes[className.split(' ')[0].replace('cp-', '').replace('-', '_')])
				// resize, button states, etc...
			}

			// if (_mouseMoveAction !== changeInputValue) preventDefault(event);
			if (!/-disp/.test(className)) {
				return preventDefault(event);
				// document.activeElement.blur();
			}
		});

		onOffEvent(_nodes.colorPicker, 'click', function(e) {
			focusInstance(THIS);
			buttonActions(e);
		});

		onOffEvent(_nodes.colorPicker, 'dblclick', buttonActions);

		onOffEvent(_nodes.colorPicker, 'keydown', function(e) {
			focusInstance(THIS);
			keyControl(e);
		});

		// keydown is before keypress and focuses already
		onOffEvent(_nodes.colorPicker, 'keypress', keyControl);
		// onOffEvent(_nodes.colorPicker, 'keyup', keyControl);

		onOffEvent(_nodes.colorPicker, 'paste', function(e) {
			e.target.firstChild.data = e.clipboardData.getData('Text');
			return preventDefault(e);
		});
	}

	addEvent(_isIE ? document.body : window, 'mouseup', stopChange);

	// ------------------------------------------------------ //
	// --------- Event listner's callback functions  -------- //
	// -------------------------------------------------------//

	function stopChange(e, action) {
		var mouseMoveAction = _mouseMoveAction;

		if (_mouseMoveAction) { // why??? please test again...
			// if (document.selection && _mouseMoveAction !== changeInputValue) {
			// 	//ie -> prevent showing the accelerator menu
			// 	document.selection.empty();
			// }
			window[cancelAnimationFrame](_renderTimer);
			removeEvent(_isIE ? document.body : window, 'mousemove', _mouseMoveAction);
			if (_delayState) { // hapens on inputs
				_valueType = {type: 'alpha'};
				renderAll();
			}
			// this is dirty... has to do with M|W|! button
			if (typeof _mouseMoveAction === 'function' || typeof _mouseMoveAction === 'number') {
				delete _options.webUnsave;
			}

			_delayState = 1;
			_mouseMoveAction = undefined;

			changeClass(_nodes.slds, 'do-drag', '');
			changeClass(_nodes.panel, '(?:start-change|do-change)', '');

			_nodes.resizer.style.cssText = '';
			_nodes.panelCover.style.cssText = '';

			_nodes.memo_store.style.cssText = 'background-color: ' +
				color2string(_colors.RND.rgb) + '; ' + getOpacityCSS(_colors.alpha);
			_nodes.memo.className = _nodes.memo.className.replace(/\s+(?:dark|light)/, '') +
				// (/dark/.test(_nodes.colorPicker.className) ? ' dark' : ' light');
				(_colors['rgbaMix' + _bgTypes[_options.alphaBG]].luminance < 0.22 ? ' dark' : ' light');
				// (_colors.rgbaMixCustom.luminance < 0.22 ? ' dark' : ' light')

			_valueType = undefined;

			resetCursors();

			if (_options.actionCallback) {
				_options.actionCallback(e, _action || mouseMoveAction.name || action || 'external');
			}
		}
	}

	function changeXYValue(e) {
		var event = e || window.event,
			scale = _options.scale,
			page = getPageXY(event),
			x = (page.X - _targetOrigin.left) * (scale === 4 ? 2 : scale),
			y = (page.Y - _targetOrigin.top) * scale,
			mode = _options.mode;

		_colors[mode.type][mode.x] = limitValue(x / 255, 0, 1);
		_colors[mode.type][mode.y] = 1 - limitValue(y / 255,  0, 1);
		convertColors();
		return preventDefault(event);
	}

	function changeZValue(e) { // make this part of changeXYValue
		var event = e || window.event,
			page = getPageXY(event),
			z = (page.Y - _targetOrigin.top) * _options.scale,
			mode = _options.mode;

		_colors[mode.type][mode.z] = 1 - limitValue(z / 255,  0, 1);
		convertColors();
		return preventDefault(event);
	}

	function changeOpacityValue(e) {
		var event = e || window.event,
			page = getPageXY(event);

		_newData = true;
		_colors.alpha = limitValue(_math.round(
			(page.X - _targetOrigin.left) / _targetOrigin.width * 100), 0, 100
		) / 100;
		convertColors('alpha');
		return preventDefault(event);
	}

	function changeInputValue(e) {
		var event = e || window.event,
			page = getPageXY(event),
			delta = _startCoords.pageY - page.Y,
			delayOffset = _options.delayOffset,
			type = _valueType.type,
			isAlpha = type === 'alpha',
			ranges;

		if (_delayState || _math.abs(delta) >= delayOffset) {
			if (!_delayState) {
				_delayState = (delta > 0 ? -delayOffset : delayOffset) +
					(+_mainTarget.firstChild.data) * (isAlpha ? 100 : 1);
				_startCoords.pageY += _delayState;
				delta += _delayState;
				_delayState = 1;
				changeClass(_nodes.panel, 'start-change', 'do-change');
				_nodes.panelCover.style.cssText = 'position:absolute;left:0;top:0;right:0;bottom:0';
				// window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
				document.activeElement.blur();
				_renderTimer = window[requestAnimationFrame](renderAll);
			}

			if (type === 'cmyk' && _options.cmyOnly) {
				type = 'cmy';
			}

			if (isAlpha) {
				_newData = true;
				_colors.alpha = limitValue(delta / 100, 0, 1);
			} else {
				ranges = _valueRanges[type][_valueType.z];
				_colors[type][_valueType.z] = type === 'Lab' ? limitValue(delta, ranges[0], ranges[1]) :
					limitValue(delta / ranges[1], 0, 1);
			}
			convertColors(isAlpha ? 'alpha' : type);
			// event.returnValue is deprecated. Please use the standard event.preventDefault() instead.
			// event.returnValue = false; // see: pauseEvent(event);
			return preventDefault(event);
		}
	}

	function keyControl(e) { // this is quite big for what it does...
		var event = e || window.event,
			keyCode =  event.which || event.keyCode,
			key = String.fromCharCode(keyCode),
			elm = document.activeElement,

			cln = elm.className.replace(_options.CSSPrefix, '').split('-'),
			type = cln[0],
			mode = cln[1],

			isAlpha = type === 'alpha',
			isHex = type === 'HEX',
			arrowKey = {k40: -1, k38: 1, k34: -10, k33: 10}['k' + keyCode] / (isAlpha ? 100 : 1),
			validKeys = {'HEX': /[0-9a-fA-F]/, 'Lab': /[\-0-9]/, 'alpha': /[\.0-9]/}[type] || /[0-9]/,
			valueRange = _valueRanges[type][type] || _valueRanges[type][mode], // let op!

			textNode = elm.firstChild, // chnge on TAB key
			rangeData = caret(elm),
			origValue = textNode.data, // do not change
			value,
			val = origValue === '0' && !isHex ? [] : origValue.split(''); // gefixt

		if (/^(?:27|13)$/.test(keyCode)) { // ENTER || ESC
			preventDefault(event);
			elm.blur();
		} else if (event.type === 'keydown') { // functional keys
			if (arrowKey) { // arrow/page keys
				value = limitValue(_math.round((+origValue + arrowKey) * 1e+6) / 1e+6, valueRange[0], valueRange[1]);
			} else if (/^(?:8|46)$/.test(keyCode)) { // DELETE / BACKSPACE
				if (!rangeData.range) {
					rangeData.range++;
					rangeData.start -= keyCode === 8 ? 1 : 0;
				}
				val.splice(rangeData.start, rangeData.range);
				value = val.join('') || '0'; // never loose elm.firstChild
			}

			if (value !== undefined) { // prevent keypress
				preventDefault(event, true);
			}
		} else if (event.type === 'keypress') {
			if (!/^(?:37|39|8|46|9)$/.test(keyCode)) { // left, right,DEL, BACK, TAB for FF
				preventDefault(event, true);
			}
			if (validKeys.test(key)) { // regular input
				val.splice(rangeData.start, rangeData.range, key);
				value = val.join('');
			}
			rangeData.start++;
		}

		if (keyCode === 13 && isHex) {
			if (textNode.data.length % 3 === 0 || textNode.data === '0') { // textNode.data.length && 
				return _colorPicker.setColor(textNode.data === '0' ? '000' : textNode.data, 'rgb', _colors.alpha, true);
			} else {
				preventDefault(event, true);
				return elm.focus();
			}
		}

		if (isHex && value !== undefined) {
			value = /^0+/.test(value) ? value : parseInt(''+value, 16) || 0;
		}

		if (value !== undefined && value !== '' && +value >= valueRange[0] && +value <= valueRange[1]) {
			if (isHex) {
				value = value.toString(16).toUpperCase() || '0';
			}
			if (isAlpha) {
				_colors[type] = +value;
			} else if (!isHex) {
				_colors[type][mode] = +value / (type === 'Lab' ? 1 : valueRange[1]);
			}
			convertColors(isAlpha ? 'alpha' : type);

			preRenderAll(_colors);
			_mouseMoveAction = true;
			stopChange(e, event.type);

			textNode.data = value; // if 
			caret(elm, _math.min(elm.firstChild.data.length, rangeData.start < 0 ? 0 : rangeData.start));
		}
	}

	function buttonActions(e) {
		var event = e || window.event,
			target = event.target || event.srcElement,
			targetClass = target.className,
			parent = target.parentNode,
			options = _options,
			RGB = _colors.RND.rgb,
			customBG, alphaBG,
			mode = _options.mode,
			newMode = '',
			prefix = options.CSSPrefix,
			isModeButton = /(?:hs|rgb)/.test(parent.className) && /^[HSBLRG]$/.test(
				target.firstChild ? target.firstChild.data : ''
			),
			isDblClick = /dblc/.test(event.type),
			buttonAction = ''; // think this over again....

		if (isDblClick && !isModeButton) {
			return;
		} else if (targetClass.indexOf('-labl ' + prefix + 'labl') !== -1) { // HSB -> HSL; CMYK -> Lab buttons
			changeClass(_nodes[targetClass.split('-')[0]], prefix + 'hide', '');
			changeClass(_nodes[parent.className.split('-')[1]], prefix + 'hide');
		} else if (targetClass.indexOf(prefix + 'butt') !== -1) { // BUTTONS
			if (isModeButton) { // set render modes
				if (isDblClick && _options.scale === 2) {
					newMode = /hs/.test(mode.type) ? 'rgb' : /hide/.test(_nodes.hsl.className) ? 'hsv' : 'hsl';
					newMode = newMode + '-' + newMode[mode.type.indexOf(mode.z)];
				}
				_colorPicker.setMode(newMode ? newMode : targetClass.replace('-butt', '').split(' ')[0]);
				buttonAction = 'modeChange';
			} else if (/^[rgb]/.test(targetClass)) { // no vertical slider rendering in RGB mode
				newMode = targetClass.split('-')[1];
				changeClass(_nodes.colorPicker, 'no-rgb-' + newMode,
					(options['noRGB' + newMode] = !options['noRGB' + newMode]) ? undefined : '');
				buttonAction = 'noRGB' + newMode;
				// preRenderAll();
			} else if (target === _nodes.alpha_labl) { // alpha button right (background of raster)
				customBG = options.customBG;
				alphaBG = options.alphaBG;
				changeClass(_nodes.colorPicker, 'alpha-bg-' + alphaBG, 'alpha-bg-' +
					(alphaBG = options.alphaBG = e.data || (alphaBG === 'w' ? (customBG ? 'c' : 'b') :
					alphaBG === 'c' ? 'b' : 'w')));
				target.firstChild.data = alphaBG.toUpperCase();
				_nodes.ctrl.style.backgroundColor = _nodes.memo.style.backgroundColor =
					alphaBG !== 'c' ? '' : 'rgb(' + _math.round(customBG.r * 255) + ', ' +
					_math.round(customBG.g * 255) + ', ' +
					_math.round(customBG.b * 255) + ')';
				_nodes.raster.style.cssText = _nodes.raster_bg.previousSibling.style.cssText =
					alphaBG !== 'c' ? '' : getOpacityCSS(customBG.luminance < 0.22 ? 0.5 : 0.4);
				buttonAction = 'alphaBackground';
			} else if (target === _nodes.alpha_butt) { // alpha button left (disable alpha rendering)
				changeClass(_nodes.colorPicker, 'mute-alpha', (options.muteAlpha = !options.muteAlpha) ? undefined : '');
				buttonAction = 'alphaState';
			} else if (target === _nodes.HEX_butt) { // make it on/off
				changeClass(_nodes.colorPicker, 'no-HEX', (options.HEXState = !options.HEXState) ? undefined : '');
				buttonAction = 'HEXState';
			} else if (target === _nodes.HEX_labl) { // web save state change
				var isWebSave = _colors.saveColor === 'web save';

				if (_colors.saveColor !== 'web smart' && !isWebSave) {
					options.webUnsave = copyColor(RGB);
					_colorPicker.setColor(_colors.webSmart, 'rgb');
				} else if (!isWebSave) {
					if (!options.webUnsave) {
						options.webUnsave = copyColor(RGB);
					}
					_colorPicker.setColor(_colors.webSave, 'rgb');
				} else {
					_colorPicker.setColor(options.webUnsave, 'rgb');
				}
				buttonAction = 'webColorState';
			} else if (/Lab-x-labl/.test(targetClass)) { //target === _nodes.cmyk_type) {
				// switch between CMYK and CMY
				changeClass(_nodes.colorPicker, 'cmy-only', (options.cmyOnly = !options.cmyOnly) ? undefined : '');
				buttonAction = 'cmykState';
			}
		} else if (target === _nodes.bsav) { // SAVE
			saveAsBackground();
			buttonAction = 'saveAsBackground';
		} else if (target === _nodes.bres) { // RESET
			var tmpColor = copyColor(RGB),
				tmpAlpha = _colors.alpha;

			// a bit heavy but... doesn't matter here
			// newCol, type, alpha, forceRender
			_colorPicker.setColor(options.color);
			saveAsBackground();
			_colorPicker.setColor(tmpColor, 'rgb', tmpAlpha);
			buttonAction = 'resetColor';
		} else if (parent === _nodes.col1) { // COLOR left
			// _colors.hsv.h = (_colors.hsv.h + 0.5) % 1; // not acurate
			_colors.hsv.h -= (_colors.hsv.h > 0.5 ? 0.5 : -0.5);
			convertColors('hsv');
			buttonAction = 'shiftColor';

		} else if (parent === _nodes.col2) { // COLOR right
			_colorPicker.setColor(target.style.backgroundColor, 'rgb', _colors.background.alpha);
			buttonAction = 'setSavedColor';
		} else if (parent === _nodes.memo) { // MEMORIES // revisit...
			var resetBlink = function() {
					if (_nodes.memos.blinker) _nodes.memos.blinker.style.cssText = _nodes.memos.cssText;
				},
				doBlink = function(elm) {
					_nodes.memos.blinker = elm;
					elm.style.cssText = 'background-color:' + (_colors.RGBLuminance > 0.22 ? '#333' : '#DDD');
					window.setTimeout(resetBlink, 200);
				};

			if (target === _nodes.memo_cursor) { // save color in memo
				resetBlink();
				_nodes.memos.blinker = undefined;
				_nodes.testNode.style.cssText = _nodes.memo_store.style.cssText;
				_nodes.memos.cssText = _nodes.testNode.style.cssText; // ...how browser sees css
				for (var n = _nodes.memos.length - 1; n--; ) { // check if color already exists
					if (_nodes.memos.cssText === _nodes.memos[n].style.cssText) {
						doBlink(_nodes.memos[n]); // sets _nodes.memos.blinker
						break;
					}
				}
				if (!_nodes.memos.blinker) { // right shift colors
					for (var n = _nodes.memos.length - 1; n--; ) {
						_nodes.memos[n + 1].style.cssText = _nodes.memos[n].style.cssText;
					}
					_nodes.memos[0].style.cssText = _nodes.memo_store.style.cssText;
				}
				buttonAction = 'toMemery';
			} else { // reset color from memo
				resetBlink();
				_colorPicker.setColor(target.style.backgroundColor, 'rgb', target.style.opacity || 1);
				_nodes.memos.cssText = target.style.cssText;
				doBlink(target);
				// this is dirty... has to do with M|W|! button
				_mouseMoveAction = 1;
				buttonAction = 'fromMemory';
			}

		}
		// think this over again, does this need to be like this??
		if (buttonAction) {
			preRenderAll(_colors);
			_mouseMoveAction = _mouseMoveAction || true; // !!!! search for: // this is dirty...
			stopChange(e, buttonAction);
		}
	}

	function resizeApp(e, size) {
		var event = e || window.event,
			page = event ? getPageXY(event) : {},
			isSize = size !== undefined,
			x = isSize ? size : page.X - _targetOrigin.left + 8,
			y = isSize ? size : page.Y - _targetOrigin.top + 8,
			values = [' S XS XXS', ' S XS', ' S', ''],
			sizes = _options.sizes, // from getUISizes();
			currentSize = isSize ? size :
				y < sizes.XXS[1] + 25 ? 0 :
				x < sizes.XS[0] + 25 ? 1 :
				x < sizes.S[0] + 25 || y < sizes.S[1] + 25 ? 2 : 3,
			value = values[currentSize],
			isXXS = false,
			mode,
			tmp = '';

		if (_cashedVars.resizer !== value) {
			isXXS = /XX/.test(value);
			mode = _options.mode;

			if (isXXS && (!/hs/.test(mode.type) || mode.z === 'h')) {
				tmp = mode.type + '-' + mode.z;
				_colorPicker.setMode(/hs/.test(mode.type) ? mode.type + '-s': 'hsv-s');
				_options.mode.original = tmp;
			} else if (mode.original) {
				// setMode(mode) creates a new object so mode.original gets deleted automatically
				_colorPicker.setMode(mode.original);
			}

			_nodes.colorPicker.className = _nodes.colorPicker.className.replace(/\s+(?:S|XS|XXS)/g, '') + value;
			_options.scale = isXXS ? 4 : /S/.test(value) ? 2 : 1;
			_options.currentSize = currentSize;

			_cashedVars.resizer = value;

			// fix this... from this point on inside if() ... convertColors();
			_newData = true;
			renderAll();
			resetCursors();
		}

		_nodes.resizer.style.cssText = 'display: block;' +
			'width: '  + (x > 10 ? x : 10) + 'px;' +
			'height: ' + (y > 10 ? y : 10) + 'px;';
	}

	// ------------------------------------------------------ //
	// --- Colors calculation and rendering related stuff  --- //
	// -------------------------------------------------------//

	function setMode(mode) {
		var ModeMatrix = {
			rgb_r : {x: 'b', y: 'g'},
			rgb_g : {x: 'b', y: 'r'},
			rgb_b : {x: 'r', y: 'g'},

			hsv_h : {x: 's', y: 'v'},
			hsv_s : {x: 'h', y: 'v'},
			hsv_v : {x: 'h', y: 's'},

			hsl_h : {x: 's', y: 'l'},
			hsl_s : {x: 'h', y: 'l'},
			hsl_l : {x: 'h', y: 's'}
		},
		key = mode.replace('-', '_'),
		regex = '\\b(?:rg|hs)\\w\\-\\w\\b'; // \\b\\w{3}\\-\\w\\b';

		// changeClass(_nodes.colorPicker, '(?:.*?)$', mode);
		// changeClass(_nodes.colorPicker, '\\b\\w{3}\\-\\w\\b', mode);
		// changeClass(_nodes.slds, '\\b\\w{3}\\-\\w\\b', mode);
		changeClass(_nodes.panel, regex, mode);
		changeClass(_nodes.slds, regex, mode);

		mode = mode.split('-');
		return _options.mode = {
			type: mode[0],
			x: ModeMatrix[key].x,
			y: ModeMatrix[key].y,
			z: mode[1]
		};
	}

	function initSliders() { // function name...
		var regex = /\s+(?:hue-)*(?:dark|light)/g,
			className = 'className'; // minification

		_nodes.curl[className] = _nodes.curl[className].replace(regex, ''); // .....
		_nodes.curr[className] = _nodes.curr[className].replace(regex, ''); // .....
		_nodes.slds[className] = _nodes.slds[className].replace(regex, '');
		// var sldrs = ['sldr_2', 'sldr_4', 'sldl_3'];
		// for (var n = sldrs.length; n--; ) {
		// 	_nodes[sldrs[n]][className] = _options.CSSPrefix + sldrs[n].replace('_', '-');
		// }
		_nodes.sldr_2[className] = _options.CSSPrefix + 'sldr-2';
		_nodes.sldr_4[className] = _options.CSSPrefix + 'sldr-4';
		_nodes.sldl_3[className] = _options.CSSPrefix + 'sldl-3';

		for (var style in _nodes.styles) {
			if (!style.indexOf('sld')) _nodes.styles[style].cssText = '';
		}
		_cashedVars = {};
	}

	function resetCursors() {
		// _renderVars.isNoRGB = undefined;
		_nodes.styles.curr.cssText = _nodes.styles.curl.cssText; // only coordinates
		_nodes.curl.className = _options.CSSPrefix + 'curl' + (
			_renderVars.noRGBZ ? ' ' + _options.CSSPrefix + 'curl-' +_renderVars.noRGBZ: '');
		_nodes.curr.className = _options.CSSPrefix + 'curr ' + _options.CSSPrefix + 'curr-' +
			(_options.mode.z === 'h' ? _renderVars.HUEContrast : _renderVars.noRGBZ ?
				_renderVars.noRGBZ : _renderVars.RGBLuminance);
	}

	function convertColors(type) {
		preRenderAll(_colorInstance.setColor(undefined, type || _options.mode.type));
		_newData = true;
	}

	function saveAsBackground(refresh) {
		_colorInstance.saveAsBackground();
		_nodes.styles.col2.cssText = 'background-color: ' + color2string(_colors.background.RGB) + ';' +
			getOpacityCSS(_colors.background.alpha);
		
		if (refresh) {
			preRenderAll(_colors);
			// renderAll();
		}
		return (_colors);
	}

	function preRenderAll(colors) {
		var _Math = _math,
			renderVars = _renderVars,
			bgType = _bgTypes[_options.alphaBG];

		renderVars.hueDelta = _Math.round(colors['rgbaMixBGMix' + bgType].hueDelta * 100);
		// renderVars.RGBLuminanceDelta = _Math.round(colors.RGBLuminanceDelta * 100);
		renderVars.luminanceDelta = _Math.round(colors['rgbaMixBGMix' + bgType].luminanceDelta * 100);
		renderVars.RGBLuminance = colors.RGBLuminance > 0.22 ? 'light' : 'dark';
		renderVars.HUEContrast = colors.HUELuminance > 0.22 ? 'light' : 'dark';
		// renderVars.contrast = renderVars.RGBLuminanceDelta > renderVars.hueDelta ? 'contrast' : '';
		renderVars.contrast = renderVars.luminanceDelta > renderVars.hueDelta ? 'contrast' : '';
		renderVars.readabiltiy =
			colors['rgbaMixBGMix' + bgType].WCAG2Ratio >= 7 ? 'green' :
			colors['rgbaMixBGMix' + bgType].WCAG2Ratio >= 4.5 ? 'orange': '';
		renderVars.noRGBZ = _options['no' + _options.mode.type.toUpperCase() + _options.mode.z] ?
			(_options.mode.z === 'g' && colors.rgb.g < 0.59 || _options.mode.z === 'b' || _options.mode.z === 'r' ?
			'dark' : 'light') : undefined;
	}

	function renderAll() { // maybe render alpha seperately...
		if (_mouseMoveAction) {
			// _renderTimer = window[requestAnimationFrame](renderAll);
			if (!_newData) return (_renderTimer = window[requestAnimationFrame](renderAll));
			_newData = false;
		}
		// console.time('renderAll');
		var options = _options,
			mode = options.mode,
			scale = options.scale,
			prefix = options.CSSPrefix,
			colors = _colors,
			nodes = _nodes,
			CSS = nodes.styles,
			textNodes = nodes.textNodes,
			valueRanges = _valueRanges,
			valueType = _valueType,
			renderVars = _renderVars,
			cashedVars = _cashedVars,

			_Math = _math,
			_getOpacityCSS = getOpacityCSS,
			_color2string = color2string,

			a = 0,
			b = 0,
			x  = colors[mode.type][mode.x],
			X = _Math.round(x * 255 / (scale === 4 ? 2 : scale)),
			y_ = colors[mode.type][mode.y],
			y = 1 - y_,
			Y = _Math.round(y * 255 / scale),
			z  = 1 - colors[mode.type][mode.z],
			Z = _Math.round(z * 255 / scale),
			coords = (1 === 1) ? [x, y_] : [0, 0], // (1 === 2) button label up

			isRGB = mode.type === 'rgb',
			isHue = mode.z === 'h',
			isHSL = mode.type === 'hsl',
			isHSL_S = isHSL && mode.z === 's',
			moveXY = _mouseMoveAction === changeXYValue,
			moveZ  = _mouseMoveAction === changeZValue,
			display, tmp, value, slider;

		if (isRGB) {
			if (coords[0] >= coords[1]) b = 1; else a = 1;
			if (cashedVars.sliderSwap !== a) {
				nodes.sldr_2.className = options.CSSPrefix + 'sldr-' + (3 - a);
				cashedVars.sliderSwap = a;
			}
		}
		if ((isRGB && !moveZ) || (isHue && !moveXY) || (!isHue && !moveZ)) {
			CSS[isHue ? 'sldl_2' : 'sldr_2'][isRGB ? 'cssText' : 'backgroundColor'] =
				isRGB ? _getOpacityCSS((coords[a] - coords[b]) / (1 - (coords[b]) || 0)) : _color2string(colors.hueRGB);
		}
		if (!isHue) {
			if (!moveZ)  CSS.sldr_4.cssText = _getOpacityCSS(isRGB ? coords[b] : isHSL_S ? _Math.abs(1 - y * 2) : y);
			if (!moveXY) CSS.sldl_3.cssText = _getOpacityCSS(isHSL && mode.z === 'l' ? _Math.abs(1 - z * 2) : z);
			if (isHSL) { // switch slider class name for black/white color half way through in HSL(S|L) mode(s)
				slider = isHSL_S ? 'sldr_4' : 'sldl_3';
				tmp = isHSL_S ? 'r-' : 'l-';
				value = isHSL_S ? (y > 0.5 ? 4 : 3) : (z > 0.5 ? 3 : 4);

				if (cashedVars[slider] !== value) {
					nodes[slider].className = options.CSSPrefix + 'sld' + tmp + value;
					cashedVars[slider] = value;
				}
			}
		}

		if (!moveZ) CSS.curm.cssText = 'left: ' + X + 'px; top: ' + Y + 'px;';
		if (!moveXY) CSS.curl.top = Z + 'px';
		if (valueType) CSS.curr.top = Z + 'px'; // && valueType.type !== mode.type
		if ((valueType && valueType.type === 'alpha') || _mainTarget === nodes.opacity) {
			CSS.opacity_slider.left = options.opacityPositionRelative ? (colors.alpha * (
				(_targetOrigin.width || nodes.opacity.offsetWidth) -
				(_targetOrigin.childWidth || nodes.opacity_slider.offsetWidth))) + 'px' :
				(colors.alpha * 100) + '%';
		}

		CSS.col1.cssText = 'background-color: ' + _color2string(colors.RND.rgb) + '; ' +
			(options.muteAlpha ? '' : _getOpacityCSS(colors.alpha));
		CSS.opacity.backgroundColor = _color2string(colors.RND.rgb);
		CSS.cold.width = renderVars.hueDelta + '%';
		CSS.cont.width = renderVars.luminanceDelta + '%';

		for (display in textNodes) {
			tmp = display.split('_');
			if (options.cmyOnly) {
				tmp[0] = tmp[0].replace('k', '');
			}
			value = tmp[1] ? colors.RND[tmp[0]][tmp[1]] : colors.RND[tmp[0]] || colors[tmp[0]];
			if (cashedVars[display] !== value) {
				cashedVars[display] = value;
				textNodes[display].data = value > 359.5 && display !== 'HEX' ? 0 : value;

				if (display !== 'HEX' && !options.noRangeBackground) {
					value = colors[tmp[0]][tmp[1]] !== undefined ? colors[tmp[0]][tmp[1]] : colors[tmp[0]];
					if (tmp[0] === 'Lab') {
						value = (value - valueRanges[tmp[0]][tmp[1]][0]) /
							(valueRanges[tmp[0]][tmp[1]][1] - valueRanges[tmp[0]][tmp[1]][0]);
					}
					CSS[display].backgroundPosition = _Math.round((1 - value) * 100) + '% 0%';
				}
			}
		}
		// Lab out of gammut
		tmp = colors._rgb ? [
			colors._rgb.r !== colors.rgb.r,
			colors._rgb.g !== colors.rgb.g,
			colors._rgb.b !== colors.rgb.b
		] : [];
		if (tmp.join('') !== cashedVars.outOfGammut) {
			nodes.rgb_r_labl.firstChild.data = tmp[0] ? '!' : ' ';
			nodes.rgb_g_labl.firstChild.data = tmp[1] ? '!' : ' ';
			nodes.rgb_b_labl.firstChild.data = tmp[2] ? '!' : ' ';
			cashedVars.outOfGammut = tmp.join('');
		}
		if (renderVars.noRGBZ) {
			if (cashedVars.noRGBZ !== renderVars.noRGBZ) {
				nodes.curl.className = prefix + 'curl ' + prefix + 'curl-' + renderVars.noRGBZ;
					
				if (!moveZ) {
					nodes.curr.className = prefix + 'curr ' + prefix + 'curr-' + renderVars.noRGBZ;
				}
				cashedVars.noRGBZ = renderVars.noRGBZ;
			}
		}
		if (cashedVars.HUEContrast !== renderVars.HUEContrast && mode.z === 'h') {
			nodes.slds.className = nodes.slds.className.replace(/\s+hue-(?:dark|light)/, '') +
				' hue-' + renderVars.HUEContrast;
			if (!moveZ) {
				nodes.curr.className = prefix + 'curr ' + prefix + 'curr-' + renderVars.HUEContrast;
			}
			cashedVars.HUEContrast = renderVars.HUEContrast;
		} else if (cashedVars.RGBLuminance !== renderVars.RGBLuminance) { // test for no else
			nodes.colorPicker.className = nodes.colorPicker.className.replace(/\s+(?:dark|light)/, '') +
				' ' + renderVars.RGBLuminance;
			if (!moveZ && mode.z !== 'h' && !renderVars.noRGBZ) {
				nodes.curr.className = prefix + 'curr ' + prefix + 'curr-' + renderVars.RGBLuminance;
			}
			cashedVars.RGBLuminance = renderVars.RGBLuminance;
		}

		if (cashedVars.contrast !== renderVars.contrast || cashedVars.readabiltiy !== renderVars.readabiltiy) {
			nodes.ctrl.className = nodes.ctrl.className.replace(' contrast', '').replace(/\s*(?:orange|green)/, '') +
				(renderVars.contrast ? ' ' + renderVars.contrast : '') +
				(renderVars.readabiltiy ? ' ' + renderVars.readabiltiy : '');
			cashedVars.contrast = renderVars.contrast;
			cashedVars.readabiltiy = renderVars.readabiltiy;
		}

		if (cashedVars.saveColor !== colors.saveColor) {
			nodes.HEX_labl.firstChild.data = !colors.saveColor ? '!' : colors.saveColor === 'web save' ? 'W' : 'M';
			cashedVars.saveColor = colors.saveColor;
		}

		if (options.renderCallback) {
			options.renderCallback(colors, mode); // maybe more parameters
		}

		if (_mouseMoveAction) {
			_renderTimer = window[requestAnimationFrame](renderAll);
		}

		// console.timeEnd('renderAll')
	}


	// ------------------------------------------------------ //
	// ------------------ helper functions ------------------ //
	// -------------------------------------------------------//

	function copyColor(color) {
		var newColor = {};

		for (var n in color) {
			newColor[n] = color[n];
		}
		return newColor;
	}

	// function color2string(color, type) {
	// 	var out = [],
	// 		n = 0;

	// 	type = type || 'rgb';
	// 	while (type.charAt(n)) { // IE7 // V8 type[n] || 
	// 		out.push(color[type.charAt(n)]);
	// 		n++;
	// 	}
	// 	return type + '(' + out.join(', ') + ')';
	// }

	function color2string(color, type) { // ~2 x faster on V8
		var out = '',
			t = (type || 'rgb').split(''),
			n = t.length;

		for ( ; n--; ) {
			out = ', ' + color[t[n]] + out;
		}
		return (type || 'rgb') + '(' + out.substr(2) + ')';
	}


	function limitValue(value, min, max) {
		// return Math.max(min, Math.min(max, value)); // faster??
		return (value > max ? max : value < min ? min : value);
	}

	function getOpacityCSS(value) {
		if (value === undefined) value = 1;

		if (_doesOpacity) {
			return 'opacity: ' + (_math.round(value * 10000000000) / 10000000000) + ';'; // value.toFixed(16) = 99% slower
			// some speed test:
			// return ['opacity: ', (Math.round(value * 1e+10) / 1e+10), ';'].join('');
		} else {
			return 'filter: alpha(opacity=' + _math.round(value * 100) + ');';
		}
	}

	function preventDefault(e, skip) {
		e.preventDefault ? e.preventDefault() : e.returnValue = false;
		if (!skip) window.getSelection ? window.getSelection().removeAllRanges() : document.selection.empty();
		return false;
	}

	function changeClass(elm, cln, newCln) {
		return  !elm ? false : elm.className = (newCln !== undefined ?
			elm.className.replace(new RegExp('\\s+?' + cln, 'g'), newCln ? ' ' + newCln : '') :
			elm.className + ' ' + cln);
	}

	function getOrigin(elm) {
		var box = (elm.getBoundingClientRect) ? elm.getBoundingClientRect() : {top: 0, left: 0},
			doc = elm && elm.ownerDocument,
			body = doc.body,
			win = doc.defaultView || doc.parentWindow || window,
			docElem = doc.documentElement || body.parentNode,
			clientTop  = docElem.clientTop  || body.clientTop  || 0, // border on html or body or both
			clientLeft =  docElem.clientLeft || body.clientLeft || 0;

		return {
			left: box.left + (win.pageXOffset || docElem.scrollLeft) - clientLeft,
			top:  box.top  + (win.pageYOffset || docElem.scrollTop)  - clientTop
		};
	}

	function getPageXY(e) {
		var doc = window.document;

		return {
			X: e.pageX || e.clientX + doc.body.scrollLeft + doc.documentElement.scrollLeft,
			Y: e.pageY || e.clientY + doc.body.scrollTop + doc.documentElement.scrollTop
		};
	}

	function addEvent(obj, type, func) {
		addEvent.cache = addEvent.cache || {
			_get: function(obj, type, func, checkOnly) {
				var cache = addEvent.cache[type] || [];

				for (var n = cache.length; n--; ) {
					if (obj === cache[n].obj && '' + func === '' + cache[n].func) {
						func = cache[n].func;
						if (!checkOnly) {
							cache[n] = cache[n].obj = cache[n].func = null;
							cache.splice(n, 1);
						}
						return func;
					}
				}
			},
			_set: function(obj, type, func) {
				var cache = addEvent.cache[type] = addEvent.cache[type] || [];
				
				if (addEvent.cache._get(obj, type, func, true)) {
					return true;
				} else {
					cache.push({
						func: func,
						obj: obj
					});
				}
			}
		};

		if (!func.name && addEvent.cache._set(obj, type, func) || typeof func !== 'function') {
			return;
		}

		if (obj.addEventListener) obj.addEventListener(type, func, false);
		else obj.attachEvent('on' + type, func);
	}

	function removeEvent(obj, type, func) {
		if (typeof func !== 'function') return;
		if (!func.name) {
			func = addEvent.cache._get(obj, type, func) || func;
		}

		if (obj.removeEventListener) obj.removeEventListener(type, func, false);
		else obj.detachEvent('on' + type, func);
	}

	function caret(target, pos) { // only for contenteditable
		var out = {};

		if (pos === undefined) { // get
			if (window.getSelection) { // HTML5
				target.focus();
				var range1 = window.getSelection().getRangeAt(0),
					range2 = range1.cloneRange();
				range2.selectNodeContents(target);
				range2.setEnd(range1.endContainer, range1.endOffset);
				out = {
					end: range2.toString().length,
					range: range1.toString().length
				};
			} else { // IE < 9
				target.focus();
				var range1 = document.selection.createRange(),
					range2 = document.body.createTextRange();
				range2.moveToElementText(target);
				range2.setEndPoint('EndToEnd', range1);
				out = {
					end: range2.text.length,
					range: range1.text.length
				};
			}
			out.start = out.end - out.range;
			return out;
		}
		// set
		if (pos == -1) pos = target['text']().length;
		
		if (window.getSelection) { // HTML5
			target.focus();
			window.getSelection().collapse(target.firstChild, pos);
		} else { // IE < 9
			var range = document.body.createTextRange();
			range.moveToElementText(target);
			range.moveStart('character', pos);
			range.collapse(true);
			range.select();
		}
		return pos;
	}

	// ------------- requestAnimationFrame shim ------------- //
	// ---------- quite optimized for minification ---------- //

	for(var n = vendors.length; n-- && !window[requestAnimationFrame]; ) {
		window[requestAnimationFrame] = window[vendors[n] + 'Request' + animationFrame];
		window[cancelAnimationFrame]  = window[vendors[n] + 'Cancel'  + animationFrame] ||
			window[vendors[n] + 'CancelRequest' + animationFrame];
	}

	window[requestAnimationFrame] = window[requestAnimationFrame] || function(callback) {
		// this is good enough... and better than setTimeout
			return window.setTimeout(callback, 1000 / _options.fps);
		// return _renderTimer ? _renderTimer : window.setInterval(callback, 1000 / _options.fps);
	};

	window[cancelAnimationFrame] = window[cancelAnimationFrame] || function(id) {
		// console.log('OFF-', id + '-' + _renderTimer)
		window.clearTimeout(id);
		return _renderTimer = null;
	};

})(window);