/*
Script: Core.js
	MooTools - My Object Oriented JavaScript Tools.

License:
	MIT-style license.

Copyright:
	Copyright (c) 2006-2008 [Valerio Proietti](http://mad4milk.net/).

Code & Documentation:
	[The MooTools production team](http://mootools.net/developers/).

Inspiration:
	- Class implementation inspired by [Base.js](http://dean.edwards.name/weblog/2006/03/base/) Copyright (c) 2006 Dean Edwards, [GNU Lesser General Public License](http://opensource.org/licenses/lgpl-license.php)
	- Some functionality inspired by [Prototype.js](http://prototypejs.org) Copyright (c) 2005-2007 Sam Stephenson, [MIT License](http://opensource.org/licenses/mit-license.php)
*/

	var MooTools = {
		'version': '1.2.3',
		'build': '4980aa0fb74d2f6eb80bcd9f5b8e1fd6fbb8f607'
	};

	var Native = function(options){
		options = options || {};
		var name = options.name;
		var legacy = options.legacy;
		var protect = options.protect;
		var methods = options.implement;
		var generics = options.generics;
		var initialize = options.initialize;
		var afterImplement = options.afterImplement || function(){};
		var object = initialize || legacy;
		generics = generics !== false;

		object.constructor = Native;
		object.$family = {name: 'native'};
		if (legacy && initialize) object.prototype = legacy.prototype;
		object.prototype.constructor = object;

		if (name){
			var family = name.toLowerCase();
			object.prototype.$family = {name: family};
			Native.typize(object, family);
		}

		var add = function(obj, name, method, force){
			if (!protect || force || !obj.prototype[name]) obj.prototype[name] = method;
			if (generics) Native.genericize(obj, name, protect);
			afterImplement.call(obj, name, method);
			return obj;
		};

		object.alias = function(a1, a2, a3){
			if (typeof a1 == 'string'){
				var pa1 = this.prototype[a1];
				if ((a1 = pa1)) return add(this, a2, a1, a3);
			}
			for (var a in a1) this.alias(a, a1[a], a2);
			return this;
		};

		object.implement = function(a1, a2, a3){
			if (typeof a1 == 'string') return add(this, a1, a2, a3);
			for (var p in a1) add(this, p, a1[p], a2);
			return this;
		};

		if (methods) object.implement(methods);

		return object;
	};

	Native.genericize = function(object, property, check){
		if ((!check || !object[property]) && typeof object.prototype[property] == 'function') object[property] = function(){
			var args = Array.prototype.slice.call(arguments);
			return object.prototype[property].apply(args.shift(), args);
		};
	};

	Native.implement = function(objects, properties){
		for (var i = 0, l = objects.length; i < l; i++) objects[i].implement(properties);
	};

	Native.typize = function(object, family){
		if (!object.type) object.type = function(item){
			return ($type(item) === family);
		};
	};

	(function(){
		var natives = {'Array': Array, 'Date': Date, 'Function': Function, 'Number': Number, 'RegExp': RegExp, 'String': String};
		for (var n in natives) new Native({name: n, initialize: natives[n], protect: true});

		var types = {'boolean': Boolean, 'native': Native, 'object': Object};
		for (var t in types) Native.typize(types[t], t);

		var generics = {
			'Array': ["concat", "indexOf", "join", "lastIndexOf", "pop", "push", "reverse", "shift", "slice", "sort", "splice", "toString", "unshift", "valueOf"],
			'String': ["charAt", "charCodeAt", "concat", "indexOf", "lastIndexOf", "match", "replace", "search", "slice", "split", "substr", "substring", "toLowerCase", "toUpperCase", "valueOf"]
		};
		for (var g in generics){
			for (var i = generics[g].length; i--;) Native.genericize(natives[g], generics[g][i], true);
		}
	})();

	var Hash = new Native({

		name: 'Hash',

		initialize: function(object){
			if ($type(object) == 'hash') object = $unlink(object.getClean());
			for (var key in object) this[key] = object[key];
			return this;
		}

	});

	Hash.implement({

		forEach: function(fn, bind){
			for (var key in this){
				if (this.hasOwnProperty(key)) fn.call(bind, this[key], key, this);
			}
		},

		getClean: function(){
			var clean = {};
			for (var key in this){
				if (this.hasOwnProperty(key)) clean[key] = this[key];
			}
			return clean;
		},

		getLength: function(){
			var length = 0;
			for (var key in this){
				if (this.hasOwnProperty(key)) length++;
			}
			return length;
		}

	});

	Hash.alias('forEach', 'each');

	Array.implement({

		forEach: function(fn, bind){
			for (var i = 0, l = this.length; i < l; i++) fn.call(bind, this[i], i, this);
		}

	});

	Array.alias('forEach', 'each');

	function $A(iterable){
		if (iterable.item){
			var l = iterable.length, array = new Array(l);
			while (l--) array[l] = iterable[l];
			return array;
		}
		return Array.prototype.slice.call(iterable);
	};

	function $arguments(i){
		return function(){
			return arguments[i];
		};
	};

	function $chk(obj){
		return !!(obj || obj === 0);
	};

	function $clear(timer){
		clearTimeout(timer);
		clearInterval(timer);
		return null;
	};

	function $defined(obj){
		return (obj != undefined);
	};

	function $each(iterable, fn, bind){
		var type = $type(iterable);
		((type == 'arguments' || type == 'collection' || type == 'array') ? Array : Hash).each(iterable, fn, bind);
	};

	function $empty(){};

	function $extend(original, extended){
		for (var key in (extended || {})) original[key] = extended[key];
		return original;
	};

	function $H(object){
		return new Hash(object);
	};

	function $lambda(value){
		return ($type(value) == 'function') ? value : function(){
			return value;
		};
	};

	function $merge(){
		var args = Array.slice(arguments);
		args.unshift({});
		return $mixin.apply(null, args);
	};

	function $mixin(mix){
		for (var i = 1, l = arguments.length; i < l; i++){
			var object = arguments[i];
			if ($type(object) != 'object') continue;
			for (var key in object){
				var op = object[key], mp = mix[key];
				mix[key] = (mp && $type(op) == 'object' && $type(mp) == 'object') ? $mixin(mp, op) : $unlink(op);
			}
		}
		return mix;
	};

	function $pick(){
		for (var i = 0, l = arguments.length; i < l; i++){
			if (arguments[i] != undefined) return arguments[i];
		}
		return null;
	};

	function $random(min, max){
		return Math.floor(Math.random() * (max - min + 1) + min);
	};

	function $splat(obj){
		var type = $type(obj);
		return (type) ? ((type != 'array' && type != 'arguments') ? [obj] : obj) : [];
	};

	var $time = Date.now || function(){
		return +new Date;
	};

	function $try(){
		for (var i = 0, l = arguments.length; i < l; i++){
			try {
				return arguments[i]();
			} catch(e){}
		}
		return null;
	};

	function $type(obj){
		if (obj == undefined) return false;
		if (obj.$family) return (obj.$family.name == 'number' && !isFinite(obj)) ? false : obj.$family.name;
		if (obj.nodeName){
			switch (obj.nodeType){
				case 1: return 'element';
				case 3: return (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace';
			}
		} else if (typeof obj.length == 'number'){
			if (obj.callee) return 'arguments';
			else if (obj.item) return 'collection';
		}
		return typeof obj;
	};

	function $unlink(object){
		var unlinked;
		switch ($type(object)){
			case 'object':
				unlinked = {};
				for (var p in object) unlinked[p] = $unlink(object[p]);
			break;
			case 'hash':
				unlinked = new Hash(object);
			break;
			case 'array':
				unlinked = [];
				for (var i = 0, l = object.length; i < l; i++) unlinked[i] = $unlink(object[i]);
			break;
			default: return object;
		}
		return unlinked;
	};


	/*
	Script: Browser.js
		The Browser Core. Contains Browser initialization, Window and Document, and the Browser Hash.

	License:
		MIT-style license.
	*/

	var Browser = $merge({

		Engine: {name: 'unknown', version: 0},

		Platform: {name: (window.orientation != undefined) ? 'ipod' : (navigator.platform.match(/mac|win|linux/i) || ['other'])[0].toLowerCase()},

		Features: {xpath: !!(document.evaluate), air: !!(window.runtime), query: !!(document.querySelector)},

		Plugins: {},

		Engines: {

			presto: function(){
				return (!window.opera) ? false : ((arguments.callee.caller) ? 960 : ((document.getElementsByClassName) ? 950 : 925));
			},

			trident: function(){
				return (!window.ActiveXObject) ? false : ((window.XMLHttpRequest) ? 5 : 4);
			},

			webkit: function(){
				return (navigator.taintEnabled) ? false : ((Browser.Features.xpath) ? ((Browser.Features.query) ? 525 : 420) : 419);
			},

			gecko: function(){
				return (document.getBoxObjectFor == undefined) ? false : ((document.getElementsByClassName) ? 19 : 18);
			}

		}

	}, Browser || {});

	Browser.Platform[Browser.Platform.name] = true;

	Browser.detect = function(){

		for (var engine in this.Engines){
			var version = this.Engines[engine]();
			if (version){
				this.Engine = {name: engine, version: version};
				this.Engine[engine] = this.Engine[engine + version] = true;
				break;
			}
		}

		return {name: engine, version: version};

	};

	Browser.detect();

	Browser.Request = function(){
		return $try(function(){
			return new XMLHttpRequest();
		}, function(){
			return new ActiveXObject('MSXML2.XMLHTTP');
		});
	};

	Browser.Features.xhr = !!(Browser.Request());

	Browser.Plugins.Flash = (function(){
		var version = ($try(function(){
			return navigator.plugins['Shockwave Flash'].description;
		}, function(){
			return new ActiveXObject('ShockwaveFlash.ShockwaveFlash').GetVariable('$version');
		}) || '0 r0').match(/\d+/g);
		return {version: parseInt(version[0] || 0 + '.' + version[1], 10) || 0, build: parseInt(version[2], 10) || 0};
	})();

	function $exec(text){
		if (!text) return text;
		if (window.execScript){
			window.execScript(text);
		} else {
			var script = document.createElement('script');
			script.setAttribute('type', 'text/javascript');
			script[(Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerText' : 'text'] = text;
			document.head.appendChild(script);
			document.head.removeChild(script);
		}
		return text;
	};

	Native.UID = 1;

	var $uid = (Browser.Engine.trident) ? function(item){
		return (item.uid || (item.uid = [Native.UID++]))[0];
	} : function(item){
		return item.uid || (item.uid = Native.UID++);
	};

	var Window = new Native({

		name: 'Window',

		legacy: (Browser.Engine.trident) ? null: window.Window,

		initialize: function(win){
			$uid(win);
			if (!win.Element){
				win.Element = $empty;
				if (Browser.Engine.webkit) win.document.createElement("iframe"); //fixes safari 2
				win.Element.prototype = (Browser.Engine.webkit) ? window["[[DOMElement.prototype]]"] : {};
			}
			win.document.window = win;
			return $extend(win, Window.Prototype);
		},

		afterImplement: function(property, value){
			window[property] = Window.Prototype[property] = value;
		}

	});

	Window.Prototype = {$family: {name: 'window'}};

	new Window(window);

	var Document = new Native({

		name: 'Document',

		legacy: (Browser.Engine.trident) ? null: window.Document,

		initialize: function(doc){
			$uid(doc);
			doc.head = doc.getElementsByTagName('head')[0];
			doc.html = doc.getElementsByTagName('html')[0];
			if (Browser.Engine.trident && Browser.Engine.version <= 4) $try(function(){
				doc.execCommand("BackgroundImageCache", false, true);
			});
			if (Browser.Engine.trident) doc.window.attachEvent('onunload', function() {
				doc.window.detachEvent('onunload', arguments.callee);
				doc.head = doc.html = doc.window = null;
			});
			return $extend(doc, Document.Prototype);
		},

		afterImplement: function(property, value){
			document[property] = Document.Prototype[property] = value;
		}

	});

	Document.Prototype = {$family: {name: 'document'}};

	new Document(document);


	/*
	Script: Array.js
		Contains Array Prototypes like each, contains, and erase.

	License:
		MIT-style license.
	*/

	Array.implement({

		every: function(fn, bind){
			for (var i = 0, l = this.length; i < l; i++){
				if (!fn.call(bind, this[i], i, this)) return false;
			}
			return true;
		},

		filter: function(fn, bind){
			var results = [];
			for (var i = 0, l = this.length; i < l; i++){
				if (fn.call(bind, this[i], i, this)) results.push(this[i]);
			}
			return results;
		},

		clean: function() {
			return this.filter($defined);
		},

		indexOf: function(item, from){
			var len = this.length;
			for (var i = (from < 0) ? Math.max(0, len + from) : from || 0; i < len; i++){
				if (this[i] === item) return i;
			}
			return -1;
		},

		map: function(fn, bind){
			var results = [];
			for (var i = 0, l = this.length; i < l; i++) results[i] = fn.call(bind, this[i], i, this);
			return results;
		},

		some: function(fn, bind){
			for (var i = 0, l = this.length; i < l; i++){
				if (fn.call(bind, this[i], i, this)) return true;
			}
			return false;
		},

		associate: function(keys){
			var obj = {}, length = Math.min(this.length, keys.length);
			for (var i = 0; i < length; i++) obj[keys[i]] = this[i];
			return obj;
		},

		link: function(object){
			var result = {};
			for (var i = 0, l = this.length; i < l; i++){
				for (var key in object){
					if (object[key](this[i])){
						result[key] = this[i];
						delete object[key];
						break;
					}
				}
			}
			return result;
		},

		contains: function(item, from){
			return this.indexOf(item, from) != -1;
		},

		extend: function(array){
			for (var i = 0, j = array.length; i < j; i++) this.push(array[i]);
			return this;
		},
		
		getLast: function(){
			return (this.length) ? this[this.length - 1] : null;
		},

		getRandom: function(){
			return (this.length) ? this[$random(0, this.length - 1)] : null;
		},

		include: function(item){
			if (!this.contains(item)) this.push(item);
			return this;
		},

		combine: function(array){
			for (var i = 0, l = array.length; i < l; i++) this.include(array[i]);
			return this;
		},

		erase: function(item){
			for (var i = this.length; i--; i){
				if (this[i] === item) this.splice(i, 1);
			}
			return this;
		},

		empty: function(){
			this.length = 0;
			return this;
		},

		flatten: function(){
			var array = [];
			for (var i = 0, l = this.length; i < l; i++){
				var type = $type(this[i]);
				if (!type) continue;
				array = array.concat((type == 'array' || type == 'collection' || type == 'arguments') ? Array.flatten(this[i]) : this[i]);
			}
			return array;
		},

		hexToRgb: function(array){
			if (this.length != 3) return null;
			var rgb = this.map(function(value){
				if (value.length == 1) value += value;
				return value.toInt(16);
			});
			return (array) ? rgb : 'rgb(' + rgb + ')';
		},

		rgbToHex: function(array){
			if (this.length < 3) return null;
			if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
			var hex = [];
			for (var i = 0; i < 3; i++){
				var bit = (this[i] - 0).toString(16);
				hex.push((bit.length == 1) ? '0' + bit : bit);
			}
			return (array) ? hex : '#' + hex.join('');
		}

	});


	/*
	Script: Function.js
		Contains Function Prototypes like create, bind, pass, and delay.

	License:
		MIT-style license.
	*/

	Function.implement({

		extend: function(properties){
			for (var property in properties) this[property] = properties[property];
			return this;
		},

		create: function(options){
			var self = this;
			options = options || {};
			return function(event){
				var args = options.arguments;
				args = (args != undefined) ? $splat(args) : Array.slice(arguments, (options.event) ? 1 : 0);
				if (options.event) args = [event || window.event].extend(args);
				var returns = function(){
					return self.apply(options.bind || null, args);
				};
				if (options.delay) return setTimeout(returns, options.delay);
				if (options.periodical) return setInterval(returns, options.periodical);
				if (options.attempt) return $try(returns);
				return returns();
			};
		},

		run: function(args, bind){
			return this.apply(bind, $splat(args));
		},

		pass: function(args, bind){
			return this.create({bind: bind, arguments: args});
		},

		bind: function(bind, args){
			return this.create({bind: bind, arguments: args});
		},

		bindWithEvent: function(bind, args){
			return this.create({bind: bind, arguments: args, event: true});
		},

		attempt: function(args, bind){
			return this.create({bind: bind, arguments: args, attempt: true})();
		},

		delay: function(delay, bind, args){
			return this.create({bind: bind, arguments: args, delay: delay})();
		},

		periodical: function(periodical, bind, args){
			return this.create({bind: bind, arguments: args, periodical: periodical})();
		}

	});


	/*
	Script: Number.js
		Contains Number Prototypes like limit, round, times, and ceil.

	License:
		MIT-style license.
	*/

	Number.implement({

		limit: function(min, max){
			return Math.min(max, Math.max(min, this));
		},

		round: function(precision){
			precision = Math.pow(10, precision || 0);
			return Math.round(this * precision) / precision;
		},

		times: function(fn, bind){
			for (var i = 0; i < this; i++) fn.call(bind, i, this);
		},

		toFloat: function(){
			return parseFloat(this);
		},

		toInt: function(base){
			return parseInt(this, base || 10);
		}

	});

	Number.alias('times', 'each');

	(function(math){
		var methods = {};
		math.each(function(name){
			if (!Number[name]) methods[name] = function(){
				return Math[name].apply(null, [this].concat($A(arguments)));
			};
		});
		Number.implement(methods);
	})(['abs', 'acos', 'asin', 'atan', 'atan2', 'ceil', 'cos', 'exp', 'floor', 'log', 'max', 'min', 'pow', 'sin', 'sqrt', 'tan']);


	/*
	Script: String.js
		Contains String Prototypes like camelCase, capitalize, test, and toInt.

	License:
		MIT-style license.
	*/

	String.implement({

		test: function(regex, params){
			return ((typeof regex == 'string') ? new RegExp(regex, params) : regex).test(this);
		},

		contains: function(string, separator){
			return (separator) ? (separator + this + separator).indexOf(separator + string + separator) > -1 : this.indexOf(string) > -1;
		},

		trim: function(){
			return this.replace(/^\s+|\s+$/g, '');
		},

		clean: function(){
			return this.replace(/\s+/g, ' ').trim();
		},

		camelCase: function(){
			return this.replace(/-\D/g, function(match){
				return match.charAt(1).toUpperCase();
			});
		},

		hyphenate: function(){
			return this.replace(/[A-Z]/g, function(match){
				return ('-' + match.charAt(0).toLowerCase());
			});
		},

		capitalize: function(){
			return this.replace(/\b[a-z]/g, function(match){
				return match.toUpperCase();
			});
		},

		escapeRegExp: function(){
			return this.replace(/([-.*+?^${}()|[\]\/\\])/g, '\\$1');
		},

		toInt: function(base){
			return parseInt(this, base || 10);
		},

		toFloat: function(){
			return parseFloat(this);
		},

		hexToRgb: function(array){
			var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
			return (hex) ? hex.slice(1).hexToRgb(array) : null;
		},

		rgbToHex: function(array){
			var rgb = this.match(/\d{1,3}/g);
			return (rgb) ? rgb.rgbToHex(array) : null;
		},

		stripScripts: function(option){
			var scripts = '';
			var text = this.replace(/<script[^>]*>([\s\S]*?)<\/script>/gi, function(){
				scripts += arguments[1] + '\n';
				return '';
			});
			if (option === true) $exec(scripts);
			else if ($type(option) == 'function') option(scripts, text);
			return text;
		},

		substitute: function(object, regexp){
			return this.replace(regexp || (/\\?\{([^{}]+)\}/g), function(match, name){
				if (match.charAt(0) == '\\') return match.slice(1);
				return (object[name] != undefined) ? object[name] : '';
			});
		}

	});


	/*
	Script: Hash.js
		Contains Hash Prototypes. Provides a means for overcoming the JavaScript practical impossibility of extending native Objects.

	License:
		MIT-style license.
	*/

	Hash.implement({

		has: Object.prototype.hasOwnProperty,

		keyOf: function(value){
			for (var key in this){
				if (this.hasOwnProperty(key) && this[key] === value) return key;
			}
			return null;
		},

		hasValue: function(value){
			return (Hash.keyOf(this, value) !== null);
		},

		extend: function(properties){
			Hash.each(properties || {}, function(value, key){
				Hash.set(this, key, value);
			}, this);
			return this;
		},

		combine: function(properties){
			Hash.each(properties || {}, function(value, key){
				Hash.include(this, key, value);
			}, this);
			return this;
		},

		erase: function(key){
			if (this.hasOwnProperty(key)) delete this[key];
			return this;
		},

		get: function(key){
			return (this.hasOwnProperty(key)) ? this[key] : null;
		},

		set: function(key, value){
			if (!this[key] || this.hasOwnProperty(key)) this[key] = value;
			return this;
		},

		empty: function(){
			Hash.each(this, function(value, key){
				delete this[key];
			}, this);
			return this;
		},

		include: function(key, value){
			if (this[key] == undefined) this[key] = value;
			return this;
		},

		map: function(fn, bind){
			var results = new Hash;
			Hash.each(this, function(value, key){
				results.set(key, fn.call(bind, value, key, this));
			}, this);
			return results;
		},

		filter: function(fn, bind){
			var results = new Hash;
			Hash.each(this, function(value, key){
				if (fn.call(bind, value, key, this)) results.set(key, value);
			}, this);
			return results;
		},

		every: function(fn, bind){
			for (var key in this){
				if (this.hasOwnProperty(key) && !fn.call(bind, this[key], key)) return false;
			}
			return true;
		},

		some: function(fn, bind){
			for (var key in this){
				if (this.hasOwnProperty(key) && fn.call(bind, this[key], key)) return true;
			}
			return false;
		},

		getKeys: function(){
			var keys = [];
			Hash.each(this, function(value, key){
				keys.push(key);
			});
			return keys;
		},

		getValues: function(){
			var values = [];
			Hash.each(this, function(value){
				values.push(value);
			});
			return values;
		},

		toQueryString: function(base){
			var queryString = [];
			Hash.each(this, function(value, key){
				if (base) key = base + '[' + key + ']';
				var result;
				switch ($type(value)){
					case 'object': result = Hash.toQueryString(value, key); break;
					case 'array':
						var qs = {};
						value.each(function(val, i){
							qs[i] = val;
						});
						result = Hash.toQueryString(qs, key);
					break;
					default: result = key + '=' + encodeURIComponent(value);
				}
				if (value != undefined) queryString.push(result);
			});

			return queryString.join('&');
		}

	});

	Hash.alias({keyOf: 'indexOf', hasValue: 'contains'});


	/*
	Script: Event.js
		Contains the Event Native, to make the event object completely crossbrowser.

	License:
		MIT-style license.
	*/

	var Event = new Native({

		name: 'Event',

		initialize: function(event, win){
			win = win || window;
			var doc = win.document;
			event = event || win.event;
			if (event.$extended) return event;
			this.$extended = true;
			var type = event.type;
			var target = event.target || event.srcElement;
			while (target && target.nodeType == 3) target = target.parentNode;

			if (type.test(/key/)){
				var code = event.which || event.keyCode;
				var key = Event.Keys.keyOf(code);
				if (type == 'keydown'){
					var fKey = code - 111;
					if (fKey > 0 && fKey < 13) key = 'f' + fKey;
				}
				key = key || String.fromCharCode(code).toLowerCase();
			} else if (type.match(/(click|mouse|menu)/i)){
				doc = (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
				var page = {
					x: event.pageX || event.clientX + doc.scrollLeft,
					y: event.pageY || event.clientY + doc.scrollTop
				};
				var client = {
					x: (event.pageX) ? event.pageX - win.pageXOffset : event.clientX,
					y: (event.pageY) ? event.pageY - win.pageYOffset : event.clientY
				};
				if (type.match(/DOMMouseScroll|mousewheel/)){
					var wheel = (event.wheelDelta) ? event.wheelDelta / 120 : -(event.detail || 0) / 3;
				}
				var rightClick = (event.which == 3) || (event.button == 2);
				var related = null;
				if (type.match(/over|out/)){
					switch (type){
						case 'mouseover': related = event.relatedTarget || event.fromElement; break;
						case 'mouseout': related = event.relatedTarget || event.toElement;
					}
					if (!(function(){
						while (related && related.nodeType == 3) related = related.parentNode;
						return true;
					}).create({attempt: Browser.Engine.gecko})()) related = false;
				}
			}

			return $extend(this, {
				event: event,
				type: type,

				page: page,
				client: client,
				rightClick: rightClick,

				wheel: wheel,

				relatedTarget: related,
				target: target,

				code: code,
				key: key,

				shift: event.shiftKey,
				control: event.ctrlKey,
				alt: event.altKey,
				meta: event.metaKey
			});
		}

	});

	Event.Keys = new Hash({
		'enter': 13,
		'up': 38,
		'down': 40,
		'left': 37,
		'right': 39,
		'esc': 27,
		'space': 32,
		'backspace': 8,
		'tab': 9,
		'delete': 46
	});

	Event.implement({

		stop: function(){
			return this.stopPropagation().preventDefault();
		},

		stopPropagation: function(){
			if (this.event.stopPropagation) this.event.stopPropagation();
			else this.event.cancelBubble = true;
			return this;
		},

		preventDefault: function(){
			if (this.event.preventDefault) this.event.preventDefault();
			else this.event.returnValue = false;
			return this;
		}

	});


	/*
	Script: Class.js
		Contains the Class Function for easily creating, extending, and implementing reusable Classes.

	License:
		MIT-style license.
	*/

	function Class(params){
		
		if (params instanceof Function) params = {initialize: params};
		
		var newClass = function(){
			Object.reset(this);
			if (newClass._prototyping) return this;
			this._current = $empty;
			var value = (this.initialize) ? this.initialize.apply(this, arguments) : this;
			delete this._current; delete this.caller;
			return value;
		}.extend(this);
		
		newClass.implement(params);
		
		newClass.constructor = Class;
		newClass.prototype.constructor = newClass;

		return newClass;

	};

	Function.prototype.protect = function(){
		this._protected = true;
		return this;
	};

	Object.reset = function(object, key){
			
		if (key == null){
			for (var p in object) Object.reset(object, p);
			return object;
		}
		
		delete object[key];
		
		switch ($type(object[key])){
			case 'object':
				var F = function(){};
				F.prototype = object[key];
				var i = new F;
				object[key] = Object.reset(i);
			break;
			case 'array': object[key] = $unlink(object[key]); break;
		}
		
		return object;
		
	};

	new Native({name: 'Class', initialize: Class}).extend({

		instantiate: function(F){
			F._prototyping = true;
			var proto = new F;
			delete F._prototyping;
			return proto;
		},
		
		wrap: function(self, key, method){
			if (method._origin) method = method._origin;
			
			return function(){
				if (method._protected && this._current == null) throw new Error('The method "' + key + '" cannot be called.');
				var caller = this.caller, current = this._current;
				this.caller = current; this._current = arguments.callee;
				var result = method.apply(this, arguments);
				this._current = current; this.caller = caller;
				return result;
			}.extend({_owner: self, _origin: method, _name: key});

		}
		
	});

	Class.implement({
		
		implement: function(key, value){
			
			if ($type(key) == 'object'){
				for (var p in key) this.implement(p, key[p]);
				return this;
			}
			
			var mutator = Class.Mutators[key];
			
			if (mutator){
				value = mutator.call(this, value);
				if (value == null) return this;
			}
			
			var proto = this.prototype;

			switch ($type(value)){
				
				case 'function':
					if (value._hidden) return this;
					proto[key] = Class.wrap(this, key, value);
				break;
				
				case 'object':
					var previous = proto[key];
					if ($type(previous) == 'object') $mixin(previous, value);
					else proto[key] = $unlink(value);
				break;
				
				case 'array':
					proto[key] = $unlink(value);
				break;
				
				default: proto[key] = value;

			}
			
			return this;

		}
		
	});

	Class.Mutators = {
		
		Extends: function(parent){

			this.parent = parent;
			this.prototype = Class.instantiate(parent);

			this.implement('parent', function(){
				var name = this.caller._name, previous = this.caller._owner.parent.prototype[name];
				if (!previous) throw new Error('The method "' + name + '" has no parent.');
				return previous.apply(this, arguments);
			}.protect());

		},

		Implements: function(items){
			$splat(items).each(function(item){
				if (item instanceof Function) item = Class.instantiate(item);
				this.implement(item);
			}, this);

		}
		
	};


	/*
	Script: Class.Extras.js
		Contains Utility Classes that can be implemented into your own Classes to ease the execution of many common tasks.

	License:
		MIT-style license.
	*/

	var Chain = new Class({

		$chain: [],

		chain: function(){
			this.$chain.extend(Array.flatten(arguments));
			return this;
		},

		callChain: function(){
			return (this.$chain.length) ? this.$chain.shift().apply(this, arguments) : false;
		},

		clearChain: function(){
			this.$chain.empty();
			return this;
		}

	});

	var Events = new Class({

		$events: {},

		addEvent: function(type, fn, internal){
			type = Events.removeOn(type);
			if (fn != $empty){
				this.$events[type] = this.$events[type] || [];
				this.$events[type].include(fn);
				if (internal) fn.internal = true;
			}
			return this;
		},

		addEvents: function(events){
			for (var type in events) this.addEvent(type, events[type]);
			return this;
		},

		fireEvent: function(type, args, delay){
			type = Events.removeOn(type);
			if (!this.$events || !this.$events[type]) return this;
			this.$events[type].each(function(fn){
				fn.create({'bind': this, 'delay': delay, 'arguments': args})();
			}, this);
			return this;
		},

		removeEvent: function(type, fn){
			type = Events.removeOn(type);
			if (!this.$events[type]) return this;
			if (!fn.internal) this.$events[type].erase(fn);
			return this;
		},

		removeEvents: function(events){
			var type;
			if ($type(events) == 'object'){
				for (type in events) this.removeEvent(type, events[type]);
				return this;
			}
			if (events) events = Events.removeOn(events);
			for (type in this.$events){
				if (events && events != type) continue;
				var fns = this.$events[type];
				for (var i = fns.length; i--; i) this.removeEvent(type, fns[i]);
			}
			return this;
		}

	});

	Events.removeOn = function(string){
		return string.replace(/^on([A-Z])/, function(full, first) {
			return first.toLowerCase();
		});
	};

	var Options = new Class({

		setOptions: function(){
			this.options = $merge.run([this.options].extend(arguments));
			if (!this.addEvent) return this;
			for (var option in this.options){
				if ($type(this.options[option]) != 'function' || !(/^on[A-Z]/).test(option)) continue;
				this.addEvent(option, this.options[option]);
				delete this.options[option];
			}
			return this;
		}

	});


	/*
	Script: Element.js
		One of the most important items in MooTools. Contains the dollar function, the dollars function, and an handful of cross-browser,
		time-saver methods to let you easily work with HTML Elements.

	License:
		MIT-style license.
	*/

	var Element = new Native({

		name: 'Element',

		legacy: window.Element,

		initialize: function(tag, props){
			var konstructor = Element.Constructors.get(tag);
			if (konstructor) return konstructor(props);
			if (typeof tag == 'string') return document.newElement(tag, props);
			return document.id(tag).set(props);
		},

		afterImplement: function(key, value){
			Element.Prototype[key] = value;
			if (Array[key]) return;
			Elements.implement(key, function(){
				var items = [], elements = true;
				for (var i = 0, j = this.length; i < j; i++){
					var returns = this[i][key].apply(this[i], arguments);
					items.push(returns);
					if (elements) elements = ($type(returns) == 'element');
				}
				return (elements) ? new Elements(items) : items;
			});
		}

	});

	Element.Prototype = {$family: {name: 'element'}};

	Element.Constructors = new Hash;

	var IFrame = new Native({

		name: 'IFrame',

		generics: false,

		initialize: function(){
			var params = Array.link(arguments, {properties: Object.type, iframe: $defined});
			var props = params.properties || {};
			var iframe = document.id(params.iframe);
			var onload = props.onload || $empty;
			delete props.onload;
			props.id = props.name = $pick(props.id, props.name, iframe ? (iframe.id || iframe.name) : 'IFrame_' + $time());
			iframe = new Element(iframe || 'iframe', props);
			var onFrameLoad = function(){
				var host = $try(function(){
					return iframe.contentWindow.location.host;
				});
				if (!host || host == window.location.host){
					var win = new Window(iframe.contentWindow);
					new Document(iframe.contentWindow.document);
					$extend(win.Element.prototype, Element.Prototype);
				}
				onload.call(iframe.contentWindow, iframe.contentWindow.document);
			};
			var contentWindow = $try(function(){
				return iframe.contentWindow;
			});
			((contentWindow && contentWindow.document.body) || window.frames[props.id]) ? onFrameLoad() : iframe.addListener('load', onFrameLoad);
			return iframe;
		}

	});

	var Elements = new Native({

		initialize: function(elements, options){
			options = $extend({ddup: true, cash: true}, options);
			elements = elements || [];
			if (options.ddup || options.cash){
				var uniques = {}, returned = [];
				for (var i = 0, l = elements.length; i < l; i++){
					var el = document.id(elements[i], !options.cash);
					if (options.ddup){
						if (uniques[el.uid]) continue;
						uniques[el.uid] = true;
					}
					returned.push(el);
				}
				elements = returned;
			}
			return (options.cash) ? $extend(elements, this) : elements;
		}

	});

	Elements.implement({

		filter: function(filter, bind){
			if (!filter) return this;
			return new Elements(Array.filter(this, (typeof filter == 'string') ? function(item){
				return item.match(filter);
			} : filter, bind));
		}

	});

	Document.implement({

		newElement: function(tag, props){
			if (Browser.Engine.trident && props){
				['name', 'type', 'checked'].each(function(attribute){
					if (!props[attribute]) return;
					tag += ' ' + attribute + '="' + props[attribute] + '"';
					if (attribute != 'checked') delete props[attribute];
				});
				tag = '<' + tag + '>';
			}
			return document.id(this.createElement(tag)).set(props);
		},

		newTextNode: function(text){
			return this.createTextNode(text);
		},

		getDocument: function(){
			return this;
		},

		getWindow: function(){
			return this.window;
		},
		
		id: (function(){
			
			var types = {

				string: function(id, nocash, doc){
					id = doc.getElementById(id);
					return (id) ? types.element(id, nocash) : null;
				},
				
				element: function(el, nocash){
					$uid(el);
					if (!nocash && !el.$family && !(/^object|embed$/i).test(el.tagName)){
						var proto = Element.Prototype;
						for (var p in proto) el[p] = proto[p];
					};
					return el;
				},
				
				object: function(obj, nocash, doc){
					if (obj.toElement) return types.element(obj.toElement(doc), nocash);
					return null;
				}
				
			};

			types.textnode = types.whitespace = types.window = types.document = $arguments(0);
			
			return function(el, nocash, doc){
				if (el && el.$family && el.uid) return el;
				var type = $type(el);
				return (types[type]) ? types[type](el, nocash, doc || document) : null;
			};

		})()

	});

	if (window.$ == null) Window.implement({
		$: function(el, nc){
			return document.id(el, nc, this.document);
		}
	});

	Window.implement({

		$$: function(selector){
			if (arguments.length == 1 && typeof selector == 'string') return this.document.getElements(selector);
			var elements = [];
			var args = Array.flatten(arguments);
			for (var i = 0, l = args.length; i < l; i++){
				var item = args[i];
				switch ($type(item)){
					case 'element': elements.push(item); break;
					case 'string': elements.extend(this.document.getElements(item, true));
				}
			}
			return new Elements(elements);
		},

		getDocument: function(){
			return this.document;
		},

		getWindow: function(){
			return this;
		}

	});

	Native.implement([Element, Document], {

		getElement: function(selector, nocash){
			return document.id(this.getElements(selector, true)[0] || null, nocash);
		},

		getElements: function(tags, nocash){
			tags = tags.split(',');
			var elements = [];
			var ddup = (tags.length > 1);
			tags.each(function(tag){
				var partial = this.getElementsByTagName(tag.trim());
				(ddup) ? elements.extend(partial) : elements = partial;
			}, this);
			return new Elements(elements, {ddup: ddup, cash: !nocash});
		}

	});

	(function(){

	var collected = {}, storage = {};
	var props = {input: 'checked', option: 'selected', textarea: (Browser.Engine.webkit && Browser.Engine.version < 420) ? 'innerHTML' : 'value'};

	var get = function(uid){
		return (storage[uid] || (storage[uid] = {}));
	};

	var clean = function(item, retain){
		if (!item) return;
		var uid = item.uid;
		if (Browser.Engine.trident){
			if (item.clearAttributes){
				var clone = retain && item.cloneNode(false);
				item.clearAttributes();
				if (clone) item.mergeAttributes(clone);
			} else if (item.removeEvents){
				item.removeEvents();
			}
			if ((/object/i).test(item.tagName)){
				for (var p in item){
					if (typeof item[p] == 'function') item[p] = $empty;
				}
				Element.dispose(item);
			}
		}	
		if (!uid) return;
		collected[uid] = storage[uid] = null;
	};

	var purge = function(){
		Hash.each(collected, clean);
		if (Browser.Engine.trident) $A(document.getElementsByTagName('object')).each(clean);
		if (window.CollectGarbage) CollectGarbage();
		collected = storage = null;
	};

	var walk = function(element, walk, start, match, all, nocash){
		var el = element[start || walk];
		var elements = [];
		while (el){
			if (el.nodeType == 1 && (!match || Element.match(el, match))){
				if (!all) return document.id(el, nocash);
				elements.push(el);
			}
			el = el[walk];
		}
		return (all) ? new Elements(elements, {ddup: false, cash: !nocash}) : null;
	};

	var attributes = {
		'html': 'innerHTML',
		'class': 'className',
		'for': 'htmlFor',
		'defaultValue': 'defaultValue',
		'text': (Browser.Engine.trident || (Browser.Engine.webkit && Browser.Engine.version < 420)) ? 'innerText' : 'textContent'
	};
	var bools = ['compact', 'nowrap', 'ismap', 'declare', 'noshade', 'checked', 'disabled', 'readonly', 'multiple', 'selected', 'noresize', 'defer'];
	var camels = ['value', 'type', 'defaultValue', 'accessKey', 'cellPadding', 'cellSpacing', 'colSpan', 'frameBorder', 'maxLength', 'readOnly', 'rowSpan', 'tabIndex', 'useMap'];

	bools = bools.associate(bools);

	Hash.extend(attributes, bools);
	Hash.extend(attributes, camels.associate(camels.map(String.toLowerCase)));

	var inserters = {

		before: function(context, element){
			if (element.parentNode) element.parentNode.insertBefore(context, element);
		},

		after: function(context, element){
			if (!element.parentNode) return;
			var next = element.nextSibling;
			(next) ? element.parentNode.insertBefore(context, next) : element.parentNode.appendChild(context);
		},

		bottom: function(context, element){
			element.appendChild(context);
		},

		top: function(context, element){
			var first = element.firstChild;
			(first) ? element.insertBefore(context, first) : element.appendChild(context);
		}

	};

	inserters.inside = inserters.bottom;

	Hash.each(inserters, function(inserter, where){

		where = where.capitalize();

		Element.implement('inject' + where, function(el){
			inserter(this, document.id(el, true));
			return this;
		});

		Element.implement('grab' + where, function(el){
			inserter(document.id(el, true), this);
			return this;
		});

	});

	Element.implement({

		set: function(prop, value){
			switch ($type(prop)){
				case 'object':
					for (var p in prop) this.set(p, prop[p]);
					break;
				case 'string':
					var property = Element.Properties.get(prop);
					(property && property.set) ? property.set.apply(this, Array.slice(arguments, 1)) : this.setProperty(prop, value);
			}
			return this;
		},

		get: function(prop){
			var property = Element.Properties.get(prop);
			return (property && property.get) ? property.get.apply(this, Array.slice(arguments, 1)) : this.getProperty(prop);
		},

		erase: function(prop){
			var property = Element.Properties.get(prop);
			(property && property.erase) ? property.erase.apply(this) : this.removeProperty(prop);
			return this;
		},

		setProperty: function(attribute, value){
			var key = attributes[attribute];
			if (value == undefined) return this.removeProperty(attribute);
			if (key && bools[attribute]) value = !!value;
			(key) ? this[key] = value : this.setAttribute(attribute, '' + value);
			return this;
		},

		setProperties: function(attributes){
			for (var attribute in attributes) this.setProperty(attribute, attributes[attribute]);
			return this;
		},

		getProperty: function(attribute){
			var key = attributes[attribute];
			var value = (key) ? this[key] : this.getAttribute(attribute, 2);
			return (bools[attribute]) ? !!value : (key) ? value : value || null;
		},

		getProperties: function(){
			var args = $A(arguments);
			return args.map(this.getProperty, this).associate(args);
		},

		removeProperty: function(attribute){
			var key = attributes[attribute];
			(key) ? this[key] = (key && bools[attribute]) ? false : '' : this.removeAttribute(attribute);
			return this;
		},

		removeProperties: function(){
			Array.each(arguments, this.removeProperty, this);
			return this;
		},

		hasClass: function(className){
			return this.className.contains(className, ' ');
		},

		addClass: function(className){
			if (!this.hasClass(className)) this.className = (this.className + ' ' + className).clean();
			return this;
		},

		removeClass: function(className){
			this.className = this.className.replace(new RegExp('(^|\\s)' + className + '(?:\\s|$)'), '$1');
			return this;
		},

		toggleClass: function(className){
			return this.hasClass(className) ? this.removeClass(className) : this.addClass(className);
		},

		adopt: function(){
			Array.flatten(arguments).each(function(element){
				element = document.id(element, true);
				if (element) this.appendChild(element);
			}, this);
			return this;
		},

		appendText: function(text, where){
			return this.grab(this.getDocument().newTextNode(text), where);
		},

		grab: function(el, where){
			inserters[where || 'bottom'](document.id(el, true), this);
			return this;
		},

		inject: function(el, where){
			inserters[where || 'bottom'](this, document.id(el, true));
			return this;
		},

		replaces: function(el){
			el = document.id(el, true);
			el.parentNode.replaceChild(this, el);
			return this;
		},

		wraps: function(el, where){
			el = document.id(el, true);
			return this.replaces(el).grab(el, where);
		},

		getPrevious: function(match, nocash){
			return walk(this, 'previousSibling', null, match, false, nocash);
		},

		getAllPrevious: function(match, nocash){
			return walk(this, 'previousSibling', null, match, true, nocash);
		},

		getNext: function(match, nocash){
			return walk(this, 'nextSibling', null, match, false, nocash);
		},

		getAllNext: function(match, nocash){
			return walk(this, 'nextSibling', null, match, true, nocash);
		},

		getFirst: function(match, nocash){
			return walk(this, 'nextSibling', 'firstChild', match, false, nocash);
		},

		getLast: function(match, nocash){
			return walk(this, 'previousSibling', 'lastChild', match, false, nocash);
		},

		getParent: function(match, nocash){
			return walk(this, 'parentNode', null, match, false, nocash);
		},

		getParents: function(match, nocash){
			return walk(this, 'parentNode', null, match, true, nocash);
		},
		
		getSiblings: function(match, nocash) {
			return this.getParent().getChildren(match, nocash).erase(this);
		},

		getChildren: function(match, nocash){
			return walk(this, 'nextSibling', 'firstChild', match, true, nocash);
		},

		getWindow: function(){
			return this.ownerDocument.window;
		},

		getDocument: function(){
			return this.ownerDocument;
		},

		getElementById: function(id, nocash){
			var el = this.ownerDocument.getElementById(id);
			if (!el) return null;
			for (var parent = el.parentNode; parent != this; parent = parent.parentNode){
				if (!parent) return null;
			}
			return document.id(el, nocash);
		},

		getSelected: function(){
			return new Elements($A(this.options).filter(function(option){
				return option.selected;
			}));
		},

		getComputedStyle: function(property){
			if (this.currentStyle) return this.currentStyle[property.camelCase()];
			var computed = this.getDocument().defaultView.getComputedStyle(this, null);
			return (computed) ? computed.getPropertyValue([property.hyphenate()]) : null;
		},

		toQueryString: function(){
			var queryString = [];
			this.getElements('input, select, textarea', true).each(function(el){
				if (!el.name || el.disabled || el.type == 'submit' || el.type == 'reset' || el.type == 'file') return;
				var value = (el.tagName.toLowerCase() == 'select') ? Element.getSelected(el).map(function(opt){
					return opt.value;
				}) : ((el.type == 'radio' || el.type == 'checkbox') && !el.checked) ? null : el.value;
				$splat(value).each(function(val){
					if (typeof val != 'undefined') queryString.push(el.name + '=' + encodeURIComponent(val));
				});
			});
			return queryString.join('&');
		},

		clone: function(contents, keepid){
			contents = contents !== false;
			var clone = this.cloneNode(contents);
			var clean = function(node, element){
				if (!keepid) node.removeAttribute('id');
				if (Browser.Engine.trident){
					node.clearAttributes();
					node.mergeAttributes(element);
					node.removeAttribute('uid');
					if (node.options){
						var no = node.options, eo = element.options;
						for (var j = no.length; j--;) no[j].selected = eo[j].selected;
					}
				}
				var prop = props[element.tagName.toLowerCase()];
				if (prop && element[prop]) node[prop] = element[prop];
			};

			if (contents){
				var ce = clone.getElementsByTagName('*'), te = this.getElementsByTagName('*');
				for (var i = ce.length; i--;) clean(ce[i], te[i]);
			}

			clean(clone, this);
			return document.id(clone);
		},

		destroy: function(){
			Element.empty(this);
			Element.dispose(this);
			clean(this, true);
			return null;
		},

		empty: function(){
			$A(this.childNodes).each(function(node){
				Element.destroy(node);
			});
			return this;
		},

		dispose: function(){
			return (this.parentNode) ? this.parentNode.removeChild(this) : this;
		},

		hasChild: function(el){
			el = document.id(el, true);
			if (!el) return false;
			if (Browser.Engine.webkit && Browser.Engine.version < 420) return $A(this.getElementsByTagName(el.tagName)).contains(el);
			return (this.contains) ? (this != el && this.contains(el)) : !!(this.compareDocumentPosition(el) & 16);
		},

		match: function(tag){
			return (!tag || (tag == this) || (Element.get(this, 'tag') == tag));
		}

	});

	Native.implement([Element, Window, Document], {

		addListener: function(type, fn){
			if (type == 'unload'){
				var old = fn, self = this;
				fn = function(){
					self.removeListener('unload', fn);
					old();
				};
			} else {
				collected[this.uid] = this;
			}
			if (this.addEventListener) this.addEventListener(type, fn, false);
			else this.attachEvent('on' + type, fn);
			return this;
		},

		removeListener: function(type, fn){
			if (this.removeEventListener) this.removeEventListener(type, fn, false);
			else this.detachEvent('on' + type, fn);
			return this;
		},

		retrieve: function(property, dflt){
			var storage = get(this.uid), prop = storage[property];
			if (dflt != undefined && prop == undefined) prop = storage[property] = dflt;
			return $pick(prop);
		},

		store: function(property, value){
			var storage = get(this.uid);
			storage[property] = value;
			return this;
		},

		eliminate: function(property){
			var storage = get(this.uid);
			delete storage[property];
			return this;
		}

	});

	window.addListener('unload', purge);

	})();

	Element.Properties = new Hash;

	Element.Properties.style = {

		set: function(style){
			this.style.cssText = style;
		},

		get: function(){
			return this.style.cssText;
		},

		erase: function(){
			this.style.cssText = '';
		}

	};

	Element.Properties.tag = {

		get: function(){
			return this.tagName.toLowerCase();
		}

	};

	Element.Properties.html = (function(){
		var wrapper = document.createElement('div');

		var translations = {
			table: [1, '<table>', '</table>'],
			select: [1, '<select>', '</select>'],
			tbody: [2, '<table><tbody>', '</tbody></table>'],
			tr: [3, '<table><tbody><tr>', '</tr></tbody></table>']
		};
		translations.thead = translations.tfoot = translations.tbody;

		var html = {
			set: function(){
				var html = Array.flatten(arguments).join('');
				var wrap = Browser.Engine.trident && translations[this.get('tag')];
				if (wrap){
					var first = wrapper;
					first.innerHTML = wrap[1] + html + wrap[2];
					for (var i = wrap[0]; i--;) first = first.firstChild;
					this.empty().adopt(first.childNodes);
				} else {
					this.innerHTML = html;
				}
			}
		};

		html.erase = html.set;

		return html;
	})();

	if (Browser.Engine.webkit && Browser.Engine.version < 420) Element.Properties.text = {
		get: function(){
			if (this.innerText) return this.innerText;
			var temp = this.ownerDocument.newElement('div', {html: this.innerHTML}).inject(this.ownerDocument.body);
			var text = temp.innerText;
			temp.destroy();
			return text;
		}
	};


	/*
	Script: Element.Event.js
		Contains Element methods for dealing with events, and custom Events.

	License:
		MIT-style license.
	*/

	Element.Properties.events = {set: function(events){
		this.addEvents(events);
	}};

	Native.implement([Element, Window, Document], {

		addEvent: function(type, fn){
			var events = this.retrieve('events', {});
			events[type] = events[type] || {'keys': [], 'values': []};
			if (events[type].keys.contains(fn)) return this;
			events[type].keys.push(fn);
			var realType = type, custom = Element.Events.get(type), condition = fn, self = this;
			if (custom){
				if (custom.onAdd) custom.onAdd.call(this, fn);
				if (custom.condition){
					condition = function(event){
						if (custom.condition.call(this, event)) return fn.call(this, event);
						return true;
					};
				}
				realType = custom.base || realType;
			}
			var defn = function(){
				return fn.call(self);
			};
			var nativeEvent = Element.NativeEvents[realType];
			if (nativeEvent){
				if (nativeEvent == 2){
					defn = function(event){
						event = new Event(event, self.getWindow());
						if (condition.call(self, event) === false) event.stop();
					};
				}
				this.addListener(realType, defn);
			}
			events[type].values.push(defn);
			return this;
		},

		removeEvent: function(type, fn){
			var events = this.retrieve('events');
			if (!events || !events[type]) return this;
			var pos = events[type].keys.indexOf(fn);
			if (pos == -1) return this;
			events[type].keys.splice(pos, 1);
			var value = events[type].values.splice(pos, 1)[0];
			var custom = Element.Events.get(type);
			if (custom){
				if (custom.onRemove) custom.onRemove.call(this, fn);
				type = custom.base || type;
			}
			return (Element.NativeEvents[type]) ? this.removeListener(type, value) : this;
		},

		addEvents: function(events){
			for (var event in events) this.addEvent(event, events[event]);
			return this;
		},

		removeEvents: function(events){
			var type;
			if ($type(events) == 'object'){
				for (type in events) this.removeEvent(type, events[type]);
				return this;
			}
			var attached = this.retrieve('events');
			if (!attached) return this;
			if (!events){
				for (type in attached) this.removeEvents(type);
				this.eliminate('events');
			} else if (attached[events]){
				while (attached[events].keys[0]) this.removeEvent(events, attached[events].keys[0]);
				attached[events] = null;
			}
			return this;
		},

		fireEvent: function(type, args, delay){
			var events = this.retrieve('events');
			if (!events || !events[type]) return this;
			events[type].keys.each(function(fn){
				fn.create({'bind': this, 'delay': delay, 'arguments': args})();
			}, this);
			return this;
		},

		cloneEvents: function(from, type){
			from = document.id(from);
			var fevents = from.retrieve('events');
			if (!fevents) return this;
			if (!type){
				for (var evType in fevents) this.cloneEvents(from, evType);
			} else if (fevents[type]){
				fevents[type].keys.each(function(fn){
					this.addEvent(type, fn);
				}, this);
			}
			return this;
		}

	});

	Element.NativeEvents = {
		click: 2, dblclick: 2, mouseup: 2, mousedown: 2, contextmenu: 2, //mouse buttons
		mousewheel: 2, DOMMouseScroll: 2, //mouse wheel
		mouseover: 2, mouseout: 2, mousemove: 2, selectstart: 2, selectend: 2, //mouse movement
		keydown: 2, keypress: 2, keyup: 2, //keyboard
		focus: 2, blur: 2, change: 2, reset: 2, select: 2, submit: 2, //form elements
		load: 1, unload: 1, beforeunload: 2, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
		error: 1, abort: 1, scroll: 1 //misc
	};

	(function(){

	var $check = function(event){
		var related = event.relatedTarget;
		if (related == undefined) return true;
		if (related === false) return false;
		return ($type(this) != 'document' && related != this && related.prefix != 'xul' && !this.hasChild(related));
	};

	Element.Events = new Hash({

		mouseenter: {
			base: 'mouseover',
			condition: $check
		},

		mouseleave: {
			base: 'mouseout',
			condition: $check
		},

		mousewheel: {
			base: (Browser.Engine.gecko) ? 'DOMMouseScroll' : 'mousewheel'
		}

	});

	})();


	/*
	Script: Element.Style.js
		Contains methods for interacting with the styles of Elements in a fashionable way.

	License:
		MIT-style license.
	*/

	Element.Properties.styles = {set: function(styles){
		this.setStyles(styles);
	}};

	Element.Properties.opacity = {

		set: function(opacity, novisibility){
			if (!novisibility){
				if (opacity == 0){
					if (this.style.visibility != 'hidden') this.style.visibility = 'hidden';
				} else {
					if (this.style.visibility != 'visible') this.style.visibility = 'visible';
				}
			}
			if (!this.currentStyle || !this.currentStyle.hasLayout) this.style.zoom = 1;
			if (Browser.Engine.trident) this.style.filter = (opacity == 1) ? '' : 'alpha(opacity=' + opacity * 100 + ')';
			this.style.opacity = opacity;
			this.store('opacity', opacity);
		},

		get: function(){
			return this.retrieve('opacity', 1);
		}

	};

	Element.implement({

		setOpacity: function(value){
			return this.set('opacity', value, true);
		},

		getOpacity: function(){
			return this.get('opacity');
		},

		setStyle: function(property, value){
			switch (property){
				case 'opacity': return this.set('opacity', parseFloat(value));
				case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat';
			}
			property = property.camelCase();
			if ($type(value) != 'string'){
				var map = (Element.Styles.get(property) || '@').split(' ');
				value = $splat(value).map(function(val, i){
					if (!map[i]) return '';
					return ($type(val) == 'number') ? map[i].replace('@', Math.round(val)) : val;
				}).join(' ');
			} else if (value == String(Number(value))){
				value = Math.round(value);
			}
			this.style[property] = value;
			return this;
		},

		getStyle: function(property){
			switch (property){
				case 'opacity': return this.get('opacity');
				case 'float': property = (Browser.Engine.trident) ? 'styleFloat' : 'cssFloat';
			}
			property = property.camelCase();
			var result = this.style[property];
			if (!$chk(result)){
				result = [];
				for (var style in Element.ShortStyles){
					if (property != style) continue;
					for (var s in Element.ShortStyles[style]) result.push(this.getStyle(s));
					return result.join(' ');
				}
				result = this.getComputedStyle(property);
			}
			if (result){
				result = String(result);
				var color = result.match(/rgba?\([\d\s,]+\)/);
				if (color) result = result.replace(color[0], color[0].rgbToHex());
			}
			if (Browser.Engine.presto || (Browser.Engine.trident && !$chk(parseInt(result, 10)))){
				if (property.test(/^(height|width)$/)){
					var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'], size = 0;
					values.each(function(value){
						size += this.getStyle('border-' + value + '-width').toInt() + this.getStyle('padding-' + value).toInt();
					}, this);
					return this['offset' + property.capitalize()] - size + 'px';
				}
				if ((Browser.Engine.presto) && String(result).test('px')) return result;
				if (property.test(/(border(.+)Width|margin|padding)/)) return '0px';
			}
			return result;
		},

		setStyles: function(styles){
			for (var style in styles) this.setStyle(style, styles[style]);
			return this;
		},

		getStyles: function(){
			var result = {};
			Array.flatten(arguments).each(function(key){
				result[key] = this.getStyle(key);
			}, this);
			return result;
		}

	});

	Element.Styles = new Hash({
		left: '@px', top: '@px', bottom: '@px', right: '@px',
		width: '@px', height: '@px', maxWidth: '@px', maxHeight: '@px', minWidth: '@px', minHeight: '@px',
		backgroundColor: 'rgb(@, @, @)', backgroundPosition: '@px @px', color: 'rgb(@, @, @)',
		fontSize: '@px', letterSpacing: '@px', lineHeight: '@px', clip: 'rect(@px @px @px @px)',
		margin: '@px @px @px @px', padding: '@px @px @px @px', border: '@px @ rgb(@, @, @) @px @ rgb(@, @, @) @px @ rgb(@, @, @)',
		borderWidth: '@px @px @px @px', borderStyle: '@ @ @ @', borderColor: 'rgb(@, @, @) rgb(@, @, @) rgb(@, @, @) rgb(@, @, @)',
		zIndex: '@', 'zoom': '@', fontWeight: '@', textIndent: '@px', opacity: '@'
	});

	Element.ShortStyles = {margin: {}, padding: {}, border: {}, borderWidth: {}, borderStyle: {}, borderColor: {}};

	['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
		var Short = Element.ShortStyles;
		var All = Element.Styles;
		['margin', 'padding'].each(function(style){
			var sd = style + direction;
			Short[style][sd] = All[sd] = '@px';
		});
		var bd = 'border' + direction;
		Short.border[bd] = All[bd] = '@px @ rgb(@, @, @)';
		var bdw = bd + 'Width', bds = bd + 'Style', bdc = bd + 'Color';
		Short[bd] = {};
		Short.borderWidth[bdw] = Short[bd][bdw] = All[bdw] = '@px';
		Short.borderStyle[bds] = Short[bd][bds] = All[bds] = '@';
		Short.borderColor[bdc] = Short[bd][bdc] = All[bdc] = 'rgb(@, @, @)';
	});


	/*
	Script: Element.Dimensions.js
		Contains methods to work with size, scroll, or positioning of Elements and the window object.

	License:
		MIT-style license.

	Credits:
		- Element positioning based on the [qooxdoo](http://qooxdoo.org/) code and smart browser fixes, [LGPL License](http://www.gnu.org/licenses/lgpl.html).
		- Viewport dimensions based on [YUI](http://developer.yahoo.com/yui/) code, [BSD License](http://developer.yahoo.com/yui/license.html).
	*/

	(function(){

	Element.implement({

		scrollTo: function(x, y){
			if (isBody(this)){
				this.getWindow().scrollTo(x, y);
			} else {
				this.scrollLeft = x;
				this.scrollTop = y;
			}
			return this;
		},

		getSize: function(){
			if (isBody(this)) return this.getWindow().getSize();
			return {x: this.offsetWidth, y: this.offsetHeight};
		},

		getScrollSize: function(){
			if (isBody(this)) return this.getWindow().getScrollSize();
			return {x: this.scrollWidth, y: this.scrollHeight};
		},

		getScroll: function(){
			if (isBody(this)) return this.getWindow().getScroll();
			return {x: this.scrollLeft, y: this.scrollTop};
		},

		getScrolls: function(){
			var element = this, position = {x: 0, y: 0};
			while (element && !isBody(element)){
				position.x += element.scrollLeft;
				position.y += element.scrollTop;
				element = element.parentNode;
			}
			return position;
		},

		getOffsetParent: function(){
			var element = this;
			if (isBody(element)) return null;
			if (!Browser.Engine.trident) return element.offsetParent;
			while ((element = element.parentNode) && !isBody(element)){
				if (styleString(element, 'position') != 'static') return element;
			}
			return null;
		},

		getOffsets: function(){		
			if (this.getBoundingClientRect){
				var bound = this.getBoundingClientRect(),
				html = document.id(this.getDocument().documentElement),
				scroll = html.getScroll(),
				isFixed = (styleString(this, 'position') == 'fixed');
				return {
					x: parseInt(bound.left, 10) + ((isFixed) ? 0 : scroll.x) - html.clientLeft,
					y: parseInt(bound.top, 10) +  ((isFixed) ? 0 : scroll.y) - html.clientTop
				};
			}

			var element = this, position = {x: 0, y: 0};
			if (isBody(this)) return position;

			while (element && !isBody(element)){
				position.x += element.offsetLeft;
				position.y += element.offsetTop;

				if (Browser.Engine.gecko){
					if (!borderBox(element)){
						position.x += leftBorder(element);
						position.y += topBorder(element);
					}
					var parent = element.parentNode;
					if (parent && styleString(parent, 'overflow') != 'visible'){
						position.x += leftBorder(parent);
						position.y += topBorder(parent);
					}
				} else if (element != this && Browser.Engine.webkit){
					position.x += leftBorder(element);
					position.y += topBorder(element);
				}

				element = element.offsetParent;
			}
			if (Browser.Engine.gecko && !borderBox(this)){
				position.x -= leftBorder(this);
				position.y -= topBorder(this);
			}
			return position;
		},

		getPosition: function(relative){
			if (isBody(this)) return {x: 0, y: 0};
			var offset = this.getOffsets(), scroll = this.getScrolls();
			var position = {x: offset.x - scroll.x, y: offset.y - scroll.y};
			var relativePosition = (relative && (relative = document.id(relative))) ? relative.getPosition() : {x: 0, y: 0};
			return {x: position.x - relativePosition.x, y: position.y - relativePosition.y};
		},

		getCoordinates: function(element){
			if (isBody(this)) return this.getWindow().getCoordinates();
			var position = this.getPosition(element), size = this.getSize();
			var obj = {left: position.x, top: position.y, width: size.x, height: size.y};
			obj.right = obj.left + obj.width;
			obj.bottom = obj.top + obj.height;
			return obj;
		},

		computePosition: function(obj){
			return {left: obj.x - styleNumber(this, 'margin-left'), top: obj.y - styleNumber(this, 'margin-top')};
		},

		setPosition: function(obj){
			return this.setStyles(this.computePosition(obj));
		}

	});


	Native.implement([Document, Window], {

		getSize: function(){
			if (Browser.Engine.presto || Browser.Engine.webkit) {
				var win = this.getWindow();
				return {x: win.innerWidth, y: win.innerHeight};
			}
			var doc = getCompatElement(this);
			return {x: doc.clientWidth, y: doc.clientHeight};
		},

		getScroll: function(){
			var win = this.getWindow(), doc = getCompatElement(this);
			return {x: win.pageXOffset || doc.scrollLeft, y: win.pageYOffset || doc.scrollTop};
		},

		getScrollSize: function(){
			var doc = getCompatElement(this), min = this.getSize();
			return {x: Math.max(doc.scrollWidth, min.x), y: Math.max(doc.scrollHeight, min.y)};
		},

		getPosition: function(){
			return {x: 0, y: 0};
		},

		getCoordinates: function(){
			var size = this.getSize();
			return {top: 0, left: 0, bottom: size.y, right: size.x, height: size.y, width: size.x};
		}

	});

	// private methods

	var styleString = Element.getComputedStyle;

	function styleNumber(element, style){
		return styleString(element, style).toInt() || 0;
	};

	function borderBox(element){
		return styleString(element, '-moz-box-sizing') == 'border-box';
	};

	function topBorder(element){
		return styleNumber(element, 'border-top-width');
	};

	function leftBorder(element){
		return styleNumber(element, 'border-left-width');
	};

	function isBody(element){
		return (/^(?:body|html)$/i).test(element.tagName);
	};

	function getCompatElement(element){
		var doc = element.getDocument();
		return (!doc.compatMode || doc.compatMode == 'CSS1Compat') ? doc.html : doc.body;
	};

	})();

	//aliases
	Element.alias('setPosition', 'position'); //compatability

	Native.implement([Window, Document, Element], {

		getHeight: function(){
			return this.getSize().y;
		},

		getWidth: function(){
			return this.getSize().x;
		},

		getScrollTop: function(){
			return this.getScroll().y;
		},

		getScrollLeft: function(){
			return this.getScroll().x;
		},

		getScrollHeight: function(){
			return this.getScrollSize().y;
		},

		getScrollWidth: function(){
			return this.getScrollSize().x;
		},

		getTop: function(){
			return this.getPosition().y;
		},

		getLeft: function(){
			return this.getPosition().x;
		}

	});


	/*
	Script: Selectors.js
		Adds advanced CSS Querying capabilities for targeting elements. Also includes pseudoselectors support.

	License:
		MIT-style license.
	*/

	Native.implement([Document, Element], {

		getElements: function(expression, nocash){
			expression = expression.split(',');
			var items, local = {};
			for (var i = 0, l = expression.length; i < l; i++){
				var selector = expression[i], elements = Selectors.Utils.search(this, selector, local);
				if (i != 0 && elements.item) elements = $A(elements);
				items = (i == 0) ? elements : (items.item) ? $A(items).concat(elements) : items.concat(elements);
			}
			return new Elements(items, {ddup: (expression.length > 1), cash: !nocash});
		}

	});

	Element.implement({

		match: function(selector){
			if (!selector || (selector == this)) return true;
			var tagid = Selectors.Utils.parseTagAndID(selector);
			var tag = tagid[0], id = tagid[1];
			if (!Selectors.Filters.byID(this, id) || !Selectors.Filters.byTag(this, tag)) return false;
			var parsed = Selectors.Utils.parseSelector(selector);
			return (parsed) ? Selectors.Utils.filter(this, parsed, {}) : true;
		}

	});

	var Selectors = {Cache: {nth: {}, parsed: {}}};

	Selectors.RegExps = {
		id: (/#([\w-]+)/),
		tag: (/^(\w+|\*)/),
		quick: (/^(\w+|\*)$/),
		splitter: (/\s*([+>~\s])\s*([a-zA-Z#.*:\[])/g),
		combined: (/\.([\w-]+)|\[(\w+)(?:([!*^$~|]?=)(["']?)([^\4]*?)\4)?\]|:([\w-]+)(?:\(["']?(.*?)?["']?\)|$)/g)
	};

	Selectors.Utils = {

		chk: function(item, uniques){
			if (!uniques) return true;
			var uid = $uid(item);
			if (!uniques[uid]) return uniques[uid] = true;
			return false;
		},

		parseNthArgument: function(argument){
			if (Selectors.Cache.nth[argument]) return Selectors.Cache.nth[argument];
			var parsed = argument.match(/^([+-]?\d*)?([a-z]+)?([+-]?\d*)?$/);
			if (!parsed) return false;
			var inta = parseInt(parsed[1], 10);
			var a = (inta || inta === 0) ? inta : 1;
			var special = parsed[2] || false;
			var b = parseInt(parsed[3], 10) || 0;
			if (a != 0){
				b--;
				while (b < 1) b += a;
				while (b >= a) b -= a;
			} else {
				a = b;
				special = 'index';
			}
			switch (special){
				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 - 1), special: 'index'};
			}

			return Selectors.Cache.nth[argument] = parsed;
		},

		parseSelector: function(selector){
			if (Selectors.Cache.parsed[selector]) return Selectors.Cache.parsed[selector];
			var m, parsed = {classes: [], pseudos: [], attributes: []};
			while ((m = Selectors.RegExps.combined.exec(selector))){
				var cn = m[1], an = m[2], ao = m[3], av = m[5], pn = m[6], pa = m[7];
				if (cn){
					parsed.classes.push(cn);
				} else if (pn){
					var parser = Selectors.Pseudo.get(pn);
					if (parser) parsed.pseudos.push({parser: parser, argument: pa});
					else parsed.attributes.push({name: pn, operator: '=', value: pa});
				} else if (an){
					parsed.attributes.push({name: an, operator: ao, value: av});
				}
			}
			if (!parsed.classes.length) delete parsed.classes;
			if (!parsed.attributes.length) delete parsed.attributes;
			if (!parsed.pseudos.length) delete parsed.pseudos;
			if (!parsed.classes && !parsed.attributes && !parsed.pseudos) parsed = null;
			return Selectors.Cache.parsed[selector] = parsed;
		},

		parseTagAndID: function(selector){
			var tag = selector.match(Selectors.RegExps.tag);
			var id = selector.match(Selectors.RegExps.id);
			return [(tag) ? tag[1] : '*', (id) ? id[1] : false];
		},

		filter: function(item, parsed, local){
			var i;
			if (parsed.classes){
				for (i = parsed.classes.length; i--; i){
					var cn = parsed.classes[i];
					if (!Selectors.Filters.byClass(item, cn)) return false;
				}
			}
			if (parsed.attributes){
				for (i = parsed.attributes.length; i--; i){
					var att = parsed.attributes[i];
					if (!Selectors.Filters.byAttribute(item, att.name, att.operator, att.value)) return false;
				}
			}
			if (parsed.pseudos){
				for (i = parsed.pseudos.length; i--; i){
					var psd = parsed.pseudos[i];
					if (!Selectors.Filters.byPseudo(item, psd.parser, psd.argument, local)) return false;
				}
			}
			return true;
		},

		getByTagAndID: function(ctx, tag, id){
			if (id){
				var item = (ctx.getElementById) ? ctx.getElementById(id, true) : Element.getElementById(ctx, id, true);
				return (item && Selectors.Filters.byTag(item, tag)) ? [item] : [];
			} else {
				return ctx.getElementsByTagName(tag);
			}
		},

		search: function(self, expression, local){
			var splitters = [];

			var selectors = expression.trim().replace(Selectors.RegExps.splitter, function(m0, m1, m2){
				splitters.push(m1);
				return ':)' + m2;
			}).split(':)');

			var items, filtered, item;

			for (var i = 0, l = selectors.length; i < l; i++){

				var selector = selectors[i];

				if (i == 0 && Selectors.RegExps.quick.test(selector)){
					items = self.getElementsByTagName(selector);
					continue;
				}

				var splitter = splitters[i - 1];

				var tagid = Selectors.Utils.parseTagAndID(selector);
				var tag = tagid[0], id = tagid[1];

				if (i == 0){
					items = Selectors.Utils.getByTagAndID(self, tag, id);
				} else {
					var uniques = {}, found = [];
					for (var j = 0, k = items.length; j < k; j++) found = Selectors.Getters[splitter](found, items[j], tag, id, uniques);
					items = found;
				}

				var parsed = Selectors.Utils.parseSelector(selector);

				if (parsed){
					filtered = [];
					for (var m = 0, n = items.length; m < n; m++){
						item = items[m];
						if (Selectors.Utils.filter(item, parsed, local)) filtered.push(item);
					}
					items = filtered;
				}

			}

			return items;

		}

	};

	Selectors.Getters = {

		' ': function(found, self, tag, id, uniques){
			var items = Selectors.Utils.getByTagAndID(self, tag, id);
			for (var i = 0, l = items.length; i < l; i++){
				var item = items[i];
				if (Selectors.Utils.chk(item, uniques)) found.push(item);
			}
			return found;
		},

		'>': function(found, self, tag, id, uniques){
			var children = Selectors.Utils.getByTagAndID(self, tag, id);
			for (var i = 0, l = children.length; i < l; i++){
				var child = children[i];
				if (child.parentNode == self && Selectors.Utils.chk(child, uniques)) found.push(child);
			}
			return found;
		},

		'+': function(found, self, tag, id, uniques){
			while ((self = self.nextSibling)){
				if (self.nodeType == 1){
					if (Selectors.Utils.chk(self, uniques) && Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self);
					break;
				}
			}
			return found;
		},

		'~': function(found, self, tag, id, uniques){
			while ((self = self.nextSibling)){
				if (self.nodeType == 1){
					if (!Selectors.Utils.chk(self, uniques)) break;
					if (Selectors.Filters.byTag(self, tag) && Selectors.Filters.byID(self, id)) found.push(self);
				}
			}
			return found;
		}

	};

	Selectors.Filters = {

		byTag: function(self, tag){
			return (tag == '*' || (self.tagName && self.tagName.toLowerCase() == tag));
		},

		byID: function(self, id){
			return (!id || (self.id && self.id == id));
		},

		byClass: function(self, klass){
			return (self.className && self.className.contains(klass, ' '));
		},

		byPseudo: function(self, parser, argument, local){
			return parser.call(self, argument, local);
		},

		byAttribute: function(self, name, operator, value){
			var result = Element.prototype.getProperty.call(self, name);
			if (!result) return (operator == '!=');
			if (!operator || value == undefined) return true;
			switch (operator){
				case '=': return (result == value);
				case '*=': return (result.contains(value));
				case '^=': return (result.substr(0, value.length) == value);
				case '$=': return (result.substr(result.length - value.length) == value);
				case '!=': return (result != value);
				case '~=': return result.contains(value, ' ');
				case '|=': return result.contains(value, '-');
			}
			return false;
		}

	};

	Selectors.Pseudo = new Hash({

		// w3c pseudo selectors

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

		not: function(selector){
			return !Element.match(this, selector);
		},

		contains: function(text){
			return (this.innerText || this.textContent || '').contains(text);
		},

		'first-child': function(){
			return Selectors.Pseudo.index.call(this, 0);
		},

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

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

		'nth-child': function(argument, local){
			argument = (argument == undefined) ? 'n' : argument;
			var parsed = Selectors.Utils.parseNthArgument(argument);
			if (parsed.special != 'n') return Selectors.Pseudo[parsed.special].call(this, parsed.a, local);
			var count = 0;
			local.positions = local.positions || {};
			var uid = $uid(this);
			if (!local.positions[uid]){
				var self = this;
				while ((self = self.previousSibling)){
					if (self.nodeType != 1) continue;
					count ++;
					var position = local.positions[$uid(self)];
					if (position != undefined){
						count = position + count;
						break;
					}
				}
				local.positions[uid] = count;
			}
			return (local.positions[uid] % parsed.a == parsed.b);
		},

		// custom pseudo selectors

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

		even: function(argument, local){
			return Selectors.Pseudo['nth-child'].call(this, '2n+1', local);
		},

		odd: function(argument, local){
			return Selectors.Pseudo['nth-child'].call(this, '2n', local);
		},
		
		selected: function(){
			return this.selected;
		},
		
		enabled: function(){
			return (this.disabled === false);
		}

	});


	/*
	Script: Domready.js
		Contains the domready custom event.

	License:
		MIT-style license.
	*/

	Element.Events.domready = {

		onAdd: function(fn){
			if (Browser.loaded) fn.call(this);
		}

	};

	(function(){

		var domready = function(){
			if (Browser.loaded) return;
			Browser.loaded = true;
			window.fireEvent('domready');
			document.fireEvent('domready');
		};

		if (Browser.Engine.trident){
			var temp = document.createElement('div');
			(function(){
				($try(function(){
					temp.doScroll(); // Technique by Diego Perini
					return document.id(temp).inject(document.body).set('html', 'temp').dispose();
				})) ? domready() : arguments.callee.delay(50);
			})();
		} else if (Browser.Engine.webkit && Browser.Engine.version < 525){
			(function(){
				(['loaded', 'complete'].contains(document.readyState)) ? domready() : arguments.callee.delay(50);
			})();
		} else {
			window.addEvent('load', domready);
			document.addEvent('DOMContentLoaded', domready);
		}

	})();


	/*
	Script: JSON.js
		JSON encoder and decoder.

	License:
		MIT-style license.

	See Also:
		<http://www.json.org/>
	*/

	var JSON = new Hash({

		$specialChars: {'\b': '\\b', '\t': '\\t', '\n': '\\n', '\f': '\\f', '\r': '\\r', '"' : '\\"', '\\': '\\\\'},

		$replaceChars: function(chr){
			return JSON.$specialChars[chr] || '\\u00' + Math.floor(chr.charCodeAt() / 16).toString(16) + (chr.charCodeAt() % 16).toString(16);
		},

		encode: function(obj){
			switch ($type(obj)){
				case 'string':
					return '"' + obj.replace(/[\x00-\x1f\\"]/g, JSON.$replaceChars) + '"';
				case 'array':
					return '[' + String(obj.map(JSON.encode).clean()) + ']';
				case 'object': case 'hash':
					var string = [];
					Hash.each(obj, function(value, key){
						var json = JSON.encode(value);
						if (json) string.push(JSON.encode(key) + ':' + json);
					});
					return '{' + string + '}';
				case 'number': case 'boolean': return String(obj);
				case false: return 'null';
			}
			return null;
		},

		decode: function(string, secure){
			if ($type(string) != 'string' || !string.length) return null;
			if (secure && !(/^[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t]*$/).test(string.replace(/\\./g, '@').replace(/"[^"\\\n\r]*"/g, ''))) return null;
			return eval('(' + string + ')');
		}

	});

	Native.implement([Hash, Array, String, Number], {

		toJSON: function(){
			return JSON.encode(this);
		}

	});


	/*
	Script: Cookie.js
		Class for creating, loading, and saving browser Cookies.

	License:
		MIT-style license.

	Credits:
		Based on the functions by Peter-Paul Koch (http://quirksmode.org).
	*/

	var Cookie = new Class({

		Implements: Options,

		options: {
			path: false,
			domain: false,
			duration: false,
			secure: false,
			document: document
		},

		initialize: function(key, options){
			this.key = key;
			this.setOptions(options);
		},

		write: function(value){
			value = encodeURIComponent(value);
			if (this.options.domain) value += '; domain=' + this.options.domain;
			if (this.options.path) value += '; path=' + this.options.path;
			if (this.options.duration){
				var date = new Date();
				date.setTime(date.getTime() + this.options.duration * 24 * 60 * 60 * 1000);
				value += '; expires=' + date.toGMTString();
			}
			if (this.options.secure) value += '; secure';
			this.options.document.cookie = this.key + '=' + value;
			return this;
		},

		read: function(){
			var value = this.options.document.cookie.match('(?:^|;)\\s*' + this.key.escapeRegExp() + '=([^;]*)');
			return (value) ? decodeURIComponent(value[1]) : null;
		},

		dispose: function(){
			new Cookie(this.key, $merge(this.options, {duration: -1})).write('');
			return this;
		}

	});

	Cookie.write = function(key, value, options){
		return new Cookie(key, options).write(value);
	};

	Cookie.read = function(key){
		return new Cookie(key).read();
	};

	Cookie.dispose = function(key, options){
		return new Cookie(key, options).dispose();
	};


	/*
	Script: Swiff.js
		Wrapper for embedding SWF movies. Supports (and fixes) External Interface Communication.

	License:
		MIT-style license.

	Credits:
		Flash detection & Internet Explorer + Flash Player 9 fix inspired by SWFObject.
	*/

	var Swiff = new Class({

		Implements: [Options],

		options: {
			id: null,
			height: 1,
			width: 1,
			container: null,
			properties: {},
			params: {
				quality: 'high',
				allowScriptAccess: 'always',
				wMode: 'transparent',
				swLiveConnect: true
			},
			callBacks: {},
			vars: {}
		},

		toElement: function(){
			return this.object;
		},

		initialize: function(path, options){
			this.instance = 'Swiff_' + $time();

			this.setOptions(options);
			options = this.options;
			var id = this.id = options.id || this.instance;
			var container = document.id(options.container);

			Swiff.CallBacks[this.instance] = {};

			var params = options.params, vars = options.vars, callBacks = options.callBacks;
			var properties = $extend({height: options.height, width: options.width}, options.properties);

			var self = this;

			for (var callBack in callBacks){
				Swiff.CallBacks[this.instance][callBack] = (function(option){
					return function(){
						return option.apply(self.object, arguments);
					};
				})(callBacks[callBack]);
				vars[callBack] = 'Swiff.CallBacks.' + this.instance + '.' + callBack;
			}

			params.flashVars = Hash.toQueryString(vars);
			if (Browser.Engine.trident){
				properties.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000';
				params.movie = path;
			} else {
				properties.type = 'application/x-shockwave-flash';
				properties.data = path;
			}
			var build = '<object id="' + id + '"';
			for (var property in properties) build += ' ' + property + '="' + properties[property] + '"';
			build += '>';
			for (var param in params){
				if (params[param]) build += '<param name="' + param + '" value="' + params[param] + '" />';
			}
			build += '</object>';
			this.object = ((container) ? container.empty() : new Element('div')).set('html', build).firstChild;
		},

		replaces: function(element){
			element = document.id(element, true);
			element.parentNode.replaceChild(this.toElement(), element);
			return this;
		},

		inject: function(element){
			document.id(element, true).appendChild(this.toElement());
			return this;
		},

		remote: function(){
			return Swiff.remote.apply(Swiff, [this.toElement()].extend(arguments));
		}

	});

	Swiff.CallBacks = {};

	Swiff.remote = function(obj, fn){
		var rs = obj.CallFunction('<invoke name="' + fn + '" returntype="javascript">' + __flash__argumentsToXML(arguments, 2) + '</invoke>');
		return eval(rs);
	};


	/*
	Script: Fx.js
		Contains the basic animation logic to be extended by all other Fx Classes.

	License:
		MIT-style license.
	*/

	var Fx = new Class({

		Implements: [Chain, Events, Options],

		options: {
			/*
			onStart: $empty,
			onCancel: $empty,
			onComplete: $empty,
			*/
			fps: 50,
			unit: false,
			duration: 500,
			link: 'ignore'
		},

		initialize: function(options){
			this.subject = this.subject || this;
			this.setOptions(options);
			this.options.duration = Fx.Durations[this.options.duration] || this.options.duration.toInt();
			var wait = this.options.wait;
			if (wait === false) this.options.link = 'cancel';
		},

		getTransition: function(){
			return function(p){
				return -(Math.cos(Math.PI * p) - 1) / 2;
			};
		},

		step: function(){
			var time = $time();
			if (time < this.time + this.options.duration){
				var delta = this.transition((time - this.time) / this.options.duration);
				this.set(this.compute(this.from, this.to, delta));
			} else {
				this.set(this.compute(this.from, this.to, 1));
				this.complete();
			}
		},

		set: function(now){
			return now;
		},

		compute: function(from, to, delta){
			return Fx.compute(from, to, delta);
		},

		check: function(){
			if (!this.timer) return true;
			switch (this.options.link){
				case 'cancel': this.cancel(); return true;
				case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
			}
			return false;
		},

		start: function(from, to){
			if (!this.check(from, to)) return this;
			this.from = from;
			this.to = to;
			this.time = 0;
			this.transition = this.getTransition();
			this.startTimer();
			this.onStart();
			return this;
		},

		complete: function(){
			if (this.stopTimer()) this.onComplete();
			return this;
		},

		cancel: function(){
			if (this.stopTimer()) this.onCancel();
			return this;
		},

		onStart: function(){
			this.fireEvent('start', this.subject);
		},

		onComplete: function(){
			this.fireEvent('complete', this.subject);
			if (!this.callChain()) this.fireEvent('chainComplete', this.subject);
		},

		onCancel: function(){
			this.fireEvent('cancel', this.subject).clearChain();
		},

		pause: function(){
			this.stopTimer();
			return this;
		},

		resume: function(){
			this.startTimer();
			return this;
		},

		stopTimer: function(){
			if (!this.timer) return false;
			this.time = $time() - this.time;
			this.timer = $clear(this.timer);
			return true;
		},

		startTimer: function(){
			if (this.timer) return false;
			this.time = $time() - this.time;
			this.timer = this.step.periodical(Math.round(1000 / this.options.fps), this);
			return true;
		}

	});

	Fx.compute = function(from, to, delta){
		return (to - from) * delta + from;
	};

	Fx.Durations = {'short': 250, 'normal': 500, 'long': 1000};


	/*
	Script: Fx.CSS.js
		Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph, Fx.Elements.

	License:
		MIT-style license.
	*/

	Fx.CSS = new Class({

		Extends: Fx,

		//prepares the base from/to object

		prepare: function(element, property, values){
			values = $splat(values);
			var values1 = values[1];
			if (!$chk(values1)){
				values[1] = values[0];
				values[0] = element.getStyle(property);
			}
			var parsed = values.map(this.parse);
			return {from: parsed[0], to: parsed[1]};
		},

		//parses a value into an array

		parse: function(value){
			value = $lambda(value)();
			value = (typeof value == 'string') ? value.split(' ') : $splat(value);
			return value.map(function(val){
				val = String(val);
				var found = false;
				Fx.CSS.Parsers.each(function(parser, key){
					if (found) return;
					var parsed = parser.parse(val);
					if ($chk(parsed)) found = {value: parsed, parser: parser};
				});
				found = found || {value: val, parser: Fx.CSS.Parsers.String};
				return found;
			});
		},

		//computes by a from and to prepared objects, using their parsers.

		compute: function(from, to, delta){
			var computed = [];
			(Math.min(from.length, to.length)).times(function(i){
				computed.push({value: from[i].parser.compute(from[i].value, to[i].value, delta), parser: from[i].parser});
			});
			computed.$family = {name: 'fx:css:value'};
			return computed;
		},

		//serves the value as settable

		serve: function(value, unit){
			if ($type(value) != 'fx:css:value') value = this.parse(value);
			var returned = [];
			value.each(function(bit){
				returned = returned.concat(bit.parser.serve(bit.value, unit));
			});
			return returned;
		},

		//renders the change to an element

		render: function(element, property, value, unit){
			element.setStyle(property, this.serve(value, unit));
		},

		//searches inside the page css to find the values for a selector

		search: function(selector){
			if (Fx.CSS.Cache[selector]) return Fx.CSS.Cache[selector];
			var to = {};
			Array.each(document.styleSheets, function(sheet, j){
				var href = sheet.href;
				if (href && href.contains('://') && !href.contains(document.domain)) return;
				var rules = sheet.rules || sheet.cssRules;
				Array.each(rules, function(rule, i){
					if (!rule.style) return;
					var selectorText = (rule.selectorText) ? rule.selectorText.replace(/^\w+/, function(m){
						return m.toLowerCase();
					}) : null;
					if (!selectorText || !selectorText.test('^' + selector + '$')) return;
					Element.Styles.each(function(value, style){
						if (!rule.style[style] || Element.ShortStyles[style]) return;
						value = String(rule.style[style]);
						to[style] = (value.test(/^rgb/)) ? value.rgbToHex() : value;
					});
				});
			});
			return Fx.CSS.Cache[selector] = to;
		}

	});

	Fx.CSS.Cache = {};

	Fx.CSS.Parsers = new Hash({

		Color: {
			parse: function(value){
				if (value.match(/^#[0-9a-f]{3,6}$/i)) return value.hexToRgb(true);
				return ((value = value.match(/(\d+),\s*(\d+),\s*(\d+)/))) ? [value[1], value[2], value[3]] : false;
			},
			compute: function(from, to, delta){
				return from.map(function(value, i){
					return Math.round(Fx.compute(from[i], to[i], delta));
				});
			},
			serve: function(value){
				return value.map(Number);
			}
		},

		Number: {
			parse: parseFloat,
			compute: Fx.compute,
			serve: function(value, unit){
				return (unit) ? value + unit : value;
			}
		},

		String: {
			parse: $lambda(false),
			compute: $arguments(1),
			serve: $arguments(0)
		}

	});


	/*
	Script: Fx.Tween.js
		Formerly Fx.Style, effect to transition any CSS property for an element.

	License:
		MIT-style license.
	*/

	Fx.Tween = new Class({

		Extends: Fx.CSS,

		initialize: function(element, options){
			this.element = this.subject = document.id(element);
			this.parent(options);
		},

		set: function(property, now){
			if (arguments.length == 1){
				now = property;
				property = this.property || this.options.property;
			}
			this.render(this.element, property, now, this.options.unit);
			return this;
		},

		start: function(property, from, to){
			if (!this.check(property, from, to)) return this;
			var args = Array.flatten(arguments);
			this.property = this.options.property || args.shift();
			var parsed = this.prepare(this.element, this.property, args);
			return this.parent(parsed.from, parsed.to);
		}

	});

	Element.Properties.tween = {

		set: function(options){
			var tween = this.retrieve('tween');
			if (tween) tween.cancel();
			return this.eliminate('tween').store('tween:options', $extend({link: 'cancel'}, options));
		},

		get: function(options){
			if (options || !this.retrieve('tween')){
				if (options || !this.retrieve('tween:options')) this.set('tween', options);
				this.store('tween', new Fx.Tween(this, this.retrieve('tween:options')));
			}
			return this.retrieve('tween');
		}

	};

	Element.implement({

		tween: function(property, from, to){
			this.get('tween').start(arguments);
			return this;
		},

		fade: function(how){
			var fade = this.get('tween'), o = 'opacity', toggle;
			how = $pick(how, 'toggle');
			switch (how){
				case 'in': fade.start(o, 1); break;
				case 'out': fade.start(o, 0); break;
				case 'show': fade.set(o, 1); break;
				case 'hide': fade.set(o, 0); break;
				case 'toggle':
					var flag = this.retrieve('fade:flag', this.get('opacity') == 1);
					fade.start(o, (flag) ? 0 : 1);
					this.store('fade:flag', !flag);
					toggle = true;
				break;
				default: fade.start(o, arguments);
			}
			if (!toggle) this.eliminate('fade:flag');
			return this;
		},

		highlight: function(start, end){
			if (!end){
				end = this.retrieve('highlight:original', this.getStyle('background-color'));
				end = (end == 'transparent') ? '#fff' : end;
			}
			var tween = this.get('tween');
			tween.start('background-color', start || '#ffff88', end).chain(function(){
				this.setStyle('background-color', this.retrieve('highlight:original'));
				tween.callChain();
			}.bind(this));
			return this;
		}

	});


	/*
	Script: Fx.Morph.js
		Formerly Fx.Styles, effect to transition any number of CSS properties for an element using an object of rules, or CSS based selector rules.

	License:
		MIT-style license.
	*/

	Fx.Morph = new Class({

		Extends: Fx.CSS,

		initialize: function(element, options){
			this.element = this.subject = document.id(element);
			this.parent(options);
		},

		set: function(now){
			if (typeof now == 'string') now = this.search(now);
			for (var p in now) this.render(this.element, p, now[p], this.options.unit);
			return this;
		},

		compute: function(from, to, delta){
			var now = {};
			for (var p in from) now[p] = this.parent(from[p], to[p], delta);
			return now;
		},

		start: function(properties){
			if (!this.check(properties)) return this;
			if (typeof properties == 'string') properties = this.search(properties);
			var from = {}, to = {};
			for (var p in properties){
				var parsed = this.prepare(this.element, p, properties[p]);
				from[p] = parsed.from;
				to[p] = parsed.to;
			}
			return this.parent(from, to);
		}

	});

	Element.Properties.morph = {

		set: function(options){
			var morph = this.retrieve('morph');
			if (morph) morph.cancel();
			return this.eliminate('morph').store('morph:options', $extend({link: 'cancel'}, options));
		},

		get: function(options){
			if (options || !this.retrieve('morph')){
				if (options || !this.retrieve('morph:options')) this.set('morph', options);
				this.store('morph', new Fx.Morph(this, this.retrieve('morph:options')));
			}
			return this.retrieve('morph');
		}

	};

	Element.implement({

		morph: function(props){
			this.get('morph').start(props);
			return this;
		}

	});


	/*
	Script: Fx.Transitions.js
		Contains a set of advanced transitions to be used with any of the Fx Classes.

	License:
		MIT-style license.

	Credits:
		Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified and optimized to be used with MooTools.
	*/

	Fx.implement({

		getTransition: function(){
			var trans = this.options.transition || Fx.Transitions.Sine.easeInOut;
			if (typeof trans == 'string'){
				var data = trans.split(':');
				trans = Fx.Transitions;
				trans = trans[data[0]] || trans[data[0].capitalize()];
				if (data[1]) trans = trans['ease' + data[1].capitalize() + (data[2] ? data[2].capitalize() : '')];
			}
			return trans;
		}

	});

	Fx.Transition = function(transition, params){
		params = $splat(params);
		return $extend(transition, {
			easeIn: function(pos){
				return transition(pos, params);
			},
			easeOut: function(pos){
				return 1 - transition(1 - pos, params);
			},
			easeInOut: function(pos){
				return (pos <= 0.5) ? transition(2 * pos, params) / 2 : (2 - transition(2 * (1 - pos), params)) / 2;
			}
		});
	};

	Fx.Transitions = new Hash({

		linear: $arguments(0)

	});

	Fx.Transitions.extend = function(transitions){
		for (var transition in transitions) Fx.Transitions[transition] = new Fx.Transition(transitions[transition]);
	};

	Fx.Transitions.extend({

		Pow: function(p, x){
			return Math.pow(p, x[0] || 6);
		},

		Expo: function(p){
			return Math.pow(2, 8 * (p - 1));
		},

		Circ: function(p){
			return 1 - Math.sin(Math.acos(p));
		},

		Sine: function(p){
			return 1 - Math.sin((1 - p) * Math.PI / 2);
		},

		Back: function(p, x){
			x = x[0] || 1.618;
			return Math.pow(p, 2) * ((x + 1) * p - x);
		},

		Bounce: function(p){
			var value;
			for (var a = 0, b = 1; 1; a += b, b /= 2){
				if (p >= (7 - 4 * a) / 11){
					value = b * b - Math.pow((11 - 6 * a - 11 * p) / 4, 2);
					break;
				}
			}
			return value;
		},

		Elastic: function(p, x){
			return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
		}

	});

	['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){
		Fx.Transitions[transition] = new Fx.Transition(function(p){
			return Math.pow(p, [i + 2]);
		});
	});


	/*
	Script: Request.js
		Powerful all purpose Request Class. Uses XMLHTTPRequest.

	License:
		MIT-style license.
	*/

	var Request = new Class({

		Implements: [Chain, Events, Options],

		options: {/*
			onRequest: $empty,
			onComplete: $empty,
			onCancel: $empty,
			onSuccess: $empty,
			onFailure: $empty,
			onException: $empty,*/
			url: '',
			data: '',
			headers: {
				'X-Requested-With': 'XMLHttpRequest',
				'Accept': 'text/javascript, text/html, application/xml, text/xml, */*'
			},
			async: true,
			format: false,
			method: 'post',
			link: 'ignore',
			isSuccess: null,
			emulation: true,
			urlEncoded: true,
			encoding: 'utf-8',
			evalScripts: false,
			evalResponse: false,
			noCache: false
		},

		initialize: function(options){
			this.xhr = new Browser.Request();
			this.setOptions(options);
			this.options.isSuccess = this.options.isSuccess || this.isSuccess;
			this.headers = new Hash(this.options.headers);
		},

		onStateChange: function(){
			if (this.xhr.readyState != 4 || !this.running) return;
			this.running = false;
			this.status = 0;
			$try(function(){
				this.status = this.xhr.status;
			}.bind(this));
			this.xhr.onreadystatechange = $empty;
			if (this.options.isSuccess.call(this, this.status)){
				this.response = {text: this.xhr.responseText, xml: this.xhr.responseXML};
				this.success(this.response.text, this.response.xml);
			} else {
				this.response = {text: null, xml: null};
				this.failure();
			}
		},

		isSuccess: function(){
			return ((this.status >= 200) && (this.status < 300));
		},

		processScripts: function(text){
			if (this.options.evalResponse || (/(ecma|java)script/).test(this.getHeader('Content-type'))) return $exec(text);
			return text.stripScripts(this.options.evalScripts);
		},

		success: function(text, xml){
			this.onSuccess(this.processScripts(text), xml);
		},

		onSuccess: function(){
			this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
		},

		failure: function(){
			this.onFailure();
		},

		onFailure: function(){
			this.fireEvent('complete').fireEvent('failure', this.xhr);
		},

		setHeader: function(name, value){
			this.headers.set(name, value);
			return this;
		},

		getHeader: function(name){
			return $try(function(){
				return this.xhr.getResponseHeader(name);
			}.bind(this));
		},

		check: function(){
			if (!this.running) return true;
			switch (this.options.link){
				case 'cancel': this.cancel(); return true;
				case 'chain': this.chain(this.caller.bind(this, arguments)); return false;
			}
			return false;
		},

		send: function(options){
			if (!this.check(options)) return this;
			this.running = true;

			var type = $type(options);
			if (type == 'string' || type == 'element') options = {data: options};

			var old = this.options;
			options = $extend({data: old.data, url: old.url, method: old.method}, options);
			var data = options.data, url = options.url, method = options.method.toLowerCase();

			switch ($type(data)){
				case 'element': data = document.id(data).toQueryString(); break;
				case 'object': case 'hash': data = Hash.toQueryString(data);
			}

			if (this.options.format){
				var format = 'format=' + this.options.format;
				data = (data) ? format + '&' + data : format;
			}

			if (this.options.emulation && !['get', 'post'].contains(method)){
				var _method = '_method=' + method;
				data = (data) ? _method + '&' + data : _method;
				method = 'post';
			}

			if (this.options.urlEncoded && method == 'post'){
				var encoding = (this.options.encoding) ? '; charset=' + this.options.encoding : '';
				this.headers.set('Content-type', 'application/x-www-form-urlencoded' + encoding);
			}

			if (this.options.noCache){
				var noCache = 'noCache=' + new Date().getTime();
				data = (data) ? noCache + '&' + data : noCache;
			}

			var trimPosition = url.lastIndexOf('/');
			if (trimPosition > -1 && (trimPosition = url.indexOf('#')) > -1) url = url.substr(0, trimPosition);

			if (data && method == 'get'){
				url = url + (url.contains('?') ? '&' : '?') + data;
				data = null;
			}

			this.xhr.open(method.toUpperCase(), url, this.options.async);

			this.xhr.onreadystatechange = this.onStateChange.bind(this);

			this.headers.each(function(value, key){
				try {
					this.xhr.setRequestHeader(key, value);
				} catch (e){
					this.fireEvent('exception', [key, value]);
				}
			}, this);

			this.fireEvent('request');
			this.xhr.send(data);
			if (!this.options.async) this.onStateChange();
			return this;
		},

		cancel: function(){
			if (!this.running) return this;
			this.running = false;
			this.xhr.abort();
			this.xhr.onreadystatechange = $empty;
			this.xhr = new Browser.Request();
			this.fireEvent('cancel');
			return this;
		}

	});

	(function(){

	var methods = {};
	['get', 'post', 'put', 'delete', 'GET', 'POST', 'PUT', 'DELETE'].each(function(method){
		methods[method] = function(){
			var params = Array.link(arguments, {url: String.type, data: $defined});
			return this.send($extend(params, {method: method}));
		};
	});

	Request.implement(methods);

	})();

	Element.Properties.send = {

		set: function(options){
			var send = this.retrieve('send');
			if (send) send.cancel();
			return this.eliminate('send').store('send:options', $extend({
				data: this, link: 'cancel', method: this.get('method') || 'post', url: this.get('action')
			}, options));
		},

		get: function(options){
			if (options || !this.retrieve('send')){
				if (options || !this.retrieve('send:options')) this.set('send', options);
				this.store('send', new Request(this.retrieve('send:options')));
			}
			return this.retrieve('send');
		}

	};

	Element.implement({

		send: function(url){
			var sender = this.get('send');
			sender.send({data: this, url: url || sender.options.url});
			return this;
		}

	});


	/*
	Script: Request.HTML.js
		Extends the basic Request Class with additional methods for interacting with HTML responses.

	License:
		MIT-style license.
	*/

	Request.HTML = new Class({

		Extends: Request,

		options: {
			update: false,
			append: false,
			evalScripts: true,
			filter: false
		},

		processHTML: function(text){
			var match = text.match(/<body[^>]*>([\s\S]*?)<\/body>/i);
			text = (match) ? match[1] : text;

			var container = new Element('div');

			return $try(function(){
				var root = '<root>' + text + '</root>', doc;
				if (Browser.Engine.trident){
					doc = new ActiveXObject('Microsoft.XMLDOM');
					doc.async = false;
					doc.loadXML(root);
				} else {
					doc = new DOMParser().parseFromString(root, 'text/xml');
				}
				root = doc.getElementsByTagName('root')[0];
				if (!root) return null;
				for (var i = 0, k = root.childNodes.length; i < k; i++){
					var child = Element.clone(root.childNodes[i], true, true);
					if (child) container.grab(child);
				}
				return container;
			}) || container.set('html', text);
		},

		success: function(text){
			var options = this.options, response = this.response;

			response.html = text.stripScripts(function(script){
				response.javascript = script;
			});

			var temp = this.processHTML(response.html);

			response.tree = temp.childNodes;
			response.elements = temp.getElements('*');

			if (options.filter) response.tree = response.elements.filter(options.filter);
			if (options.update) document.id(options.update).empty().set('html', response.html);
			else if (options.append) document.id(options.append).adopt(temp.getChildren());
			if (options.evalScripts) $exec(response.javascript);

			this.onSuccess(response.tree, response.elements, response.html, response.javascript);
		}

	});

	Element.Properties.load = {

		set: function(options){
			var load = this.retrieve('load');
			if (load) load.cancel();
			return this.eliminate('load').store('load:options', $extend({data: this, link: 'cancel', update: this, method: 'get'}, options));
		},

		get: function(options){
			if (options || ! this.retrieve('load')){
				if (options || !this.retrieve('load:options')) this.set('load', options);
				this.store('load', new Request.HTML(this.retrieve('load:options')));
			}
			return this.retrieve('load');
		}

	};

	Element.implement({

		load: function(){
			this.get('load').send(Array.link(arguments, {data: Object.type, url: String.type}));
			return this;
		}

	});


	/*
	Script: Request.JSON.js
		Extends the basic Request Class with additional methods for sending and receiving JSON data.

	License:
		MIT-style license.
	*/

	Request.JSON = new Class({

		Extends: Request,

		options: {
			secure: true
		},

		initialize: function(options){
			this.parent(options);
			this.headers.extend({'Accept': 'application/json', 'X-Request': 'JSON'});
		},

		success: function(text){
			this.response.json = JSON.decode(text, this.options.secure);
			this.onSuccess(this.response.json, text);
		}

	});

	//MooTools More, <http://mootools.net/more>. Copyright (c) 2006-2009 Aaron Newton <http://clientcide.com/>, Valerio Proietti <http://mad4milk.net> & the MooTools team <http://mootools.net/developers>, MIT Style License.

	MooTools.More = {
		'version': '1.2.3.1'
	};

	/*
	Script: Class.Refactor.js
		Extends a class onto itself with new property, preserving any items attached to the class's namespace.

		License:
			MIT-style license.

		Authors:
			Aaron Newton
	*/

	Class.refactor = function(original, refactors){

		$each(refactors, function(item, name){
			var origin = original.prototype[name];
			if (origin && (origin = origin._origin) && typeof item == 'function') original.implement(name, function(){
				var old = this.previous;
				this.previous = origin;
				var value = item.apply(this, arguments);
				this.previous = old;
				return value;
			}); else original.implement(name, item);
		});

		return original;

	};

	/*
	Script: Class.Binds.js
		Automagically binds specified methods in a class to the instance of the class.

		License:
			MIT-style license.

		Authors:
			Aaron Newton
	*/

	Class.Mutators.Binds = function(binds){
	    return binds;
	};

	Class.Mutators.initialize = function(initialize){
		return function(){
			$splat(this.Binds).each(function(name){
				var original = this[name];
				if (original) this[name] = original.bind(this);
			}, this);
			return initialize.apply(this, arguments);
		};
	};

	/*
	Script: Element.Measure.js
		Extends the Element native object to include methods useful in measuring dimensions.

		Element.measure / .expose methods by Daniel Steigerwald
		License: MIT-style license.
		Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz

		License:
			MIT-style license.

		Authors:
			Aaron Newton

	*/

	Element.implement({

		measure: function(fn){
			var vis = function(el) {
				return !!(!el || el.offsetHeight || el.offsetWidth);
			};
			if (vis(this)) return fn.apply(this);
			var parent = this.getParent(),
				toMeasure = [], 
				restorers = [];
			while (!vis(parent) && parent != document.body) {
				toMeasure.push(parent.expose());
				parent = parent.getParent();
			}
			var restore = this.expose();
			var result = fn.apply(this);
			restore();
			toMeasure.each(function(restore){
				restore();
			});
			return result;
		},

		expose: function(){
			if (this.getStyle('display') != 'none') return $empty;
			var before = this.style.cssText;
			this.setStyles({
				display: 'block',
				position: 'absolute',
				visibility: 'hidden'
			});
			return function(){
				this.style.cssText = before;
			}.bind(this);
		},

		getDimensions: function(options){
			options = $merge({computeSize: false},options);
			var dim = {};
			var getSize = function(el, options){
				return (options.computeSize)?el.getComputedSize(options):el.getSize();
			};
			if (this.getStyle('display') == 'none'){
				dim = this.measure(function(){
					return getSize(this, options);
				});
			} else {
				try { //safari sometimes crashes here, so catch it
					dim = getSize(this, options);
				}catch(e){}
			}
			return $chk(dim.x) ? $extend(dim, {width: dim.x, height: dim.y}) : $extend(dim, {x: dim.width, y: dim.height});
		},

		getComputedSize: function(options){
			options = $merge({
				styles: ['padding','border'],
				plains: {
					height: ['top','bottom'],
					width: ['left','right']
				},
				mode: 'both'
			}, options);
			var size = {width: 0,height: 0};
			switch (options.mode){
				case 'vertical':
					delete size.width;
					delete options.plains.width;
					break;
				case 'horizontal':
					delete size.height;
					delete options.plains.height;
					break;
			}
			var getStyles = [];
			//this function might be useful in other places; perhaps it should be outside this function?
			$each(options.plains, function(plain, key){
				plain.each(function(edge){
					options.styles.each(function(style){
						getStyles.push((style == 'border') ? style + '-' + edge + '-' + 'width' : style + '-' + edge);
					});
				});
			});
			var styles = {};
			getStyles.each(function(style){ styles[style] = this.getComputedStyle(style); }, this);
			var subtracted = [];
			$each(options.plains, function(plain, key){ //keys: width, height, plains: ['left', 'right'], ['top','bottom']
				var capitalized = key.capitalize();
				size['total' + capitalized] = 0;
				size['computed' + capitalized] = 0;
				plain.each(function(edge){ //top, left, right, bottom
					size['computed' + edge.capitalize()] = 0;
					getStyles.each(function(style, i){ //padding, border, etc.
						//'padding-left'.test('left') size['totalWidth'] = size['width'] + [padding-left]
						if (style.test(edge)){
							styles[style] = styles[style].toInt() || 0; //styles['padding-left'] = 5;
							size['total' + capitalized] = size['total' + capitalized] + styles[style];
							size['computed' + edge.capitalize()] = size['computed' + edge.capitalize()] + styles[style];
						}
						//if width != width (so, padding-left, for instance), then subtract that from the total
						if (style.test(edge) && key != style &&
							(style.test('border') || style.test('padding')) && !subtracted.contains(style)){
							subtracted.push(style);
							size['computed' + capitalized] = size['computed' + capitalized]-styles[style];
						}
					});
				});
			});

			['Width', 'Height'].each(function(value){
				var lower = value.toLowerCase();
				if(!$chk(size[lower])) return;

				size[lower] = size[lower] + this['offset' + value] + size['computed' + value];
				size['total' + value] = size[lower] + size['total' + value];
				delete size['computed' + value];
			}, this);

			return $extend(styles, size);
		}

	});

	/*
	Script: Element.Position.js
		Extends the Element native object to include methods useful positioning elements relative to others.

		License:
			MIT-style license.

		Authors:
			Aaron Newton
	*/

	(function(){

	var original = Element.prototype.position;

	Element.implement({

		position: function(options){
			//call original position if the options are x/y values
			if (options && ($defined(options.x) || $defined(options.y))) return original ? original.apply(this, arguments) : this;
			$each(options||{}, function(v, k){ if (!$defined(v)) delete options[k]; });
			options = $merge({
				relativeTo: document.body,
				position: {
					x: 'center', //left, center, right
					y: 'center' //top, center, bottom
				},
				edge: false,
				offset: {x: 0, y: 0},
				returnPos: false,
				relFixedPosition: false,
				ignoreMargins: false,
				allowNegative: false
			}, options);
			//compute the offset of the parent positioned element if this element is in one
			var parentOffset = {x: 0, y: 0};
			var parentPositioned = false;
			/* dollar around getOffsetParent should not be necessary, but as it does not return
			 * a mootools extended element in IE, an error occurs on the call to expose. See:
			 * http://mootools.lighthouseapp.com/projects/2706/tickets/333-element-getoffsetparent-inconsistency-between-ie-and-other-browsers */
			var offsetParent = this.measure(function(){
				return document.id(this.getOffsetParent());
			});
			if (offsetParent && offsetParent != this.getDocument().body){
				parentOffset = offsetParent.measure(function(){
					return this.getPosition();
				});
				parentPositioned = true;
				options.offset.x = options.offset.x - parentOffset.x;
				options.offset.y = options.offset.y - parentOffset.y;
			}
			//upperRight, bottomRight, centerRight, upperLeft, bottomLeft, centerLeft
			//topRight, topLeft, centerTop, centerBottom, center
			var fixValue = function(option){
				if ($type(option) != 'string') return option;
				option = option.toLowerCase();
				var val = {};
				if (option.test('left')) val.x = 'left';
				else if (option.test('right')) val.x = 'right';
				else val.x = 'center';
				if (option.test('upper') || option.test('top')) val.y = 'top';
				else if (option.test('bottom')) val.y = 'bottom';
				else val.y = 'center';
				return val;
			};
			options.edge = fixValue(options.edge);
			options.position = fixValue(options.position);
			if (!options.edge){
				if (options.position.x == 'center' && options.position.y == 'center') options.edge = {x:'center', y:'center'};
				else options.edge = {x:'left', y:'top'};
			}

			this.setStyle('position', 'absolute');
			var rel = document.id(options.relativeTo) || document.body;
			var calc = rel == document.body ? window.getScroll() : rel.getPosition();
			var top = calc.y;
			var left = calc.x;

			if (Browser.Engine.trident){
				var scrolls = rel.getScrolls();
				top += scrolls.y;
				left += scrolls.x;
			}

			var dim = this.getDimensions({computeSize: true, styles:['padding', 'border','margin']});
			if (options.ignoreMargins){
				options.offset.x = options.offset.x - dim['margin-left'];
				options.offset.y = options.offset.y - dim['margin-top'];
			}
			var pos = {};
			var prefY = options.offset.y;
			var prefX = options.offset.x;
			var winSize = window.getSize();
			switch(options.position.x){
				case 'left':
					pos.x = left + prefX;
					break;
				case 'right':
					pos.x = left + prefX + rel.offsetWidth;
					break;
				default: //center
					pos.x = left + ((rel == document.body ? winSize.x : rel.offsetWidth)/2) + prefX;
					break;
			}
			switch(options.position.y){
				case 'top':
					pos.y = top + prefY;
					break;
				case 'bottom':
					pos.y = top + prefY + rel.offsetHeight;
					break;
				default: //center
					pos.y = top + ((rel == document.body ? winSize.y : rel.offsetHeight)/2) + prefY;
					break;
			}

			if (options.edge){
				var edgeOffset = {};

				switch(options.edge.x){
					case 'left':
						edgeOffset.x = 0;
						break;
					case 'right':
						edgeOffset.x = -dim.x-dim.computedRight-dim.computedLeft;
						break;
					default: //center
						edgeOffset.x = -(dim.x/2);
						break;
				}
				switch(options.edge.y){
					case 'top':
						edgeOffset.y = 0;
						break;
					case 'bottom':
						edgeOffset.y = -dim.y-dim.computedTop-dim.computedBottom;
						break;
					default: //center
						edgeOffset.y = -(dim.y/2);
						break;
				}
				pos.x = pos.x + edgeOffset.x;
				pos.y = pos.y + edgeOffset.y;
			}
			pos = {
				left: ((pos.x >= 0 || parentPositioned || options.allowNegative) ? pos.x : 0).toInt(),
				top: ((pos.y >= 0 || parentPositioned || options.allowNegative) ? pos.y : 0).toInt()
			};
			if (rel.getStyle('position') == 'fixed' || options.relFixedPosition){
				var winScroll = window.getScroll();
				pos.top = pos.top.toInt() + winScroll.y;
				pos.left = pos.left.toInt() + winScroll.x;
			}

			if (options.returnPos) return pos;
			else this.setStyles(pos);
			return this;
		}

	});

	})();

	/*
	Script: Fx.Elements.js
		Effect to change any number of CSS properties of any number of Elements.

		License:
			MIT-style license.

		Authors:
			Valerio Proietti
	*/

	Fx.Elements = new Class({

		Extends: Fx.CSS,

		initialize: function(elements, options){
			this.elements = this.subject = $$(elements);
			this.parent(options);
		},

		compute: function(from, to, delta){
			var now = {};
			for (var i in from){
				var iFrom = from[i], iTo = to[i], iNow = now[i] = {};
				for (var p in iFrom) iNow[p] = this.parent(iFrom[p], iTo[p], delta);
			}
			return now;
		},

		set: function(now){
			for (var i in now){
				var iNow = now[i];
				for (var p in iNow) this.render(this.elements[i], p, iNow[p], this.options.unit);
			}
			return this;
		},

		start: function(obj){
			if (!this.check(obj)) return this;
			var from = {}, to = {};
			for (var i in obj){
				var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {};
				for (var p in iProps){
					var parsed = this.prepare(this.elements[i], p, iProps[p]);
					iFrom[p] = parsed.from;
					iTo[p] = parsed.to;
				}
			}
			return this.parent(from, to);
		}

	});

	/*
	Script: Fx.Accordion.js
		An Fx.Elements extension which allows you to easily create accordion type controls.

		License:
			MIT-style license.

		Authors:
			Valerio Proietti
	*/

	var Accordion = Fx.Accordion = new Class({

		Extends: Fx.Elements,

		options: {/*
			onActive: $empty(toggler, section),
			onBackground: $empty(toggler, section),*/
			display: 0,
			show: false,
			height: true,
			width: false,
			opacity: true,
			fixedHeight: false,
			fixedWidth: false,
			wait: false,
			alwaysHide: false,
			trigger: 'click',
			initialDisplayFx: true
		},

		initialize: function(){
			var params = Array.link(arguments, {'container': Element.type, 'options': Object.type, 'togglers': $defined, 'elements': $defined});
			this.parent(params.elements, params.options);
			this.togglers = $$(params.togglers);
			this.container = document.id(params.container);
			this.previous = -1;
			if (this.options.alwaysHide) this.options.wait = true;
			if ($chk(this.options.show)){
				this.options.display = false;
				this.previous = this.options.show;
			}
			if (this.options.start){
				this.options.display = false;
				this.options.show = false;
			}
			this.effects = {};
			if (this.options.opacity) this.effects.opacity = 'fullOpacity';
			if (this.options.width) this.effects.width = this.options.fixedWidth ? 'fullWidth' : 'offsetWidth';
			if (this.options.height) this.effects.height = this.options.fixedHeight ? 'fullHeight' : 'scrollHeight';
			for (var i = 0, l = this.togglers.length; i < l; i++) this.addSection(this.togglers[i], this.elements[i]);
			this.elements.each(function(el, i){
				if (this.options.show === i){
					this.fireEvent('active', [this.togglers[i], el]);
				} else {
					for (var fx in this.effects) el.setStyle(fx, 0);
				}
			}, this);
			if ($chk(this.options.display)) this.display(this.options.display, this.options.initialDisplayFx);
		},

		addSection: function(toggler, element){
			toggler = document.id(toggler);
			element = document.id(element);
			var test = this.togglers.contains(toggler);
			this.togglers.include(toggler);
			this.elements.include(element);
			var idx = this.togglers.indexOf(toggler);
			toggler.addEvent(this.options.trigger, this.display.bind(this, idx));
			if (this.options.height) element.setStyles({'padding-top': 0, 'border-top': 'none', 'padding-bottom': 0, 'border-bottom': 'none'});
			if (this.options.width) element.setStyles({'padding-left': 0, 'border-left': 'none', 'padding-right': 0, 'border-right': 'none'});
			element.fullOpacity = 1;
			if (this.options.fixedWidth) element.fullWidth = this.options.fixedWidth;
			if (this.options.fixedHeight) element.fullHeight = this.options.fixedHeight;
			element.setStyle('overflow', 'hidden');
			if (!test){
				for (var fx in this.effects) element.setStyle(fx, 0);
			}
			return this;
		},

		display: function(index, useFx){
			useFx = $pick(useFx, true);
			index = ($type(index) == 'element') ? this.elements.indexOf(index) : index;
			if ((this.timer && this.options.wait) || (index === this.previous && !this.options.alwaysHide)) return this;
			this.previous = index;
			var obj = {};
			this.elements.each(function(el, i){
				obj[i] = {};
				var hide = (i != index) || (this.options.alwaysHide && (el.offsetHeight > 0));
				this.fireEvent(hide ? 'background' : 'active', [this.togglers[i], el]);
				for (var fx in this.effects) obj[i][fx] = hide ? 0 : el[this.effects[fx]];
			}, this);
			return useFx ? this.start(obj) : this.set(obj);
		}

	});

	/*
	Script: Fx.Scroll.js
		Effect to smoothly scroll any element, including the window.

		License:
			MIT-style license.

		Authors:
			Valerio Proietti
	*/

	Fx.Scroll = new Class({

		Extends: Fx,

		options: {
			offset: {x: 0, y: 0},
			wheelStops: true
		},

		initialize: function(element, options){
			this.element = this.subject = document.id(element);
			this.parent(options);
			var cancel = this.cancel.bind(this, false);

			if ($type(this.element) != 'element') this.element = document.id(this.element.getDocument().body);

			var stopper = this.element;

			if (this.options.wheelStops){
				this.addEvent('start', function(){
					stopper.addEvent('mousewheel', cancel);
				}, true);
				this.addEvent('complete', function(){
					stopper.removeEvent('mousewheel', cancel);
				}, true);
			}
		},

		set: function(){
			var now = Array.flatten(arguments);
			this.element.scrollTo(now[0], now[1]);
		},

		compute: function(from, to, delta){
			return [0, 1].map(function(i){
				return Fx.compute(from[i], to[i], delta);
			});
		},

		start: function(x, y){
			if (!this.check(x, y)) return this;
			var offsetSize = this.element.getSize(), scrollSize = this.element.getScrollSize();
			var scroll = this.element.getScroll(), values = {x: x, y: y};
			for (var z in values){
				var max = scrollSize[z] - offsetSize[z];
				if ($chk(values[z])) values[z] = ($type(values[z]) == 'number') ? values[z].limit(0, max) : max;
				else values[z] = scroll[z];
				values[z] += this.options.offset[z];
			}
			return this.parent([scroll.x, scroll.y], [values.x, values.y]);
		},

		toTop: function(){
			return this.start(false, 0);
		},

		toLeft: function(){
			return this.start(0, false);
		},

		toRight: function(){
			return this.start('right', false);
		},

		toBottom: function(){
			return this.start(false, 'bottom');
		},

		toElement: function(el){
			var position = document.id(el).getPosition(this.element);
			return this.start(position.x, position.y);
		},

		scrollIntoView: function(el, axes, offset){
			axes = axes ? $splat(axes) : ['x','y'];
			var to = {};
			el = document.id(el);
			var pos = el.getPosition(this.element);
			var size = el.getSize();
			var scroll = this.element.getScroll();
			var containerSize = this.element.getSize();
			var edge = {
				x: pos.x + size.x,
				y: pos.y + size.y
			};
			['x','y'].each(function(axis) {
				if (axes.contains(axis)) {
					if (edge[axis] > scroll[axis] + containerSize[axis]) to[axis] = edge[axis] - containerSize[axis];
					if (pos[axis] < scroll[axis]) to[axis] = pos[axis];
				}
				if (to[axis] == null) to[axis] = scroll[axis];
				if (offset && offset[axis]) to[axis] = to[axis] + offset[axis];
			}, this);
			if (to.x != scroll.x || to.y != scroll.y) this.start(to.x, to.y);
			return this;
		}

	});


	/*
	Script: Fx.Slide.js
		Effect to slide an element in and out of view.

		License:
			MIT-style license.

		Authors:
			Valerio Proietti
	*/

	Fx.Slide = new Class({

		Extends: Fx,

		options: {
			mode: 'vertical'
		},

		initialize: function(element, options){
			this.addEvent('complete', function(){
				this.open = (this.wrapper['offset' + this.layout.capitalize()] != 0);
				if (this.open && Browser.Engine.webkit419) this.element.dispose().inject(this.wrapper);
			}, true);
			this.element = this.subject = document.id(element);
			this.parent(options);
			var wrapper = this.element.retrieve('wrapper');
			this.wrapper = wrapper || new Element('div', {
				styles: $extend(this.element.getStyles('margin', 'position'), {overflow: 'hidden'})
			}).wraps(this.element);
			this.element.store('wrapper', this.wrapper).setStyle('margin', 0);
			this.now = [];
			this.open = true;
		},

		vertical: function(){
			this.margin = 'margin-top';
			this.layout = 'height';
			this.offset = this.element.offsetHeight;
		},

		horizontal: function(){
			this.margin = 'margin-left';
			this.layout = 'width';
			this.offset = this.element.offsetWidth;
		},

		set: function(now){
			this.element.setStyle(this.margin, now[0]);
			this.wrapper.setStyle(this.layout, now[1]);
			return this;
		},

		compute: function(from, to, delta){
			return [0, 1].map(function(i){
				return Fx.compute(from[i], to[i], delta);
			});
		},

		start: function(how, mode){
			if (!this.check(how, mode)) return this;
			this[mode || this.options.mode]();
			var margin = this.element.getStyle(this.margin).toInt();
			var layout = this.wrapper.getStyle(this.layout).toInt();
			var caseIn = [[margin, layout], [0, this.offset]];
			var caseOut = [[margin, layout], [-this.offset, 0]];
			var start;
			switch (how){
				case 'in': start = caseIn; break;
				case 'out': start = caseOut; break;
				case 'toggle': start = (layout == 0) ? caseIn : caseOut;
			}
			return this.parent(start[0], start[1]);
		},

		slideIn: function(mode){
			return this.start('in', mode);
		},

		slideOut: function(mode){
			return this.start('out', mode);
		},

		hide: function(mode){
			this[mode || this.options.mode]();
			this.open = false;
			return this.set([-this.offset, 0]);
		},

		show: function(mode){
			this[mode || this.options.mode]();
			this.open = true;
			return this.set([0, this.offset]);
		},

		toggle: function(mode){
			return this.start('toggle', mode);
		}

	});

	Element.Properties.slide = {

		set: function(options){
			var slide = this.retrieve('slide');
			if (slide) slide.cancel();
			return this.eliminate('slide').store('slide:options', $extend({link: 'cancel'}, options));
		},

		get: function(options){
			if (options || !this.retrieve('slide')){
				if (options || !this.retrieve('slide:options')) this.set('slide', options);
				this.store('slide', new Fx.Slide(this, this.retrieve('slide:options')));
			}
			return this.retrieve('slide');
		}

	};

	Element.implement({

		slide: function(how, mode){
			how = how || 'toggle';
			var slide = this.get('slide'), toggle;
			switch (how){
				case 'hide': slide.hide(mode); break;
				case 'show': slide.show(mode); break;
				case 'toggle':
					var flag = this.retrieve('slide:flag', slide.open);
					slide[flag ? 'slideOut' : 'slideIn'](mode);
					this.store('slide:flag', !flag);
					toggle = true;
				break;
				default: slide.start(how, mode);
			}
			if (!toggle) this.eliminate('slide:flag');
			return this;
		}

	});


	/*
	Script: Fx.SmoothScroll.js
		Class for creating a smooth scrolling effect to all internal links on the page.

		License:
			MIT-style license.

		Authors:
			Valerio Proietti
	*/

	var SmoothScroll = Fx.SmoothScroll = new Class({

		Extends: Fx.Scroll,

		initialize: function(options, context){
			context = context || document;
			this.doc = context.getDocument();
			var win = context.getWindow();
			this.parent(this.doc, options);
			this.links = this.options.links ? $$(this.options.links) : $$(this.doc.links);
			var location = win.location.href.match(/^[^#]*/)[0] + '#';
			this.links.each(function(link){
				if (link.href.indexOf(location) != 0) {return;}
				var anchor = link.href.substr(location.length);
				if (anchor) this.useLink(link, anchor);
			}, this);
			if (!Browser.Engine.webkit419) {
				this.addEvent('complete', function(){
					win.location.hash = this.anchor;
				}, true);
			}
		},

		useLink: function(link, anchor){
			var el;
			link.addEvent('click', function(event){
				if (el !== false && !el) el = document.id(anchor) || this.doc.getElement('a[name=' + anchor + ']');
				if (el) {
					event.preventDefault();
					this.anchor = anchor;
					this.toElement(el);
					link.blur();
				}
			}.bind(this));
		}

	});

	/*
	Script: Fx.Sort.js
		Defines Fx.Sort, a class that reorders lists with a transition.

		License:
			MIT-style license.

		Authors:
			Aaron Newton

	*/

	Fx.Sort = new Class({

		Extends: Fx.Elements,

		options: {
			mode: 'vertical'
		},

		initialize: function(elements, options){
			this.parent(elements, options);
			this.elements.each(function(el){
				if (el.getStyle('position') == 'static') el.setStyle('position', 'relative');
			});
			this.setDefaultOrder();
		},

		setDefaultOrder: function(){
			this.currentOrder = this.elements.map(function(el, index){
				return index;
			});
		},

		sort: function(newOrder){
			if ($type(newOrder) != 'array') return false;
			var top = 0;
			var left = 0;
			var zero = {};
			var vert = this.options.mode == 'vertical';
			var current = this.elements.map(function(el, index){
				var size = el.getComputedSize({styles: ['border', 'padding', 'margin']});
				var val;
				if (vert){
					val = {
						top: top,
						margin: size['margin-top'],
						height: size.totalHeight
					};
					top += val.height - size['margin-top'];
				} else {
					val = {
						left: left,
						margin: size['margin-left'],
						width: size.totalWidth
					};
					left += val.width;
				}
				var plain = vert ? 'top' : 'left';
				zero[index] = {};
				var start = el.getStyle(plain).toInt();
				zero[index][plain] = start || 0;
				return val;
			}, this);
			this.set(zero);
			newOrder = newOrder.map(function(i){ return i.toInt(); });
			if (newOrder.length != this.elements.length){
				this.currentOrder.each(function(index){
					if (!newOrder.contains(index)) newOrder.push(index);
				});
				if (newOrder.length > this.elements.length)
					newOrder.splice(this.elements.length-1, newOrder.length - this.elements.length);
			}
			top = 0;
			left = 0;
			var margin = 0;
			var next = {};
			newOrder.each(function(item, index){
				var newPos = {};
				if (vert){
					newPos.top = top - current[item].top - margin;
					top += current[item].height;
				} else {
					newPos.left = left - current[item].left;
					left += current[item].width;
				}
				margin = margin + current[item].margin;
				next[item]=newPos;
			}, this);
			var mapped = {};
			$A(newOrder).sort().each(function(index){
				mapped[index] = next[index];
			});
			this.start(mapped);
			this.currentOrder = newOrder;
			return this;
		},

		rearrangeDOM: function(newOrder){
			newOrder = newOrder || this.currentOrder;
			var parent = this.elements[0].getParent();
			var rearranged = [];
			this.elements.setStyle('opacity', 0);
			//move each element and store the new default order
			newOrder.each(function(index){
				rearranged.push(this.elements[index].inject(parent).setStyles({
					top: 0,
					left: 0
				}));
			}, this);
			this.elements.setStyle('opacity', 1);
			this.elements = $$(rearranged);
			this.setDefaultOrder();
			return this;
		},

		getDefaultOrder: function(){
			return this.elements.map(function(el, index){
				return index;
			});
		},

		forward: function(){
			return this.sort(this.getDefaultOrder());
		},

		backward: function(){
			return this.sort(this.getDefaultOrder().reverse());
		},

		reverse: function(){
			return this.sort(this.currentOrder.reverse());
		},

		sortByElements: function(elements){
			return this.sort(elements.map(function(el){
				return this.elements.indexOf(el);
			}, this));
		},

		swap: function(one, two){
			if ($type(one) == 'element') one = this.elements.indexOf(one);
			if ($type(two) == 'element') two = this.elements.indexOf(two);
			
			var newOrder = $A(this.currentOrder);
			newOrder[this.currentOrder.indexOf(one)] = two;
			newOrder[this.currentOrder.indexOf(two)] = one;
			this.sort(newOrder);
		}

	});

	/*
		Script: Fx.Reveal.js
			Defines Fx.Reveal, a class that shows and hides elements with a transition.

			License:
				MIT-style license.

			Authors:
				Aaron Newton

	*/

	Fx.Reveal = new Class({

			Extends: Fx.Morph,

			options: {/*	  
				onShow: $empty(thisElement),
				onHide: $empty(thisElement),
				onComplete: $empty(thisElement),
				heightOverride: null,
				widthOverride: null, */
				styles: ['padding', 'border', 'margin'],
				transitionOpacity: !Browser.Engine.trident4,
				mode: 'vertical',
				display: 'block',
				hideInputs: Browser.Engine.trident ? 'select, input, textarea, object, embed' : false
			},

			dissolve: function(){
				try {
					if (!this.hiding && !this.showing){
						if (this.element.getStyle('display') != 'none'){
							this.hiding = true;
							this.showing = false;
							this.hidden = true;
							var startStyles = this.element.getComputedSize({
								styles: this.options.styles,
								mode: this.options.mode
							});
							var setToAuto = (this.element.style.height === ''||this.element.style.height == 'auto');
							this.element.setStyle('display', 'block');
							if (this.options.transitionOpacity) startStyles.opacity = 1;
							var zero = {};
							$each(startStyles, function(style, name){
								zero[name] = [style, 0];
							}, this);
							var overflowBefore = this.element.getStyle('overflow');
							this.element.setStyle('overflow', 'hidden');
							var hideThese = this.options.hideInputs ? this.element.getElements(this.options.hideInputs) : null;
							this.$chain.unshift(function(){
								if (this.hidden){
									this.hiding = false;
									$each(startStyles, function(style, name){
										startStyles[name] = style;
									}, this);
									this.element.setStyles($merge({display: 'none', overflow: overflowBefore}, startStyles));
									if (setToAuto){
										if (['vertical', 'both'].contains(this.options.mode)) this.element.style.height = '';
										if (['width', 'both'].contains(this.options.mode)) this.element.style.width = '';
									}
									if (hideThese) hideThese.setStyle('visibility', 'visible');
								}
								this.fireEvent('hide', this.element);
								this.callChain();
							}.bind(this));
							if (hideThese) hideThese.setStyle('visibility', 'hidden');
							this.start(zero);
						} else {
							this.callChain.delay(10, this);
							this.fireEvent('complete', this.element);
							this.fireEvent('hide', this.element);
						}
					} else if (this.options.link == 'chain'){
						this.chain(this.dissolve.bind(this));
					} else if (this.options.link == 'cancel' && !this.hiding){
						this.cancel();
						this.dissolve();
					}
				} catch(e){
					this.hiding = false;
					this.element.setStyle('display', 'none');
					this.callChain.delay(10, this);
					this.fireEvent('complete', this.element);
					this.fireEvent('hide', this.element);
				}
				return this;
			},

			reveal: function(){
				try {
					if (!this.showing && !this.hiding){
						if (this.element.getStyle('display') == 'none' ||
							 this.element.getStyle('visiblity') == 'hidden' ||
							 this.element.getStyle('opacity') == 0){
							this.showing = true;
							this.hiding = false;
							this.hidden = false;
							var setToAuto, startStyles;
							//toggle display, but hide it
							this.element.measure(function(){
								setToAuto = (this.element.style.height === '' || this.element.style.height == 'auto');
								//create the styles for the opened/visible state
								startStyles = this.element.getComputedSize({
									styles: this.options.styles,
									mode: this.options.mode
								});
							}.bind(this));
							$each(startStyles, function(style, name){
								startStyles[name] = style;
							});
							//if we're overridding height/width
							if ($chk(this.options.heightOverride)) startStyles.height = this.options.heightOverride.toInt();
							if ($chk(this.options.widthOverride)) startStyles.width = this.options.widthOverride.toInt();
							if (this.options.transitionOpacity) {
								this.element.setStyle('opacity', 0);
								startStyles.opacity = 1;
							}
							//create the zero state for the beginning of the transition
							var zero = {
								height: 0,
								display: this.options.display
							};
							$each(startStyles, function(style, name){ zero[name] = 0; });
							var overflowBefore = this.element.getStyle('overflow');
							//set to zero
							this.element.setStyles($merge(zero, {overflow: 'hidden'}));
							//hide inputs
							var hideThese = this.options.hideInputs ? this.element.getElements(this.options.hideInputs) : null;
							if (hideThese) hideThese.setStyle('visibility', 'hidden');
							//start the effect
							this.start(startStyles);
							this.$chain.unshift(function(){
								this.element.setStyle('overflow', overflowBefore);
								if (!this.options.heightOverride && setToAuto){
									if (['vertical', 'both'].contains(this.options.mode)) this.element.style.height = '';
									if (['width', 'both'].contains(this.options.mode)) this.element.style.width = '';
								}
								if (!this.hidden) this.showing = false;
								if (hideThese) hideThese.setStyle('visibility', 'visible');
								this.callChain();
								this.fireEvent('show', this.element);
							}.bind(this));
						} else {
							this.callChain();
							this.fireEvent('complete', this.element);
							this.fireEvent('show', this.element);
						}
					} else if (this.options.link == 'chain'){
						this.chain(this.reveal.bind(this));
					} else if (this.options.link == 'cancel' && !this.showing){
						this.cancel();
						this.reveal();
					}
				} catch(e){
					this.element.setStyles({
						display: this.options.display,
						visiblity: 'visible',
						opacity: 1
					});
					this.showing = false;
					this.callChain.delay(10, this);
					this.fireEvent('complete', this.element);
					this.fireEvent('show', this.element);
				}
				return this;
			},

			toggle: function(){
				if (this.element.getStyle('display') == 'none' ||
					 this.element.getStyle('visiblity') == 'hidden' ||
					 this.element.getStyle('opacity') == 0){
					this.reveal();
				} else {
					this.dissolve();
				}
				return this;
			}

});

Element.Properties.reveal = {

			set: function(options){
				var reveal = this.retrieve('reveal');
				if (reveal) reveal.cancel();
				return this.eliminate('reveal').store('reveal:options', $extend({link: 'cancel'}, options));
			},

			get: function(options){
				if (options || !this.retrieve('reveal')){
					if (options || !this.retrieve('reveal:options')) this.set('reveal', options);
					this.store('reveal', new Fx.Reveal(this, this.retrieve('reveal:options')));
				}
				return this.retrieve('reveal');
			}

};

Element.Properties.dissolve = Element.Properties.reveal;

Element.implement({

			reveal: function(options){
				this.get('reveal', options).reveal();
				return this;
			},

			dissolve: function(options){
				this.get('reveal', options).dissolve();
				return this;
			},

			nix: function(){
				var params = Array.link(arguments, {destroy: Boolean.type, options: Object.type});
				this.get('reveal', params.options).dissolve().chain(function(){
					this[params.destroy ? 'destroy' : 'dispose']();
				}.bind(this));
				return this;
			},

			wink: function(){
				var params = Array.link(arguments, {duration: Number.type, options: Object.type});
				var reveal = this.get('reveal', params.options);
				reveal.reveal().chain(function(){
					(function(){
						reveal.dissolve();
					}).delay(params.duration || 2000);
				});
			}


});
	/*
	Script: Drag.js
		The base Drag Class. Can be used to drag and resize Elements using mouse events.

		License:
			MIT-style license.

		Authors:
			Valerio Proietti
			Tom Occhinno
			Jan Kassens
	*/

	var Drag = new Class({

		Implements: [Events, Options],

		options: {/*
			onBeforeStart: $empty(thisElement),
			onStart: $empty(thisElement, event),
			onSnap: $empty(thisElement)
			onDrag: $empty(thisElement, event),
			onCancel: $empty(thisElement),
			onComplete: $empty(thisElement, event),*/
			snap: 6,
			unit: 'px',
			grid: false,
			style: true,
			limit: false,
			handle: false,
			invert: false,
			preventDefault: false,
			modifiers: {x: 'left', y: 'top'}
		},

		initialize: function(){
			var params = Array.link(arguments, {'options': Object.type, 'element': $defined});
			this.element = document.id(params.element);
			this.document = this.element.getDocument();
			this.setOptions(params.options || {});
			var htype = $type(this.options.handle);
			this.handles = ((htype == 'array' || htype == 'collection') ? $$(this.options.handle) : document.id(this.options.handle)) || this.element;
			this.mouse = {'now': {}, 'pos': {}};
			this.value = {'start': {}, 'now': {}};

			this.selection = (Browser.Engine.trident) ? 'selectstart' : 'mousedown';

			this.bound = {
				start: this.start.bind(this),
				check: this.check.bind(this),
				drag: this.drag.bind(this),
				stop: this.stop.bind(this),
				cancel: this.cancel.bind(this),
				eventStop: $lambda(false)
			};
			this.attach();
		},

		attach: function(){
			this.handles.addEvent('mousedown', this.bound.start);
			return this;
		},

		detach: function(){
			this.handles.removeEvent('mousedown', this.bound.start);
			return this;
		},

		start: function(event){
			if (this.options.preventDefault) event.preventDefault();
			this.mouse.start = event.page;
			this.fireEvent('beforeStart', this.element);
			var limit = this.options.limit;
			this.limit = {x: [], y: []};
			for (var z in this.options.modifiers){
				if (!this.options.modifiers[z]) continue;
				if (this.options.style) this.value.now[z] = this.element.getStyle(this.options.modifiers[z]).toInt();
				else this.value.now[z] = this.element[this.options.modifiers[z]];
				if (this.options.invert) this.value.now[z] *= -1;
				this.mouse.pos[z] = event.page[z] - this.value.now[z];
				if (limit && limit[z]){
					for (var i = 2; i--; i){
						if ($chk(limit[z][i])) this.limit[z][i] = $lambda(limit[z][i])();
					}
				}
			}
			if ($type(this.options.grid) == 'number') this.options.grid = {x: this.options.grid, y: this.options.grid};
			this.document.addEvents({mousemove: this.bound.check, mouseup: this.bound.cancel});
			this.document.addEvent(this.selection, this.bound.eventStop);
		},

		check: function(event){
			if (this.options.preventDefault) event.preventDefault();
			var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
			if (distance > this.options.snap){
				this.cancel();
				this.document.addEvents({
					mousemove: this.bound.drag,
					mouseup: this.bound.stop
				});
				this.fireEvent('start', [this.element, event]).fireEvent('snap', this.element);
			}
		},

		drag: function(event){
			if (this.options.preventDefault) event.preventDefault();
			this.mouse.now = event.page;
			for (var z in this.options.modifiers){
				if (!this.options.modifiers[z]) continue;
				this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
				if (this.options.invert) this.value.now[z] *= -1;
				if (this.options.limit && this.limit[z]){
					if ($chk(this.limit[z][1]) && (this.value.now[z] > this.limit[z][1])){
						this.value.now[z] = this.limit[z][1];
					} else if ($chk(this.limit[z][0]) && (this.value.now[z] < this.limit[z][0])){
						this.value.now[z] = this.limit[z][0];
					}
				}
				if (this.options.grid[z]) this.value.now[z] -= ((this.value.now[z] - (this.limit[z][0]||0)) % this.options.grid[z]);
				if (this.options.style) this.element.setStyle(this.options.modifiers[z], this.value.now[z] + this.options.unit);
				else this.element[this.options.modifiers[z]] = this.value.now[z];
			}
			this.fireEvent('drag', [this.element, event]);
		},

		cancel: function(event){
			this.document.removeEvent('mousemove', this.bound.check);
			this.document.removeEvent('mouseup', this.bound.cancel);
			if (event){
				this.document.removeEvent(this.selection, this.bound.eventStop);
				this.fireEvent('cancel', this.element);
			}
		},

		stop: function(event){
			this.document.removeEvent(this.selection, this.bound.eventStop);
			this.document.removeEvent('mousemove', this.bound.drag);
			this.document.removeEvent('mouseup', this.bound.stop);
			if (event) this.fireEvent('complete', [this.element, event]);
		}

	});

	Element.implement({

		makeResizable: function(options){
			var drag = new Drag(this, $merge({modifiers: {x: 'width', y: 'height'}}, options));
			this.store('resizer', drag);
			return drag.addEvent('drag', function(){
				this.fireEvent('resize', drag);
			}.bind(this));
		}

	});


	/*
	Script: Drag.Move.js
		A Drag extension that provides support for the constraining of draggables to containers and droppables.

		License:
			MIT-style license.

		Authors:
			Valerio Proietti
			Tom Occhinno
			Jan Kassens*/

	Drag.Move = new Class({

		Extends: Drag,

		options: {/*
			onEnter: $empty(thisElement, overed),
			onLeave: $empty(thisElement, overed),
			onDrop: $empty(thisElement, overed, event),*/
			droppables: [],
			container: false,
			precalculate: false,
			includeMargins: true,
			checkDroppables: true
		},

		initialize: function(element, options){
			this.parent(element, options);
			this.droppables = $$(this.options.droppables);
			this.container = document.id(this.options.container);
			if (this.container && $type(this.container) != 'element') this.container = document.id(this.container.getDocument().body);

			var position = this.element.getStyle('position');
			if (position=='static') position = 'absolute';
			if ([this.element.getStyle('left'), this.element.getStyle('top')].contains('auto')) this.element.position(this.element.getPosition(this.element.offsetParent));
			this.element.setStyle('position', position);

			this.addEvent('start', this.checkDroppables, true);

			this.overed = null;
		},

		start: function(event){
			if (this.container){
				var ccoo = this.container.getCoordinates(this.element.getOffsetParent()), cbs = {}, ems = {};

				['top', 'right', 'bottom', 'left'].each(function(pad){
					cbs[pad] = this.container.getStyle('border-' + pad).toInt();
					ems[pad] = this.element.getStyle('margin-' + pad).toInt();
				}, this);

				var width = this.element.offsetWidth + ems.left + ems.right;
				var height = this.element.offsetHeight + ems.top + ems.bottom;

				if (this.options.includeMargins) {
					$each(ems, function(value, key) {
						ems[key] = 0;
					});
				}
				if (this.container == this.element.getOffsetParent()) {
					this.options.limit = {
						x: [0 - ems.left, ccoo.right - cbs.left - cbs.right - width + ems.right],
						y: [0 - ems.top, ccoo.bottom - cbs.top - cbs.bottom - height + ems.bottom]
					};
				} else {
					this.options.limit = {
						x: [ccoo.left + cbs.left - ems.left, ccoo.right - cbs.right - width + ems.right],
						y: [ccoo.top + cbs.top - ems.top, ccoo.bottom - cbs.bottom - height + ems.bottom]
					};
				}

			}
			if (this.options.precalculate){
				this.positions = this.droppables.map(function(el) {
					return el.getCoordinates();
				});
			}
			this.parent(event);
		},

		checkAgainst: function(el, i){
			el = (this.positions) ? this.positions[i] : el.getCoordinates();
			var now = this.mouse.now;
			return (now.x > el.left && now.x < el.right && now.y < el.bottom && now.y > el.top);
		},

		checkDroppables: function(){
			var overed = this.droppables.filter(this.checkAgainst, this).getLast();
			if (this.overed != overed){
				if (this.overed) this.fireEvent('leave', [this.element, this.overed]);
				if (overed) this.fireEvent('enter', [this.element, overed]);
				this.overed = overed;
			}
		},

		drag: function(event){
			this.parent(event);
			if (this.options.checkDroppables && this.droppables.length) this.checkDroppables();
		},

		stop: function(event){
			this.checkDroppables();
			this.fireEvent('drop', [this.element, this.overed, event]);
			this.overed = null;
			return this.parent(event);
		}

	});

	Element.implement({

		makeDraggable: function(options){
			var drag = new Drag.Move(this, options);
			this.store('dragger', drag);
			return drag;
		}

	});


	/*
	Script: Slider.js
		Class for creating horizontal and vertical slider controls.

		License:
			MIT-style license.

		Authors:
			Valerio Proietti
	*/

	var Slider = new Class({

		Implements: [Events, Options],

		Binds: ['clickedElement', 'draggedKnob', 'scrolledElement'],

		options: {/*
			onTick: $empty(intPosition),
			onChange: $empty(intStep),
			onComplete: $empty(strStep),*/
			onTick: function(position){
				if (this.options.snap) position = this.toPosition(this.step);
				this.knob.setStyle(this.property, position);
			},
			snap: false,
			offset: 0,
			range: false,
			wheel: false,
			steps: 100,
			mode: 'horizontal'
		},

		initialize: function(element, knob, options){
			this.setOptions(options);
			this.element = document.id(element);
			this.knob = document.id(knob);
			this.previousChange = this.previousEnd = this.step = -1;
			var offset, limit = {}, modifiers = {'x': false, 'y': false};
			switch (this.options.mode){
				case 'vertical':
					this.axis = 'y';
					this.property = 'top';
					offset = 'offsetHeight';
					break;
				case 'horizontal':
					this.axis = 'x';
					this.property = 'left';
					offset = 'offsetWidth';
			}
			this.half = this.knob[offset] / 2;
			this.full = this.element[offset] - this.knob[offset] + (this.options.offset * 2);
			this.min = $chk(this.options.range[0]) ? this.options.range[0] : 0;
			this.max = $chk(this.options.range[1]) ? this.options.range[1] : this.options.steps;
			this.range = this.max - this.min;
			this.steps = this.options.steps || this.full;
			this.stepSize = Math.abs(this.range) / this.steps;
			this.stepWidth = this.stepSize * this.full / Math.abs(this.range) ;

			this.knob.setStyle('position', 'relative').setStyle(this.property, - this.options.offset);
			modifiers[this.axis] = this.property;
			limit[this.axis] = [- this.options.offset, this.full - this.options.offset];

			this.bound = {
				clickedElement: this.clickedElement.bind(this),
				scrolledElement: this.scrolledElement.bindWithEvent(this),
				draggedKnob: this.draggedKnob.bind(this)
			};

			var dragOptions = {
				snap: 0,
				limit: limit,
				modifiers: modifiers,
				onDrag: this.bound.draggedKnob,
				onStart: this.bound.draggedKnob,
				onBeforeStart: (function(){
					this.isDragging = true;
				}).bind(this),
				onComplete: function(){
					this.isDragging = false;
					this.draggedKnob();
					this.end();
				}.bind(this)
			};
			if (this.options.snap){
				dragOptions.grid = Math.ceil(this.stepWidth);
				dragOptions.limit[this.axis][1] = this.full;
			}

			this.drag = new Drag(this.knob, dragOptions);
			this.attach();
		},

		attach: function(){
			this.element.addEvent('mousedown', this.bound.clickedElement);
			if (this.options.wheel) this.element.addEvent('mousewheel', this.bound.scrolledElement);
			this.drag.attach();
			return this;
		},

		detach: function(){
			this.element.removeEvent('mousedown', this.bound.clickedElement);
			this.element.removeEvent('mousewheel', this.bound.scrolledElement);
			this.drag.detach();
			return this;
		},

		set: function(step){
			if (!((this.range > 0) ^ (step < this.min))) step = this.min;
			if (!((this.range > 0) ^ (step > this.max))) step = this.max;

			this.step = Math.round(step);
			this.checkStep();
			this.fireEvent('tick', this.toPosition(this.step));
			this.end();
			return this;
		},

		clickedElement: function(event){
			if (this.isDragging || event.target == this.knob) return;

			var dir = this.range < 0 ? -1 : 1;
			var position = event.page[this.axis] - this.element.getPosition()[this.axis] - this.half;
			position = position.limit(-this.options.offset, this.full -this.options.offset);

			this.step = Math.round(this.min + dir * this.toStep(position));
			this.checkStep();
			this.fireEvent('tick', position);
			this.end();
		},

		scrolledElement: function(event){
			var mode = (this.options.mode == 'horizontal') ? (event.wheel < 0) : (event.wheel > 0);
			this.set(mode ? this.step - this.stepSize : this.step + this.stepSize);
			event.stop();
		},

		draggedKnob: function(){
			var dir = this.range < 0 ? -1 : 1;
			var position = this.drag.value.now[this.axis];
			position = position.limit(-this.options.offset, this.full -this.options.offset);
			this.step = Math.round(this.min + dir * this.toStep(position));
			this.checkStep();
		},

		checkStep: function(){
			if (this.previousChange != this.step){
				this.previousChange = this.step;
				this.fireEvent('change', this.step);
			}
		},

		end: function(){
			if (this.previousEnd !== this.step){
				this.previousEnd = this.step;
				this.fireEvent('complete', this.step + '');
			}
		},

		toStep: function(position){
			var step = (position + this.options.offset) * this.stepSize / this.full * this.steps;
			return this.options.steps ? Math.round(step -= step % this.stepSize) : step;
		},

		toPosition: function(step){
			return (this.full * Math.abs(this.min - step)) / (this.steps * this.stepSize) - this.options.offset;
		}

	});

	/*
	Script: Sortables.js
		Class for creating a drag and drop sorting interface for lists of items.

		License:
			MIT-style license.

		Authors:
			Tom Occhino
	*/

	var Sortables = new Class({

		Implements: [Events, Options],

		options: {/*
			onSort: $empty(element, clone),
			onStart: $empty(element, clone),
			onComplete: $empty(element),*/
			snap: 4,
			opacity: 1,
			clone: false,
			revert: false,
			handle: false,
			constrain: false
		},

		initialize: function(lists, options){
			this.setOptions(options);
			this.elements = [];
			this.lists = [];
			this.idle = true;

			this.addLists($$(document.id(lists) || lists));
			if (!this.options.clone) this.options.revert = false;
			if (this.options.revert) this.effect = new Fx.Morph(null, $merge({duration: 250, link: 'cancel'}, this.options.revert));
		},

		attach: function(){
			this.addLists(this.lists);
			return this;
		},

		detach: function(){
			this.lists = this.removeLists(this.lists);
			return this;
		},

		addItems: function(){
			Array.flatten(arguments).each(function(element){
				this.elements.push(element);
				var start = element.retrieve('sortables:start', this.start.bindWithEvent(this, element));
				(this.options.handle ? element.getElement(this.options.handle) || element : element).addEvent('mousedown', start);
			}, this);
			return this;
		},

		addLists: function(){
			Array.flatten(arguments).each(function(list){
				this.lists.push(list);
				this.addItems(list.getChildren());
			}, this);
			return this;
		},

		removeItems: function(){
			return $$(Array.flatten(arguments).map(function(element){
				this.elements.erase(element);
				var start = element.retrieve('sortables:start');
				(this.options.handle ? element.getElement(this.options.handle) || element : element).removeEvent('mousedown', start);
				
				return element;
			}, this));
		},

		removeLists: function(){
			return $$(Array.flatten(arguments).map(function(list){
				this.lists.erase(list);
				this.removeItems(list.getChildren());
				
				return list;
			}, this));
		},

		getClone: function(event, element){
			if (!this.options.clone) return new Element('div').inject(document.body);
			if ($type(this.options.clone) == 'function') return this.options.clone.call(this, event, element, this.list);
			return element.clone(true).setStyles({
				margin: '0px',
				position: 'absolute',
				visibility: 'hidden',
				'width': element.getStyle('width')
			}).inject(this.list).position(element.getPosition(element.getOffsetParent()));
		},

		getDroppables: function(){
			var droppables = this.list.getChildren();
			if (!this.options.constrain) droppables = this.lists.concat(droppables).erase(this.list);
			return droppables.erase(this.clone).erase(this.element);
		},

		insert: function(dragging, element){
			var where = 'inside';
			if (this.lists.contains(element)){
				this.list = element;
				this.drag.droppables = this.getDroppables();
			} else {
				where = this.element.getAllPrevious().contains(element) ? 'before' : 'after';
			}
			this.element.inject(element, where);
			this.fireEvent('sort', [this.element, this.clone]);
		},

		start: function(event, element){
			if (!this.idle) return;
			this.idle = false;
			this.element = element;
			this.opacity = element.get('opacity');
			this.list = element.getParent();
			this.clone = this.getClone(event, element);

			this.drag = new Drag.Move(this.clone, {
				snap: this.options.snap,
				container: this.options.constrain && this.element.getParent(),
				droppables: this.getDroppables(),
				onSnap: function(){
					event.stop();
					this.clone.setStyle('visibility', 'visible');
					this.element.set('opacity', this.options.opacity || 0);
					this.fireEvent('start', [this.element, this.clone]);
				}.bind(this),
				onEnter: this.insert.bind(this),
				onCancel: this.reset.bind(this),
				onComplete: this.end.bind(this)
			});

			this.clone.inject(this.element, 'before');
			this.drag.start(event);
		},

		end: function(){
			this.drag.detach();
			this.element.set('opacity', this.opacity);
			if (this.effect){
				var dim = this.element.getStyles('width', 'height');
				var pos = this.clone.computePosition(this.element.getPosition(this.clone.offsetParent));
				this.effect.element = this.clone;
				this.effect.start({
					top: pos.top,
					left: pos.left,
					width: dim.width,
					height: dim.height,
					opacity: 0.25
				}).chain(this.reset.bind(this));
			} else {
				this.reset();
			}
		},

		reset: function(){
			this.idle = true;
			this.clone.destroy();
			this.fireEvent('complete', this.element);
		},

		serialize: function(){
			var params = Array.link(arguments, {modifier: Function.type, index: $defined});
			var serial = this.lists.map(function(list){
				return list.getChildren().map(params.modifier || function(element){
					return element.get('id');
				}, this);
			}, this);

			var index = params.index;
			if (this.lists.length == 1) index = 0;
			return $chk(index) && index >= 0 && index < this.lists.length ? serial[index] : serial;
		}

	});


	/*
	Script: Assets.js
		Provides methods to dynamically load JavaScript, CSS, and Image files into the document.

		License:
			MIT-style license.

		Authors:
			Valerio Proietti
	*/

	var Asset = {

		javascript: function(source, properties){
			properties = $extend({
				onload: $empty,
				document: document,
				check: $lambda(true)
			}, properties);

			var script = new Element('script', {src: source, type: 'text/javascript'});

			var load = properties.onload.bind(script), check = properties.check, doc = properties.document;
			delete properties.onload; delete properties.check; delete properties.document;

			script.addEvents({
				load: load,
				readystatechange: function(){
					if (['loaded', 'complete'].contains(this.readyState)) load();
				}
			}).set(properties);

			if (Browser.Engine.webkit419) var checker = (function(){
				if (!$try(check)) return;
				$clear(checker);
				load();
			}).periodical(50);

			return script.inject(doc.head);
		},

		css: function(source, properties){
			return new Element('link', $merge({
				rel: 'stylesheet', media: 'screen', type: 'text/css', href: source
			}, properties)).inject(document.head);
		},

		image: function(source, properties){
			properties = $merge({
				onload: $empty,
				onabort: $empty,
				onerror: $empty
			}, properties);
			var image = new Image();
			var element = document.id(image) || new Element('img');
			['load', 'abort', 'error'].each(function(name){
				var type = 'on' + name;
				var event = properties[type];
				delete properties[type];
				image[type] = function(){
					if (!image) return;
					if (!element.parentNode){
						element.width = image.width;
						element.height = image.height;
					}
					image = image.onload = image.onabort = image.onerror = null;
					event.delay(1, element, element);
					element.fireEvent(name, element, 1);
				};
			});
			image.src = element.src = source;
			if (image && image.complete) image.onload.delay(1);
			return element.set(properties);
		},

		images: function(sources, options){
			options = $merge({
				onComplete: $empty,
				onProgress: $empty,
				onError: $empty,
				properties: {}
			}, options);
			sources = $splat(sources);
			var images = [];
			var counter = 0;
			return new Elements(sources.map(function(source){
				return Asset.image(source, $extend(options.properties, {
					onload: function(){
						options.onProgress.call(this, counter, sources.indexOf(source));
						counter++;
						if (counter == sources.length) options.onComplete();
					},
					onerror: function(){
						options.onError.call(this, counter, sources.indexOf(source));
						counter++;
						if (counter == sources.length) options.onComplete();
					}
				}));
			}));
		}

	};

	/*
	Script: Color.js
		Class for creating and manipulating colors in JavaScript. Supports HSB -> RGB Conversions and vice versa.

		License:
			MIT-style license.

		Authors:
			Valerio Proietti
	*/

	var Color = new Native({

		initialize: function(color, type){
			if (arguments.length >= 3){
				type = 'rgb'; color = Array.slice(arguments, 0, 3);
			} else if (typeof color == 'string'){
				if (color.match(/rgb/)) color = color.rgbToHex().hexToRgb(true);
				else if (color.match(/hsb/)) color = color.hsbToRgb();
				else color = color.hexToRgb(true);
			}
			type = type || 'rgb';
			switch (type){
				case 'hsb':
					var old = color;
					color = color.hsbToRgb();
					color.hsb = old;
				break;
				case 'hex': color = color.hexToRgb(true); break;
			}
			color.rgb = color.slice(0, 3);
			color.hsb = color.hsb || color.rgbToHsb();
			color.hex = color.rgbToHex();
			return $extend(color, this);
		}

	});

	Color.implement({

		mix: function(){
			var colors = Array.slice(arguments);
			var alpha = ($type(colors.getLast()) == 'number') ? colors.pop() : 50;
			var rgb = this.slice();
			colors.each(function(color){
				color = new Color(color);
				for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha));
			});
			return new Color(rgb, 'rgb');
		},

		invert: function(){
			return new Color(this.map(function(value){
				return 255 - value;
			}));
		},

		setHue: function(value){
			return new Color([value, this.hsb[1], this.hsb[2]], 'hsb');
		},

		setSaturation: function(percent){
			return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb');
		},

		setBrightness: function(percent){
			return new Color([this.hsb[0], this.hsb[1], percent], 'hsb');
		}

	});

	var $RGB = function(r, g, b){
		return new Color([r, g, b], 'rgb');
	};

	var $HSB = function(h, s, b){
		return new Color([h, s, b], 'hsb');
	};

	var $HEX = function(hex){
		return new Color(hex, 'hex');
	};

	Array.implement({

		rgbToHsb: function(){
			var red = this[0], green = this[1], blue = this[2];
			var hue, saturation, brightness;
			var max = Math.max(red, green, blue), min = Math.min(red, green, blue);
			var delta = max - min;
			brightness = max / 255;
			saturation = (max != 0) ? delta / max : 0;
			if (saturation == 0){
				hue = 0;
			} else {
				var rr = (max - red) / delta;
				var gr = (max - green) / delta;
				var br = (max - blue) / delta;
				if (red == max) hue = br - gr;
				else if (green == max) hue = 2 + rr - br;
				else hue = 4 + gr - rr;
				hue /= 6;
				if (hue < 0) hue++;
			}
			return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)];
		},

		hsbToRgb: function(){
			var br = Math.round(this[2] / 100 * 255);
			if (this[1] == 0){
				return [br, br, br];
			} else {
				var hue = this[0] % 360;
				var f = hue % 60;
				var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255);
				var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255);
				var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255);
				switch (Math.floor(hue / 60)){
					case 0: return [br, t, p];
					case 1: return [q, br, p];
					case 2: return [p, br, t];
					case 3: return [p, q, br];
					case 4: return [t, p, br];
					case 5: return [br, p, q];
				}
			}
			return false;
		}

	});

	String.implement({

		rgbToHsb: function(){
			var rgb = this.match(/\d{1,3}/g);
			return (rgb) ? rgb.rgbToHsb() : null;
		},

		hsbToRgb: function(){
			var hsb = this.match(/\d{1,3}/g);
			return (hsb) ? hsb.hsbToRgb() : null;
		}

	});


	/*
	Script: Group.js
		Class for monitoring collections of events

		License:
			MIT-style license.

		Authors:
			Valerio Proietti
	*/

	var Group = new Class({

		initialize: function(){
			this.instances = Array.flatten(arguments);
			this.events = {};
			this.checker = {};
		},

		addEvent: function(type, fn){
			this.checker[type] = this.checker[type] || {};
			this.events[type] = this.events[type] || [];
			if (this.events[type].contains(fn)) return false;
			else this.events[type].push(fn);
			this.instances.each(function(instance, i){
				instance.addEvent(type, this.check.bind(this, [type, instance, i]));
			}, this);
			return this;
		},

		check: function(type, instance, i){
			this.checker[type][i] = true;
			var every = this.instances.every(function(current, j){
				return this.checker[type][j] || false;
			}, this);
			if (!every) return;
			this.checker[type] = {};
			this.events[type].each(function(event){
				event.call(this, this.instances, instance);
			}, this);
		}

	});


	/*
	Script: Hash.Cookie.js
		Class for creating, reading, and deleting Cookies in JSON format.

		License:
			MIT-style license.

		Authors:
			Valerio Proietti
			Aaron Newton
	*/

	Hash.Cookie = new Class({

		Extends: Cookie,

		options: {
			autoSave: true
		},

		initialize: function(name, options){
			this.parent(name, options);
			this.load();
		},

		save: function(){
			var value = JSON.encode(this.hash);
			if (!value || value.length > 4096) return false; //cookie would be truncated!
			if (value == '{}') this.dispose();
			else this.write(value);
			return true;
		},

		load: function(){
			this.hash = new Hash(JSON.decode(this.read(), true));
			return this;
		}

	});

	Hash.each(Hash.prototype, function(method, name){
		if (typeof method == 'function') Hash.Cookie.implement(name, function(){
			var value = method.apply(this.hash, arguments);
			if (this.options.autoSave) this.save();
			return value;
		});
	});

	/*
	Script: Scroller.js
		Class which scrolls the contents of any Element (including the window) when the mouse reaches the Element's boundaries.

		License:
			MIT-style license.

		Authors:
			Valerio Proietti
	*/

	var Scroller = new Class({

		Implements: [Events, Options],

		options: {
			area: 20,
			velocity: 1,
			onChange: function(x, y){
				this.element.scrollTo(x, y);
			},
			fps: 50
		},

		initialize: function(element, options){
			this.setOptions(options);
			this.element = document.id(element);
			this.listener = ($type(this.element) != 'element') ? document.id(this.element.getDocument().body) : this.element;
			this.timer = null;
			this.bound = {
				attach: this.attach.bind(this),
				detach: this.detach.bind(this),
				getCoords: this.getCoords.bind(this)
			};
		},

		start: function(){
			this.listener.addEvents({
				mouseenter: this.bound.attach,
				mouseleave: this.bound.detach
			});
		},

		stop: function(){
			this.listener.removeEvents({
				mouseenter: this.bound.attach,
				mouseleave: this.bound.detach
			});
			this.timer = $clear(this.timer);
		},

		attach: function(){
			this.listener.addEvent('mousemove', this.bound.getCoords);
		},

		detach: function(){
			this.listener.removeEvent('mousemove', this.bound.getCoords);
			this.timer = $clear(this.timer);
		},

		getCoords: function(event){
			this.page = (this.listener.get('tag') == 'body') ? event.client : event.page;
			if (!this.timer) this.timer = this.scroll.periodical(Math.round(1000 / this.options.fps), this);
		},

		scroll: function(){
			var size = this.element.getSize(), 
				scroll = this.element.getScroll(), 
				pos = this.element.getOffsets(), 
				scrollSize = this.element.getScrollSize(), 
				change = {x: 0, y: 0};
			for (var z in this.page){
				if (this.page[z] < (this.options.area + pos[z]) && scroll[z] != 0)
					change[z] = (this.page[z] - this.options.area - pos[z]) * this.options.velocity;
				else if (this.page[z] + this.options.area > (size[z] + pos[z]) && scroll[z] + size[z] != scrollSize[z])
					change[z] = (this.page[z] - size[z] + this.options.area - pos[z]) * this.options.velocity;
			}
			if (change.y || change.x) this.fireEvent('change', [scroll.x + change.x, scroll.y + change.y]);
		}

	});

	/*
	Script: Tips.js
		Class for creating nice tips that follow the mouse cursor when hovering an element.

		License:
			MIT-style license.

		Authors:
			Valerio Proietti
			Christoph Pojer
	*/

	var Tips = new Class({

		Implements: [Events, Options],

		options: {
			onShow: function(tip){
				tip.setStyle('visibility', 'visible');
			},
			onHide: function(tip){
				tip.setStyle('visibility', 'hidden');
			},
			title: 'title',
			text: function(el){
				return el.get('rel') || el.get('href');
			},
			showDelay: 100,
			hideDelay: 100,
			className: null,
			offset: {x: 16, y: 16},
			fixed: false
		},

		initialize: function(){
			var params = Array.link(arguments, {options: Object.type, elements: $defined});
			if (params.options && params.options.offsets) params.options.offset = params.options.offsets;
			this.setOptions(params.options);
			this.container = new Element('div', {'class': 'tip'});
			this.tip = this.getTip();
			
			if (params.elements) this.attach(params.elements);
		},

		getTip: function(){
			return new Element('div', {
				'class': this.options.className,
				styles: {
					visibility: 'hidden',
					display: 'none',
					position: 'absolute',
					top: 0,
					left: 0
				}
			}).adopt(
				new Element('div', {'class': 'tip-top'}),
				this.container,
				new Element('div', {'class': 'tip-bottom'})
			).inject(document.body);
		},

		attach: function(elements){
			var read = function(option, element){
				if (option == null) return '';
				return $type(option) == 'function' ? option(element) : element.get(option);
			};
			$$(elements).each(function(element){
				var title = read(this.options.title, element);
				element.erase('title').store('tip:native', title).retrieve('tip:title', title);
				element.retrieve('tip:text', read(this.options.text, element));
				
				var events = ['enter', 'leave'];
				if (!this.options.fixed) events.push('move');
				
				events.each(function(value){
					element.addEvent('mouse' + value, element.retrieve('tip:' + value, this['element' + value.capitalize()].bindWithEvent(this, element)));
				}, this);
			}, this);
			
			return this;
		},

		detach: function(elements){
			$$(elements).each(function(element){
				['enter', 'leave', 'move'].each(function(value){
					element.removeEvent('mouse' + value, element.retrieve('tip:' + value) || $empty);
				});
				
				element.eliminate('tip:enter').eliminate('tip:leave').eliminate('tip:move');
				
				if ($type(this.options.title) == 'string' && this.options.title == 'title'){
					var original = element.retrieve('tip:native');
					if (original) element.set('title', original);
				}
			}, this);
			
			return this;
		},

		elementEnter: function(event, element){
			$A(this.container.childNodes).each(Element.dispose);
			
			['title', 'text'].each(function(value){
				var content = element.retrieve('tip:' + value);
				if (!content) return;
				
				this[value + 'Element'] = new Element('div', {'class': 'tip-' + value}).inject(this.container);
				this.fill(this[value + 'Element'], content);
			}, this);
			
			this.timer = $clear(this.timer);
			this.timer = this.show.delay(this.options.showDelay, this, element);
			this.tip.setStyle('display', 'block');
			this.position((!this.options.fixed) ? event : {page: element.getPosition()});
		},

		elementLeave: function(event, element){
			$clear(this.timer);
			this.tip.setStyle('display', 'none');
			this.timer = this.hide.delay(this.options.hideDelay, this, element);
		},

		elementMove: function(event){
			this.position(event);
		},

		position: function(event){
			var size = window.getSize(), scroll = window.getScroll(),
				tip = {x: this.tip.offsetWidth, y: this.tip.offsetHeight},
				props = {x: 'left', y: 'top'},
				obj = {};
			
			for (var z in props){
				obj[props[z]] = event.page[z] + this.options.offset[z];
				if ((obj[props[z]] + tip[z] - scroll[z]) > size[z]) obj[props[z]] = event.page[z] - this.options.offset[z] - tip[z];
			}
			
			this.tip.setStyles(obj);
		},

		fill: function(element, contents){
			if(typeof contents == 'string') element.set('html', contents);
			else element.adopt(contents);
		},

		show: function(el){
			this.fireEvent('show', [this.tip, el]);
		},

		hide: function(el){
			this.fireEvent('hide', [this.tip, el]);
		}

	});

	Element.implement({ 
		hide: function () {
			
				var params = Array.link(arguments, {options: Object.type, elt: $defined}),
				 elt = $$(params.elt || this), options = $extend({link: 'chain', transition: 'pow:in', duration: 500}, params.options),
				
				elts = elt.filter(function (e) { return e.getStyle('display') != 'none' });
				
				if(elts.length > 0) {
					
					var fx = new Fx.Elements(elts, options);
					
					var styles = [];
					var o = {};
					
					for(var i = 0; i < elts.length; i++) {

						styles[i] = {};
						['opacity', 'visibility'].each(function (k) { styles[i][k] = elts[i].get(k) });
						o[i] = {'opacity': 0};
					}
						
					fx.start(o).chain(function () { for(var i = 0; i < elts.length; i++) {
					
							styles[i]['display'] = 'none';
							elts[i].setStyles(styles[i]);
						}
					})
				}
				
				//allow chain
				return elt.length == 1 ? elt[0] : elt;
			},
		show: function () {
			
			var params = Array.link(arguments, {options: Object.type, elt: $defined}),
			 elt = $$(params.elt || this), options = $extend({link: 'chain', transition: 'pow:in', duration: 500}, params.options),
			
			 elts = elt.filter(function (e) { return e.getStyle('display') == 'none' });
			
			if(elts.length > 0) {
				
				var o = {};
				for(var i = 0; i < elts.length; i++) o[i] = {'opacity': elts[i].get('opacity')};
				
				new Fx.Elements(elts.setStyles({'opacity': 0, 'display': 'block', 'visibility': 'visible'}), options).start(o);
			}
			
			//allow chain
			return elt.length == 1 ? elt[0]: elt;
		}
		});
/*
Script: URI.js
	Provides methods useful in managing the window location and uris.

	License:
		MIT-style license.

	Authors:
		Sebastian Markbåge, Aaron Newton
*/

var URI = new Class({

	Implements: Options,

	options: {
		/*base: false*/
	},

	regex: /^(?:(\w+):)?(?:\/\/(?:(?:([^:@]*):?([^:@]*))?@)?([^:\/?#]*)(?::(\d*))?)?(\.\.?$|(?:[^?#\/]*\/)*)([^?#]*)(?:\?([^#]*))?(?:#(.*))?/,
	parts: ['scheme', 'user', 'password', 'host', 'port', 'directory', 'file', 'query', 'fragment'],
	schemes: {http: 80, https: 443, ftp: 21, rtsp: 554, mms: 1755, file: 0},

	initialize: function(uri, options){
		this.setOptions(options);
		var base = this.options.base || URI.base;
		if(!uri) uri = base;
		
		if (uri && uri.parsed) this.parsed = $unlink(uri.parsed);
		else this.set('value', uri.href || uri.toString(), base ? new URI(base) : false);
	},

	parse: function(value, base){
		var bits = value.match(this.regex);
		if (!bits) return false;
		bits.shift();
		return this.merge(bits.associate(this.parts), base);
	},

	merge: function(bits, base){
		if ((!bits || !bits.scheme) && (!base || !base.scheme)) return false;
		if (base){
			this.parts.every(function(part){
				if (bits[part]) return false;
				bits[part] = base[part] || '';
				return true;
			});
		}
		bits.port = bits.port || this.schemes[bits.scheme.toLowerCase()];
		bits.directory = bits.directory ? this.parseDirectory(bits.directory, base ? base.directory : '') : '/';
		return bits;
	},

	parseDirectory: function(directory, baseDirectory) {
		directory = (directory.substr(0, 1) == '/' ? '' : (baseDirectory || '/')) + directory;
		if (!directory.test(URI.regs.directoryDot)) return directory;
		var result = [];
		directory.replace(URI.regs.endSlash, '').split('/').each(function(dir){
			if (dir == '..' && result.length > 0) result.pop();
			else if (dir != '.') result.push(dir);
		});
		return result.join('/') + '/';
	},

	combine: function(bits){
		return bits.value || bits.scheme + '://' +
			(bits.user ? bits.user + (bits.password ? ':' + bits.password : '') + '@' : '') +
			(bits.host || '') + (bits.port && bits.port != this.schemes[bits.scheme] ? ':' + bits.port : '') +
			(bits.directory || '/') + (bits.file || '') +
			(bits.query ? '?' + bits.query : '') +
			(bits.fragment ? '#' + bits.fragment : '');
	},

	set: function(part, value, base){
		if (part == 'value'){
			var scheme = value.match(URI.regs.scheme);
			if (scheme) scheme = scheme[1];
			if (scheme && !$defined(this.schemes[scheme.toLowerCase()])) this.parsed = { scheme: scheme, value: value };
			else this.parsed = this.parse(value, (base || this).parsed) || (scheme ? { scheme: scheme, value: value } : { value: value });
		} else if (part == 'data') {
			this.setData(value);
		} else {
			this.parsed[part] = value;
		}
		return this;
	},

	get: function(part, base){
		switch(part){
			case 'value': return this.combine(this.parsed, base ? base.parsed : false);
			case 'data' : return this.getData();
		}
		return this.parsed[part] || null;
	},

	go: function(){
		document.location.href = this.toString();
	},

	toURI: function(){
		return this;
	},

	getData: function(key, part){
		var qs = this.get(part || 'query');
		if (!$chk(qs)) return key ? null : {};
		var obj = qs.parseQueryString();
		return key ? obj[key] : obj;
	},

	setData: function(values, merge, part){
		if ($type(arguments[0]) == 'string'){ 
			values = this.getData(); 
			values[arguments[0]] = arguments[1]; 
		} else if (merge) {
			values = $merge(this.getData(), values);
		}
		return this.set(part || 'query', Hash.toQueryString(values));
	},

	clearData: function(part){
		return this.set(part || 'query', '');
	}

});

['toString', 'valueOf'].each(function(method){
	URI.prototype[method] = function(){
		return this.get('value');
	};
});


URI.regs = {
	endSlash: /\/$/,
	scheme: /^(\w+):/,
	directoryDot: /\.\/|\.$/
};

URI.base = new URI(document.getElements('base[href]', true).getLast(), {base: document.location});

String.implement({

	toURI: function(options){
		return new URI(this, options);
	}

});
/*
Script: String.QueryString.js
	...

	License:
		MIT-style license.

	Authors:
		Sebastian Markbåge, Aaron Newton, Lennart Pilon, Valerio Proietti
*/

String.implement({

	parseQueryString: function(){
		var vars = this.split(/[&;]/), res = {};
		if (vars.length) vars.each(function(val){
			var index = val.indexOf('='),
				keys = index < 0 ? [''] : val.substr(0, index).match(/[^\]\[]+/g),
				value = decodeURIComponent(val.substr(index + 1)),
				obj = res;
			keys.each(function(key, i){
				var current = obj[key];
				if(i < keys.length - 1)
					obj = obj[key] = current || {};
				else if($type(current) == 'array')
					current.push(value);
				else
					obj[key] = $defined(current) ? [current, value] : value;
			});
		});
		return res;
	},

	cleanQueryString: function(method){
		return this.split('&').filter(function(val){
			var index = val.indexOf('='),
			key = index < 0 ? '' : val.substr(0, index),
			value = val.substr(index + 1);
			return method ? method.run([key, value]) : $chk(value);
		}).join('&');
	}

});
