/**
* debug function
* @author Keith Foster
*/
function debug(msg) {
	if(typeof(console)!="undefined") { console.log(msg); }
}

/**
* Extending core javascript functionality
* @author Alex Tse
*/

/**
* extending object with additional properties
* @param dest Destination of object to extend
* @param src Object with additional properties
* @return Object with extended properties
*/
Object.extend = function(dest, src) {
	for (var property in src) 	{ dest[property] = src[property]; }
	return dest;
};

/**
* Return a new object that is inherited from the given object
* @param obj Object to be inherited
* @param extend (Optional) Properties added to the new object
* @return New object inheriting the given object
*/
Object.inherit = function(obj, extend) {
	function F() {}
	F.prototype = obj;
	var tmp = new F();
	return Object.extend(tmp, extend);
};

/**
* Determine if the object has no property
* @param obj Object to be checked
* @return boolean, false if object has property, true otherwise
*/
Object.isEmpty = function(obj) {
	for (var property in obj) { return false; }
	return true;
};

/**
* call function for each property, 1st parameter = the object, as 2nd parameter = property name
* keyword 'this' is the iterating property value of the object, 
* @param func function to call for each element
* @return the object
*/ 
Object.each = function(obj, fn) {
	for (var property in obj) { fn.call(obj[property], obj, property); }
	return obj;
};

Array.prototype = Object.extend(Array.prototype, {
	/**
	* call function for each element, 1st parameter = array object, as 2nd parameter = index
	* keyword 'this' is the iterating object of the array, 
	* @param func function to call for each element
	* @return array object
	*/ 
	each: function(func) {
		for (var i=0, len = this.length; i<len; i++) { func.call(this[i], this, i); }
		return this;
	},
	
	/**
	* Search for the given object in the array
	* @param obj Object to search for
	* @param fromIndex Index to start from
	* @return Index of the first occurance identical object in the array, -1 if not find
	*/
	indexOf: function(obj, fromIndex) {
		var fromIndex = fromIndex || 0;
		for (var i=fromIndex, len = this.length; i<len; i++) { if (obj === this[i]) return i; }
		return -1;
	},
	
	/**
	* merge an array with an enumerable obj
	* @param subject Enumerable object
	* @return array without duplicated items
	*/
	merge: function(subject) {
		var ar = this.slice(0);
		for (var i=0, len = subject.length; i<len; i++) { if (ar.indexOf(subject[i]) < 0) ar.push(subject[i]); }
		return ar;
	},
	
	/**
	* Return an array without elements in the subject
	* @param subject Array of item to subtract
	* @return Array of items that do not exist in subject
	*/
	subtract: function(subject) {
		var ar = new Array();
		for (var i=0, len = this.length; i<len; i++) { if (subject.indexOf(this[i]) < 0) ar.push(this[i]); }
		return ar;
	},
	
	/**
	* @param subject Array to compare
	* @param strict boolean to specify a strict comparsion or loose, strict: positions and elements are the same, loose: elements are the same
	* @return true/false
	*/
	equals: function(subject, strict) {
		if (this.length != subject.length) return false;
		if (!!strict) {
			for (var i=0, len = this.length; i<len; i++) { if (this[i] !== subject[i]) return false; }
			return true;
		} else {
			var ar = $a(this);
			for (var i=0, len = subject.length; i<len; i++) {
				var matchIndex = ar.indexOf(subject[i]);
				if (matchIndex < 0) return false;
				ar.splice(matchIndex, 1);
			}
			return ar.length == 0;
		}
	}
	
});

String.prototype = Object.extend(String.prototype, {
	/**
	* Trim the head and tail whitespaces
	* @return String without whitespaces at head and tail
	*/
	trim: function() {
		return this.replace(/^\s*(\S*(\s+\S+)*)\s*$/ , '$1');
	},
	
	/**
	* Substitude {str} with str property in given object and return the String
	* @param obj Given object with corresponding properties
	* @return String with substituded value
	*
	* Example use:
	* "<p>{greeting}, my name is {name}</p>".supplant({ 'greeting': 'Hello World!', 'name': 'Alex' })
	* will return "<p>Hello World!, my name is Alex</p>"
	*/
	supplant: function(obj) {
		return this.replace(/{([^{}]*)}/g, function(a, b) {
			return (typeof obj[b] === 'string')? obj[b] : a;
		});
	}
});

/**
* Return a new Function that is inherited from the given object
* @param obj Object to be inherited
* @param extend (Optional) Properties added to the new object
* @return New function inheriting the given object
*/
var Class = {
	inherit: function(obj, extend) {
		function F() {};
		F.prototype = obj;
		for (property in extend) F.prototype[property] = extend[property];
		return F;
	}
}

var Window = {
	$_GET: function() {
		var aQuery = location.search.slice(1).split('&');
		var oq = {}
		for (var i=0, len=aQuery.length; i < len; i++) {
			var pair = aQuery[i].split('=', 2);
			oq[pair[0]] = decodeURI(pair[1]);
		}
		return oq;
	}(),
	/**
	* Return the width of viewport in pixel
	* @return Viewport width in pixel
	*/
	getInnerWidth: function() {
		return window.innerWidth || document.documentElement.clientWidth;	
	},
	
	/**
	* Return the height of viewport in pixel
	* @return Viewport height in pixel
	*/
	getInnerHeight: function() {
		return window.innerHeight || document.documentElement.clientHeight;
	},
	
	getScrollWidth: function() {
		return window.pageXOffset || document.documentElement.scrollLeft;
	},
	
	getScrollHeight: function() {
		return window.pageYOffset || document.documentElement.scrollTop;
	},
	
	/**
	* DOM is always got generated before all binary has been downloaded.
	* Fire function when the DOM is ready
	* @param fn Function to call when the DOM is ready
	*/
	onDomReady: function(fn) {
		var loaded = false;
		var _domReadyTimer;
		var __ie_domReady__;
		
		var _domReady = function() {
			if (loaded) return;
			loaded = true;
			clearInterval(_domReadyTimer);
			if (__ie_domReady__) __ie_domReady__.remove();
			return fn();
		}
		// for browsers support DOMContentLoaded event
		$(document).addListener('DOMContentLoaded', _domReady);
		// for safari hack
		if (/WebKit/i.test(navigator.userAgent)) {
			_domReadyTimer = setInterval( function() {
				if (/loaded|complete/.test(document.readyState)) _domReady();
			}, 5);
		} else if (Browser.isIE) { //for IE hack
			var str = "<scr" + "ipt id='__ie_domReady__' defer='' src='//:'></scr" + "ipt>";
			document.write(str);
			__ie_domReady__ = $('__ie_domReady__');
			__ie_domReady__.addListener('readystatechange', function() {
				if (/complete/i.test(this.readyState)) _domReady();
			});
		}
		// for all other browsers
		var _onload = window.onload || function() {};
		window.onload = function() {
			_domReady();
			_onload();
		}
	},
	
	onLoad: function(fn) {
		var _onload = window.onload || function() {};
		window.onload = function() {
			_onload();
			fn();
		}
	}
}

var Event = {
	/**
	* Return the event source element
	* @param e Event object
	* @return Element which fire the event
	*/
	getTarget: function(e) {
		return e.target || e.srcElement;
	},
	
	/**
	* Return the element came from with given event
	* @param e Event object
	* @return Element came from when the event fired
	*/	
	getFromTarget: function(e) {
		return e.relatedTarget || e.fromElement;
	},
	
	/**
	* Return the element goes to with given event
	* @param e Event object
	* @return Element goes to when the event fired
	*/	
	getToTarget: function(e) {
		return e.relatedTarget || e.toElement;
	}
}

var Browser = {
	isIE: /*@cc_on!@*/false,
	IEScriptVersion: /*@cc_on@_jscript_version @*/+0,
	fromApple: (/\bApple\b/).test(navigator.vendor)
}

/**
* Return an enumerable object as an array
* @param arr Enumerable Object
* @return Array which contains the item in the given enumerable object
*/
function $a(arr) {
	var ar = [];
	for (var i=0; i<arr.length; i++) ar.push(arr[i]);
	return ar;
}

/**
* Function to create new element with extended functions
* @param tagName Tag name of the new element
* @param attrList (Optional) Attribute list in object: value pairs
* @return Element in given tag name and attributes with extended functions
*/
function Element(tagName, attrList) {
	if (Browser.isIE && !!attrList && !!attrList.name) {
		tagName = "<" + tagName + " name=\"" + attrList.name + "\" >";
		delete attrList.name;
	}
	var el = document.createElement(tagName);
	for (var attrName in attrList) {
		if (attrName === 'class') {
			el.className = attrList[attrName];
		} else {
			el.setAttribute(attrName, attrList[attrName]);
		}
	}
	return $(el);
}

Element.prototype = {
	
}

function $(element) {
	var el = (typeof(element) == "string")? ((document.getElementById)? document.getElementById(element) : document.all[element]) : element;
	if (!el) return null;
	if (el.__IS_EXTENDED && el.__SELF == el) return el;
	return Object.extend(el, {
		__IS_EXTENDED : true, // indicator that the element has been extended.
		__ANIMATION_MSPF : 20,  // ms per frame used on transformStyle
		__SELF: el,
		
		/**
		* DOM modify related section
		*/
		
		/**
		* Remove element from the DOM
		* @return Current element after operation
		*/
		remove: function() {
			this.purge().parentNode.removeChild(this);
			return this;
		},
		
		/**
		* Remove all children of the element from the DOM
		* @return Current element after operation
		*/
		removeChildren: function() {
			while (this.hasChildNodes()) $(this.lastChild).remove();
			return this;
		},
		
		/**
		* Replace an element with itself
		* @param oldNode Target element to be replaced.
		* @return Current element after operation
		*/
		replace: function(oldNode) {
			$(oldNode).purge().parentNode.replaceChild(this, oldNode);
			return this;
		},
		
		/**
		* Removing all functions/events from the element to avoid memory leak.
		* @return Current element node
		*/
		purge: function() {
			if (this.__AnimationTimer) clearTimeout(this.__AnimationTimer);
			for (var property in this) if (typeof this[property] === 'function') this[property] = null;
			return this;
		},
		
		/**
		* Append child and return the current element
		* @param element Child element to append
		* @return Current element node
		*/
		addChild: function(element) {
			this.appendChild(element);
			return this;
		},
		
		setHTML: function(html) {
			this.innerHTML = html;
			return this;
		},
		
		/**
		* Setting properties for the element object
		* @param json Properties to be added/replaced on the current element in JSON format
		* @return Current element node
		*/
		setProperty: function(json) {
			return Object.extend(this, json);
		},
		
		/**
		* Return an array of elements with matching properties
		* @param proName Property name to match
		* @param proValue (Optional) property value of the given property name
		* @return Array of elements with matched properties, null if no matching elements
		*/
		getElementsByProperty: function(proName, proValue) {
			var descendent = this.getElementsByTagName("*");
			var els = new Array();
			for (var i=0; i<descendent.length; i++) {
				if (descendent[i][proName] === undefined) continue;
				if (proValue === undefined) { els.push(descendent[i]); continue; }
				else if (proValue == descendent[i][proName]) els.push(descendent[i]);
			}
			return (els.length > 0)? els : null;
		},
		
		/**
		* Return an array of elements with matching attributes
		* @param attrName Attribute name to match
		* @param attrValue (Optional) attribute value of the given attribute name
		* @return Array of elements with matched attributes, null if no matching elements
		*/
		getElementsByAttribute: function(attrName, attrValue) {
			var descendent = this.getElementsByTagName("*");
			var els = new Array();
			for (var i=0; i<descendent.length; i++) {
				if (!descendent[i].getAttribute(attrName)) continue;
				if (!attrValue) { els.push(descendent[i]); continue; }
				else if (attrValue == descendent[i].getAttribute(attrName)) els.push(descendent[i]);
			}
			return (els.length > 0)? els : null;
		},
		
		/**
		* Return an array of elements with matched given selector, starting from current element
		* p.s: if the element has multiple class name, it will return the element if one of its class name matched the selector.
		* support mulitple elements with same id
		* support '>' child selector
		* support checking for existence of attribute
		* support comparison of attribute value (supporting operators: =, !=, >=, <=, >, <)
		* support multiple selectos separated by commas
		* @param selector Selectors given to search within the DOM
		* @return Array without duplicated elements, or null if no matched elements
		*
		* Example Selector with child selector: "div > span"
		* Example Selector with existence of attribute: "form[method]"
		* Example Selector with comparison of attribute value: "input[type='radio']"
		*
		* selectors = /([^,]+('|")[^\2]*\2[^,]+)|([^,]+)/g
		* tag = (^[^#.\[\s]+)?
		* id = (#([^.\[\s]+))?
		* class = (\.([^#\[\s]+))?
		* attribute = (\[\s*(\w*)\s*((!=|<=|>=|<|>|=).*)?\])?
		*
		* matched[1] = tag
		* matched[3] = id
		* matched[5] = class
		* matched[7] = attribute name
		* matched[8] = attribute condition
		* matched[9] = attribute condition operator
		*/
		getElementsBySelector: function(selectors) {
			function _helper(selector) {
				var result = [];
				var head = selector.match(/\S+(\[?([^\[\]]*\]))*/);		if (typeof head === 'object') head = head[0];	
				var tail = selector.replace(/\S+(\[?([^\[\]]*\]))*\s*/, '');	if (typeof tail === 'object') tail = tail[0];
				if (head === '>') return _helper.call(this, tail);
				
				var matched = head.match(/(^[^#.\[\s]+)?(#([^.\[\s]+))?(\.([^#\[\s]+))?(\[\s*(\w*)\s*((!=|<=|>=|<|>|=).*)?\])?/);
				if (matched[1] && matched[1].toUpperCase() != this.tagName) return;
				if (matched[3] && matched[3] != this.getAttribute('id')) return;
				if (matched[5] && !new RegExp('(^|\\s)' + matched[5] + '($|\\s)').test(this.className)) return;
				if (matched[7] && !this.getAttribute(matched[7])) return;
				if (matched[8] && !eval( "'" + this.getAttribute(matched[7]) + "'" + ((matched[9] === '=')? "=" : "") + matched[8]) ) return;
				// when got to here, this element matches the head selector
				if (tail == '') return [this];
				
				// when got to here, recursive on its descendants
				var desc = (tail.indexOf('>') === 0)? this.childNodes : this.getElementsByTagName('*');
				for (var i=0; i<desc.length; i++) {
					var matchedDesc = _helper.call(desc[i], tail);
					if (matchedDesc) {
						result = result.merge(matchedDesc);
					}
				}
				return result;
			}
			
			if (typeof selectors !== 'string' || selectors === '') return null;
			var root = (this === document)? document.documentElement || document.body : this;
			var aSelectors = selectors.match(/([^,]+('|")[^\2]*\2[^,]+)|([^,]+)/g);
			var result = [];
			for (var i=0, len=aSelectors.length; i<len; i++) {
				var selector = aSelectors[i];
				result = result.merge(_helper.call(root, root.tagName + " " + selector));
			}
			return (result.length > 0)? result : null;
		},
		
		/**
		* Event related section
		*/
		
		/**
		* Add Event Listener to the element
		* @param e Event type
		* @param fn Event handler function, with keyword 'this' assigned to current element
		*/
		addListener: function() {
			if (window.addEventListener) {
				return function(e, fn) {
					el.addEventListener(e, fn, false);
					return el;
				}
			} else if (window.attachEvent) {
				return function(e, fn) {
					var f = function() {
						fn.call(el, window.event);
					};
					el.attachEvent('on' + e, f);
					return el;
				}
			} else {
				return function(e, fn) {
					el['on' + e] = fn;
					return el;
				}
			}
		}(),
				
		/**
		* Style retreiving related section
		*/
		
		/**
		* Retrieve style of the element / dominated style from top/right/bottom/left
		* @param style Style to retrieve
		* @return Style of the element
		*/
		getStyle: function(style) {
			var styleObj = (document.defaultView)? document.defaultView.getComputedStyle(this, null) : this.currentStyle;
			// border / padding / margin retreive the dominated value from top / right / bottom / left
			if (!this.style[style] && !styleObj[style]) {
				do {
					var match = style.match(/(border|padding|margin)(top|right|bottom|left)?(.*)/i);
					if (!match || !!match[2]) break;
					// matched without top/right/bottom/left
					var aPos = ['Top', 'Right', 'Bottom', 'Left'];
					var counter = {};
					for (var i=0; i<aPos.length; i++) {
						var posStyle = style.replace(/(border|padding|margin)(top|right|bottom|left)?(.*)/i, '$1'+aPos[i]+'$3');
						counter[styleObj[posStyle]] = (counter[styleObj[posStyle]])? counter[styleObj[posStyle]] + 1 : 1;
					}
					// take the dominated value
					var nOccurance, dominatedValue;
					for (var value in counter) {
						if (nOccurance && counter[value] < nOccurance) continue;
						nOccurance = counter[value];
						dominatedValue = value;
					}
					return dominatedValue;
				} while (false);
			}
			return this.style[style] || styleObj[style];
		},
		
		/**
		* Get Left position according to document
		* @return Left position according to document in pixels
		*/
		getX: function() {
			var tmpX = this.offsetLeft;
			var op = this;
			while ( op = op.offsetParent ) {
				if (op === document.body && this.getStyle("position").toLowerCase() === 'absolute') break; //Safari fix
				tmpX += op.offsetLeft;
			}
			return tmpX;
		},
		
		/**
		* Get Top position according to document
		* @return Top position according to document in pixels
		*/
		getY: function() {
			var tmpY = this.offsetTop;
			var op = this;
			while ( op = op.offsetParent ) {
				if (op === document.body && this.getStyle("position").toLowerCase() === 'absolute') break; //Safari fix
				tmpY += op.offsetTop;
			}
			return tmpY;
		},
		
		/**
		* Get element width
		* @return Element width in pixels
		*/
		getWidth: function() {
			return this.offsetWidth;
		},
		
		/**
		* Get element height
		* @return Element height in pixels
		*/
		getHeight: function() {
			return this.offsetHeight;
		},
		
		/**
		* Get the center point coordinate.  Use to compare position with other element
		* @return center coordinate of the element relative to document
		*/
		getCenter: function() {
			return [(this.getX() + this.getWidth()) / 2, (this.getY() + this.getHeight()) / 2];
		},
		
		/**
		* Return an array of elements from the given collection which the current element overlapped with.
		* @param collection Array of elements to be checked for collision
		* @return null or Array of collided elements
		*/
		getCollidedElements: function(collection) {
			var ar = new Array();
			for (var i=0; i<collection.length; i++) {
				var box = $(collection[i]);
				if (box === this) continue;
				if (this.getX() < box.getX() + box.getWidth() && this.getY() < box.getY() + box.getHeight() && box.getX() < this.getX() + this.getWidth() && box.getY() < this.getY() + this.getHeight()) ar.push(box);
			}
			return (ar.length > 0)? ar : null;
		},
		/**
		* Style writing related section
		*/
		
		/**
		* Automatically pixelize the height of the element
		* @return Element after operation
		*/
		pixelizeHeight: function(addition) {
			this.style.height = "auto";
			this.style.height = this.offsetHeight + (addition || 0) + "px";
			return this;
		},
		
		/**
		* Automatically pixelize the height of the element
		* @return Element after operation
		*/
		pixelizeWidth: function(addition) {
			this.style.width = "auto";
			this.style.width = this.offsetWidth + (addition || 0) + "px";
			return this;
		},
		
		/**
		* Set the styles of the element, support multiple style in the json object
		* handle different properties for the same style across browsers
		* @param json JSON object of "property-name":"value" pair
		* @return Element after operation
		*
		* Example use:
		* element.setStyle({ width: 100px, height: 100px });
		*/
		setStyle: function(json) {
			for (var style in json) {
				var value = json[style];
				switch (style) {
					case "opacity":
						value = Math.round(value*10000)/10000;
						this.style.opacity = value;
						this.style.filter = "alpha(opacity=" + value*100 + ")";
					break;
					
					case "float":
						this.style.cssFloat = value;
						this.style.styleFloat = value;
					break;
					
					default:
						this.style[style] = value.toString();
					break;
				}
			}
			return this;
		},
		
		/**
		* Remove style set by javascript
		*/
		removeStyle: function(json) {
			var oStyleSet = {};
			for (var oStyle in json) {
				oStyleSet[oStyle] = '';
			}
			this.setStyle(oStyleSet);
		},
		
		/**
		* Transform style of element, support transforming multiple style in the json object
		* A new triggered transformation will override any previous transformation
		* @param json JSON object with the following properties/structure
		* 			<style name>: {
		*				start: <optional: init value>,
		*				end: <stop value>,
		*				accelerate: <optional: factor of acceleration, between value of -1 and 1, < -1 or > 1 will yield funky effect>, 
		*				unit: <optional: unit of the style>,
		*				duration: <approximate duration of the transformation>,
		*				onComplete: <optional: function to call after the transformation of this style is completed>
		*			}
		* @param onAllComplete function to call after all given transformation is completed
		*/
		transformStyle: function(json, onAllComplete) {
			clearTimeout(this.__AnimationTimer);
			var onAllComplete = onAllComplete || function() {};
			var styleToTransform = {};
			var itself = this;
			
			var _helper = function(_json) {
				var _styleToSet = {};
				for (var style in _json) {
					with ( _json[style] ) {
						// setting variable _f for accelerate
						if (style.search(/color$/i) < 0) {
							var _f = -(_accelerate*_step + (-2*_accelerate*_step/_nFrame)*_iFrame);
						} else {
							var _f = [];
							for (var i=0; i<3; i++) _f[i] = -(_accelerate*_step[i] + (-2*_accelerate*_step[i]/_nFrame)*_iFrame);
						}
						
						if (_iFrame > _nFrame || (_step >= 0 && start >= end) || (_step < 0 && start < end)) {
							_styleToSet[style] = end.toString() + unit;
							delete _json[style];
						} else {
							_styleToSet[style] = start.toString() + unit;
							start = (style.search(/color$/i) < 0)? start + _step + _f: start.sum(_step).sum(_f);
							_iFrame = _iFrame + 1;
						}
						if (_iFrame > _nFrame || end.toString() == start.toString()) onComplete.call(this);
					}
				}
				this.setStyle(_styleToSet);
				if (Object.isEmpty(_json)) {  clearInterval(this.__AnimationTimer); onAllComplete.call(this); }
			};
					
			// parameters initialization
			for (var style in json) {
				if (style.search(/color$/i) > -1) {
					if (json[style].start != undefined) {
						var start = (typeof(json[style].start) == 'string')? new RGB(json[style].start) : json[style].start;
					} else {
						var start = new RGB((this.getStyle(style).indexOf('rgb') > -1)? this.getStyle(style).match(/\d+/g) : this.getStyle(style));
					}
				} else var start = json[style].start || parseFloat(this.getStyle(style));
				var end = json[style].end;
				var unit = json[style].unit || "";
				var duration = json[style].duration;
				var onComplete = json[style].onComplete || function() {};
				// below vars are for private calculation
				var _accelerate = json[style].accelerate || 0;
				var _nFrame = Math.ceil(duration / this.__ANIMATION_MSPF);
				if (style.search(/color$/i) < 0) {
					var _step = (end - start)/_nFrame;
				} else {
					var _step = start.getDifference(end).each(function(aStep, index){aStep[index] = this/_nFrame;});
				}
				styleToTransform[style] = { "start": start, "end": end, "unit": unit, "onComplete": onComplete, "_accelerate": _accelerate, "_nFrame": _nFrame, "_iFrame": 0, "_step": _step };
			}
			var transformFunc = function() { _helper.call(itself, styleToTransform); }
			this.__AnimationTimer = setInterval(transformFunc, this.__ANIMATION_MSPF);
		},
		
		/**
		* A simplified version of transformStyle, transform style of element with a style set
		* Non-numeric styles will be set prior to transformation
		* @param styleSet JSON object with the style: value pair, for example
		* 			{"width": "100px", "height": "200px", "color": "#ffffff"}
		*			P.S: value must specify units if applicable
		* @param options Options to the transformation, for example
		*			{"duration": 500, "accelerate": 1, onComplete: fn}
		*			P.S: accelerate and onComplete are optional
		*/
		transformStyleSet: function(styleSet, options) {
			var transformSet = {};
			var nonNumericStyles = {};
			for (var style in styleSet) {
				if ((isNaN(parseFloat(styleSet[style], 10)) && !(/color$/i).test(style)) ||
					(styleSet[style] === 'transparent' || styleSet[style] === 'none') && (/color$/i).test(style)) {
					nonNumericStyles[style] = styleSet[style];
				} else {
					transformSet[style] = {
						end: (!(/color$/i).test(style) && (/-?\d+(\.\d+)?/).test(styleSet[style]))? parseFloat(styleSet[style].match(/-?\d+(\.\d+)?/)[0], 10) : styleSet[style],
						unit: (!(/color$/i).test(style) && (/[^\-\d\.]+/).test(styleSet[style]))? styleSet[style].match(/[^\-\d\.]+/)[0] + '' : '',
						accelerate: options.accelerate,
						duration: options.duration
					}
				}
			}
			this.setStyle(nonNumericStyles).transformStyle(transformSet, options.onComplete);
			return this;
		},
		
		/**
		* Apply effect marco to the element
		* @param effectObject Effect Object
		*/
		applyEffect: function(effectObject, json) {
			effectObject.call(this, json);
			return this;
		}
				
	});
}

/**
* Define getElementsBySelector on document object
*/
document.getElementsBySelector = function(selector) {
	return $(document.documentElement).getElementsBySelector(selector);
}
