/*
---

name: Core

description: The core of MooTools, contains all the base functions and the Native and Hash implementations. Required by all the other scripts.

license: MIT-style license.

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

authors: 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)

provides: [MooTools, Native, Hash.base, Array.each, $util]

...
 */

var MooTools = {
	'version' : '1.2.5',
	'build' : '008d8f0f2fcc2044e54fdd3635341aaab274e757'
};

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;
};

/*
 * ---
 * 
 * name: Array
 * 
 * description: Contains Array Prototypes like each, contains, and erase.
 * 
 * license: MIT-style license.
 * 
 * requires: [$util, Array.each]
 * 
 * provides: Array
 * 
 * ...
 */

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('');
			}

		});

/*
 * ---
 * 
 * name: String
 * 
 * description: Contains String Prototypes like camelCase, capitalize, test, and
 * toInt.
 * 
 * license: MIT-style license.
 * 
 * requires: Native
 * 
 * provides: String
 * 
 * ...
 */

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] : '';
		});
	}

});

/*
 * ---
 * 
 * name: Function
 * 
 * description: Contains Function Prototypes like create, bind, pass, and delay.
 * 
 * license: MIT-style license.
 * 
 * requires: [Native, $util]
 * 
 * provides: Function
 * 
 * ...
 */

try {
	delete Function.prototype.bind;
} catch (e) {
}

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
		})();
	}

});

/*
 * ---
 * 
 * name: Number
 * 
 * description: Contains Number Prototypes like limit, round, times, and ceil.
 * 
 * license: MIT-style license.
 * 
 * requires: [Native, $util]
 * 
 * provides: Number
 * 
 * ...
 */

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' ]);

/*
 * ---
 * 
 * name: Hash
 * 
 * description: Contains Hash Prototypes. Provides a means for overcoming the
 * JavaScript practical impossibility of extending native Objects.
 * 
 * license: MIT-style license.
 * 
 * requires: Hash.base
 * 
 * provides: Hash
 * 
 * ...
 */

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'
});

/*
 * ---
 * 
 * name: Class
 * 
 * description: Contains the Class Function for easily creating, extending, and
 * implementing reusable Classes.
 * 
 * license: MIT-style license.
 * 
 * requires: [$util, Native, Array, String, Function, Number, Hash]
 * 
 * provides: Class
 * 
 * ...
 */

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);

	}

};

/*
 * ---
 * 
 * name: Class.Extras
 * 
 * description: Contains Utility Classes that can be implemented into your own
 * Classes to ease the execution of many common tasks.
 * 
 * license: MIT-style license.
 * 
 * requires: Class
 * 
 * provides: [Chain, Events, Options, Class.Extras]
 * 
 * ...
 */

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;
	}

});

/*
 * ---
 * 
 * name: Browser
 * 
 * description: The Browser Core. Contains Browser initialization, Window and
 * Document, and the Browser Hash.
 * 
 * license: MIT-style license.
 * 
 * requires: [Native, $util]
 * 
 * provides: [Browser, Window, Document, $exec]
 * 
 * ...
 */

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) ? ((document.querySelectorAll) ? 6
									: 5)
									: 4);
				},

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

				gecko : function() {
					return (!document.getBoxObjectFor && window.mozInnerScreenX == null) ? 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');
	}, function() {
		return new ActiveXObject('Microsoft.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);

/*
 * ---
 * 
 * name: Element
 * 
 * description: 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.
 * 
 * requires: [Window, Document, Array, String, Function, Number, Hash]
 * 
 * provides: [Element, Elements, $, $$, Iframe]
 * 
 * ...
 */

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;
				}
				if (el)
					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));
	}

});

(function() {

	/* <ltIE8> */
	var createElementAcceptsHTML;
	try {
		var x = document.createElement('<input name=x>');
		createElementAcceptsHTML = (x.name == 'x');
	} catch (e) {
	}

	var escapeQuotes = function(html) {
		return ('' + html).replace(/&/g, '&amp;').replace(/"/g, '&quot;');
	};
	/* </ltIE8> */

	Document
			.implement({

				newElement : function(tag, props) {
					if (props && props.checked != null)
						props.defaultChecked = props.checked;
					/* <ltIE8> */// Fix for readonly name and type properties
					// in
					// IE < 8
					if (createElementAcceptsHTML && props) {
						tag = '<' + tag;
						if (props.name)
							tag += ' name="' + escapeQuotes(props.name) + '"';
						if (props.type)
							tag += ' type="' + escapeQuotes(props.type) + '"';
						tag += '>';
						delete props.name;
						delete props.type;
					}
					/* </ltIE8> */
					return this.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 (retain !== true)
			retain = false;
		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;
		}
	};

/*
 * ---
 * 
 * name: Element.Dimensions
 * 
 * description: 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).
 * 
 * requires: Element
 * 
 * provides: Element.Dimensions
 * 
 * ...
 */

(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), htmlScroll = html
								.getScroll(), elemScrolls = this.getScrolls(), elemScroll = this
								.getScroll(), isFixed = (styleString(this,
								'position') == 'fixed');

						return {
							x : bound.left.toInt() + elemScrolls.x
									- elemScroll.x
									+ ((isFixed) ? 0 : htmlScroll.x)
									- html.clientLeft,
							y : bound.top.toInt() + elemScrolls.y
									- elemScroll.y
									+ ((isFixed) ? 0 : htmlScroll.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;
	}

});

/*
 * ---
 * 
 * name: Event
 * 
 * description: Contains the Event Class, to make the event object
 * cross-browser.
 * 
 * license: MIT-style license.
 * 
 * requires: [Window, Document, Hash, Array, Function, String]
 * 
 * provides: Event
 * 
 * ...
 */

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;
	}

});

/*
 * ---
 * 
 * name: Element.Event
 * 
 * description: Contains Element methods for dealing with events. This file also
 * includes mouseenter and mouseleave custom Element Events.
 * 
 * license: MIT-style license.
 * 
 * requires: [Element, Event]
 * 
 * provides: Element.Event
 * 
 * ...
 */

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;
					}

				});

// IE9
try {
	if (typeof HTMLElement != 'undefined')
		HTMLElement.prototype.fireEvent = Element.prototype.fireEvent;
} catch (e) {
}

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'
		}

	});

})();

/*
 * ---
 * 
 * name: Element.Style
 * 
 * description: Contains methods for interacting with the styles of Elements in
 * a fashionable way.
 * 
 * license: MIT-style license.
 * 
 * requires: Element
 * 
 * provides: Element.Style
 * 
 * ...
 */

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(@, @, @)';
});

/*
 * ---
 * 
 * name: Fx
 * 
 * description: Contains the basic animation logic to be extended by all other
 * Fx Classes.
 * 
 * license: MIT-style license.
 * 
 * requires: [Chain, Events, Options]
 * 
 * provides: Fx
 * 
 * ...
 */

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
};

/*
 * ---
 * 
 * name: Fx.CSS
 * 
 * description: Contains the CSS animation logic. Used by Fx.Tween, Fx.Morph,
 * Fx.Elements.
 * 
 * license: MIT-style license.
 * 
 * requires: [Fx, Element.Style]
 * 
 * provides: Fx.CSS
 * 
 * ...
 */

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)
	}

});

/*
 * ---
 * 
 * name: Fx.Morph
 * 
 * description: 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.
 * 
 * requires: Fx.CSS
 * 
 * provides: Fx.Morph
 * 
 * ...
 */

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;
	}

});

/*
 * ---
 * 
 * name: Fx.Transitions
 * 
 * description: 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.
 * 
 * requires: Fx
 * 
 * provides: Fx.Transitions
 * 
 * ...
 */

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 ]);
	});
});

/*
 * ---
 * 
 * name: Fx.Tween
 * 
 * description: Formerly Fx.Style, effect to transition any CSS property for an
 * element.
 * 
 * license: MIT-style license.
 * 
 * requires: Fx.CSS
 * 
 * provides: [Fx.Tween, Element.fade, Element.highlight]
 * 
 * ...
 */

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;
	}

});

/*
 * ---
 * 
 * name: Request
 * 
 * description: Powerful all purpose Request Class. Uses XMLHTTPRequest.
 * 
 * license: MIT-style license.
 * 
 * requires: [Element, Chain, Events, Options, Browser]
 * 
 * provides: Request
 * 
 * ...
 */

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 = String(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;
	}

});

/*
 * ---
 * 
 * name: Request.HTML
 * 
 * description: Extends the basic Request Class with additional methods for
 * interacting with HTML responses.
 * 
 * license: MIT-style license.
 * 
 * requires: [Request, Element]
 * 
 * provides: Request.HTML
 * 
 * ...
 */

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;
	}

});

/*
 * ---
 * 
 * name: JSON
 * 
 * description: JSON encoder and decoder.
 * 
 * license: MIT-style license.
 * 
 * see: <http://www.json.org/>
 * 
 * requires: [Array, String, Number, Function, Hash]
 * 
 * provides: JSON
 * 
 * ...
 */

var JSON = new Hash(this.JSON && {
	stringify : JSON.stringify,
	parse : JSON.parse
}).extend({

	$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 + ')');
	}

});

/*
 * ---
 * 
 * name: Request.JSON
 * 
 * description: Extends the basic Request Class with additional methods for
 * sending and receiving JSON data.
 * 
 * license: MIT-style license.
 * 
 * requires: [Request, JSON]
 * 
 * provides: [Request.JSON]
 * 
 * ...
 */

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);
	}

});

/*
 * ---
 * 
 * name: Cookie
 * 
 * description: Class for creating, reading, and deleting browser Cookies.
 * 
 * license: MIT-style license.
 * 
 * credits: Based on the functions by Peter-Paul Koch (http://quirksmode.org).
 * 
 * requires: Options
 * 
 * provides: Cookie
 * 
 * ...
 */

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();
};

/*
 * ---
 * 
 * name: DomReady
 * 
 * description: Contains the custom event domready.
 * 
 * license: MIT-style license.
 * 
 * requires: Element.Event
 * 
 * provides: DomReady
 * 
 * ...
 */

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');
	};

	window.addEvent('load', 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 {
		document.addEvent('DOMContentLoaded', domready);
	}

})();

/*
 * ---
 * 
 * name: Selectors
 * 
 * description: Adds advanced CSS-style querying capabilities for targeting HTML
 * Elements. Includes pseudo selectors.
 * 
 * license: MIT-style license.
 * 
 * requires: Element
 * 
 * provides: Selectors
 * 
 * ...
 */

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 && 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);
			}

		});

/*
 * ---
 * 
 * name: Swiff
 * 
 * description: Wrapper for embedding SWF movies. Supports External Interface
 * Communication.
 * 
 * license: MIT-style license.
 * 
 * credits: Flash detection & Internet Explorer + Flash Player 9 fix inspired by
 * SWFObject.
 * 
 * requires: [Options, $util]
 * 
 * provides: Swiff
 * 
 * ...
 */

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);
};

// 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.

/*
 * ---
 * 
 * script: More.js
 * 
 * name: More
 * 
 * description: MooTools More
 * 
 * license: MIT-style license
 * 
 * requires: - Core/MooTools
 * 
 * provides: [MooTools.More]
 * 
 * ...
 */

MooTools.More = {
	'version' : '1.2.5.1',
	'build' : '254884f2b83651bf95260eed5c6cceb838e22d8e'
};

/*
 * ---
 * 
 * script: Class.Binds.js
 * 
 * name: Class.Binds
 * 
 * description: Automagically binds specified methods in a class to the instance
 * of the class.
 * 
 * license: MIT-style license
 * 
 * authors: - Aaron Newton
 * 
 * requires: - Core/Class - /MooTools.More
 * 
 * provides: [Class.Binds]
 * 
 * ...
 */

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: Class.Occlude.js
 * 
 * name: Class.Occlude
 * 
 * description: Prevents a class from being applied to a DOM element twice.
 * 
 * license: MIT-style license.
 * 
 * authors: - Aaron Newton
 * 
 * requires: - Core/Class - Core/Element - /MooTools.More
 * 
 * provides: [Class.Occlude]
 * 
 * ...
 */

Class.Occlude = new Class({

	occlude : function(property, element) {
		element = document.id(element || this.element);
		var instance = element.retrieve(property || this.property);
		if (instance && !$defined(this.occluded))
			return this.occluded = instance;

		this.occluded = false;
		element.store(property || this.property, this);
		return this.occluded;
	}

});

/*
 * ---
 * 
 * script: Element.Measure.js
 * 
 * name: Element.Measure
 * 
 * description: Extends the Element native object to include methods useful in
 * measuring dimensions.
 * 
 * credits: "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
 * 
 * requires: - Core/Element.Style - Core/Element.Dimensions - /MooTools.More
 * 
 * provides: [Element.Measure]
 * 
 * ...
 */

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(), restorers = [], toMeasure = [];
		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();
		};
		var parent = this.getParent('body');
		if (parent && this.getStyle('display') == 'none') {
			dim = this.measure(function() {
				return getSize(this, options);
			});
		} else if (parent) {
			try { // safari sometimes crashes here, so catch it
				dim = getSize(this, options);
			} catch (e) {
			}
		} else {
			dim = {
				x : 0,
				y : 0
			};
		}
		return $chk(dim.x) ? $extend(dim, {
			width : dim.x,
			height : dim.y
		}) : $extend(dim, {
			x : dim.width,
			y : dim.height
		});
	},

	getComputedSize : function(options) {
		// legacy support for my stupid spelling error
		if (options && options.plains)
			options.planes = options.plains;

		options = $merge({
			styles : [ 'padding', 'border' ],
			planes : {
				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.planes.width;
			break;
		case 'horizontal':
			delete size.height;
			delete options.planes.height;
			break;
		}
		var getStyles = [];
		// this function might be useful in other places; perhaps it should be
		// outside this function?
		$each(options.planes, function(plane, key) {
			plane.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.planes, function(plane, key) { // keys: width, height,
			// planes: ['left',
			// 'right'],
			// ['top','bottom']
			var capitalized = key.capitalize();
			size['total' + capitalized] = size['computed' + capitalized] = 0;
			plane.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
 * 
 * name: Element.Position
 * 
 * description: Extends the Element native object to include methods useful
 * positioning elements relative to others.
 * 
 * license: MIT-style license
 * 
 * authors: - Aaron Newton
 * 
 * requires: - Core/Element.Dimensions - /Element.Measure
 * 
 * provides: [Element.Position]
 * 
 * ...
 */

(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({
						// minimum: { x: 0, y: 0 },
						// maximum: { x: 0, y: 0},
						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,
						ignoreScroll : false,
						allowNegative : false
					}, options);
					// compute the offset of the parent positioned element if
					// this element is in one
					var parentOffset = {
						x : 0,
						y : 0
					}, 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 = offsetParent != document
								.id(options.relativeTo);
						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, calc = rel == document.body ? window
							.getScroll()
							: rel.getPosition(), top = calc.y, left = calc.x;

					var dim = this.getDimensions({
						computeSize : true,
						styles : [ 'padding', 'border', 'margin' ]
					});
					var pos = {}, prefY = options.offset.y, prefX = options.offset.x, 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.totalWidth / 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.totalHeight / 2);
							break;
						}
						pos.x += edgeOffset.x;
						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()
					};
					var xy = {
						left : 'x',
						top : 'y'
					};
					[ 'minimum', 'maximum' ].each(function(minmax) {
						[ 'left', 'top' ].each(function(lr) {
							var val = options[minmax] ? options[minmax][xy[lr]]
									: null;
							if (val != null
									&& ((minmax == 'minimum') ? pos[lr] < val
											: pos[lr] > val))
								pos[lr] = val;
						});
					});
					if (rel.getStyle('position') == 'fixed'
							|| options.relFixedPosition) {
						var winScroll = window.getScroll();
						pos.top += winScroll.y;
						pos.left += winScroll.x;
					}
					var relScroll = rel.getScroll();
					if (options.ignoreScroll) {
						pos.top -= relScroll.y;
						pos.left -= relScroll.x;
					} else {
						pos.top += relScroll.y;
						pos.left += relScroll.x;
					}
					if (options.ignoreMargins) {
						pos.left += (options.edge.x == 'right' ? dim['margin-right']
								: options.edge.x == 'center' ? -dim['margin-left']
										+ ((dim['margin-right'] + dim['margin-left']) / 2)
										: -dim['margin-left']);
						pos.top += (options.edge.y == 'bottom' ? dim['margin-bottom']
								: options.edge.y == 'center' ? -dim['margin-top']
										+ ((dim['margin-bottom'] + dim['margin-top']) / 2)
										: -dim['margin-top']);
					}
					pos.left = Math.ceil(pos.left);
					pos.top = Math.ceil(pos.top);
					if (options.returnPos)
						return pos;
					else
						this.setStyles(pos);
					return this;
				}

			});

})();

/*
 * ---
 * 
 * script: Element.Shortcuts.js
 * 
 * name: Element.Shortcuts
 * 
 * description: Extends the Element native object to include some shortcut
 * methods.
 * 
 * license: MIT-style license
 * 
 * authors: - Aaron Newton
 * 
 * requires: - Core/Element.Style - /MooTools.More
 * 
 * provides: [Element.Shortcuts]
 * 
 * ...
 */

Element.implement({

	isDisplayed : function() {
		return this.getStyle('display') != 'none';
	},

	isVisible : function() {
		var w = this.offsetWidth, h = this.offsetHeight;
		return (w == 0 && h == 0) ? false : (w > 0 && h > 0) ? true
				: this.style.display != 'none';
	},

	toggle : function() {
		return this[this.isDisplayed() ? 'hide' : 'show']();
	},

	hide : function() {
		var d;
		try {
			// IE fails here if the element is not in the dom
			d = this.getStyle('display');
		} catch (e) {
		}
		if (d == "none")
			return this;
		return this.store('element:_originalDisplay', d || '').setStyle(
				'display', 'none');
	},

	show : function(display) {
		if (!display && this.isDisplayed())
			return this;
		display = display || this.retrieve('element:_originalDisplay')
				|| 'block';
		return this
				.setStyle('display', (display == 'none') ? 'block' : display);
	},

	swapClass : function(remove, add) {
		return this.removeClass(remove).addClass(add);
	}
});

Document.implement({
	clearSelection : function() {
		if (document.selection && document.selection.empty) {
			document.selection.empty();
		} else if (window.getSelection) {
			var selection = window.getSelection();
			if (selection && selection.removeAllRanges)
				selection.removeAllRanges();
		}
	}
});

/*
 * ---
 * 
 * script: OverText.js
 * 
 * name: OverText
 * 
 * description: Shows text over an input that disappears when the user clicks
 * into it. The text remains hidden if the user adds a value.
 * 
 * license: MIT-style license
 * 
 * authors: - Aaron Newton
 * 
 * requires: - Core/Options - Core/Events - Core/Element.Event - /Class.Binds -
 * /Class.Occlude - /Element.Position - /Element.Shortcuts
 * 
 * provides: [OverText]
 * 
 * ...
 */

var OverText = new Class(
		{

			Implements : [ Options, Events, Class.Occlude ],

			Binds : [ 'reposition', 'assert', 'focus', 'hide' ],

			options : {/*
						 * textOverride: null, onFocus: $empty() onTextHide:
						 * $empty(textEl, inputEl), onTextShow: $empty(textEl,
						 * inputEl),
						 */
				element : 'label',
				positionOptions : {
					position : 'upperLeft',
					edge : 'upperLeft',
					offset : {
						x : 4,
						y : 2
					}
				},
				poll : false,
				pollInterval : 250,
				wrap : false
			},

			property : 'OverText',

			initialize : function(element, options) {
				this.element = document.id(element);
				if (this.occlude())
					return this.occluded;
				this.setOptions(options);
				this.attach(this.element);
				OverText.instances.push(this);
				if (this.options.poll)
					this.poll();
				return this;
			},

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

			attach : function() {
				var val = this.options.textOverride || this.element.get('alt')
						|| this.element.get('title');
				if (!val)
					return;
				this.text = new Element(this.options.element, {
					'class' : 'overTxtLabel',
					styles : {
						lineHeight : 'normal',
						position : 'absolute',
						cursor : 'text'
					},
					html : val,
					events : {
						click : this.hide.pass(this.options.element == 'label',
								this)
					}
				}).inject(this.element, 'after');
				if (this.options.element == 'label') {
					if (!this.element.get('id'))
						this.element.set('id', 'input_' + new Date().getTime());
					this.text.set('for', this.element.get('id'));
				}

				if (this.options.wrap) {
					this.textHolder = new Element('div', {
						styles : {
							lineHeight : 'normal',
							position : 'relative'
						},
						'class' : 'overTxtWrapper'
					}).adopt(this.text).inject(this.element, 'before');
				}

				return this.enable();
			},

			destroy : function() {
				this.element.eliminate('OverTextDiv').eliminate('OverText');
				this.disable();
				if (this.text)
					this.text.destroy();
				if (this.textHolder)
					this.textHolder.destroy();
				return this;
			},

			disable : function() {
				this.element.removeEvents({
					focus : this.focus,
					blur : this.assert,
					change : this.assert
				});
				window.removeEvent('resize', this.reposition);
				this.hide(true, true);
				return this;
			},

			enable : function() {
				this.element.addEvents({
					focus : this.focus,
					blur : this.assert,
					change : this.assert
				});
				window.addEvent('resize', this.reposition);
				this.assert(true);
				this.reposition();
				return this;
			},

			wrap : function() {
				if (this.options.element == 'label') {
					if (!this.element.get('id'))
						this.element.set('id', 'input_' + new Date().getTime());
					this.text.set('for', this.element.get('id'));
				}
			},

			startPolling : function() {
				this.pollingPaused = false;
				return this.poll();
			},

			poll : function(stop) {
				// start immediately
				// pause on focus
				// resumeon blur
				if (this.poller && !stop)
					return this;
				var test = function() {
					if (!this.pollingPaused)
						this.assert(true);
				}.bind(this);
				if (stop)
					$clear(this.poller);
				else
					this.poller = test.periodical(this.options.pollInterval,
							this);
				return this;
			},

			stopPolling : function() {
				this.pollingPaused = true;
				return this.poll(true);
			},

			focus : function() {
				if (this.text
						&& (!this.text.isDisplayed() || this.element
								.get('disabled')))
					return;
				this.hide();
			},

			hide : function(suppressFocus, force) {
				if (this.text
						&& (this.text.isDisplayed() && (!this.element
								.get('disabled') || force))) {
					this.text.hide();
					this.fireEvent('textHide', [ this.text, this.element ]);
					this.pollingPaused = true;
					if (!suppressFocus) {
						try {
							this.element.fireEvent('focus');
							this.element.focus();
						} catch (e) {
						} // IE barfs if you call focus on hidden elements
					}
				}
				return this;
			},

			show : function() {
				if (this.text && !this.text.isDisplayed()) {
					this.text.show();
					this.reposition();
					this.fireEvent('textShow', [ this.text, this.element ]);
					this.pollingPaused = false;
				}
				return this;
			},

			assert : function(suppressFocus) {
				this[this.test() ? 'show' : 'hide'](suppressFocus);
			},

			test : function() {
				var v = this.element.get('value');
				return !v;
			},

			reposition : function() {
				this.assert(true);
				if (!this.element.isVisible())
					return this.stopPolling().hide();
				if (this.text && this.test())
					this.text.position($merge(this.options.positionOptions, {
						relativeTo : this.element
					}));
				return this;
			}

		});

OverText.instances = [];

$extend(OverText, {

	each : function(fn) {
		return OverText.instances.map(function(ot, i) {
			if (ot.element && ot.text)
				return fn.apply(OverText, [ ot, i ]);
			return null; // the input or the text was destroyed
		});
	},

	update : function() {

		return OverText.each(function(ot) {
			return ot.reposition();
		});

	},

	hideAll : function() {

		return OverText.each(function(ot) {
			return ot.hide(true, true);
		});

	},

	showAll : function() {
		return OverText.each(function(ot) {
			return ot.show();
		});
	}

});

if (window.Fx && Fx.Reveal) {
	Fx.Reveal
			.implement({
				hideInputs : Browser.Engine.trident ? 'select, input, textarea, object, embed, .overTxtLabel'
						: false
			});
}

/*
 * ---
 * 
 * script: Fx.Elements.js
 * 
 * name: Fx.Elements
 * 
 * description: Effect to change any number of CSS properties of any number of
 * Elements.
 * 
 * license: MIT-style license
 * 
 * authors: - Valerio Proietti
 * 
 * requires: - Core/Fx.CSS - /MooTools.More
 * 
 * provides: [Fx.Elements]
 * 
 * ...
 */

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) {
			if (!this.elements[i])
				continue;

			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) {
			if (!this.elements[i])
				continue;

			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
 * 
 * name: Fx.Accordion
 * 
 * description: An Fx.Elements extension which allows you to easily create
 * accordion type controls.
 * 
 * license: MIT-style license
 * 
 * authors: - Valerio Proietti
 * 
 * requires: - Core/Element.Event - /Fx.Elements
 * 
 * provides: [Fx.Accordion]
 * 
 * ...
 */

Fx.Accordion = new Class(
		{

			Extends : Fx.Elements,

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

			initialize : function() {
				var params = Array.link(arguments, {
					'container' : Element.type, // deprecated
					'options' : Object.type,
					'togglers' : $defined,
					'elements' : $defined
				});
				this.parent(params.elements, params.options);
				this.togglers = $$(params.togglers);
				this.previous = -1;
				this.internalChain = new Chain();
				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.options.initialDisplayFx === false)
					this.display(this.options.display,
							this.options.initialDisplayFx);
				if (this.options.fixedHeight !== false)
					this.options.returnHeightToAuto = false;
				this.addEvent('complete', this.internalChain.callChain
						.bind(this.internalChain));
			},

			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);
				var displayer = this.display.bind(this, idx);
				toggler.store('accordion:display', displayer);
				toggler.addEvent(this.options.trigger, displayer);
				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;
			},

			removeSection : function(toggler, displayIndex) {
				var idx = this.togglers.indexOf(toggler);
				var element = this.elements[idx];
				var remover = function() {
					this.togglers.erase(toggler);
					this.elements.erase(element);
					this.detach(toggler);
				}.bind(this);
				if (this.now == idx || displayIndex != undefined)
					this.display(
							$pick(displayIndex, idx - 1 >= 0 ? idx - 1 : 0))
							.chain(remover);
				else
					remover();
				return this;
			},

			detach : function(toggler) {
				var remove = function(toggler) {
					toggler.removeEvent(this.options.trigger, toggler
							.retrieve('accordion:display'));
				}.bind(this);
				if (!toggler)
					this.togglers.each(remove);
				else
					remove(toggler);
				return this;
			},

			display : function(index, useFx) {
				if (!this.check(index, useFx))
					return this;
				useFx = $pick(useFx, true);
				index = ($type(index) == 'element') ? this.elements
						.indexOf(index) : index;
				if (index == this.previous && !this.options.alwaysHide)
					return this;
				if (this.options.returnHeightToAuto) {
					var prev = this.elements[this.previous];
					if (prev && !this.selfHidden) {
						for ( var fx in this.effects) {
							prev.setStyle(fx, prev[this.effects[fx]]);
						}
					}
				}
				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;
									if (i != index) {
										hide = true;
									} else if (this.options.alwaysHide
											&& ((el.offsetHeight > 0 && this.options.height) || el.offsetWidth > 0
													&& this.options.width)) {
										hide = true;
										this.selfHidden = true;
									}
									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);
				this.internalChain.clearChain();
				this.internalChain.chain(function() {
					if (this.options.returnHeightToAuto && !this.selfHidden) {
						var el = this.elements[index];
						if (el)
							el.setStyle('height', 'auto');
					}
					;
				}.bind(this));
				return useFx ? this.start(obj) : this.set(obj);
			}

		});

/*
 * Compatibility with 1.2.0
 */
var Accordion = new Class({

	Extends : Fx.Accordion,

	initialize : function() {
		this.parent.apply(this, arguments);
		var params = Array.link(arguments, {
			'container' : Element.type
		});
		this.container = params.container;
	},

	addSection : function(toggler, element, pos) {
		toggler = document.id(toggler);
		element = document.id(element);
		var test = this.togglers.contains(toggler);
		var len = this.togglers.length;
		if (len && (!test || pos)) {
			pos = $pick(pos, len - 1);
			toggler.inject(this.togglers[pos], 'before');
			element.inject(toggler, 'after');
		} else if (this.container && !test) {
			toggler.inject(this.container);
			element.inject(this.container);
		}
		return this.parent.apply(this, arguments);
	}

});

/*
 * ---
 * 
 * script: Fx.Reveal.js
 * 
 * name: Fx.Reveal
 * 
 * description: Defines Fx.Reveal, a class that shows and hides elements with a
 * transition.
 * 
 * license: MIT-style license
 * 
 * authors: - Aaron Newton
 * 
 * requires: - Core/Fx.Morph - /Element.Shortcuts - /Element.Measure
 * 
 * provides: [Fx.Reveal]
 * 
 * ...
 */

Fx.Reveal = new Class(
		{

			Extends : Fx.Morph,

			options : {/*
						 * onShow: $empty(thisElement), onHide:
						 * $empty(thisElement), onComplete: $empty(thisElement),
						 * heightOverride: null, widthOverride: null,
						 */
				link : 'cancel',
				styles : [ 'padding', 'border', 'margin' ],
				transitionOpacity : !Browser.Engine.trident4,
				mode : 'vertical',
				display : function() {
					return this.element.get('tag') != 'tr' ? 'block'
							: 'table-row';
				},
				hideInputs : Browser.Engine.trident ? 'select, input, textarea, object, embed'
						: false,
				opacity : 1
			},

			dissolve : function() {
				try {
					if (!this.hiding && !this.showing) {
						if (this.element.getStyle('display') != 'none') {
							this.hiding = true;
							this.showing = false;
							this.hidden = true;
							this.cssText = this.element.style.cssText;
							var startStyles = this.element.getComputedSize({
								styles : this.options.styles,
								mode : this.options.mode
							});
							this.element.setStyle('display', $lambda(
									this.options.display).apply(this));
							if (this.options.transitionOpacity)
								startStyles.opacity = this.options.opacity;
							var zero = {};
							$each(startStyles, function(style, name) {
								zero[name] = [ style, 0 ];
							}, this);
							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.style.cssText = this.cssText;
									this.element.setStyle('display', 'none');
									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.showing = true;
							this.hiding = this.hidden = false;
							var startStyles;
							this.cssText = this.element.style.cssText;
							// toggle display, but hide it
							this.element.measure(function() {
								// 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 = this.options.opacity;
							}
							// create the zero state for the beginning of the
							// transition
							var zero = {
								height : 0,
								display : $lambda(this.options.display).apply(
										this)
							};
							$each(startStyles, function(style, name) {
								zero[name] = 0;
							});
							// 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.style.cssText = this.cssText;
										this.element.setStyle('display',
												$lambda(this.options.display)
														.apply(this));
										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 : $lambda(this.options.display).apply(this),
						visiblity : 'visible',
						opacity : this.options.opacity
					});
					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.reveal();
				} else {
					this.dissolve();
				}
				return this;
			},

			cancel : function() {
				this.parent.apply(this, arguments);
				this.element.style.cssText = this.cssText;
				this.hiding = false;
				this.showing = false;
				return this;
			}

		});

Element.Properties.reveal = {

	set : function(options) {
		var reveal = this.retrieve('reveal');
		if (reveal)
			reveal.cancel();
		return this.eliminate('reveal').store('reveal:options', 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
 * 
 * name: Drag
 * 
 * description: 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
 * 
 * requires: - Core/Events - Core/Options - Core/Element.Event -
 * Core/Element.Style - Core/Element.Dimensions - /MooTools.More
 * 
 * provides: [Drag] ...
 * 
 */

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,
				stopPropagation : 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 (event.rightClick)
					return;
				if (this.options.preventDefault)
					event.preventDefault();
				if (this.options.stopPropagation)
					event.stopPropagation();
				this.mouse.start = event.page;
				this.fireEvent('beforeStart', this.element);
				var limit = this.options.limit;
				this.limit = {
					x : [],
					y : []
				};
				var styles = this.element.getStyles('left', 'right', 'top',
						'bottom');
				this._invert = {
					x : this.options.modifiers.x == 'left'
							&& styles.left == 'auto'
							&& !isNaN(styles.right.toInt())
							&& (this.options.modifiers.x = 'right'),
					y : this.options.modifiers.y == 'top'
							&& styles.top == 'auto'
							&& !isNaN(styles.bottom.toInt())
							&& (this.options.modifiers.y = 'bottom')
				};

				var z, coordinates;
				for (z in this.options.modifiers) {
					if (!this.options.modifiers[z])
						continue;

					var style = this.element
							.getStyle(this.options.modifiers[z]);

					// Some browsers (IE and Opera) don't always return pixels.
					if (style && !style.match(/px$/)) {
						if (!coordinates)
							coordinates = this.element
									.getCoordinates(this.element
											.getOffsetParent());
						style = coordinates[this.options.modifiers[z]];
					}

					if (this.options.style)
						this.value.now[z] = (style || 0).toInt();
					else
						this.value.now[z] = this.element[this.options.modifiers[z]];

					if (this.options.invert)
						this.value.now[z] *= -1;
					if (this._invert[z])
						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._invert[z])
						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: Slider.js
 * 
 * name: Slider
 * 
 * description: Class for creating horizontal and vertical slider controls.
 * 
 * license: MIT-style license
 * 
 * authors: - Valerio Proietti
 * 
 * requires: - Core/Element.Dimensions - /Class.Binds - /Drag - /Element.Measure
 * 
 * provides: [Slider]
 * 
 * ...
 */

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);
		},
		initialStep : 0,
		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.full = this.element.measure(function() {
			this.half = this.knob[offset] / 2;
			return this.element[offset] - this.knob[offset]
					+ (this.options.offset * 2);
		}.bind(this));

		this.setRange(this.options.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 ];

		var dragOptions = {
			snap : 0,
			limit : limit,
			modifiers : modifiers,
			onDrag : this.draggedKnob,
			onStart : this.draggedKnob,
			onBeforeStart : (function() {
				this.isDragging = true;
			}).bind(this),
			onCancel : function() {
				this.isDragging = false;
			}.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();
		if (this.options.initialStep != null)
			this.set(this.options.initialStep)
	},

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

	detach : function() {
		this.element.removeEvent('mousedown', this.clickedElement);
		this.element.removeEvent('mousewheel', this.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;
	},

	setRange : function(range, pos) {
		this.min = $pick(range[0], 0);
		this.max = $pick(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.set($pick(pos, this.step).floor(this.min).max(this.max));
		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: Assets.js
 * 
 * name: Assets
 * 
 * description: Provides methods to dynamically load JavaScript, CSS, and Image
 * files into the document.
 * 
 * license: MIT-style license
 * 
 * authors: - Valerio Proietti
 * 
 * requires: - Core/Element.Event - /MooTools.More
 * 
 * provides: [Assets]
 * 
 * ...
 */

var Asset = {

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

		if (properties.onLoad) {
			properties.onload = properties.onLoad;
			delete properties.onLoad;
		}
		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) {
		properties = properties || {};
		var onload = properties.onload || properties.onLoad;
		if (onload) {
			properties.events = properties.events || {};
			properties.events.load = onload;
			delete properties.onload;
			delete properties.onLoad;
		}
		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 cap = name.capitalize();
			if (properties['on' + cap]) {
				properties[type] = properties['on' + cap];
				delete properties['on' + cap];
			}
			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, index) {
			return Asset.image(source, $extend(options.properties, {
				onload : function() {
					options.onProgress.call(this, counter, index);
					counter++;
					if (counter == sources.length)
						options.onComplete();
				},
				onerror : function() {
					options.onError.call(this, counter, index);
					counter++;
					if (counter == sources.length)
						options.onComplete();
				}
			}));
		}));
	}

};

/**
 * MooColumns beta [for mootools 1.2]
 * @author Jason J. Jaeger | greengeckodesign.com
 * @version 0.67 
 * @license MIT-style License
 *			Permission is hereby granted, free of charge, to any person obtaining a copy
 *			of this software and associated documentation files (the "Software"), to deal
 *			in the Software without restriction, including without limitation the rights
 *			to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
 *			copies of the Software, and to permit persons to whom the Software is
 *			furnished to do so, subject to the following conditions:
 *	
 *			The above copyright notice and this permission notice shall be included in
 *			all copies or substantial portions of the Software.
 *	
 *			THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
 *			IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
 *			FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
 *			AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
 *			LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
 *			OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
 *			THE SOFTWARE.
 *	
 *  @changeLog_________________________________________________________________________________
 *  July 20th 2008
 *  JJJ - Incremented version to 0.67
 *  	- Fixed gutter height bug with IE6
 *  
 *  July 5th 2008
 *  JJJ - Incremented version to 0.66
 *  	- Made columns widths, gutters widths, and x positioning % based so they can be used in fluid layouts
 *  	- Made work based on selector to choose which page elements should be affected
 *  	- Tested with multiple mooColumns per page
 *  	- Created example files
 *  
 *  June 29th 2008:
 *  JJJ - Incremented version to 0.65
 *  	- Tested with official release of Mootools 1.2
 *  
 *  May 13th 2008:
 *  JJJ - Incremented version to 0.6
 *  	- Added colBreaksTrump option (true by default) which disables the auto-split of content if column-breaks are present, this is 
 *  	  because it can be difficult to predict where the column break is going to end up when the content is auto-split first
 **/
/*
 * In your screen stylesheet:
 *  .multiColumn{ display:block; }
 *  .multiColumn-screen{ display:block; } 
 *  .multiColumn-print{ display:none; }
 *  
 * In your print stylesheet:
 *  .multiColumn{ display:block; }
 *  .multiColumn-screen{ display:none; } 
 *  .multiColumn-print{ display:block; }
 *  
 *  [ Assuming "multiColumn" is your className ]
 */

var MooColumns = new Class({
	Implements: Options,
	options: {
		selector:'.multiColumn',
		className:'multiColumn',
       	numOfColumns:2,
		defaultNumOfColumns:2,
		gutterWidth:5,//% by default.. can set to a string with px at the end to set width in px
		gutterClassName:'gutter',
		columnClassName:'column',
		tweak:{x:0,y:0,width:0},
		splittableElements:['p','div','span','ul'],
		morePrecise:true,//setting to false will make script run faster
		tolerance:10,//how far below the target bottom non-splittable elements can hang before they are moved to the next column
		colBreaksTrump:true,
		debug:false		
    },
	
	sizerElWrapper:null,
	columnParents:[],
	mooColumnsAreasArr:[],
	
	initialize: function(options){
		this.setOptions(options);
		if($type(parseFloat(this.options.numOfColumns)) != 'number'){
			this.options.numOfColumns = this.options.defaultNumOfColumns;
		}		
		
		//create wrapper for sizer elements
		this.sizerElWrapper = new Element('div', {'id': 'sizerElWrapper'	}).inject(document.body,'inside').setStyles({
			'visibility':'hidden','position':'absolute','display':'block','padding':0,'margin':0,'top':0,'left':0,'width':0,'height':0,'overflow':'hidden'
		});
		
		this.columnParents = $(document.body).getElements(this.options.selector);

		this.columnParents.each(function(item,index){
			var id = this.getOrSetId($(item));
			var aMooColumnsArea = new MooColumnsArea(this.options,item);
			this.mooColumnsAreasArr[id] = aMooColumnsArea;
			item.className +='-screen';
		}.bind(this));

    },

	getOrSetId: function(obj){
		if(!obj.id){
			var d = new Date();
			var milli = d.getMilliseconds().toString();
			var randomNumber = Math.floor(Math.random()*1000);
			if(obj.nodeName){
				obj.id = obj.nodeName.toString() + '-' + milli + randomNumber.toString();
			}else{
				obj.id = "element" + milli + randomNumber.toString();	
			}
		}
		return obj.id;
	}	
	
});

var MooColumnsArea = new Class({
	Implements: Options,
   
 	options: {
		parentEl:null,
		printEl:null,
		debug:null,
		colBreakDepth:5
	},	
	columnElsArr:[],
	gutterElsArr:[],
	colWidth:null,
	targetHeight:null,
	tempContentHolder:null,
	sizerEl:null,
	tallest:0,
	unsplittableTags:['td','tr','table','tbody'],
	hasColBreaks:false,
	
	initialize: function(options,parentEl){
		this.setOptions(options);
		
		this.options.parentEl = parentEl;

		//create sizer element
		this.sizerEl = new Element('div').inject($('sizerElWrapper'),'inside').setStyles({ 'visibility':'hidden','position':'absolute','display':'block','padding':0,'margin':0,'top':0,'left':0 });
		
		//backup original content into a div for printing and refreshing
		this.options.printEl = new Element('div', {'class':this.options.className +'-print'	}).inject(this.options.parentEl,'after').set('html',this.options.parentEl.innerHTML);
		this.options.parentEl.empty();
		var innerWrapper =  new Element('div').setStyles({'display':'block','position':'relative','padding':0,'margin':0}).inject(this.options.parentEl,'top');
		this.options.parentEl = parentEl.getFirst();
		
		//apply width & x tweaks
		var targetWidth = innerWrapper.getStyle('width').toInt()+this.options.tweak.width.toInt();
		var targetWidthPercent = (100 * targetWidth)/innerWrapper.getStyle('width').toInt();
		innerWrapper.setStyles({'width':targetWidthPercent+'%',left:this.options.tweak.x});
		
		this.go();
		 
		window.addEvent('resize', function() {			
			$clear(refreshTimeout);
			var refreshTimeout = (function(){ this.setHeights(); }.bind(this)).delay(100);			
		}.bind(this)); 

	},
	
	go:function(){
		
		var numOfGutters = this.options.numOfColumns -1;
		if (this.options.gutterWidth.toString().contains('px')) {
			//convert gutterWidth from px to %
			this.options.gutterWidth = Math.round((100*parseFloat(this.options.gutterWidth))/this.options.parentEl.getCoordinates().width);
		}
		
		//how much width% to subtract from each column to make room for the gutters
		var gutterWidthAdjustment = Math.round(numOfGutters*parseFloat(this.options.gutterWidth)/ this.options.numOfColumns);

		//determine column width
		this.colWidth = Math.round(100/this.options.numOfColumns)-gutterWidthAdjustment;

		//determine sizerElSize in px
		var sizerWidth = Math.round((this.options.parentEl.getCoordinates().width * this.colWidth)/100);

		//determine & set target height
		this.sizerEl.setStyle('width', sizerWidth).set('html', this.options.printEl.innerHTML );
		this.targetHeight = Math.round(this.sizerEl.getCoordinates().height / this.options.numOfColumns );
		
		//create empty columns and gutters
		this.makeWireFrame();
		
		//strip scripts and html comments
		this.options.printEl.set('html',this.options.printEl.get('html').stripScripts().split(/<!--[^(-->)]*-->/).join(""));

		//wraps naked text nodes within a <p> tag
		this.wrapTextNodes(this.options.printEl);
		
		//convert 2 consecutive horizontal rules to a span with a class of 'colBreak'
		this.convertColBreaks(this.options.printEl);
		
		if($(this.options.printEl).getElements('.colBreak').length > 0){
			this.hasColBreaks = true;
		}				
		
		//if the column breaks are inside of something, split it
		while(this.options.colBreakDepth > 0){
			this.splitColBreakParents(this.options.printEl);
			this.options.colBreakDepth--;
		}
		
		if(this.options.colBreaksTrump === true && this.hasColBreaks){ this.options.morePrecise = false;}
				
		//divide the content
		var columnsContentArr;
		if(this.hasColBreaks && this.options.colBreaksTrump){
			//just divide on colBreaks and don't auto split content
			columnsContentArr = this.divideContent2();
		}else{
			columnsContentArr = this.divideContent();
		}		
		
		//fill the columns
		for(i=0;i<columnsContentArr.length;i++){
			this.columnElsArr[i].set('html',columnsContentArr[i].innerHTML);
		}
		
		//check for & fix overlap
		if (this.options.morePrecise) { this.shaveColumns(); 	}
		
		//set the heights
		this.columnsContentArr = columnsContentArr;
		this.setHeights();
	},
	
	setHeights:function(){
		this.options.parentEl.setStyles({'overflow':'hidden','height':'0'});
		this.tallest = 0;
		if (!this.options.debug) {
			for (i = 0; i < this.columnsContentArr.length; i++) {
				if (this.columnElsArr[i].getCoordinates().height > this.tallest) {
					this.tallest = this.columnElsArr[i].getCoordinates().height;
				}
			}
			
			if(this.options.parentEl.getScrollSize().y >0){
				this.tallest = this.options.parentEl.getScrollSize().y;
			}
			
			for (i = 0; i < this.columnElsArr.length; i++) {
				this.columnElsArr[i].setStyles({'top':0,'bottom':0});
				this.options.parentEl.setStyle('height', this.tallest);				
			}
						
			if(Browser.Engine.trident4){
				this.options.parentEl.getParent().setStyle('height',this.tallest);				
				this.gutterElsArr.each(function(item,index){	item.setStyle('height','100%');	});
			}
		}	
	},
	
	makeWireFrame:function(){
		this.options.parentEl.empty();
		for(i=0;i < this.options.numOfColumns;i++){
			//create column
			colLeft = (i*(parseFloat(this.colWidth)+parseFloat(this.options.gutterWidth)));
			this.columnElsArr[i] = new Element('div', {'class':'column'}).inject(this.options.parentEl,'inside').setStyles({'display':'block','position':'absolute','left':colLeft+'%','top':this.options.tweak.y, 'width':this.colWidth+'%'});
			
			//create gutter	
			if(i < this.options.numOfColumns-1){
				this.gutterElsArr[i] = new Element('div', {'class':'gutter'}).inject(this.options.parentEl,'inside').setStyles({'display':'block','position':'absolute','left':(colLeft + parseFloat(this.colWidth)+'%'),'top':this.options.tweak.y,'width':parseFloat(this.options.gutterWidth)+'%','bottom':0});
				if(this.options.debug){	this.gutterElsArr[i].setStyle('background','yellow');}
			}
			if(this.options.debug){	this.columnElsArr[i].setStyle('background','#eee');	}
		}
		if(this.options.debug){this.options.parentEl.setStyle('background','#ccc');}
	},
	
	convertColBreaks:function(sourceEl){
		var horizontalRules = $(sourceEl).getElements('hr');
		for (var i=0; i<horizontalRules.length; i++) {
			var item =$( horizontalRules[i]);
			
			//<hr /> <hr />
			var case1 = item.getNext() && item.getNext().get('tag') === 'hr';
			
			//<hr /> <br /> <hr />
			var case2 = item.getNext() && 
						item.getNext().get('tag') === 'br' && 
						item.getNext().getNext() && 
						item.getNext().getNext().get('tag') === 'hr';
			
			//<p><hr/></p> <p><hr /></p>
			//since having an hr inside of a paragraph tag is invalid the browser will rearrange
			//things to try to fix it. case3 targets the end result html for FireFox and case4 is for IE7
			var case3 = item.getNext() && 
						item.getNext().get('tag') === 'p' &&
						!item.getNext().get('html') &&
						item.getNext().getNext() && 
						item.getNext().getNext().get('tag') === 'hr';
			
			var case4 = item.getNext() && 
						item.getNext().getNext() &&
						item.getNext().getNext().getNext() &&
						item.getNext().get('tag') === 'p' &&
						item.getNext().getNext().get('tag') === 'p' &&
						item.getNext().getNext().getNext().get('tag') === 'hr' &&
						!item.getNext().get('html') &&
						!item.getNext().getNext().get('html');
			
			if (case4 || case3 || case2 || case1) {
				var colBreak = new Element('span', {'class': 'colBreak'	}).inject(item,'before');
				this.hasColBreaks = true;
			}			
			
			if(case4){
				item.getNext().getNext().getNext().dispose();				
			}
			if(case4 || case3 || case2){
				item.getNext().getNext().dispose();	
			}
			
			if( case4 || case3 || case2 || case1){
				item.getNext().dispose();
				item.dispose();
			}
			
		};
	},
	
	splitColBreakParents:function(sourceEl){
		$(sourceEl).getElements('.colBreak').each(function(item,index){
			if(!item.getParent()){return;}
			if(	!item.getParent().hasClass('wrapper-print') && 	!this.unsplittableTags.contains(item.getParent().get('tag'))	){
				var original = item.getParent();
				var firstHalf = original.clone(false);//the false means the clone will be empty
				if(!firstHalf || firstHalf.hasClass('colBreak')){return;}
				var nodeArr1 = $(original).childNodes;
				var currentNode = nodeArr1[0];
				var currentNodeNumber = 1;
				
				//copy everything before the col break into firstHalf
				while ( currentNode && !currentNode.className || currentNode && currentNode.className != 'colBreak') {
					if(currentNode.parentNode.className && currentNode.parentNode.className.contains('multi')){
						return;
					}
					
					if($type(currentNode) === 'textnode' || $type(currentNode) === 'whitespace'){
						if(firstHalf && firstHalf.innerHTML && currentNode.nodeValue){
							//firstHalf.innerHTML +=currentNode.nodeValue;
							firstHalf.set('html',firstHalf.get('html')+currentNode.nodeValue);
							
							currentNode.parentNode.removeChild(currentNode);

						}else{
							break;
						}
						
					}else{
						currentNode.inject(firstHalf,'bottom');
					}
					currentNodeNumber++;
					currentNode = nodeArr1[currentNodeNumber];
				}
				
				//all that is left in original is the second half of the contents,   
				//so duplicate original and replace the first original with firstHalf
				var secondHalf = original.clone(true,true).inject(original,'after');
				$(firstHalf).replaces($(original));
				
				//now move the col break from the inside of secondHalf to inbetween firstHalf & secondHalf
				if(secondHalf.getElements('.colBreak').length >=1){
					$(secondHalf.getElements('.colBreak')[0]).inject(firstHalf,'after');
				}
					
			}
		}.bind(this));
	},
	
	wrapTextNodes:function(sourceEl){
		var nodeArr = $(sourceEl).childNodes;
		for(i=0; i< nodeArr.length;i++){
			if($type(nodeArr[i]) === 'textnode'){
				var clothes = new Element('p').inject(nodeArr[i],'after').set('html', nodeArr[i].nodeValue );
				nodeArr[i].parentNode.removeChild(nodeArr[i]);
			}
		}
	},
	
	divideContent:function(){
		this.tempContentHolder = new Element('div', {'id':'tempContentHolder'}).set('html',this.options.printEl.innerHTML).inject(document.body,'inside').setStyles({'display':'none','position':'absolute'});
		this.sizerEl.empty();
		var columnsContent = [];
		var currentColNum = 0;
		var limit = 1300;

		while(this.sizerEl.getCoordinates().height <= this.targetHeight && $(this.tempContentHolder).getFirst() && limit > 0){
		 	limit--;
			if(!columnsContent[currentColNum]){	columnsContent[currentColNum] = new Element('div');	}
			$(this.tempContentHolder).getFirst().inject(this.sizerEl,'inside');
			if( this.sizerEl.getCoordinates().height >= this.targetHeight || this.sizerEl.getLast().hasClass('colBreak') ){
				$(columnsContent[currentColNum]).set('html',this.sizerEl.innerHTML);
				this.sizerEl.empty();
				
				currentColNum++;
				if(currentColNum >= this.options.numOfColumns){currentColNum = this.options.numOfColumns -1;}
				//catch any leftover text nodes and put it at the top of the next column
				if(!columnsContent[currentColNum]){	
					columnsContent[currentColNum] = new Element('div');	
				}
				
				if(!this.tempContentHolder.getFirst()){
					$(columnsContent[currentColNum]).set('html',this.tempContentHolder.innerHTML + $(columnsContent[currentColNum]).innerHTML);}
				}
		}
		$(columnsContent[currentColNum]).set('html',$(columnsContent[currentColNum]).innerHTML + this.sizerEl.innerHTML);
		this.sizerEl.empty();
		return columnsContent;
	},
	
	divideContent2:function(){
		this.tempContentHolder = new Element('div', {'id':'tempContentHolder'}).set('html',this.options.printEl.innerHTML).inject(document.body,'inside').setStyles({'display':'none','position':'absolute'});
		this.sizerEl.empty();
		var columnsContent = [];
		var currentColNum = 0;
		var limit = 1300;
		
		while(currentColNum < this.options.numOfColumns  && limit > 0){
		 	limit--;
			if(!columnsContent[currentColNum]){	columnsContent[currentColNum] = new Element('div');	}
			if (this.tempContentHolder.getFirst()) {
				$(this.tempContentHolder).getFirst().inject(this.sizerEl,'inside');
			}
			if(  this.sizerEl.getLast().hasClass('colBreak') ){
				$(columnsContent[currentColNum]).set('html',this.sizerEl.innerHTML);
				this.sizerEl.empty();
				
				currentColNum++;
				if(currentColNum >= this.options.numOfColumns){currentColNum = this.options.numOfColumns -1;}
				//catch any leftover text nodes and put it at the top of the next column
				if(!columnsContent[currentColNum]){	
					columnsContent[currentColNum] = new Element('div');	
				}
				
				if(!this.tempContentHolder.getFirst()){
					$(columnsContent[currentColNum]).set('html',this.tempContentHolder.innerHTML + $(columnsContent[currentColNum]).innerHTML);
				}
			}
		}
		$(columnsContent[currentColNum]).set('html',$(columnsContent[currentColNum]).innerHTML + this.sizerEl.innerHTML);
		
		
		this.sizerEl.empty();
		
		return columnsContent;
	},
	
	shaveColumns:function(){
		for (i = 0; i < this.columnElsArr.length-1; i++) {
			var overlap = this.columnElsArr[i].getCoordinates().height - this.targetHeight;
			var theLast = this.columnElsArr[i].getLast();
			var ElementIsSplittable = theLast.get('tag') && this.options.splittableElements.contains(theLast.get('tag'));
			var original = this.columnElsArr[i].getLast();
			
			if(overlap > 0 && ElementIsSplittable ){	
				var elementTargetHeight = original.getCoordinates().height - overlap + this.options.tolerance;
				var clone = original.clone().inject($('sizerElWrapper'),'inside');
				clone.empty();
				var limit = 50;

				
				while(limit >0 && original.childNodes.length  && original.getCoordinates().height > elementTargetHeight){
					limit--;
					
					if($type(original.childNodes[original.childNodes.length-1]) === 'textnode' ||$type(original.childNodes[original.childNodes.length-1]) === 'whitespace'){
						//split and move textnodes
						var limit2 = 50;
						var textArr = original.childNodes[original.childNodes.length-1].nodeValue.split(" ");
						
						while ( limit2 > 0 && original.childNodes[original.childNodes.length-1].nodeValue.length >= 0 && original.getCoordinates().height > elementTargetHeight) {
							limit2--;
							//copy last word to clone
							if($defined(textArr.getLast())){	clone.innerHTML = textArr.getLast().toString()+ " " + clone.innerHTML;	}
							
							//remove the last word from the array
							textArr = textArr.filter(function(item, index){	return index < (textArr.length-1) && $defined(item) ;	});
							//set original textnode value to flattened array
							original.childNodes[original.childNodes.length-1].nodeValue = textArr.join(" ");
						}
						if(!$defined(textArr.getLast())){
							//delete last childNode in original
							original.removeChild(original.childNodes[original.childNodes.length-1]);
						}
					}else if($(original.childNodes[original.childNodes.length-1])){
						if($(original.childNodes[original.childNodes.length-1]).hasClass('colBreak') ){
							limit = 0;
						}else{
							$(original.childNodes[original.childNodes.length-1]).inject(clone,'top');
							//delete last childNode in original
							//original.removeChild(original.childNodes[original.childNodes.length-1]);
						}
					}	
				}
				
				clone.inject(this.columnElsArr[i+1],'top');
				
			}else if (overlap > this.options.tolerance && !ElementIsSplittable){
				original.inject(this.columnElsArr[i+1],'top');
			}
		}
	}
	
});

var SimpleMenus = [];

var SimpleMenu = new Class({
	Implements: Options,
   
 	options: {
		toggler:null,
		menu:null,
		hideDelay:250
	},	
	
	initialize: function(options){
		this.setOptions(options);
		if (this.options.menu && this.options.toggler) {
			this.options.menu.addEvent('mouseleave', function() {
				this.hideAll();
			}.bind(this));
			this.options.menu.addEvent('mouseenter', function() {
				this.cancelHide();
			}.bind(this));
			var a = this.options.toggler.getChildren('a');
			
			a.addEvent('click', function(e) {
				e.preventDefault();
				this.toggleMenu(this.options.toggler, this.options.menu, true);
				SimpleMenus.each(function(item){
					if (item!=this) {
						item.cancelHide();
						item.toggleMenu(item.options.toggler, item.options.menu, false);
					}
				},this);
				OverText.update();
			}.bind(this));
			a.addEvent('mouseleave', function() {
				this.hideAll();
			}.bind(this));
			a.addEvent('mouseenter', function() {
				this.cancelHide();
			}.bind(this));
			window.addEvent('resize', function() {
				this.cancelHide();
				this.toggleMenu(this.options.toggler, this.options.menu, false);
			}.bind(this));
		}
		SimpleMenus.include(this);
	},

	toggleMenu : function (m, ml, mode) {
		//
		if (mode) {
			if (!m.hasClass('open')) {
				m.addClass('open')
				ml.setStyles({
					'display' : 'block',
					'opacity' : 1
				});
			}
		} else {
			m.removeClass('open')
			ml.setStyles({
				'display' : 'none',
				'opacity' : 0
			});
		}
	},
	hideAll : function() {
		$clear(this.hideAllMenusTimeout);
		this.hideAllMenusTimeout = (function(){
			$clear(this.hideAllMenusTimeout);			
			this.toggleMenu(this.options.toggler, this.options.menu, false);			
		}).bind(this).delay(this.options.hideDelay);
	},

	cancelHide: function(){ 
		$clear(this.hideAllMenusTimeout);	
	}
	
});


document.addEvent('domready', function() {

	(function() {

		var $ = document.id;

		$$('input[alt!=""]').each(function(el) {
			new OverText(el, {
				positionOptions : {
					offset : {
						x : 9,
						y : 9
					}
				}
			});
		});
		$$('textarea[alt!=""]').each(function(el) {
			new OverText(el, {
				positionOptions : {
					offset : {
						x : 9,
						y : 9
					}
				}
			});
		});
		var m1 = new SimpleMenu({toggler:$('linkvorort'),menu:$('storelist')})
		var m2 = new SimpleMenu({toggler:$('linkblogs'),menu:$('bloglist')})
		makeAccordions();
		function makeAccordions(debug) {
			$$('ul.accordion li.widget').each(
					function(panel) {
						var minHeight = 0;
						panel.getElements('div.stretcher').each(
								function(stretcher) {
									if (minHeight < stretcher.scrollHeight)
										minHeight = stretcher.scrollHeight;
								});
						panel.getElements('div.stretcher').setStyle('height',
								minHeight + 'px');
					});
			new Accordion($$('ul.accordion li.widget h2.stretchtoggle'),
					$$('ul.accordion li.widget div.stretcher'), {
						onActive : function(toggler, el) {
							toggler.setStyle('height', '40px');
							toggler.removeClass('closed');
							(function() {
								if (this.previousClick > 0) {
									var top = $(
											this.elements[this.previousClick])
											.getPosition().y - 50;
									scroller.scrollTo(0, top);
								}
							}).bind(this).delay(500);
						},
						onBackground : function(toggler, el) {
							toggler.setStyle('height', '20px');
							toggler.addClass('closed');
						},
						duration : 250
					});

		}
		var ssubmit = $('ssubmit');
		if (ssubmit)
			ssubmit.addEvent('focus', function(e) {
				this.blur();
			});

	})();
	
	var doubleColumns = new MooColumns({numOfColumns:2});

});
