/*
	Deft 0.2
	
	========================================================================================
	   NOTICE - this is an early look, and should not be used in a production environment
	========================================================================================
	
	Created by Timothy Marshall of Duzo Design (www.duzodesign.com)
	
	Inspired by MooTools, Prototype.js and jQuery

	Deft is freely distributable under the terms of an MIT-style license.
*/

(function() {
	var 
		window = this,
		
		Denizen = window.Denizen = function(name, options) {
			options = options || {};
			var object = options.extend || options.init || function() {};
			object.constructor = Denizen;
			object.$type = 'Denizen';
			object.$extended = (options.extend) ? true : false;
			
			object.prototype.constructor = options.extend || object;
			object.prototype.$type = name.toLowerCase();
				
			object.prototype.implemented = ['implemented', 'implement', 'isImplemented', '$type', '$key', '$extended'];
			
			object.prototype.isImplemented = function(key) {
				if(Array.prototype.indexOf)
					return (this.implemented.indexOf(key) < 0) ? false : true;
				else {
					for(var i = 0; i < this.implemented.length; i++)
						if(this.implemented[i] === key) return true;
					return false;
				}
			};
			
			object.prototype.implement = function(funcs) {
				for(var fn in funcs) {
					this.prototype[fn] = funcs[fn];
					this.implemented.push(fn);
				}
				return this;
			};
			
			window[name] = object;
			_bank.lot.push(name);
		
			return object;
		},
		
		Deft = window.Deft = {
			version: '0.1.5',
			
			author: {
				company: 'Duzo Design, LLC',
				person: 'Timothy Marshall'
			}
		},
		
		/*
			locals
		*/
		_bank = {
			vault: {},
			$: {},
			events: {},
			lot: ['$A', '$args', '$chk', 'Denizen', '$fetch', '$func', '$H', '$random', '$store', '$type']
		},
		
		$_genKey = function(obj) {
			return obj.$key || (obj.$key = ++$_key);
		},
		
		$_key = 0,
		
		/*
			utility functions
		*/
		$args = window.$args = function(args) {
			var returns = [];
			for (var i = 0, l = args.length; i < l; i++)
				returns.push(args[i]);
			return returns;
		},
		
		$chk = window.$chk = function(obj) {
			return (obj === undefined || obj === null) ? false : true;
		},
		
		$clear = window.$clear = function(t) {
			clearTimeout(t);
			clearInterval(t);
			return null;
		},
		
		$fetch = window.$fetch = function(key) {
			if(!key)
				return _bank.vault;
			return _bank.vault[key];
		},
		
		$func = window.$func = function() { return this; },
		
		$implement = window.$implement = function(Denizens, funcs) {
			Denizens = $args(Denizens);
			$H(funcs).each(function(name, func) {
				if(Denizens.any(function(den) {
					return den.isImplemented(name);
				}))
					return;
				Denizens.each(function(den) {
					den.prototype[name] = func;
					den.implemented.push(name);
				});
			});
			return Denizens;
		},
		
		$random = window.$random = function(min, max) {
			if(!$chk(max)) {
				max = min;
				min = 0;
			}
			return Math.floor(Math.random() * (max - min + 1) + min);
		},
		
		$store = window.$store = function() {
			if(arguments.length === 2) {
				_bank.vault[arguments[0]] = arguments[1];
				return true;
			}
			$H(arguments[0]).each(function(k, v) {
				_bank.vault[k] = v;
			});
		},
		
		$type = window.$type = function(obj) {
			return (obj === undefined || obj === null) ? false :
				(obj.$type) ? obj.$type.toLowerCase() :
				(obj.nodeName) ? (function() {
					switch(obj.nodeType) {
						case 1: return 'element';
						case 3: return 'text';
					}
				})() :
				(typeof obj == 'object') ? (function() {
					return (typeof obj.length == 'number') ? 'array' : 'object';
				})() :
				typeof obj;
		},
		
		$unstore = window.$unstore = function() {
			if(arguments.length === 0) {
				_bank.vault = {};
				return;
			}
			$args(arguments).each(function(o) {
				delete _bank.vault[o];
			});
		},

		/*
			shorthands
		*/
		$H = window.$H = function(obj) {
			obj = obj || {};
			return new Hash(obj);
		};
		
		
	/*
		implementing denizens
	*/
	(function() {
		var inits = {
			'Array': Array, 
			'String': String, 
			'Function': Function, 
			'Number': Number, 
			'Date': Date
		};
		for (var i in inits)
			new Denizen(i, {
				extend: inits[i]
			});
	})();
	
	
	/*
		FUNCTION
	*/
	var _runFunc = function() {
		this[0].apply(this[1], this[2]);
	};
	Function.implement({
		attempt: function() {
			try {
				return this.apply(this, $args(arguments));
			}
			catch(err) {
				return null;
			}
		},
		
		bind: function() {
			var fn = this, args = Array.prototype.slice.call(arguments), obj = args.shift();
			return function() {
				return fn.apply(obj, args.concat(Array.prototype.slice.call(arguments)));
			};
		},
		
		delay: function(ms, bind) {
			return setTimeout(_runFunc.bind([this, $chk(bind) ? bind : this, $args(arguments).slice(2)]), ms);
		},
		
		periodical: function(ms, bind) { 
			return setInterval(_runFunc.bind([this, $chk(bind) ? bind : this, $args(arguments).slice(2)]), ms);
		}
	});
	
	
	/*
		HASH
	*/
	new Denizen('Hash', {
		init: function(obj) {
			for(var o in obj)
				this[o] = obj[o];
			return this;
		}
	});
	
	Hash.implement({
		add: function(obj) {
			var returns = this.clone();
			$H(obj).keys().each(function(o) {
				if(!$chk(this[o]))
					this[o] = obj[o];
			}.bind(returns));
			return returns;
		},
		
		any: function(fn, bind) {
			bind = $chk(bind) ? bind : this;
			var ya = false;
			this.keys().each(function(o, i) {
				if(fn.call(bind, o, this[o], i) === true)
					ya = true;
			}.bind(this));
			return ya;
		},
		
		clone: function() {
			var c = {};
			this.each(function(k, v) {
				c[k] = (['hash', 'array'].has($type(v))) ? v.clone() : v;
			});
			return $H(c);
		},
		
		each: function(fn, bind) {
			bind = $chk(bind) ? bind : this;
			this.keys().each(function(o, i) {
				fn.call(bind, o, this[o], i);
			}.bind(this));
			return this;
		},
		
		erase: function(keys) {
			keys = $A(keys);
			var c = this.clone();
			keys.each(function(o) {
				delete this[o];
			}.bind(c));
			return c;
		},
		
		every: function(fn, bind) {
			bind = $chk(bind) ? bind : this;
			var ya = true;
			this.keys().each(function(o, i) {
				if(fn.call(bind, o, this[o], i) === false)
					ya = false;
			}.bind(this));
			return ya;
		},
		
		filter: function(fn, bind) {
			bind = $chk(bind) ? bind : this;
			var i = 0, result = {};
			for(var o in this)
				if(!Hash.isImplemented(o)) {
					if(fn.call(bind, o, this[o], i) === true)
						result[o] = this[o];
					i++;
				}
			return new $H(result);
		},
		
		firstKey: function() {
			return this.keys()[0];
		},
		
		firstValue: function() {
			return this[this.firstKey()];
		},
		
		hasKey: function(key) {
			return this.keys().has(key);
		},
		
		hasValue: function(val) {
			return this.values().has(val);
		},
		
		keyOf: function(val) {
			var is = false;
			this.keys().each(function(o) {
				if(this[o] === val)
					is = o;
			}.bind(this));
			return is;
		},
		
		keys: function() {
			var returns = [];
			for(var o in this)
				if(!Hash.isImplemented(o))
					returns.push(o);
			return returns;
		},
		
		lastKey: function() {
			return this.keys().last();
		},
		
		lastValue: function() {
			return this.values().last();
		},
		
		length: function() {
			return this.keys().length;
		},
		
		none: function(fn, bind) {
			bind = $chk(bind) ? bind : this;
			return !this.any(fn, bind);
		},
		
		random: function(fn, bind) {
			bind = $chk(bind) ? bind : this;
			var keys = this.keys();
			var i = $random(keys.length - 1);
			fn.call(bind, keys[i], this[keys[i]], i);
			return this;
		},
		
		take: function(obj) {
			var c = this.clone();
			$H(obj).keys().each(function(o) {
				c[o] = obj[o];
			}.bind(this));
			return c;
		},
		
		values: function() {
			var result = [];
			for(var o in this)
				if(!Hash.isImplemented(o))
					result.push(this[o]);
			return result;
		}
	});
	
	
	/*
		ARRAY
	*/
	Array.implement({
		add: function() {
			var args = $args(arguments);
			var c = this.clone();
			args.each(function(o) {
				c.push(o);
			});
			return c;
		},
		
		any: function(fn, bind) {
			bind = $chk(bind) ? bind : this;
			try {
				return this.some(fn.bind(bind));
			}
			catch(err) {
				var ya = false;
				this.each(function(o, i) {
					if(fn.call(bind, o, i) === true)
						ya = true;
				}.bind(this));
				return ya;
			}
		},
		
		asc: function() {
			var sortBy = function(a, b) {
				return a > b;
			};
			
			return this.sort(sortNumbers);
		},
		
		clear: function() {
			this.length = 0;
			return this;
		},
		
		clone: function() {
			var c = [];
			this.each(function(o) {
				c.push((['object', 'array'].has($type(o))) ? o.clone() : o);
			});
			return c;
		},
		
		count: function(obj) {
			return this.filter(function(o) {
				return o === obj;
			}).length;
		},
		
		desc: function() {
			return this.asc().reverse();
		},
		
		// shortcut to forEach
		each: function(func) {
			return this.forEach(func);
		},
		
		erase: function() {
			var args = $args(arguments);
			var c = this.clone();
			for (var i = c.length - 1, l = 0; l <= i; i--)
				if(args.has(c[i]))
					c.splice(i, 1);
			return c;
		},
		
		// see https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/every
		every: function(func) {
			var len = this.length >>> 0;
			if($type(func) != 'function')
				throw new TypeError();
			var thisp = arguments[1];
			for(var i = 0; i < len; i++)
				if(i in this && !func.call(thisp, this[i], i, this))
					return false;
			return true;
		},
		
		// see https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/filter
		filter: function(func) {
			var len = this.length >>> 0;
			if($type(func) != "function")
				throw new TypeError();
			var 
				res = new Array(),
				thisp = arguments[1];
			for(var i = 0; i < len; i++)
				if(i in this) {
					var val = this[i];
					if(func.call(thisp, val, i, this))
					res.push(val);
				}
			return res;
		},
		
		// see https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/forEach
		forEach: function(func) {
			var len = this.length >>> 0;
			if($type(func) != "function")
				throw new TypeError();
			var thisp = arguments[1];
			for(var i = 0; i < len; i++)
				if(i in this)
					func.call(thisp, this[i], i, this);
			return this;
		},
		
		has: function() {
			var objs = $args(arguments);
			if(objs.length === 1)
				return ($chk(Array.indexOf) === true) ? 
					(this.indexOf(objs[0]) === -1) ? false : true : 
					(function() {
						for(var i = 0; i < this.length; i++)
							if(this[i] === objs[0])
								return true;
						return false;
					}.bind(this))();
			var ya = true;
			objs.each(function(o) {
				if(!this.has(o))
					ya = false;
			}.bind(this));
			return ya;
		},
		
		include: function() {
			var c = this.clone(), objs = $args(arguments);
			objs.each(function(o) {
				if(!this.has(o))
					this.push(o);
			}.bind(c));
			return c;
		},
		
		// see https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/indexOf
		indexOf: function(val, from) {
			for(var i = $chk(from) ? from : 0, l = this.length; i < l; i++)
				if(this[i] === val)
					return i;
			return -1;
		},
		
		insensitiveHas: function() {
			var ya = true, c = [];
			this.each(function(o) {
				c.push(o.toLowerCase());
			});
			$args(arguments).each(function(o) {
				if(!c.has(o.toLowerCase()))
					ya = false;
			});
			return ya;
		},
		
		insensitiveSort: function() {
			var result = [], tmp = [], tmpH = $H();
			this.each(function(o) {
				tmp.push(o.toString().toLowerCase());
				tmpH.include(o, o.toString().toLowerCase());
			});
			tmp = tmp.sort();
			tmp.each(function(o) {
				results.push(tmpH.keyOf(o));
			});
			return results;
		},
		
		last: function() {
			return this[this.length-1];
		},
		
		// see https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Objects/Array/lastIndexOf
		lastIndexOf: function(val) {
			var
				len = this.length, 
				from = Number(arguments[1]);
			if(isNaN(from))
				from = len - 1;
			else {
				from = (from < 0) ? Math.ceil(from) : Math.floor(from);
				if(from < 0)
					from += len;
				else if(from >= len)
					from = len - 1;
			}
			for(; from > -1; from--) {
				if (from in this && this[from] === elt)
				return from;
			}
			return -1;
		},
		
		none: function(fn, bind) {
			bind = $chk(bind) ? bind : this;
			return !this.any(fn, bind);
		},
		
		// see https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Global_Objects/Array/Reduce
		reduce: function(func) {
			var len = this.length >>> 0;
			if($type(func) != 'function')
				throw new TypeError();
			if(len == 0 && arguments.length == 1)
				throw new TypeError();
			var i = 0;
			if(arguments.length >= 2)
				var rv = arguments[1];
			else
				do {
					if(i in this) {
						rv = this[i++];
						break;
					}
					if(++i >= len)
						throw new TypeError();
				} while(true);

			for(; i < len; i++) {
				if(i in this)
				rv = func.call(null, rv, this[i], i, this);
			}
			return rv;
		},
		
		// see https://developer.mozilla.org/En/Core_JavaScript_1.5_Reference/Global_Objects/Array/ReduceRight
		reduceRight: function(func) {
			var len = this.length >>> 0;
			if($type(func) != 'function')
				throw new TypeError();
			if(len == 0 && arguments.length == 1)
				throw new TypeError();
			var i = len - 1;
			if(arguments.length >= 2)
				var rv = arguments[1];
			else
				do {
					if(i in this) {
						rv = this[i--];
						break;
					}
					if(--i < 0)
						throw new TypeError();
				} while(true);
			for(; i >= 0; i--) {
				if(i in this)
				rv = func.call(null, rv, this[i], i, this);
			}
			return rv;
		},
		
		unique: function() {
			var c = [];
			this.each(function(o) {
				c = c.include(o);
			});
			return c;
		}
	});
		
		
	/*
		NUMBER
	*/
	Number.implement({
		abs: function() {
			return Math.abs(this);
		},
		
		between: function(min, max) {
			return this >= min && this <= max;
		},
		
		limit: function(min, max) {
			return (this < min) ? min :
				(this > max) ? max :
				this;
		},
		
		negative: function() {
			return this < 0;
		},
		
		positive: function() {
			return this > 0;
		},
		
		random: function() {
			return $random(this.round());
		},
		
		round: function() {
			return (Math.ceil(this) - this < 0.5) ? 
				((this >= 0) ? Math.ceil(this) : Math.floor(this)) : 
				((this >= 0) ? Math.floor(this) : Math.ceil(this));
		},
		
		times: function(fn, bind) {
			bind = $chk(bind) ? bind : this;
			for (var i = 0, l = parseInt(this); i < l; i++)
				fn.call(bind, i);
			return this;
		},
		
		toFloat: function() {
			return this.toString().toFloat();
		},
		
		toInt: function() {
			return this.toString().toInt();
		}
	});
	
	
	/*
		STRING
	*/
	String.implement({
		blank: function() {
			return this.trim().length === 0;
		},
		
		camelCase: function() {
			var cases = this.match(/(-\w)/g), returns = this;
			if(!cases)
				return this;
			cases.each(function(o) {
				returns = returns.replace(o, o.toUpperCase());
			});
			return returns.replace(/-/g, '');
		},
		
		empty: function() {
			return this.length === 0;
		},
		
		escapeRegExp: function() {
			return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
		},
		
		fromEntities: function() {
			return this.replace(/&#\d+;/g, function(o) {
				return String.fromCharCode(o.replace(/&#(\d+);/, '$1').toInt());
			});
		},
		
		lower: function() {
			return this.toLowerCase();
		},
		
		stripScripts: function() {
			return this.replace(/<script[^>]*>.*<\/script>/gi, '');
		},
		
		stripTags: function() {
			return this.replace(/<\/?[^>]+>/gi, '');
		},
		
		substitute: function(obj, options) {
			options = options || {};
			var encap = {
				left: $chk(options.left) ? options.left.escapeRegExp() : '{',
				right: $chk(options.right) ? options.right.escapeRegExp() : '}'
			}, result = this;
			$H(obj).keys().each(function(o) {
				result = result.replace(new RegExp(encap.left + o + encap.right, 'g'), obj[o]);
			});
			return result;
		},
		
		test: function(reg) {
			return reg.test(this);
		},
		
		toEntities: function() {
			var returns = '';
			(this.length).times(function(i) {
				returns += '&#' + this.charCodeAt(i) + ';';
			}, this);
			return returns;
		},
		
		toFloat: function() {
			return parseFloat(this);
		},
		
		toInt: function() {
			return parseInt(this);
		},
		
		trim: function() {
			return this.replace(/^\s*|\s*$/g, '');
		},
		
		upper: function() {
			return this.toUpperCase();
		}
	});
	
	
	/*
		ELEMENT
	*/
	new Denizen('Element', {
		init: function(el, props) {
			props = props || {};
			if($type(el) == 'array') {
				el.each(function(o) {
					o = new Element(o);
				});
				return el;
			}
			if($type(el) == 'string') {
				el = new Element(document.createElement(el));
				for(var i in props) {
					el.set(i, props[i]);
				}
				return el;
			}
			try {
				if(!el.$type)	
					try {
					for(var i in Element.prototype)
						el[i] = Element.prototype[i];
					} catch(err){ alert(i); }
			} catch(err) { }
			$_genKey(el);
			return el;
		}
	});
	
	Element.implement({
		/*
			Starting Sly prototypes
		*/
		$: function(sel) {
			return $(sel, this);
		},
		
		$$: function(sel) {
			return $$(sel, this);
		},
		
		filter: function(sel) {
			return $.filter(sel, this);
		},
		
		find: function(sel) {
			return $.find(sel, this);
		},
		
		match: function(sel) {
			return $.match(sel, this);
		},
		
		parse: function(sel) {
			return $.parse(sel, this);
		},
		
		search: function(sel) {
			return $.search(sel, this);
		},
		
		/*
			Ending Sly prototypes
		*/
		
		addClass: function() {
			var klass = 
				this
				.className
				.split(' ');
			$args(arguments).each(function(o) {
				klass.push(o);
			}.bind(klass));
			this.className =
				klass
				.unique()
				.join(' ');
			return this;
		},
		
		append: function(el) {
			this.appendChild(el);
			return this;
		},
		
		fetch: function(key) {
			if(!$chk(key))
				return _bank.$[this.$key] || {};
			return (_bank.$[this.$key] || {})[key];
		},
		
		get: function(what) {
			return (what == 'html') ? this.innerHTML :
				(what == 'text') ? this.innerHTML.stripScripts().stripTags() :
				(what == 'tag') ? this.tagName.toLowerCase() :
				(what == 'class') ? this.className :
				(what == 'style') ? $chk(arguments[1]) ? this.style[arguments[1]] : this.style :
				this[what];
		},
		
		hasClass: function() {
			var
				args = $args(arguments),
				klass = this
					.className
					.split(' '),
				ya = true;
			if(args.length === 0)
				return false;
			args.each(function(o) {
				if(!klass.has(o))
					ya = false;
			});
			return ya;
		},
		
		identify: function(pre) {
			pre = pre || '';
			var incID = function(num) {
				if(!$type($(pre + num.toString())))
					return pre + num.toString();
				return incID(++num);
			};
			
			if(!this.id)
				this.id = incID(0);
			return this;
		},
		
		into: function(el) {
			el.appendChild(this);
			return this;
		},
		
		listen: function(type, func) {
			return this.addListener(type, func);
		},
		
		purge: function() {
			this
				.parentNode
				.removeChild(this);
		},
		
		remove: function(what) {
			this[(what == 'class') ? 'className' : what] = undefined;
			return this;
		},
		
		removeClass: function() {
			var args = $args(arguments);
			this.className =
				this
				.className
				.split(' ')
				.filter(function(o) {
					return !args.has(o);
				})
				.unique()
				.join(' ');
			return this;
		},
		
		set: function(what, to) {
			switch(what) {
				case 'html':
					this.innerHTML = to;
					break;
				
				case 'text':
					this.innerHTML = to
						.replace(/&/g, '&amp;')
						.replace(/>/g, '&gt;')
						.replace(/</g, '&lt;')
						.replace(/"/, '&quot;');
					break;
					
				case 'class':
					this.className = to;
					break;
					
				case 'style':
					(($type(to) == 'object') ? $H(to) : to).each(function(k, v) {
						this.setStyle(k, v);
					}.bind(this));
					break;
					
				default:
					this[what] = to;
					break;
			}
			return this;
		},
		
		setStyle: function() {
			var doSetStyle = function(key, val) {
				key = key.toString();
				if(key === 'opacity') {
					val = Number(val);
					//this.style.filters.item('DXImageTransform.Microsoft.Alpha').opacity = (val * 100);
					//this.style.filters.item('alpha').opacity = (val * 100);
					say('alpha(opacity=' + (val * 100) + ')');
					//this.style.filter = (val === 1) ? '' : 'alpha(opacity=' + (val * 100) + ')';
					this.filters.alpha.opacity = '32';
					
					//this.style.opacity = val;
					//this.style.mozOpacity = val;
					return;
				}
				this.style[key.camelCase()] = val;
			};
			doSetStyle = doSetStyle.bind(this);
			
			if(arguments.length === 2)
				doSetStyle(arguments[0], arguments[1]);
			else
				$H(arguments[0]).each(function(k, v) {
					doSetStyle(k, v);
				});
			return this;
		},
		
		store: function() {
			if(!$chk(_bank.$[this.$key]))
				_bank.$[this.$key] = {};
			if(arguments.length === 2) {
				_bank.$[this.$key][arguments[0]] = arguments[1];
				return this;
			}
			$H(arguments[0]).each(function(k, v) {
				_bank.$[this.$key][k] = v;
			}.bind(this));
			return this;
		},
		
		toggleClass: function() {
			$args(arguments).each(function(o) {
				if(this.hasClass(o))
					this.removeClass(o);
				else
					this.addClass(o);
			}.bind(this));
			return this;
		},
		
		unstore: function() {
			if(arguments.length === 0) {
				_bank.$[this.$key] = {};
				return this;
			}
			$args(arguments).each(function(o) {
				try {
					delete _bank.$[this.$key][o]
				} catch(err) { }
			}.bind(this));
			return this;
		},
		
		addListener: function(type, func) {		
			type = type.replace(/^on/, '');
			// forcing browsers to bubble events 
			if(this.addEventListener)
				this.addEventListener(type, function(e) {
					func.bind(this)(new Event(e));
				}.bind(this), false);
			else
				this.attachEvent('on' + type, function(e) {
					func.bind(this)(new Event(e));
				}.bind(this));
			return this;
		},
		
		removeListener: function(type, func) {
			type = type.replace(/^on/, '');
			if(this.removeEventListener)
				this.removeEventListener(type, function(e) {
					func.bind(this)(new Event(e));
				}.bind(this), false);
			else
				this.detachEvent('on' + type, function(e) {
					func.bind(this)(new Event(e));
				}.bind(this));
			return this;
		}
	});
	
	
	/*
		EVENT
	*/
	new Denizen('Event', {
		init: function(e) {
			this.event = e;
			this.key = (e.keyCode) ? e.keyCode : e.which;
			this.type = e.type;
			return this;
		}
	});
	
	Event.implement({
		preventDefault: function() {
			if(this.event.preventDefault)
				this.event.preventDefault();
			else
				this.event.returnValue = false;
			return this;
		},
		
		stop: function() {
			return this.stopBubble().preventDefault();
		},
		
		stopBubble: function() {
			if(this.event.stopPropagation)
				this.event.stopPropagation();
			else
				this.event.cancelBubble = true;
			return this;
		}
	});
	
	
	/*
		AJAX
	*/
	new Denizen('Ajax', {
		init: function(settings) {
			this.xmlhttp = (function() {
				return (window.XMLHttpRequest) ? new XMLHttpRequest() : 
					(window.ActiveXObject) ? new ActiveXObject('Microsoft.XMLHTTP') :
					$func;
			})();
			this.onComplete = settings.onComplete || $func;
			this.onFailure = settings.onFailure || $func;
			this.method = settings.method;
			this.url = settings.url;
			this.data = settings.data || {};
			this.noCache = $chk(settings.noCache) ? settings.noCache : false;
			this.mode = 'normal';
			this.xmlhttp.onreadystatechange = function() {
				if(this.xmlhttp.readyState === 4)
					this.onComplete((this.mode == 'normal') ? this.xmlhttp.responseText :
						(this.mode == 'json') ? this.xmlhttp.responseText.decodeJSON() :
						null);
			}.bind(this);
			return this;
		}
	});
	
	Ajax.implement({
		request: function(mode) {
			this.mode = mode || 'normal';
			
			var url = this.url;
			$H(this.data).each(function(k, v, i) {
				url += ((i === 0) ? '?' : '&') + escape(k) + '=' + escape(v);
			});
		
			try {
				this.xmlhttp.open(this.method, url, true);
				this.xmlhttp.send(null);
			}
			catch(err) {
				this.onFailure(err);
			}
		},
		
		requestJSON: function() {
			this.request('json');
		}
	});
	
	
	/*
		CLASS
	*/
	new Denizen('Class', {
		init: function(props) {
			props = props || {};
			var self = this;
			var klass = function() {
				for(var k in self.prototype)
					this.prototype[k] = self.prototype[k];
				for(var k in props)
					this.prototype[k] = props[k];
				this.constructor = Class;
				var instance = (this.init) ? this.init.apply(this, Array.prototype.slice.call(arguments, 1)) : this;
				return instance;
			};
			return klass;
			
		
			/*
			this.xmlhttp = (function() {
				return (window.XMLHttpRequest) ? new XMLHttpRequest() : 
					(window.ActiveXObject) ? new ActiveXObject('Microsoft.XMLHTTP') :
					$func;
			})();
			this.onComplete = settings.onComplete || $func;
			this.onFailure = settings.onFailure || $func;
			this.method = settings.method;
			this.url = settings.url;
			this.data = settings.data || {};
			this.noCache = $chk(settings.noCache) ? settings.noCache : false;
			this.mode = 'normal';
			this.xmlhttp.onreadystatechange = function() {
				if(this.xmlhttp.readyState === 4)
					this.onComplete((this.mode == 'normal') ? this.xmlhttp.responseText :
						(this.mode == 'json') ? this.xmlhttp.responseText.decodeJSON() :
						null);
			}.bind(this);
			return this; */
		}
	});
	
	
	/*
		JSON support
		inspired by JSON.js
		http://www.JSON.org/json2.js
	*/
	(function() {
		var
			f = function(n) {
				return n < 10 ? '0' + n : n;
			},
			cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
			escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
			gap,
			indent,
			meta = {
				'\b': '\\b',
				'\t': '\\t',
				'\n': '\\n',
				'\f': '\\f',
				'\r': '\\r',
				'"' : '\\"',
				'\\': '\\\\'
			},
			rep,
			JSON = {};
			
		var toJSON = function(obj) {
			if($type(obj) === 'date')
				return obj.getUTCFullYear()   + '-' +
					f(obj.getUTCMonth() + 1) + '-' +
					f(obj.getUTCDate())      + 'T' +
					f(obj.getUTCHours())     + ':' +
					f(obj.getUTCMinutes())   + ':' +
					f(obj.getUTCSeconds())   + 'Z';
			return obj.valueOf();
		};
			
		var quote = function(str) {
			escapable.lastIndex = 0;
			return escapable.test(str) ?
			'"' + str.replace(escapable, function (a) {
				var c = meta[a];
				return $type(c) === 'string' ? c : '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
			}) + '"' : '"' + str + '"';
		};
		
		var str = function(key, holder) {
			var i, k, v, length, mind = gap, partial, value = holder[key];
			if(value && $type(value) === 'object' && $type(toJSON(value)) === 'function')
				value = toJSON(key);
			if($type(rep) === 'function')
				value = rep.call(holder, key, value);
			switch($type(value)) {
				case 'string':
					return quote(value);

				case 'number':
					return isFinite(value) ? String(value) : 'null';

				case 'boolean':
				case 'null':
					return String(value);
					
				case 'object':
					if (!value)
						return 'null';
						
				gap += indent;
				partial = [];

				if(Object.toString.apply(value) === '[object Array]') {
					length = value.length;
					for(i = 0; i < length; i += 1)
						partial[i] = str(i, value) || 'null';
					v = partial.length === 0 ? '[]' : gap ? '[\n' + gap + partial.join(',\n' + gap) + '\n' + mind + ']' : 
						'[' + partial.join(',') + ']';
					gap = mind;
					return v;
				}

				if(rep && $type(rep) === 'object') {
					length = rep.length;
					for(i = 0; i < length; i += 1) {
						k = rep[i];
						if($type(k) === 'string') {
							v = str(k, value);
							if(v)
								partial.push(quote(k) + (gap ? ': ' : ':') + v);
						}
					}
				}
				else {
					value.each(function(k, val) {
						if(Object.hasKey.call(val, k)) {
							v = str(k, val);
							if(v)
								partial.push(quote(k) + (gap ? ': ' : ':') + v);
						}
					});
				}

				v = partial.length === 0 ? '{}' :
					gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' + mind + '}' : '{' + partial.join(',') + '}';
				gap = mind;
				return v;
			}
		};
		
		JSON.stringify = function (value, replacer, space) {
			var i;
            gap = '';
            indent = '';
			
			if($type(space) === 'number')
				for (i = 0; i < space; i += 1)
				indent += ' ';
			else if($type(space) === 'string')
				indent = space;
				
			rep = replacer;
			if(replacer && $type(replacer) !== 'function' && ($type(replacer) !== 'object' || $type(replacer.length) !== 'number'))
				throw new Error('JSON stringify failed');
				
			return str('', {'': value});
		};
		
		JSON.parse = function (text, reviver) {
			var walk = function(holder, key) {
				var k, v, value = holder[key];
				if(value && $type(value) === 'object') {
					value.each(function(k, val) {
						if(Object.hasOwnProperty.call(val, k))
							v = walk(value, k);
						if(v !== undefined)
							value[k] = v;
						else
							delete value[k];
					});
				}
				return reviver.call(holder, key, value);
			};
			
			cx.lastIndex = 0;
			if(cx.test(text))
				text = text.replace(cx, function (a) {
					return '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
				});
			if(/^[\],:{}\s]*$/
				.test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@')
				.replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']')
				.replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {
					j = eval('(' + text + ')');
					return $type(reviver) === 'function' ? walk({'': j}, '') : j;
				}
			throw new Error('JSON parsing failed');
		};
		
		String.implement({
			decodeJSON: function() {
				return JSON.parse(this);
			}
		});
	})();
	
	
	/*
		Sly v1.0rc0 <http://sly.digitarald.com>
		(C) 2009 Harald Kirschner <http://digitarald.de>
		Open source under MIT License
		----
		Modified by Timothy Marshall
	*/

	(function() {

		var cache = {};

		/**
		 * Sly::constructor
		 */
		var Sly = function(text) {
			return cache[text] || (cache[text] = new Sly.initialize(text));
		};

		Sly.initialize = function(text) {
			// normalise
			this.text = (typeof(text) == 'string') ? text.replace(/^\s+/, '') : '';
		};

		var proto = Sly.initialize.prototype = Sly.prototype;


		/**
		 * Sly.implement
		 */
		Sly.implement = function(key, properties) {
			for (var prop in properties) Sly[key][prop] = properties[prop];
		};


		/**
		 * Sly.features
		 *
		 * @todo Check proper support with more tests
		 */
		var features = Sly.features = {
			querySelector: !!(document.querySelectorAll),
			elementsByClass: !!(document.getElementsByClassName)
		};


		var locateFast = function() {
			return true;
		};

		/**
		 * Sly::search
		 */
		proto.search = function(context) {
			var iterate;

			if (!context) context = document;
			else iterate = (context instanceof Array);

			var results; // overall result

			if (features.querySelector && !iterate && context.nodeType == 9) {
				try {
					results = context.querySelectorAll(this.text);
				} catch(e) {}
				if (results) return Sly.toArray(results);
			}

			var parsed = this.parse();

			var current = {}, // unique ids for one iteration process
				combined, // found nodes from one iteration process
				nodes, // context nodes from one iteration process
				all = {}, // unique ids for overall result
				state = {}; // matchers temporary state

			// unifiers
			var getUid = Sly.getUid;
			var locateCurrent = function(node) {
				var uid = getUid(node);
				return (current[uid]) ? null : (current[uid] = true);
			};

			for (var i = 0, selector; (selector = parsed[i]); i++) {

				var locate = locateCurrent;

				if (selector.first) {
					if (!results) locate = locateFast;
					if (iterate) nodes = context;
					else if (selector.combinator) nodes = [context]; // allows combinators before selectors
				}

				if (selector.last && results) {
					current = all;
					combined = results;
				} else {
					// default stack
					current = {};
					combined = [];
				}

				if (!selector.combinator) {
					// without prepended combinator
					combined = selector.combine(combined, context, selector, state, locate, !(combined.length));
				} else {
					// with prepended combinators
					for (var k = 0, l = nodes.length; k < l; k++) {
						combined = selector.combine(combined, nodes[k], selector, state, locate);
					}
				}
				if (selector.last) {
					if (combined.length) results = combined;
				} else {
					nodes = combined;
				}
			}

			return results || [];
		};


		/**
		 * Sly::find
		 */
		proto.find = function(context) {
			return this.search(context)[0];
		};


		/**
		 * Sly::match
		 */
		proto.match = function(node) {
			return !!(this.parse()[0].match(node, {}));
		};


		/**
		 * Sly::filter
		 */
		proto.filter = function(nodes) {
			var results = [], match = this.parse()[0].match;
			for (var i = 0, node; (node = nodes[i]); i++) {
				if (match(node)) results.push(node);
			}
			return results;
		};


		/**
		 * Sly.recompile()
		 */
		var pattern;

		Sly.recompile = function() {

			var key, combList = [','], operList = ['!'];

			for (key in combinators) {
				if (key != ' ') {
					combList[(key.length > 1) ? 'unshift' : 'push'](Sly.escapeRegExp(key));
				}
			}
			for (key in operators) operList.push(key);

			/**
			  The regexp is a group of every possible selector part including combinators.
			  "|" separates the possible selectors.

				Capturing parentheses:
				1 - Combinator (only requires to allow multiple-character combinators)
				2 - Attribute name
				3 - Attribute operator
				4, 5, 6 - The value
				7 - Pseudo name
				8, 9, 10 - The value
			 */

			pattern = new RegExp(
				// A tagname
				'[\\w\\u00c0-\\uFFFF][\\w\\u00c0-\\uFFFF-]*|' +

				// An id or the classname
				'[#.][\\w\\u00c0-\\uFFFF-]+|' +

				// Whitespace (descendant combinator)
				'[ \\t\\r\\n\\f](?=[\\w\\u00c0-\\uFFFF*#.[:])|' +

				// Other combinators and the comma
				'(' + combList.join('|') + ')[ \\t\\r\\n\\f]*|' +

				// An attribute, with the various and optional value formats ([name], [name=value], [name="value"], [name='value']
				'\\[([\\w\\u00c0-\\uFFFF-]+)(?:([' + operList.join('') + ']?=)(?:"([^"]*)"|\'([^\']*)\'|([^\\]]*)))?]|' +

				// A pseudo-class, with various formats
				':([-\\w\\u00c0-\\uFFFF]+)(?:\\((?:"([^"]*)"|\'([^\']*)\'|([^)]*))\\))?|' +

				// The universial selector, not process
				'\\*', 'g'
			);
		};


		// I prefer it outside, not sure if this is faster
		var create = function(combinator) {
			return {
				ident: [],
				classes: [],
				attributes: [],
				pseudos: [],
				combinator: combinator
			};
		};

		var blank = function($0) {
			return $0;
		};

		/**
		 * Sly::parse
		 *
		 * Returns an array with one object for every selector:
		 *
		 * {
		 *   tag: (String) Tagname (defaults to null for universal *)
		 *   id: (String) Id
		 *   classes: (Array) Classnames
		 *   attributes: (Array) Attribute objects with "name", "operator" and "value"
		 *   pseudos: (Array) Pseudo objects with "name" and "value"
		 *   operator: (Char) The prepended operator (not comma)
		 *   first: (Boolean) true if it is the first selector or the first after a comma
		 *   last: (Boolean) true if it is the last selector or the last before a comma
		 *   ident: (Array) All parsed matches, can be used as cache identifier.
		 * }
		 */
		proto.parse = function(plain) {
			var save = (plain) ? 'plain' : 'parsed';
			if (this[save]) return this[save];

			var compute = (plain) ? blank : this.compute;

			var parsed = [], current = create(null);
			current.first = true;

			var refresh = function(combinator) {
				parsed.push(compute(current));
				current = create(combinator);
			};

			var match, $0;

			while ((match = pattern.exec(this.text))) {
				$0 = match[0];

				switch ($0.charAt(0)) {
					case '.':
						current.classes.push($0.slice(1));
						break;
					case '#':
						current.id = $0.slice(1);
						break;
					case '[':
						current.attributes.push({
							name: match[2],
							operator: match[3] || null,
							value: match[4] || match[5] || match[6] || null
						});
						break;
					case ':':
						current.pseudos.push({
							name: match[7],
							value: match[8] || match[9] || match[10] || null
						});
						break;
					case ',':
						current.last = true;
						refresh(null);
						current.first = true;
						continue;
					case ' ': case '\t': case '\r': case '\n': case '\f':
						match[1] = ' ';
					default:
						var combinator = match[1];
						if (combinator) {
							if (current.first && !current.ident.length) current.combinator = combinator;
							else refresh(combinator);
						} else {
							if ($0 != '*') current.tag = $0;
						}
				}
				current.ident.push($0);
			}

			current.last = true;
			parsed.push(compute(current));

			return (this[save] = parsed);
		};


		// chains two given functions

		function chain(prepend, append, aux, unshift) {
			var fn = (prepend) ? ((unshift) ? function(node, state) {
				return append(node, aux, state) && prepend(node, state);
			} : function(node, state) {
				return prepend(node, state) && append(node, aux, state);
			}) : function(node, state) {
				return append(node, aux, state);
			};
			fn.$slyIndex = (prepend) ? (prepend.$slyIndex + 1) : 0;
			return fn;
		};


		// prepared match comperators, probably needs namespacing
		var empty = function() {
			return true;
		};

		var matchId = function(node, id) {
			return (node.id == id);
		};

		var matchTag = function(node, tag) {
			return (node.nodeName == tag);
		};

		var prepareClass = function(name) {
			return (new RegExp('(?:^|[ \\t\\r\\n\\f])' + name + '(?:$|[ \\t\\r\\n\\f])'));
		};

		var matchClass = function(node, expr) {
			return node.className && expr.test(node.className);
		};

		var prepareAttribute = function(attr) {
			if (!attr.operator || !attr.value) return attr;
			var parser = operators[attr.operator];
			if (parser) { // @todo: Allow functions, not only regex
				attr.escaped = Sly.escapeRegExp(attr.value);
				attr.pattern = new RegExp(parser(attr.value, attr.escaped, attr));
			}
			return attr;
		};

		var matchAttribute = function(node, attr) {
			var read = Sly.getAttribute(node, attr.name);
			switch (attr.operator) {
				case null: return read;
				case '=': return (read == attr.value);
				case '!=': return (read != attr.value);
			}
			if (!read && attr.value) return false;
			return attr.pattern.test(read);
		};


		/**
		 * Sly::compute
		 *
		 * Attaches the following methods to the selector object:
		 *
		 * {
		 *   search: Uses the most convinient properties (id, tag and/or class) of the selector as search.
		 *   matchAux: If search does not contain all selector properties, this method matches an element against the rest.
		 *   match: Matches an element against all properties.
		 *   simple: Set when matchAux is not needed.
		 *   combine: The callback for the combinator
		 * }
		 */
		proto.compute = function(selector) {

			var i, item, match, search, matchSearch, tagged,
				tag = selector.tag,
				id = selector.id,
				classes = selector.classes;

			var nodeName = (tag) ? tag.toUpperCase() : null;

			if (id) {
				tagged = true;

				matchSearch = chain(matchSearch, matchId, id);

				search = function(context) {
					if (context.getElementById) {
						var el = context.getElementById(id);
						return (el && (!nodeName || el.nodeName == nodeName)) ? [el] : [];
					}

					var query = context.getElementsByTagName(tag || '*');
					for (var j = 0, node; (node = query[j]); j++) {
						if (node.id == id) return [node];
					}
					return [];
				};
			}

			if (classes.length > 0) {

				if (!search && Sly.features.elementsByClass) {

					for (i = 0; (item = classes[i]); i++) {
						matchSearch = chain(matchSearch, matchClass, prepareClass(item));
					}

					var joined = classes.join(' ');
					search = function(context) {
						return context.getElementsByClassName(joined);
					};

				} else if (!search && classes.length == 1) { // optimised for typical .one-class-only

					tagged = true;

					var expr = prepareClass(classes[0]);
					matchSearch = chain(matchSearch, matchClass, expr);

					search = function(context) {
						var query = context.getElementsByTagName(tag || '*');
						var found = [];
						for (var i = 0, node; (node = query[i]); i++) {
							if (node.className && expr.test(node.className)) found.push(node);
						}
						return found;
					};

				} else {

					for (i = 0; (item = classes[i]); i++) {
						match = chain(match, matchClass, prepareClass(item));
					}

				}
			}

			if (tag) {

				if (!search) {
					matchSearch = chain(matchSearch, matchTag, nodeName);

					search = function(context) {
						return context.getElementsByTagName(tag);
					};
				} else if (!tagged) { // search does not filter by tag yet
					match = chain(match, matchTag, nodeName);
				}

			} else if (!search) { // default engine

				search = function(context) {
					return context.getElementsByTagName('*');
				};

			}

			for (i = 0; (item = selector.pseudos[i]); i++) {

				if (item.name == 'not') { // optimised :not(), fast as possible
					match = chain(match, function(node, not) {
						return !not(node);
					}, Sly(item.value).parse()[0].match);
				} else {
					var parser = pseudos[item.name];
					// chain(match, matchAttribute, prepareAttribute(item))
					if (parser) match = chain(match, parser, item.value);
				}

			}

			for (i = 0; (item = selector.attributes[i]); i++) {
				match = chain(match, matchAttribute, prepareAttribute(item));
			}

			if ((selector.simple = !(match))) {
				selector.matchAux = empty;
			} else {
				selector.matchAux = match;
				matchSearch = chain(matchSearch, match);
			}

			selector.match = matchSearch || empty;

			selector.combine = Sly.combinators[selector.combinator || ' '];

			selector.search = search;

			return selector;
		};


		/**
		 * Combinators
		 */
		var combinators = Sly.combinators = {

			' ': function(combined, context, selector, state, locate, fast) {
				var nodes = selector.search(context);
				if (fast && selector.simple) return Sly.toArray(nodes);
				for (var i = 0, node, aux = selector.matchAux; (node = nodes[i]); i++) {
					if (locate(node) && aux(node, state)) combined.push(node);
				}
				return combined;
			},

			'>': function(combined, context, selector, state, locate) {
				var nodes = selector.search(context);
				for (var i = 0, node; (node = nodes[i]); i++) {
					if (node.parentNode == context && locate(node) && selector.matchAux(node, state)) combined.push(node);
				}
				return combined;
			},

			'+': function(combined, context, selector, state, locate) {
				while ((context = context.nextSibling)) {
					if (context.nodeType == 1) {
						if (locate(context) && selector.match(context, state)) combined.push(context);
						break;
					}

				}
				return combined;
			},

			'~': function(combined, context, selector, state, locate) {
				while ((context = context.nextSibling)) {
					if (context.nodeType == 1) {
						if (!locate(context)) break;
						if (selector.match(context, state)) combined.push(context);
					}
				}
				return combined;
			}

		};


		/**
		 * Pseudo-Classes
		 */
		var pseudos = Sly.pseudos = {

			// w3c pseudo classes

			'first-child': function(node) {
				return pseudos.index(node, 0);
			},

			'last-child': function(node) {
				while ((node = node.nextSibling)) {
					if (node.nodeType === 1) return false;
				}
				return true;
			},

			'only-child': function(node) {
				var prev = node;
				while ((prev = prev.previousSibling)) {
					if (prev.nodeType === 1) return false;
				}
				var next = node;
				while ((next = next.nextSibling)) {
					if (next.nodeType === 1) return false;
				}
				return true;
			},

			'nth-child': function(node, value, state) {
				var parsed = Sly.parseNth(value || 'n');
				if (parsed.special != 'n') return pseudos[parsed.special](node, parsed.a, state);
				state = state || {}; // just to be sure
				state.positions = state.positions || {};
				var uid = Sly.getUid(node) ;
				if (!state.positions[uid]) {
					var count = 0;
					while ((node = node.previousSibling)) {
						if (node.nodeType != 1) continue;
						count++;
						var position = state.positions[Sly.getUid(node)];
						if (position != undefined) {
							count = position + count;
							break;
						}
					}
					state.positions[uid] = count;
				}
				return (state.positions[uid] % parsed.a == parsed.b);
			},

			'empty': function(node) {
				return !(node.innerText || node.textContent || '').length;
			},

			'contains': function(node, text) {
				return (node.innerText || node.textContent || '').indexOf(text) != -1;
			},

			'index': function(node, index) {
				var count = 0;
				while ((node = node.previousSibling)) {
					if (node.nodeType == 1 && ++count > index) return false;
				}
				return (count == index);
			},

			'even': function(node, value, state) {
				return pseudos['nth-child'](node, '2n', state);
			},

			'odd': function(node, value, state) {
				return pseudos['nth-child'](node, '2n+1', state);
			}

		};


		/**
		 * Attribute operators
		 */
		var operators = Sly.operators = {

			'*=': function(value, escaped) {
				return escaped;
			},

			'^=': function(value, escaped) {
				return '^' + escaped;
			},

			'$=': function(value, escaped) {
				return value + '$';
			},

			'~=': function(value, escaped) {
				return '(?:^|[ \\t\\r\\n\\f])' + escaped + '(?:$|[ \\t\\r\\n\\f])';
			},

			'|=': function(value, escaped) {
				return '(?:^|\\|)' + escaped + '(?:$|\\|)';
			}

		};


		// public, overridable

		Sly.getAttribute = function(node, name) {
			if (name == 'class') return node.className;
			return node.getAttribute(name, 2); // 2 for IE, others ignore it
		};


		var toArray = function(nodes) {
			return Array.prototype.slice.call(nodes);
		};

		try {
			toArray(document.documentElement.childNodes);
		} catch (e) {
			toArray = function(nodes) {
				if (nodes instanceof Array) return nodes;
				var i = nodes.length, results = new Array(i);
				while (i--) results[i] = nodes[i];
				return results;
			};
		}

		Sly.toArray = toArray;


		var nextUid = 1;

		Sly.getUid = (window.ActiveXObject) ? function(node) {
			return (node.$slyUid || (node.$slyUid = {id: nextUid++})).id;
		} : function(node) {
			return node.$slyUid || (node.$slyUid = nextUid++);
		};


		var nthCache = {};

		Sly.parseNth = function(value) {
			if (nthCache[value]) return nthCache[value];

			var parsed = value.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/);
			if (!parsed) return false;

			var a = parseInt(parsed[1], 10), b = (parseInt(parsed[3], 10) || 0) - 1;

			if ((a = (isNaN(a)) ? 1 : a)) {
				while (b < 1) b += a;
				while (b >= a) b -= a;
			}
			switch (parsed[2]) {
				case 'n': parsed = {a: a, b: b, special: 'n'}; break;
				case 'odd': parsed = {a: 2, b: 0, special: 'n'}; break;
				case 'even': parsed = {a: 2, b: 1, special: 'n'}; break;
				case 'first': parsed = {a: 0, special: 'index'}; break;
				case 'last': parsed = {special: 'last-child'}; break;
				case 'only': parsed = {special: 'only-child'}; break;
				default: parsed = {a: (a) ? (a - 1) : b, special: 'index'};
			}

			return (nthCache[value] = parsed);
		};


		Sly.escapeRegExp = function(text) {
			return text.replace(/[-.*+?^${}()|[\]\/\\]/g, '\\$&');
		};


		// generic accessors

		Sly.generise = function(name) {
			Sly[name] = function(text) {
				var cls = Sly(text);
				return cls[name].apply(cls, Array.prototype.slice.call(arguments, 1));
			}
		};

		var generics = ['parse', 'search', 'find', 'match', 'filter'];
		for (var i = 0; generics[i]; i++) Sly.generise(generics[i]);


		// compile pattern for the first time

		Sly.recompile();

		// FIN
		
		window.$sly = document.$sly = Sly;
		window.$ = document.$ = function(sel, par) {
			if($type(sel) == 'element')
				return (sel.$type) ? sel : new Element(sel);
			return new Element(Sly.find('#' + sel.replace(/^#?/, ''), par));
		};
		window.$$ = document.$$ = function(sel, par) {
			return new Element(Sly.search(sel, par));
		};
		['find', 'search', 'match', 'filter', 'parse'].each(function(o) {
			window.$[o] = function(sel, par) {
				return new Element(Sly[o](sel, par));
			};
		});
	})();
})()