/*
---

script: Core.js

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.4',
	'build': '0d9113241a90b9cd5643b926795852a2026710d4'
};

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

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

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

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

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

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

	if (methods) object.implement(methods);

	return object;
};

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

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

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

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

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

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

var Hash = new Native({

	name: 'Hash',

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

});

Hash.implement({

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

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

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

});

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

Array.implement({

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

});

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

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

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

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

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

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

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

function $empty(){};

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

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

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

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

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

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

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

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

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

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

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

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


/*
---

script: Browser.js

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


/*
---

script: Array.js

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

});


/*
---

script: Function.js

description: Contains Function Prototypes like create, bind, pass, and delay.

license: MIT-style license.

requires:
- /Native
- /$util

provides: [Function]

...
*/

Function.implement({

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

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

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

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

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

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

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

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

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

});


/*
---

script: Number.js

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


/*
---

script: String.js

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

});


/*
---

script: Hash.js

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


/*
---

script: Event.js

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

});


/*
---

script: Class.js

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

	}
	
};


/*
---

script: Class.Extras.js

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]

...
*/

var Chain = new Class({

	$chain: [],

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

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

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

});

var Events = new Class({

	$events: {},

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

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

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

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

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

});

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

var Options = new Class({

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

});


/*
---

script: Element.js

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

});

Document.implement({

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

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

	getDocument: function(){
		return this;
	},

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

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

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

	})()

});

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

Window.implement({

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

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

	getWindow: function(){
		return this;
	}

});

Native.implement([Element, Document], {

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

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

});

(function(){

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

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

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

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

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

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

bools = bools.associate(bools);

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

var inserters = {

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

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

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

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

};

inserters.inside = inserters.bottom;

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

	where = where.capitalize();

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

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

});

Element.implement({

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

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

});

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

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

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

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

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

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

});

window.addListener('unload', purge);

})();

Element.Properties = new Hash;

Element.Properties.style = {

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

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

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

};

Element.Properties.tag = {

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

};

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

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

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

	html.erase = html.set;

	return html;
})();

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


/*
---

script: Element.Event.js

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

});

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

(function(){

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

Element.Events = new Hash({

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

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

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

});

})();


/*
---

script: Element.Style.js

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


/*
---

script: Element.Dimensions.js

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

});


/*
---

script: Selectors.js

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

});


/*
---

script: DomReady.js

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

})();


/*
---

script: JSON.js

description: JSON encoder and decoder.

license: MIT-style license.

See Also: <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 + ')');
	}

});

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

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

});


/*
---

script: Cookie.js

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


/*
---

script: Swiff.js

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


/*
---

script: Fx.js

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


/*
---

script: Fx.CSS.js

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

});


/*
---

script: Fx.Tween.js

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

});


/*
---

script: Fx.Morph.js

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

});


/*
---

script: Fx.Transitions.js

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


/*
---

script: Request.js

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

});


/*
---

script: Request.HTML.js

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

});


/*
---

script: Request.JSON.js

description: Extends the basic Request Class with additional methods for sending and receiving JSON data.

license: MIT-style license.

requires:
- /Request JSON

provides: [Request.HTML]

...
*/

Request.JSON = new Class({

	Extends: Request,

	options: {
		secure: true
	},

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

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

});


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

MooTools.More={version:"1.2.4.2",build:"bd5a93c0913cce25917c48cbdacde568e15e02ef"};(function(){var a={language:"en-US",languages:{"en-US":{}},cascades:["en-US"]};
var b;MooTools.lang=new Events();$extend(MooTools.lang,{setLanguage:function(c){if(!a.languages[c]){return this;}a.language=c;this.load();this.fireEvent("langChange",c);
return this;},load:function(){var c=this.cascade(this.getCurrentLanguage());b={};$each(c,function(e,d){b[d]=this.lambda(e);},this);},getCurrentLanguage:function(){return a.language;
},addLanguage:function(c){a.languages[c]=a.languages[c]||{};return this;},cascade:function(e){var c=(a.languages[e]||{}).cascades||[];c.combine(a.cascades);
c.erase(e).push(e);var d=c.map(function(f){return a.languages[f];},this);return $merge.apply(this,d);},lambda:function(c){(c||{}).get=function(e,d){return $lambda(c[e]).apply(this,$splat(d));
};return c;},get:function(e,d,c){if(b&&b[e]){return(d?b[e].get(d,c):b[e]);}},set:function(d,e,c){this.addLanguage(d);langData=a.languages[d];if(!langData[e]){langData[e]={};
}$extend(langData[e],c);if(d==this.getCurrentLanguage()){this.load();this.fireEvent("langChange",d);}return this;},list:function(){return Hash.getKeys(a.languages);
}});})();(function(){var c=this;var b=function(){if(c.console&&console.log){try{console.log.apply(console,arguments);}catch(d){console.log(Array.slice(arguments));
}}else{Log.logged.push(arguments);}return this;};var a=function(){this.logged.push(arguments);return this;};this.Log=new Class({logged:[],log:a,resetLog:function(){this.logged.empty();
return this;},enableLog:function(){this.log=b;this.logged.each(function(d){this.log.apply(this,d);},this);return this.resetLog();},disableLog:function(){this.log=a;
return this;}});Log.extend(new Log).enableLog();Log.logger=function(){return this.log.apply(this,arguments);};})();Class.Mutators.Binds=function(a){return a;
};Class.Mutators.initialize=function(a){return function(){$splat(this.Binds).each(function(b){var c=this[b];if(c){this[b]=c.bind(this);}},this);return a.apply(this,arguments);
};};(function(){var a={wait:function(b){return this.chain(function(){this.callChain.delay($pick(b,500),this);}.bind(this));}};Chain.implement(a);if(window.Fx){Fx.implement(a);
["Css","Tween","Elements"].each(function(b){if(Fx[b]){Fx[b].implement(a);}});}Element.implement({chains:function(b){$splat($pick(b,["tween","morph","reveal"])).each(function(c){c=this.get(c);
if(!c){return;}c.setOptions({link:"chain"});},this);return this;},pauseFx:function(c,b){this.chains(b).get($pick(b,"tween")).wait(c);return this;}});})();
(function(){var i=this.Date;if(!i.now){i.now=$time;}i.Methods={ms:"Milliseconds",year:"FullYear",min:"Minutes",mo:"Month",sec:"Seconds",hr:"Hours"};["Date","Day","FullYear","Hours","Milliseconds","Minutes","Month","Seconds","Time","TimezoneOffset","Week","Timezone","GMTOffset","DayOfYear","LastMonth","LastDayOfMonth","UTCDate","UTCDay","UTCFullYear","AMPM","Ordinal","UTCHours","UTCMilliseconds","UTCMinutes","UTCMonth","UTCSeconds"].each(function(p){i.Methods[p.toLowerCase()]=p;
});var d=function(q,p){return new Array(p-String(q).length+1).join("0")+q;};i.implement({set:function(t,r){switch($type(t)){case"object":for(var s in t){this.set(s,t[s]);
}break;case"string":t=t.toLowerCase();var q=i.Methods;if(q[t]){this["set"+q[t]](r);}}return this;},get:function(q){q=q.toLowerCase();var p=i.Methods;if(p[q]){return this["get"+p[q]]();
}return null;},clone:function(){return new i(this.get("time"));},increment:function(p,r){p=p||"day";r=$pick(r,1);switch(p){case"year":return this.increment("month",r*12);
case"month":var q=this.get("date");this.set("date",1).set("mo",this.get("mo")+r);return this.set("date",q.min(this.get("lastdayofmonth")));case"week":return this.increment("day",r*7);
case"day":return this.set("date",this.get("date")+r);}if(!i.units[p]){throw new Error(p+" is not a supported interval");}return this.set("time",this.get("time")+r*i.units[p]());
},decrement:function(p,q){return this.increment(p,-1*$pick(q,1));},isLeapYear:function(){return i.isLeapYear(this.get("year"));},clearTime:function(){return this.set({hr:0,min:0,sec:0,ms:0});
},diff:function(q,p){if($type(q)=="string"){q=i.parse(q);}return((q-this)/i.units[p||"day"](3,3)).toInt();},getLastDayOfMonth:function(){return i.daysInMonth(this.get("mo"),this.get("year"));
},getDayOfYear:function(){return(i.UTC(this.get("year"),this.get("mo"),this.get("date")+1)-i.UTC(this.get("year"),0,1))/i.units.day();},getWeek:function(){return(this.get("dayofyear")/7).ceil();
},getOrdinal:function(p){return i.getMsg("ordinal",p||this.get("date"));},getTimezone:function(){return this.toString().replace(/^.*? ([A-Z]{3}).[0-9]{4}.*$/,"$1").replace(/^.*?\(([A-Z])[a-z]+ ([A-Z])[a-z]+ ([A-Z])[a-z]+\)$/,"$1$2$3");
},getGMTOffset:function(){var p=this.get("timezoneOffset");return((p>0)?"-":"+")+d((p.abs()/60).floor(),2)+d(p%60,2);},setAMPM:function(p){p=p.toUpperCase();
var q=this.get("hr");if(q>11&&p=="AM"){return this.decrement("hour",12);}else{if(q<12&&p=="PM"){return this.increment("hour",12);}}return this;},getAMPM:function(){return(this.get("hr")<12)?"AM":"PM";
},parse:function(p){this.set("time",i.parse(p));return this;},isValid:function(p){return !!(p||this).valueOf();},format:function(p){if(!this.isValid()){return"invalid date";
}p=p||"%x %X";p=k[p.toLowerCase()]||p;var q=this;return p.replace(/%([a-z%])/gi,function(s,r){switch(r){case"a":return i.getMsg("days")[q.get("day")].substr(0,3);
case"A":return i.getMsg("days")[q.get("day")];case"b":return i.getMsg("months")[q.get("month")].substr(0,3);case"B":return i.getMsg("months")[q.get("month")];
case"c":return q.toString();case"d":return d(q.get("date"),2);case"H":return d(q.get("hr"),2);case"I":return((q.get("hr")%12)||12);case"j":return d(q.get("dayofyear"),3);
case"m":return d((q.get("mo")+1),2);case"M":return d(q.get("min"),2);case"o":return q.get("ordinal");case"p":return i.getMsg(q.get("ampm"));case"S":return d(q.get("seconds"),2);
case"U":return d(q.get("week"),2);case"w":return q.get("day");case"x":return q.format(i.getMsg("shortDate"));case"X":return q.format(i.getMsg("shortTime"));
case"y":return q.get("year").toString().substr(2);case"Y":return q.get("year");case"T":return q.get("GMTOffset");case"Z":return q.get("Timezone");}return r;
});},toISOString:function(){return this.format("iso8601");}});i.alias("toISOString","toJSON");i.alias("diff","compare");i.alias("format","strftime");var k={db:"%Y-%m-%d %H:%M:%S",compact:"%Y%m%dT%H%M%S",iso8601:"%Y-%m-%dT%H:%M:%S%T",rfc822:"%a, %d %b %Y %H:%M:%S %Z","short":"%d %b %H:%M","long":"%B %d, %Y %H:%M"};
var g=[];var e=i.parse;var n=function(s,u,r){var q=-1;var t=i.getMsg(s+"s");switch($type(u)){case"object":q=t[u.get(s)];break;case"number":q=t[month-1];
if(!q){throw new Error("Invalid "+s+" index: "+index);}break;case"string":var p=t.filter(function(v){return this.test(v);},new RegExp("^"+u,"i"));if(!p.length){throw new Error("Invalid "+s+" string");
}if(p.length>1){throw new Error("Ambiguous "+s);}q=p[0];}return(r)?t.indexOf(q):q;};i.extend({getMsg:function(q,p){return MooTools.lang.get("Date",q,p);
},units:{ms:$lambda(1),second:$lambda(1000),minute:$lambda(60000),hour:$lambda(3600000),day:$lambda(86400000),week:$lambda(608400000),month:function(q,p){var r=new i;
return i.daysInMonth($pick(q,r.get("mo")),$pick(p,r.get("year")))*86400000;},year:function(p){p=p||new i().get("year");return i.isLeapYear(p)?31622400000:31536000000;
}},daysInMonth:function(q,p){return[31,i.isLeapYear(p)?29:28,31,30,31,30,31,31,30,31,30,31][q];},isLeapYear:function(p){return((p%4===0)&&(p%100!==0))||(p%400===0);
},parse:function(r){var q=$type(r);if(q=="number"){return new i(r);}if(q!="string"){return r;}r=r.clean();if(!r.length){return null;}var p;g.some(function(t){var s=t.re.exec(r);
return(s)?(p=t.handler(s)):false;});return p||new i(e(r));},parseDay:function(p,q){return n("day",p,q);},parseMonth:function(q,p){return n("month",q,p);
},parseUTC:function(q){var p=new i(q);var r=i.UTC(p.get("year"),p.get("mo"),p.get("date"),p.get("hr"),p.get("min"),p.get("sec"));return new i(r);},orderIndex:function(p){return i.getMsg("dateOrder").indexOf(p)+1;
},defineFormat:function(p,q){k[p]=q;},defineFormats:function(p){for(var q in p){i.defineFormat(q,p[q]);}},parsePatterns:g,defineParser:function(p){g.push((p.re&&p.handler)?p:l(p));
},defineParsers:function(){Array.flatten(arguments).each(i.defineParser);},define2DigitYearStart:function(p){h=p%100;m=p-h;}});var m=1900;var h=70;var j=function(p){return new RegExp("(?:"+i.getMsg(p).map(function(q){return q.substr(0,3);
}).join("|")+")[a-z]*");};var a=function(p){switch(p){case"x":return((i.orderIndex("month")==1)?"%m[.-/]%d":"%d[.-/]%m")+"([.-/]%y)?";case"X":return"%H([.:]%M)?([.:]%S([.:]%s)?)? ?%p? ?%T?";
}return null;};var o={d:/[0-2]?[0-9]|3[01]/,H:/[01]?[0-9]|2[0-3]/,I:/0?[1-9]|1[0-2]/,M:/[0-5]?\d/,s:/\d+/,o:/[a-z]*/,p:/[ap]\.?m\.?/,y:/\d{2}|\d{4}/,Y:/\d{4}/,T:/Z|[+-]\d{2}(?::?\d{2})?/};
o.m=o.I;o.S=o.M;var c;var b=function(p){c=p;o.a=o.A=j("days");o.b=o.B=j("months");g.each(function(r,q){if(r.format){g[q]=l(r.format);}});};var l=function(r){if(!c){return{format:r};
}var p=[];var q=(r.source||r).replace(/%([a-z])/gi,function(t,s){return a(s)||t;}).replace(/\((?!\?)/g,"(?:").replace(/ (?!\?|\*)/g,",? ").replace(/%([a-z%])/gi,function(t,s){var u=o[s];
if(!u){return s;}p.push(s);return"("+u.source+")";}).replace(/\[a-z\]/gi,"[a-z\\u00c0-\\uffff]");return{format:r,re:new RegExp("^"+q+"$","i"),handler:function(u){u=u.slice(1).associate(p);
var s=new i().clearTime();if("d" in u){f.call(s,"d",1);}if("m" in u){f.call(s,"m",1);}for(var t in u){f.call(s,t,u[t]);}return s;}};};var f=function(p,q){if(!q){return this;
}switch(p){case"a":case"A":return this.set("day",i.parseDay(q,true));case"b":case"B":return this.set("mo",i.parseMonth(q,true));case"d":return this.set("date",q);
case"H":case"I":return this.set("hr",q);case"m":return this.set("mo",q-1);case"M":return this.set("min",q);case"p":return this.set("ampm",q.replace(/\./g,""));
case"S":return this.set("sec",q);case"s":return this.set("ms",("0."+q)*1000);case"w":return this.set("day",q);case"Y":return this.set("year",q);case"y":q=+q;
if(q<100){q+=m+(q<h?100:0);}return this.set("year",q);case"T":if(q=="Z"){q="+00";}var r=q.match(/([+-])(\d{2}):?(\d{2})?/);r=(r[1]+"1")*(r[2]*60+(+r[3]||0))+this.getTimezoneOffset();
return this.set("time",this-r*60000);}return this;};i.defineParsers("%Y([-./]%m([-./]%d((T| )%X)?)?)?","%Y%m%d(T%H(%M%S?)?)?","%x( %X)?","%d%o( %b( %Y)?)?( %X)?","%b( %d%o)?( %Y)?( %X)?","%Y %b( %d%o( %X)?)?","%o %b %d %X %T %Y");
MooTools.lang.addEvent("langChange",function(p){if(MooTools.lang.get("Date")){b(p);}}).fireEvent("langChange",MooTools.lang.getCurrentLanguage());})();
Date.implement({timeDiffInWords:function(a){return Date.distanceOfTimeInWords(this,a||new Date);},timeDiff:function(g,b){if(g==null){g=new Date;}var f=((g-this)/1000).toInt();
if(!f){return"0s";}var a={s:60,m:60,h:24,d:365,y:0};var e,d=[];for(var c in a){if(!f){break;}if((e=a[c])){d.unshift((f%e)+c);f=(f/e).toInt();}else{d.unshift(f+c);
}}return d.join(b||":");}});Date.alias("timeDiffInWords","timeAgoInWords");Date.extend({distanceOfTimeInWords:function(b,a){return Date.getTimePhrase(((a-b)/1000).toInt());
},getTimePhrase:function(f){var d=(f<0)?"Until":"Ago";if(f<0){f*=-1;}var b={minute:60,hour:60,day:24,week:7,month:52/12,year:12,eon:Infinity};var e="lessThanMinute";
for(var c in b){var a=b[c];if(f<1.5*a){if(f>0.75*a){e=c;}break;}f/=a;e=c+"s";}return Date.getMsg(e+d).substitute({delta:f.round()});}});Date.defineParsers({re:/^(?:tod|tom|yes)/i,handler:function(a){var b=new Date().clearTime();
switch(a[0]){case"tom":return b.increment();case"yes":return b.decrement();default:return b;}}},{re:/^(next|last) ([a-z]+)$/i,handler:function(e){var f=new Date().clearTime();
var b=f.getDay();var c=Date.parseDay(e[2],true);var a=c-b;if(c<=b){a+=7;}if(e[1]=="last"){a-=7;}return f.set("date",f.getDate()+a);}});Element.implement({tidy:function(){this.set("value",this.get("value").tidy());
},getTextInRange:function(b,a){return this.get("value").substring(b,a);},getSelectedText:function(){if(this.setSelectionRange){return this.getTextInRange(this.getSelectionStart(),this.getSelectionEnd());
}return document.selection.createRange().text;},getSelectedRange:function(){if($defined(this.selectionStart)){return{start:this.selectionStart,end:this.selectionEnd};
}var e={start:0,end:0};var a=this.getDocument().selection.createRange();if(!a||a.parentElement()!=this){return e;}var c=a.duplicate();if(this.type=="text"){e.start=0-c.moveStart("character",-100000);
e.end=e.start+a.text.length;}else{var b=this.get("value");var d=b.length;c.moveToElementText(this);c.setEndPoint("StartToEnd",a);if(c.text.length){d-=b.match(/[\n\r]*$/)[0].length;
}e.end=d-c.text.length;c.setEndPoint("StartToStart",a);e.start=d-c.text.length;}return e;},getSelectionStart:function(){return this.getSelectedRange().start;
},getSelectionEnd:function(){return this.getSelectedRange().end;},setCaretPosition:function(a){if(a=="end"){a=this.get("value").length;}this.selectRange(a,a);
return this;},getCaretPosition:function(){return this.getSelectedRange().start;},selectRange:function(e,a){if(this.setSelectionRange){this.focus();this.setSelectionRange(e,a);
}else{var c=this.get("value");var d=c.substr(e,a-e).replace(/\r/g,"").length;e=c.substr(0,e).replace(/\r/g,"").length;var b=this.createTextRange();b.collapse(true);
b.moveEnd("character",e+d);b.moveStart("character",e);b.select();}return this;},insertAtCursor:function(b,a){var d=this.getSelectedRange();var c=this.get("value");
this.set("value",c.substring(0,d.start)+b+c.substring(d.end,c.length));if($pick(a,true)){this.selectRange(d.start,d.start+b.length);}else{this.setCaretPosition(d.start+b.length);
}return this;},insertAroundCursor:function(b,a){b=$extend({before:"",defaultMiddle:"",after:""},b);var c=this.getSelectedText()||b.defaultMiddle;var g=this.getSelectedRange();
var f=this.get("value");if(g.start==g.end){this.set("value",f.substring(0,g.start)+b.before+c+b.after+f.substring(g.end,f.length));this.selectRange(g.start+b.before.length,g.end+b.before.length+c.length);
}else{var d=f.substring(g.start,g.end);this.set("value",f.substring(0,g.start)+b.before+d+b.after+f.substring(g.end,f.length));var e=g.start+b.before.length;
if($pick(a,true)){this.selectRange(e,e+d.length);}else{this.setCaretPosition(e+f.length);}}return this;}});Element.implement({measure:function(e){var g=function(h){return !!(!h||h.offsetHeight||h.offsetWidth);
};if(g(this)){return e.apply(this);}var d=this.getParent(),f=[],b=[];while(!g(d)&&d!=document.body){b.push(d.expose());d=d.getParent();}var c=this.expose();
var a=e.apply(this);c();b.each(function(h){h();});return a;},expose:function(){if(this.getStyle("display")!="none"){return $empty;}var a=this.style.cssText;
this.setStyles({display:"block",position:"absolute",visibility:"hidden"});return function(){this.style.cssText=a;}.bind(this);},getDimensions:function(a){a=$merge({computeSize:false},a);
var f={};var d=function(g,e){return(e.computeSize)?g.getComputedSize(e):g.getSize();};var b=this.getParent("body");if(b&&this.getStyle("display")=="none"){f=this.measure(function(){return d(this,a);
});}else{if(b){try{f=d(this,a);}catch(c){}}else{f={x:0,y:0};}}return $chk(f.x)?$extend(f,{width:f.x,height:f.y}):$extend(f,{x:f.width,y:f.height});},getComputedSize:function(a){a=$merge({styles:["padding","border"],plains:{height:["top","bottom"],width:["left","right"]},mode:"both"},a);
var c={width:0,height:0};switch(a.mode){case"vertical":delete c.width;delete a.plains.width;break;case"horizontal":delete c.height;delete a.plains.height;
break;}var b=[];$each(a.plains,function(g,f){g.each(function(h){a.styles.each(function(i){b.push((i=="border")?i+"-"+h+"-width":i+"-"+h);});});});var e={};
b.each(function(f){e[f]=this.getComputedStyle(f);},this);var d=[];$each(a.plains,function(g,f){var h=f.capitalize();c["total"+h]=c["computed"+h]=0;g.each(function(i){c["computed"+i.capitalize()]=0;
b.each(function(k,j){if(k.test(i)){e[k]=e[k].toInt()||0;c["total"+h]=c["total"+h]+e[k];c["computed"+i.capitalize()]=c["computed"+i.capitalize()]+e[k];}if(k.test(i)&&f!=k&&(k.test("border")||k.test("padding"))&&!d.contains(k)){d.push(k);
c["computed"+h]=c["computed"+h]-e[k];}});});});["Width","Height"].each(function(g){var f=g.toLowerCase();if(!$chk(c[f])){return;}c[f]=c[f]+this["offset"+g]+c["computed"+g];
c["total"+g]=c[f]+c["total"+g];delete c["computed"+g];},this);return $extend(e,c);}});(function(){var a=Element.prototype.position;Element.implement({position:function(h){if(h&&($defined(h.x)||$defined(h.y))){return a?a.apply(this,arguments):this;
}$each(h||{},function(w,u){if(!$defined(w)){delete h[u];}});h=$merge({relativeTo:document.body,position:{x:"center",y:"center"},edge:false,offset:{x:0,y:0},returnPos:false,relFixedPosition:false,ignoreMargins:false,ignoreScroll:false,allowNegative:false},h);
var s={x:0,y:0},f=false;var c=this.measure(function(){return document.id(this.getOffsetParent());});if(c&&c!=this.getDocument().body){s=c.measure(function(){return this.getPosition();
});f=c!=document.id(h.relativeTo);h.offset.x=h.offset.x-s.x;h.offset.y=h.offset.y-s.y;}var t=function(u){if($type(u)!="string"){return u;}u=u.toLowerCase();
var v={};if(u.test("left")){v.x="left";}else{if(u.test("right")){v.x="right";}else{v.x="center";}}if(u.test("upper")||u.test("top")){v.y="top";}else{if(u.test("bottom")){v.y="bottom";
}else{v.y="center";}}return v;};h.edge=t(h.edge);h.position=t(h.position);if(!h.edge){if(h.position.x=="center"&&h.position.y=="center"){h.edge={x:"center",y:"center"};
}else{h.edge={x:"left",y:"top"};}}this.setStyle("position","absolute");var g=document.id(h.relativeTo)||document.body,d=g==document.body?window.getScroll():g.getPosition(),n=d.y,i=d.x;
var e=g.getScrolls();n+=e.y;i+=e.x;var o=this.getDimensions({computeSize:true,styles:["padding","border","margin"]});var k={},p=h.offset.y,r=h.offset.x,l=window.getSize();
switch(h.position.x){case"left":k.x=i+r;break;case"right":k.x=i+r+g.offsetWidth;break;default:k.x=i+((g==document.body?l.x:g.offsetWidth)/2)+r;break;}switch(h.position.y){case"top":k.y=n+p;
break;case"bottom":k.y=n+p+g.offsetHeight;break;default:k.y=n+((g==document.body?l.y:g.offsetHeight)/2)+p;break;}if(h.edge){var b={};switch(h.edge.x){case"left":b.x=0;
break;case"right":b.x=-o.x-o.computedRight-o.computedLeft;break;default:b.x=-(o.totalWidth/2);break;}switch(h.edge.y){case"top":b.y=0;break;case"bottom":b.y=-o.y-o.computedTop-o.computedBottom;
break;default:b.y=-(o.totalHeight/2);break;}k.x+=b.x;k.y+=b.y;}k={left:((k.x>=0||f||h.allowNegative)?k.x:0).toInt(),top:((k.y>=0||f||h.allowNegative)?k.y:0).toInt()};
var j={left:"x",top:"y"};["minimum","maximum"].each(function(u){["left","top"].each(function(v){var w=h[u]?h[u][j[v]]:null;if(w!=null&&k[v]<w){k[v]=w;}});
});if(g.getStyle("position")=="fixed"||h.relFixedPosition){var m=window.getScroll();k.top+=m.y;k.left+=m.x;}if(h.ignoreScroll){var q=g.getScroll();k.top-=q.y;
k.left-=q.x;}if(h.ignoreMargins){k.left+=(h.edge.x=="right"?o["margin-right"]:h.edge.x=="center"?-o["margin-left"]+((o["margin-right"]+o["margin-left"])/2):-o["margin-left"]);
k.top+=(h.edge.y=="bottom"?o["margin-bottom"]:h.edge.y=="center"?-o["margin-top"]+((o["margin-bottom"]+o["margin-top"])/2):-o["margin-top"]);}k.left=Math.ceil(k.left);
k.top=Math.ceil(k.top);if(h.returnPos){return k;}else{this.setStyles(k);}return this;}});})();Fx.Elements=new Class({Extends:Fx.CSS,initialize:function(b,a){this.elements=this.subject=$$(b);
this.parent(a);},compute:function(g,h,j){var c={};for(var d in g){var a=g[d],e=h[d],f=c[d]={};for(var b in a){f[b]=this.parent(a[b],e[b],j);}}return c;
},set:function(b){for(var c in b){var a=b[c];for(var d in a){this.render(this.elements[c],d,a[d],this.options.unit);}}return this;},start:function(c){if(!this.check(c)){return this;
}var h={},j={};for(var d in c){var f=c[d],a=h[d]={},g=j[d]={};for(var b in f){var e=this.prepare(this.elements[d],b,f[b]);a[b]=e.from;g[b]=e.to;}}return this.parent(h,j);
}});var Accordion=Fx.Accordion=new Class({Extends:Fx.Elements,options:{display:0,show:false,height:true,width:false,opacity:true,alwaysHide:false,trigger:"click",initialDisplayFx:true,returnHeightToAuto:true},initialize:function(){var c=Array.link(arguments,{container:Element.type,options:Object.type,togglers:$defined,elements:$defined});
this.parent(c.elements,c.options);this.togglers=$$(c.togglers);this.container=document.id(c.container);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 b=0,a=this.togglers.length;b<a;b++){this.addSection(this.togglers[b],this.elements[b]);
}this.elements.each(function(e,d){if(this.options.show===d){this.fireEvent("active",[this.togglers[d],e]);}else{for(var f in this.effects){e.setStyle(f,0);
}}},this);if($chk(this.options.display)){this.display(this.options.display,this.options.initialDisplayFx);}this.addEvent("complete",this.internalChain.callChain.bind(this.internalChain));
},addSection:function(e,c){e=document.id(e);c=document.id(c);var f=this.togglers.contains(e);this.togglers.include(e);this.elements.include(c);var a=this.togglers.indexOf(e);
var b=this.display.bind(this,a);e.store("accordion:display",b);e.addEvent(this.options.trigger,b);if(this.options.height){c.setStyles({"padding-top":0,"border-top":"none","padding-bottom":0,"border-bottom":"none"});
}if(this.options.width){c.setStyles({"padding-left":0,"border-left":"none","padding-right":0,"border-right":"none"});}c.fullOpacity=1;if(this.options.fixedWidth){c.fullWidth=this.options.fixedWidth;
}if(this.options.fixedHeight){c.fullHeight=this.options.fixedHeight;}c.setStyle("overflow","hidden");if(!f){for(var d in this.effects){c.setStyle(d,0);
}}return this;},detach:function(){this.togglers.each(function(a){a.removeEvent(this.options.trigger,a.retrieve("accordion:display"));},this);},display:function(a,b){if(!this.check(a,b)){return this;
}b=$pick(b,true);if(this.options.returnHeightToAuto){var d=this.elements[this.previous];if(d&&!this.selfHidden){for(var c in this.effects){d.setStyle(c,d[this.effects[c]]);
}}}a=($type(a)=="element")?this.elements.indexOf(a):a;if((this.timer&&this.options.wait)||(a===this.previous&&!this.options.alwaysHide)){return this;}this.previous=a;
var e={};this.elements.each(function(h,g){e[g]={};var f;if(g!=a){f=true;}else{if(this.options.alwaysHide&&((h.offsetHeight>0&&this.options.height)||h.offsetWidth>0&&this.options.width)){f=true;
this.selfHidden=true;}}this.fireEvent(f?"background":"active",[this.togglers[g],h]);for(var j in this.effects){e[g][j]=f?0:h[this.effects[j]];}},this);
this.internalChain.chain(function(){if(this.options.returnHeightToAuto&&!this.selfHidden){var f=this.elements[a];if(f){f.setStyle("height","auto");}}}.bind(this));
return b?this.start(e):this.set(e);}});Fx.Move=new Class({Extends:Fx.Morph,options:{relativeTo:document.body,position:"center",edge:false,offset:{x:0,y:0}},start:function(a){return this.parent(this.element.position($merge(this.options,a,{returnPos:true})));
}});Element.Properties.move={set:function(a){var b=this.retrieve("move");if(b){b.cancel();}return this.eliminate("move").store("move:options",$extend({link:"cancel"},a));
},get:function(a){if(a||!this.retrieve("move")){if(a||!this.retrieve("move:options")){this.set("move",a);}this.store("move",new Fx.Move(this,this.retrieve("move:options")));
}return this.retrieve("move");}};Element.implement({move:function(a){this.get("move").start(a);return this;}});Fx.Scroll=new Class({Extends:Fx,options:{offset:{x:0,y:0},wheelStops:true},initialize:function(b,a){this.element=this.subject=document.id(b);
this.parent(a);var d=this.cancel.bind(this,false);if($type(this.element)!="element"){this.element=document.id(this.element.getDocument().body);}var c=this.element;
if(this.options.wheelStops){this.addEvent("start",function(){c.addEvent("mousewheel",d);},true);this.addEvent("complete",function(){c.removeEvent("mousewheel",d);
},true);}},set:function(){var a=Array.flatten(arguments);if(Browser.Engine.gecko){a=[Math.round(a[0]),Math.round(a[1])];}this.element.scrollTo(a[0],a[1]);
},compute:function(c,b,a){return[0,1].map(function(d){return Fx.compute(c[d],b[d],a);});},start:function(c,g){if(!this.check(c,g)){return this;}var e=this.element.getScrollSize(),b=this.element.getScroll(),d={x:c,y:g};
for(var f in d){var a=e[f];if($chk(d[f])){d[f]=($type(d[f])=="number")?d[f]:a;}else{d[f]=b[f];}d[f]+=this.options.offset[f];}return this.parent([b.x,b.y],[d.x,d.y]);
},toTop:function(){return this.start(false,0);},toLeft:function(){return this.start(0,false);},toRight:function(){return this.start("right",false);},toBottom:function(){return this.start(false,"bottom");
},toElement:function(b){var a=document.id(b).getPosition(this.element);return this.start(a.x,a.y);},scrollIntoView:function(c,e,d){e=e?$splat(e):["x","y"];
var h={};c=document.id(c);var f=c.getPosition(this.element);var i=c.getSize();var g=this.element.getScroll();var a=this.element.getSize();var b={x:f.x+i.x,y:f.y+i.y};
["x","y"].each(function(j){if(e.contains(j)){if(b[j]>g[j]+a[j]){h[j]=b[j]-a[j];}if(f[j]<g[j]){h[j]=f[j];}}if(h[j]==null){h[j]=g[j];}if(d&&d[j]){h[j]=h[j]+d[j];
}},this);if(h.x!=g.x||h.y!=g.y){this.start(h.x,h.y);}return this;},scrollToCenter:function(c,e,d){e=e?$splat(e):["x","y"];c=$(c);var h={},f=c.getPosition(this.element),i=c.getSize(),g=this.element.getScroll(),a=this.element.getSize(),b={x:f.x+i.x,y:f.y+i.y};
["x","y"].each(function(j){if(e.contains(j)){h[j]=f[j]-(a[j]-i[j])/2;}if(h[j]==null){h[j]=g[j];}if(d&&d[j]){h[j]=h[j]+d[j];}},this);if(h.x!=g.x||h.y!=g.y){this.start(h.x,h.y);
}return this;}});Fx.Slide=new Class({Extends:Fx,options:{mode:"vertical",hideOverflow:true},initialize:function(b,a){this.addEvent("complete",function(){this.open=(this.wrapper["offset"+this.layout.capitalize()]!=0);
if(this.open&&Browser.Engine.webkit419){this.element.dispose().inject(this.wrapper);}},true);this.element=this.subject=document.id(b);this.parent(a);var d=this.element.retrieve("wrapper");
var c=this.element.getStyles("margin","position","overflow");if(this.options.hideOverflow){c=$extend(c,{overflow:"hidden"});}this.wrapper=d||new Element("div",{styles:c}).wraps(this.element);
this.element.store("wrapper",this.wrapper).setStyle("margin",0);this.now=[];this.open=true;},vertical:function(){this.margin="margin-top";this.layout="height";
this.offset=this.element.offsetHeight;},horizontal:function(){this.margin="margin-left";this.layout="width";this.offset=this.element.offsetWidth;},set:function(a){this.element.setStyle(this.margin,a[0]);
this.wrapper.setStyle(this.layout,a[1]);return this;},compute:function(c,b,a){return[0,1].map(function(d){return Fx.compute(c[d],b[d],a);});},start:function(b,e){if(!this.check(b,e)){return this;
}this[e||this.options.mode]();var d=this.element.getStyle(this.margin).toInt();var c=this.wrapper.getStyle(this.layout).toInt();var a=[[d,c],[0,this.offset]];
var g=[[d,c],[-this.offset,0]];var f;switch(b){case"in":f=a;break;case"out":f=g;break;case"toggle":f=(c==0)?a:g;}return this.parent(f[0],f[1]);},slideIn:function(a){return this.start("in",a);
},slideOut:function(a){return this.start("out",a);},hide:function(a){this[a||this.options.mode]();this.open=false;return this.set([-this.offset,0]);},show:function(a){this[a||this.options.mode]();
this.open=true;return this.set([0,this.offset]);},toggle:function(a){return this.start("toggle",a);}});Element.Properties.slide={set:function(b){var a=this.retrieve("slide");
if(a){a.cancel();}return this.eliminate("slide").store("slide:options",$extend({link:"cancel"},b));},get:function(a){if(a||!this.retrieve("slide")){if(a||!this.retrieve("slide:options")){this.set("slide",a);
}this.store("slide",new Fx.Slide(this,this.retrieve("slide:options")));}return this.retrieve("slide");}};Element.implement({slide:function(d,e){d=d||"toggle";
var b=this.get("slide"),a;switch(d){case"hide":b.hide(e);break;case"show":b.show(e);break;case"toggle":var c=this.retrieve("slide:flag",b.open);b[c?"slideOut":"slideIn"](e);
this.store("slide:flag",!c);a=true;break;default:b.start(d,e);}if(!a){this.eliminate("slide:flag");}return this;}});Fx.Sort=new Class({Extends:Fx.Elements,options:{mode:"vertical"},initialize:function(b,a){this.parent(b,a);
this.elements.each(function(c){if(c.getStyle("position")=="static"){c.setStyle("position","relative");}});this.setDefaultOrder();},setDefaultOrder:function(){this.currentOrder=this.elements.map(function(b,a){return a;
});},sort:function(e){if($type(e)!="array"){return false;}var i=0,a=0,c={},h={},d=this.options.mode=="vertical";var f=this.elements.map(function(m,j){var l=m.getComputedSize({styles:["border","padding","margin"]});
var n;if(d){n={top:i,margin:l["margin-top"],height:l.totalHeight};i+=n.height-l["margin-top"];}else{n={left:a,margin:l["margin-left"],width:l.totalWidth};
a+=n.width;}var k=d?"top":"left";h[j]={};var o=m.getStyle(k).toInt();h[j][k]=o||0;return n;},this);this.set(h);e=e.map(function(j){return j.toInt();});
if(e.length!=this.elements.length){this.currentOrder.each(function(j){if(!e.contains(j)){e.push(j);}});if(e.length>this.elements.length){e.splice(this.elements.length-1,e.length-this.elements.length);
}}var b=i=a=0;e.each(function(l,j){var k={};if(d){k.top=i-f[l].top-b;i+=f[l].height;}else{k.left=a-f[l].left;a+=f[l].width;}b=b+f[l].margin;c[l]=k;},this);
var g={};$A(e).sort().each(function(j){g[j]=c[j];});this.start(g);this.currentOrder=e;return this;},rearrangeDOM:function(a){a=a||this.currentOrder;var b=this.elements[0].getParent();
var c=[];this.elements.setStyle("opacity",0);a.each(function(d){c.push(this.elements[d].inject(b).setStyles({top:0,left:0}));},this);this.elements.setStyle("opacity",1);
this.elements=$$(c);this.setDefaultOrder();return this;},getDefaultOrder:function(){return this.elements.map(function(b,a){return a;});},forward:function(){return this.sort(this.getDefaultOrder());
},backward:function(){return this.sort(this.getDefaultOrder().reverse());},reverse:function(){return this.sort(this.currentOrder.reverse());},sortByElements:function(a){return this.sort(a.map(function(b){return this.elements.indexOf(b);
},this));},swap:function(c,b){if($type(c)=="element"){c=this.elements.indexOf(c);}if($type(b)=="element"){b=this.elements.indexOf(b);}var a=$A(this.currentOrder);
a[this.currentOrder.indexOf(c)]=b;a[this.currentOrder.indexOf(b)]=c;return this.sort(a);}});var Drag=new Class({Implements:[Events,Options],options:{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 b=Array.link(arguments,{options:Object.type,element:$defined});
this.element=document.id(b.element);this.document=this.element.getDocument();this.setOptions(b.options||{});var a=$type(this.options.handle);this.handles=((a=="array"||a=="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(c){if(c.rightClick){return;}if(this.options.preventDefault){c.preventDefault();}if(this.options.stopPropagation){c.stopPropagation();
}this.mouse.start=c.page;this.fireEvent("beforeStart",this.element);var a=this.options.limit;this.limit={x:[],y:[]};for(var d in this.options.modifiers){if(!this.options.modifiers[d]){continue;
}if(this.options.style){this.value.now[d]=this.element.getStyle(this.options.modifiers[d]).toInt();}else{this.value.now[d]=this.element[this.options.modifiers[d]];
}if(this.options.invert){this.value.now[d]*=-1;}this.mouse.pos[d]=c.page[d]-this.value.now[d];if(a&&a[d]){for(var b=2;b--;b){if($chk(a[d][b])){this.limit[d][b]=$lambda(a[d][b])();
}}}}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(a){if(this.options.preventDefault){a.preventDefault();}var b=Math.round(Math.sqrt(Math.pow(a.page.x-this.mouse.start.x,2)+Math.pow(a.page.y-this.mouse.start.y,2)));
if(b>this.options.snap){this.cancel();this.document.addEvents({mousemove:this.bound.drag,mouseup:this.bound.stop});this.fireEvent("start",[this.element,a]).fireEvent("snap",this.element);
}},drag:function(a){if(this.options.preventDefault){a.preventDefault();}this.mouse.now=a.page;for(var b in this.options.modifiers){if(!this.options.modifiers[b]){continue;
}this.value.now[b]=this.mouse.now[b]-this.mouse.pos[b];if(this.options.invert){this.value.now[b]*=-1;}if(this.options.limit&&this.limit[b]){if($chk(this.limit[b][1])&&(this.value.now[b]>this.limit[b][1])){this.value.now[b]=this.limit[b][1];
}else{if($chk(this.limit[b][0])&&(this.value.now[b]<this.limit[b][0])){this.value.now[b]=this.limit[b][0];}}}if(this.options.grid[b]){this.value.now[b]-=((this.value.now[b]-(this.limit[b][0]||0))%this.options.grid[b]);
}if(this.options.style){this.element.setStyle(this.options.modifiers[b],this.value.now[b]+this.options.unit);}else{this.element[this.options.modifiers[b]]=this.value.now[b];
}}this.fireEvent("drag",[this.element,a]);},cancel:function(a){this.document.removeEvent("mousemove",this.bound.check);this.document.removeEvent("mouseup",this.bound.cancel);
if(a){this.document.removeEvent(this.selection,this.bound.eventStop);this.fireEvent("cancel",this.element);}},stop:function(a){this.document.removeEvent(this.selection,this.bound.eventStop);
this.document.removeEvent("mousemove",this.bound.drag);this.document.removeEvent("mouseup",this.bound.stop);if(a){this.fireEvent("complete",[this.element,a]);
}}});Element.implement({makeResizable:function(a){var b=new Drag(this,$merge({modifiers:{x:"width",y:"height"}},a));this.store("resizer",b);return b.addEvent("drag",function(){this.fireEvent("resize",b);
}.bind(this));}});Drag.Move=new Class({Extends:Drag,options:{droppables:[],container:false,precalculate:false,includeMargins:true,checkDroppables:true},initialize:function(b,a){this.parent(b,a);
b=this.element;this.droppables=$$(this.options.droppables);this.container=document.id(this.options.container);if(this.container&&$type(this.container)!="element"){this.container=document.id(this.container.getDocument().body);
}var c=b.getStyles("left","right","position");if(c.left=="auto"||c.top=="auto"){b.setPosition(b.getPosition(b.getOffsetParent()));}if(c.position=="static"){b.setStyle("position","absolute");
}this.addEvent("start",this.checkDroppables,true);this.overed=null;},start:function(a){if(this.container){this.options.limit=this.calculateLimit();}if(this.options.precalculate){this.positions=this.droppables.map(function(b){return b.getCoordinates();
});}this.parent(a);},calculateLimit:function(){var d=this.element.getOffsetParent(),g=this.container.getCoordinates(d),f={},c={},b={},i={},k={};["top","right","bottom","left"].each(function(o){f[o]=this.container.getStyle("border-"+o).toInt();
b[o]=this.element.getStyle("border-"+o).toInt();c[o]=this.element.getStyle("margin-"+o).toInt();i[o]=this.container.getStyle("margin-"+o).toInt();k[o]=d.getStyle("padding-"+o).toInt();
},this);var e=this.element.offsetWidth+c.left+c.right,n=this.element.offsetHeight+c.top+c.bottom,h=0,j=0,m=g.right-f.right-e,a=g.bottom-f.bottom-n;if(this.options.includeMargins){h+=c.left;
j+=c.top;}else{m+=c.right;a+=c.bottom;}if(this.element.getStyle("position")=="relative"){var l=this.element.getCoordinates(d);l.left-=this.element.getStyle("left").toInt();
l.top-=this.element.getStyle("top").toInt();h+=f.left-l.left;j+=f.top-l.top;m+=c.left-l.left;a+=c.top-l.top;if(this.container!=d){h+=i.left+k.left;j+=(Browser.Engine.trident4?0:i.top)+k.top;
}}else{h-=c.left;j-=c.top;if(this.container==d){m-=f.left;a-=f.top;}else{h+=g.left+f.left;j+=g.top+f.top;}}return{x:[h,m],y:[j,a]};},checkAgainst:function(c,b){c=(this.positions)?this.positions[b]:c.getCoordinates();
var a=this.mouse.now;return(a.x>c.left&&a.x<c.right&&a.y<c.bottom&&a.y>c.top);},checkDroppables:function(){var a=this.droppables.filter(this.checkAgainst,this).getLast();
if(this.overed!=a){if(this.overed){this.fireEvent("leave",[this.element,this.overed]);}if(a){this.fireEvent("enter",[this.element,a]);}this.overed=a;}},drag:function(a){this.parent(a);
if(this.options.checkDroppables&&this.droppables.length){this.checkDroppables();}},stop:function(a){this.checkDroppables();this.fireEvent("drop",[this.element,this.overed,a]);
this.overed=null;return this.parent(a);}});Element.implement({makeDraggable:function(a){var b=new Drag.Move(this,a);this.store("dragger",b);return b;}});
var Slider=new Class({Implements:[Events,Options],Binds:["clickedElement","draggedKnob","scrolledElement"],options:{onTick:function(a){if(this.options.snap){a=this.toPosition(this.step);
}this.knob.setStyle(this.property,a);},initialStep:0,snap:false,offset:0,range:false,wheel:false,steps:100,mode:"horizontal"},initialize:function(f,a,e){this.setOptions(e);
this.element=document.id(f);this.knob=document.id(a);this.previousChange=this.previousEnd=this.step=-1;var g,b={},d={x:false,y:false};switch(this.options.mode){case"vertical":this.axis="y";
this.property="top";g="offsetHeight";break;case"horizontal":this.axis="x";this.property="left";g="offsetWidth";}this.full=this.element.measure(function(){this.half=this.knob[g]/2;
return this.element[g]-this.knob[g]+(this.options.offset*2);}.bind(this));this.min=$chk(this.options.range[0])?this.options.range[0]:0;this.max=$chk(this.options.range[1])?this.options.range[1]:this.options.steps;
this.range=this.max-this.min;this.steps=this.options.steps||this.full;this.stepSize=Math.abs(this.range)/this.steps;this.stepWidth=this.stepSize*this.full/Math.abs(this.range);
this.knob.setStyle("position","relative").setStyle(this.property,this.options.initialStep?this.toPosition(this.options.initialStep):-this.options.offset);
d[this.axis]=this.property;b[this.axis]=[-this.options.offset,this.full-this.options.offset];var c={snap:0,limit:b,modifiers:d,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){c.grid=Math.ceil(this.stepWidth);c.limit[this.axis][1]=this.full;}this.drag=new Drag(this.knob,c);this.attach();},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(a){if(!((this.range>0)^(a<this.min))){a=this.min;
}if(!((this.range>0)^(a>this.max))){a=this.max;}this.step=Math.round(a);this.checkStep();this.fireEvent("tick",this.toPosition(this.step));this.end();return this;
},clickedElement:function(c){if(this.isDragging||c.target==this.knob){return;}var b=this.range<0?-1:1;var a=c.page[this.axis]-this.element.getPosition()[this.axis]-this.half;
a=a.limit(-this.options.offset,this.full-this.options.offset);this.step=Math.round(this.min+b*this.toStep(a));this.checkStep();this.fireEvent("tick",a);
this.end();},scrolledElement:function(a){var b=(this.options.mode=="horizontal")?(a.wheel<0):(a.wheel>0);this.set(b?this.step-this.stepSize:this.step+this.stepSize);
a.stop();},draggedKnob:function(){var b=this.range<0?-1:1;var a=this.drag.value.now[this.axis];a=a.limit(-this.options.offset,this.full-this.options.offset);
this.step=Math.round(this.min+b*this.toStep(a));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(a){var b=(a+this.options.offset)*this.stepSize/this.full*this.steps;return this.options.steps?Math.round(b-=b%this.stepSize):b;},toPosition:function(a){return(this.full*Math.abs(this.min-a))/(this.steps*this.stepSize)-this.options.offset;
}});var Sortables=new Class({Implements:[Events,Options],options:{snap:4,opacity:1,clone:false,revert:false,handle:false,constrain:false},initialize:function(a,b){this.setOptions(b);
this.elements=[];this.lists=[];this.idle=true;this.addLists($$(document.id(a)||a));if(!this.options.clone){this.options.revert=false;}if(this.options.revert){this.effect=new Fx.Morph(null,$merge({duration:250,link:"cancel"},this.options.revert));
}},attach:function(){this.addLists(this.lists);return this;},detach:function(){this.lists=this.removeLists(this.lists);return this;},addItems:function(){Array.flatten(arguments).each(function(a){this.elements.push(a);
var b=a.retrieve("sortables:start",this.start.bindWithEvent(this,a));(this.options.handle?a.getElement(this.options.handle)||a:a).addEvent("mousedown",b);
},this);return this;},addLists:function(){Array.flatten(arguments).each(function(a){this.lists.push(a);this.addItems(a.getChildren());},this);return this;
},removeItems:function(){return $$(Array.flatten(arguments).map(function(a){this.elements.erase(a);var b=a.retrieve("sortables:start");(this.options.handle?a.getElement(this.options.handle)||a:a).removeEvent("mousedown",b);
return a;},this));},removeLists:function(){return $$(Array.flatten(arguments).map(function(a){this.lists.erase(a);this.removeItems(a.getChildren());return a;
},this));},getClone:function(b,a){if(!this.options.clone){return new Element("div").inject(document.body);}if($type(this.options.clone)=="function"){return this.options.clone.call(this,b,a,this.list);
}return a.clone(true).setStyles({margin:"0px",position:"absolute",visibility:"hidden",width:a.getStyle("width")}).inject(this.list).setPosition(a.getPosition(a.getOffsetParent()));
},getDroppables:function(){var a=this.list.getChildren();if(!this.options.constrain){a=this.lists.concat(a).erase(this.list);}return a.erase(this.clone).erase(this.element);
},insert:function(c,b){var a="inside";if(this.lists.contains(b)){this.list=b;this.drag.droppables=this.getDroppables();}else{a=this.element.getAllPrevious().contains(b)?"before":"after";
}this.element.inject(b,a);this.fireEvent("sort",[this.element,this.clone]);},start:function(b,a){if(!this.idle){return;}this.idle=false;this.element=a;
this.opacity=a.get("opacity");this.list=a.getParent();this.clone=this.getClone(b,a);this.drag=new Drag.Move(this.clone,{snap:this.options.snap,container:this.options.constrain&&this.element.getParent(),droppables:this.getDroppables(),onSnap:function(){b.stop();
this.clone.setStyle("visibility","visible");this.element.set("opacity",this.options.opacity||0);this.fireEvent("start",[this.element,this.clone]);}.bind(this),onEnter:this.insert.bind(this),onCancel:this.reset.bind(this),onComplete:this.end.bind(this)});
this.clone.inject(this.element,"before");this.drag.start(b);},end:function(){this.drag.detach();this.element.set("opacity",this.opacity);if(this.effect){var a=this.element.getStyles("width","height");
var b=this.clone.computePosition(this.element.getPosition(this.clone.offsetParent));this.effect.element=this.clone;this.effect.start({top:b.top,left:b.left,width:a.width,height:a.height,opacity:0.25}).chain(this.reset.bind(this));
}else{this.reset();}},reset:function(){this.idle=true;this.clone.destroy();this.fireEvent("complete",this.element);},serialize:function(){var c=Array.link(arguments,{modifier:Function.type,index:$defined});
var b=this.lists.map(function(d){return d.getChildren().map(c.modifier||function(e){return e.get("id");},this);},this);var a=c.index;if(this.lists.length==1){a=0;
}return $chk(a)&&a>=0&&a<this.lists.length?b[a]:b;}});MooTools.lang.set("en-US","Date",{months:["January","February","March","April","May","June","July","August","September","October","November","December"],days:["Sunday","Monday","Tuesday","Wednesday","Thursday","Friday","Saturday"],dateOrder:["month","date","year"],shortDate:"%m/%d/%Y",shortTime:"%I:%M%p",AM:"AM",PM:"PM",ordinal:function(a){return(a>3&&a<21)?"th":["th","st","nd","rd","th"][Math.min(a%10,4)];
},lessThanMinuteAgo:"less than a minute ago",minuteAgo:"about a minute ago",minutesAgo:"{delta} minutes ago",hourAgo:"about an hour ago",hoursAgo:"about {delta} hours ago",dayAgo:"1 day ago",daysAgo:"{delta} days ago",weekAgo:"1 week ago",weeksAgo:"{delta} weeks ago",monthAgo:"1 month ago",monthsAgo:"{delta} months ago",yearAgo:"1 year ago",yearsAgo:"{delta} years ago",lessThanMinuteUntil:"less than a minute from now",minuteUntil:"about a minute from now",minutesUntil:"{delta} minutes from now",hourUntil:"about an hour from now",hoursUntil:"about {delta} hours from now",dayUntil:"1 day from now",daysUntil:"{delta} days from now",weekUntil:"1 week from now",weeksUntil:"{delta} weeks from now",monthUntil:"1 month from now",monthsUntil:"{delta} months from now",yearUntil:"1 year from now",yearsUntil:"{delta} years from now"});
var Overlay = new Class({
	Implements: [ Options, Events ],
	options: {
		'opacity': 0.9,
		'color': '#c8cfdf',
		'fadeDuration': 0,
		'parent': undefined
	},
	ovlDiv: undefined,
	replacedSelects: new Array(),
	fx: undefined,
	initialize: function(options) {
		this.setOptions(options);
		
		this.ovlDiv = new Element('div', {
			styles: {
				'left': 0,
				'top': 0,
				'z-index': 10100,
				'background-color': this.options.color,
				'opacity': this.options.opacity,
				'display': 'none'
			}
		});
		
		this.ovlDiv.addEvent('click', function(event) {
			this.fireEvent('click', event);
		}.bindWithEvent(this));
		
		
		if (this.options.parent)
			this.ovlDiv.setStyle('position', 'absolute');
		else
			this.ovlDiv.setStyle('position', 'fixed');
	
		if (!this.options.parent) {
			window.addEvent('resize', this.windowResizeHandler.bind(this));	
		}
		this.updateOverlaySize();
		
		this.ovlDiv.setStyle('display', '');
		
		if (this.options.fadeDuration > 0) {
			this.fx = new Fx.Tween(this.ovlDiv, { link: 'cancel', duration: this.options.fadeDuration });
			this.fx.set('opacity', 0);
		}
		
		return this;
	},
	show: function() {
//		if (Browser.Engine.trident && !this.options.parent)
//			this.replaceSelects();
		
		if (this.options.parent) {
			this.options.parent.grab(this.ovlDiv);
		} else {
			$(document.body).grab(this.ovlDiv);
		}
		
		if (this.options.fadeDuration > 0 && !window.effectsDisabled) {
			this.fx.addEvent('complete', function() {
				this.fireEvent('show');
				this.fx.removeEvents();
			}.bindWithEvent(this));
			this.fx.start('opacity', this.options.opacity);
		} else {
			this.fireEvent('show');
		}
		
		return this;

	},
	replaceSelects: function() {
		$$('select').each(function(item){
			var replacement = new Element('input', {
				'type': 'text',
				'value': item.value,
				'name': item.name,
				'class': 'overlaySelectReplacement'
			});
			item.getParent().insertBefore(replacement, item);
			item = item.getParent().removeChild(item);			
			this.replacedSelects.push(item);
		}.bind(this));
	},
	restoreSelects: function() {
		$$('input.overlaySelectReplacement').each(function(replacement) {
			var item = this.replacedSelects.shift();
			var parent = replacement.getParent();
			parent.insertBefore(item, replacement);
			parent.removeChild(replacement);
		}.bind(this));
		this.replacedSelects = new Array();
	},
	setOverlaySize: function(width, height) {
		this.ovlDiv.setStyles({
			'width': width,
			'height': height
		});
	},
	updateOverlaySize: function() {
		if (this.options.parent) {
			this.setOverlaySize('100%', '100%');
		} else {
			var windowSize = Page.getSize();
			var documentSize = Page.getDocumentSize();
			
			this.setOverlaySize(
				windowSize.width > documentSize.width ? windowSize.width : documentSize.width, 
				windowSize.height > documentSize.height ? windowSize.height : documentSize.height);
		}
	},
	
	/* event handlers */
	windowResizeHandler: function(event) {
		this.updateOverlaySize();
	},
	getContainer: function() {
		return this.ovlDiv;
	},
	remove: function() {
//		if (Browser.Engine.trident)
//			this.restoreSelects();
			
		if (this.options.fadeDuration > 0 && !window.effectsDisabled) {
			this.fx.addEvent('complete', function() {
				this.ovlDiv.destroy();
				this.fireEvent('remove');
				this.fx.removeEvents();
			}.bindWithEvent(this));
			this.fx.start('opacity', 0);
		} else {
			this.ovlDiv.destroy();
			this.fireEvent('remove');
		}
		
		return this;
	}
});

var Dialog = new Class({
	Implements: [Options, Events],
	overlay: undefined,
	preInit: $empty,
	postInit: $empty,
	preClose: $empty,
	postClose: $empty,
	options: {
		width: '26em',
		fadeDuration: 150,
		overlayOpacity: 0.6,
		border: '1px outset #9cb4d6',
		background: 'url('+SF.app.staticUrl+'/layouts/sofurry/images/edit_form_gradient.gif) repeat-x top #21293c',
		parent: undefined
	},
	fx: undefined,
	initialize: function(options) {
		this.setOptions(options);
		
		this.setupCallbackFunctions(options);
		
		this.overlay = new Overlay({ opacity: this.options.overlayOpacity, parent: this.options.parent, fadeDuration: this.options.parent ? 300 : 0 });

		this.msgboxContainer = new Element('div', {
			styles: {
				'top': 0,
				'left': 0,
				'z-index': 10200,
				'width': '100%',
				'height': '100%'
			}
		});
		
		if (this.options.parent)
			this.msgboxContainer.setStyle('position', 'absolute');
		else
			this.msgboxContainer.setStyle('position', 'fixed');
		
		var result = /^\s*-?(\d+(\.\d+)?)([a-zA-Z]*)\s*$/.exec(this.options.width);
		var halfwidth = (Math.round(result[1] / 2) + (result[3] == 'px' ? 10 : 0)) + result[3];
		this.dialogFrame = new Element('div', {
			styles: {
				'background': this.options.background,
				'color': '#c8cfdf',
				'border': this.options.border,
				'padding': '10px',
				'text-align': 'justify',
				'-moz-border-radius': '6px',
				'-webkit-border-radius': '6px',
				'border-radius': '6px',
				'position': 'relative'
			}
		});
		this.msgboxContainer
			.grab(
				this.dialogContainer = new Element('div', {
					'class': 'dropshadow',
					styles: {
						'position': 'absolute',
						'left': '50%',
						'margin-left': '-' + halfwidth,
						'width': this.options.width
					}
				})
				.set('html', '<span class="dropshadows">'+
					'<span class="tl"></span>'+
					'<span class="bl"></span>'+
					'<span class="tr"></span>'+
					'<span class="br"></span>'+
					'</span>')
				.grab(this.dialogFrame)
			);	
				
		if (!this.options.parent) {
			this.boundScrollHandler = this.scrollEventHandler.bindWithEvent(this);
			window.addEvent('scroll', this.boundScrollHandler);
			this.boundResizeHandler = this.resizeEventHandler.bindWithEvent(this);
			window.addEvent('resize', this.boundResizeHandler);
		}

		this.msgboxContainer.setStyle('opacity', 0.0000000001);
		this.msgboxContainer.setStyle('opacity', 1);
		
		this.fx = new Fx.Tween(this.msgboxContainer, { duration: this.options.fadeDuration, link: 'cancel' });
		
		return this;
	},
	show: function() {
		this.preInit();
		this.fireEvent('preInit');
		this.overlay.show();
		if (this.options.fadeDuration > 0 && !window.effectsDisabled) {
			this.fx.set('opacity', 0);
		}
		if (this.options.parent) {
			this.options.parent.grab(this.msgboxContainer);
		} else {
			$(document.body).grab(this.msgboxContainer);
		}
		if (this.options.fadeDuration > 0 && !window.effectsDisabled) {
			this.fx.start('opacity', 1);
		}
		this.updateBoxPosition();
		this.postInit();		
		this.fireEvent('postInit');
		return this;
	},
	setupCallbackFunctions: function(options) {
		if (this.options.preClose) this.preClose = this.options.preClose;
		if (this.options.postClose) this.postClose = this.options.postClose;
		if (this.options.preInit) this.preInit = this.options.preInit;
		if (this.options.postInit) this.postClose = this.options.postInit;
		if (this.options.onClose) this.postClose = this.options.onClose;
	},
	updateBoxPosition: function() {
		if (this.options.parent) {
			this.dialogContainer.setStyle('top', (Math.round(this.options.parent.getHeight() - this.dialogFrame.getHeight()) / 2));
		} else {
			size = Page.getSize();
			scrollPos = document.all ? Page.getScroll() : { y: 0 };
			fontSize = parseInt($(this.dialogFrame).getStyle('font-size'));
			this.dialogContainer.setStyle('top', (Math.round(size.height - this.dialogFrame.getHeight()) / 2 + scrollPos.y));
		}
	},
	close: function(args) {

		this.preClose(args);
		this.fireEvent('preClose', args);
		
		this.overlay.remove();
		
		if (!this.options.parent) {
			window.removeEvent('scroll', this.boundScrollHandler);
			window.removeEvent('resize', this.boundResizeHandler);
		}

		this.msgboxContainer.destroy();
		
		this.postClose(args);
		this.fireEvent('postClose', args);
		this.fireEvent('close', args);
		return this;
	},
	/* event handlers */
	scrollEventHandler: function() {
		this.updateBoxPosition();
	},
	resizeEventHandler: function() {
		this.updateBoxPosition();
		this.fireEvent('resize');
	}
});


MessageBoxButtons =      { Yes: 1,  OK: 2,   Confirm: 4,      Cancel: 8,      No: 16 };
MessageBoxButtonsLabel = { 1: 'Yes', 2: 'OK', 4: 'Confirm', 8: 'Cancel', 16: 'No' };
var MessageBox = new Class({
	Implements: Options,
	Extends: Dialog,
	options: {
		buttons: MessageBoxButtons.OK
	},
	initialize: function(title, text, options) {
		this.parent(options);
		this.setOptions(options);
		
		var tmp = new Element('h3', {
			styles: {
				margin: 0,
				padding: 0,
				color: '#c00',
				'font-weight': 'bold'
			}
		});
		tmp.set('text',title);
		
		this.dialogFrame.grab(tmp);
		
		tmp = new Element('hr', { 
			styles: {
				border: 0,
				borderBottom: '1px solid #c00',
				margin: '.5em 0 .5em 0',
				padding: 0
			}
		});
		this.dialogFrame.grab(tmp);
		

		/* create text */
		var lines = text.split("\n");

		for (i = 0; i < lines.length; i++) {
			this.dialogFrame.grab(document.createTextNode(lines[i]));
			if (i != lines.length - 1)
				this.dialogFrame.grab(new Element('br'));
		}

		
		if (options.buttons) {
			
			tmp = new Element('div', {
				styles: {
					'text-align': 'center',
					'margin-top': '1em'
				}
			});
			for (var prop in MessageBoxButtons) {
				var e = MessageBoxButtons[prop];
				if (!(options.buttons & e))
					continue;
				button = new SFUI_Button({
					label: MessageBoxButtonsLabel[MessageBoxButtons[prop]]
				}).getDom();
				
				tmp.grab(button);
				tmp.grab(document.createTextNode(' '));

				this.dialogFrame.grab(tmp);
				button.addEvent('click', this.close.bind(this));						
			}
		}
		return this;
	},
	close: function (event) {
		var element = $(event.target).getParent('.SFUI.Button');
		var val;
		var elementValue = element.sfui('label');
		for (var i in MessageBoxButtonsLabel) {
			if (MessageBoxButtonsLabel[i] == elementValue) {
				val = i;
				break;
			}
		}

		var button;
		for (var i in MessageBoxButtons) {
			if (val == MessageBoxButtons[i]) {
				button = MessageBoxButtons[i];
				break;
			}
		}
		this.parent({'button': button});
		
		return this;
	}
});


var AjaxIndicatorOverlay = new Class({
	Extends: Options,
	isVisible: false,
	element: undefined,
	indicatorContainer: undefined,
	indicator: undefined,
	overlay: undefined,
	options: {
		size: 'large',
		bg: 'light',
		overlay: '#c8cfdf',
		color: '#1a273f',
		opacity: 0.7,
		'text': ''
	},
	initialize: function(element, options) {
		this.element = element;
		this.setOptions(options);
	},
	show: function() {
		if (!this.indicatorContainer)
			this.createContainer();
		if (!this.overlay)
			this.addOverlay();
		if (!this.indiactor)
			this.addIndicator();
		this.indicator.set('text', this.options.text);
	},
	createContainer: function() {
		this.indicatorContainer = new Element('div', {
			styles: {
				position: 'absolute',
				top: 0,
				left: 0,
				width: '100%',
				height: '100%',
				'z-index': '10005',
				cursor: 'default'
			}
		});
		this.element.grab(this.indicatorContainer);
	},
	addOverlay: function() {
		this.indicatorContainer.grab(this.overlay = new Element('div', {
			styles: {
				position: 'absolute',
				top: 0,
				left: 0,
				width: '100%',
				height: '100%',
				background: this.options.overlay,
				opacity: this.options.opacity
			}
		}));
	},
	addIndicator: function() {
		this.indicatorContainer.grab(this.indicator = new Element('div', {
			'class': 'ajaxindicator'+(this.options.size == 'large' ? ' large' : ' small')+(this.options.bg == 'dark' ? ' dark' : ''),
			styles: {
				position: 'absolute',
				top: '50%',
				left: '50%',
				color: this.options.color,
				margin: this.options.size == 'large' ? '-16px auto 0 -16px' : '-8px auto 0 -8px'
			}
		}));
	},
	/* keeps the main overlay so that the user can't click anything */
	preventUser: function(keepOverlay) {
		if (!this.indicatorContainer)
			this.createContainer();
		if (this.overlay && !keepOverlay) {
			this.overlay.dispose();
			this.overlay = undefined;
		}
		if (this.indicator) {
			this.indicator.dispose();
			this.indicator = undefined;
		}
	},	
	hide: function() { 
		this.indicatorContainer.dispose();
		this.indicatorContainer = undefined;
		this.indicator = undefined;
		this.overlay = undefined;
	}
});

Page = {
	getSize: function() {
		var x = 0, y = 0
		if (self.innerHeight) {
			x = self.innerWidth;
			y = self.innerHeight;
		} else if (document.documentElement && document.documentElement.clientHeight) {
			x = document.documentElement.clientWidth;
			y = document.documentElement.clientHeight;
		} else if (document.body) {
			x = document.body.clientWidth;
			y = document.body.clientHeight;
		}
		return {height: y, width: x};
	},
	getScroll: function() {
		var xs = 0, ys = 0;
		if( typeof( window.pageYOffset ) == 'number' ) {
			ys = window.pageYOffset;
			xs = window.pageXOffset;
		} else if( document.body && ( document.body.scrollLeft || document.body.scrollTop ) ) {
			ys = document.body.scrollTop;
			xs = document.body.scrollLeft;
		} else if( document.documentElement && ( document.documentElement.scrollLeft || document.documentElement.scrollTop ) ) {
			ys = document.documentElement.scrollTop;
			xs = document.documentElement.scrollLeft;
		}
		return {x: xs, y: ys};
	},
	getDocumentSize: function() {
		var wrap;
		wrap = $(document.body).getFirst();
		var ret = { width: wrap.getWidth(), height: wrap.getHeight() };		
		return ret;
	},
	/* source: http://www.webscriptexpert.com/javascript/Dynamically%20Create%20CSS%20Rules%20via%20Javascript/ */
	createCSS: function(selector, declaration) {
        // test for IE
        var ua = navigator.userAgent.toLowerCase();
        var isIE = (/msie/.test(ua)) && !(/opera/.test(ua)) && (/win/.test(ua));

        // create the style node for all browsers
        var style_node = document.createElement("style");
        style_node.setAttribute("type", "text/css");
        style_node.setAttribute("media", "screen");

        // append a rule for good browsers
        if (!isIE) style_node.appendChild(document.createTextNode(selector + " {" + declaration + "}"));

        // append the style node
        document.getElementsByTagName("head")[0].appendChild(style_node);

        // use alternative methods for IE
        if (isIE && document.styleSheets && document.styleSheets.length > 0) {
                var last_style_node = document.styleSheets[document.styleSheets.length - 1];
                if (typeof(last_style_node.addRule) == "object") last_style_node.addRule(selector, declaration);
        }
	} 
};

var Profiler = new Class({
	sections: null,
	initialize: function() {
		this.sections = new Hash;
	},
	start: function(sectionname) {
		var section;
		if (!(section = this.sections.get(sectionname))) {
			section = { name: sectionname, count: 0, time: 0, lastTimeStart: null };
		}
		if (section.lastTimeStart != null)
			return;
		section.lastTimeStart = $time();
		this.sections.set(sectionname, section);
	},
	stop: function(sectionname) {
		var section;
		if (!(section = this.sections.get(sectionname)))
			return;
		
		section.count++;
		section.time += $time() - section.lastTimeStart;
		section.lastTimeStart = null;
		this.sections.set(sectionname, section);
	},
	result: function() {
		this.sections.each(function(section) {
			console.log(section.name+': ', section.time, 'ms total, ', Math.round(section.time / section.count), 'ms avg, counter: ', section.count);
		});
	}
});

if (typeof console != 'undefined') {
	Profiler = new Profiler();

	window.addEvent('load', function() {
		(function() { Profiler.result(); }).delay(100);
	});
} else {
	Profiler = { start: $empty, stop: $empty, result: $empty };
}

Number.prototype.format = function(obj) {
	if (obj == false)
		return (this+'').replace('.',',');
	if ($type(obj) == 'number')
		return (this.toFixed(obj)+'').replace('.', ',');
	return (this.toFixed(2)+'').replace('.', ',');

};

Object.values = function(obj) {
	var ret = [];
	for(var i in obj)
		ret.push(obj[i]);
	return ret;
};

Object.keys = function(obj) {
	var ret = [];
	for(var i in obj)
		ret.push(i);
	return ret;
};

Number.prototype.byteFormat = function(bool) {
	var units = ['B', 'kB', 'MB', 'GB', 'TB', 'PB'];
    var tmp = this;
    var i;
    for (i = 0; tmp > 10*1024; tmp /= 1024, i++);
    var ret = {value: tmp.toFixed(1), unit: units[i]};
    return bool ? ret : ret.value + ' ' + ret.unit;
};


String.prototype.compare = function(str) {
	if (this > str)
		return 1;
	if (this < str)
		return -1;
	return 0;
};

function getDocumentSelectedText() {
	return $try(
		function() { return window.getSelection(); },
		function() { return document.getSelection(); },
		function() {
			var selection = document.selection && document.selection.createRange();
			if (selection.text) return selection.text;
			return false;
		}
	) || false;
}


/* 
 * AUTOGROW TEXTAREA
 * Version 1.0
 * A mooTools plugin
 * by Gary Glass (www.bookballoon.com)
 * mailto:bookballoon -at- bookballoon.com
 *
 * Based on a jQuery plugin by Chrys Bader (www.chrysbader.com).
 * Thanks to Aaron Newton for reviews and improvements.
 *
 * Copyright (c) 2009 Gary Glass (www.bookballoon.com)
 * Dual licensed under the MIT (MIT-LICENSE.txt)
 * and GPL (GPL-LICENSE.txt) licenses. 
 *
 * NOTE: This script requires mooTools. Download mooTools at mootools.net.
 *
 * USAGE:
 *		new AutoGrow(element);
 * where 'element' is a textarea element. For example:
 *		new AutoGrow($('myTextarea'));
 */
var AutoGrow = new Class({

	Implements: [Options, Events],

	options: {
		interval: 333, // update interval in milliseconds
		margin: 30, // gap (in px) to maintain between last line of text and bottom of textarea
		minHeight: 0 // minimum height of textarea
	},

	initialize: function(textarea, options) {
		this.textarea = $(textarea);
		this.options.minHeight = textarea.clientHeight;
		this.setOptions(options);
		this.dummy =  new Element("div", {
			styles:	{
				"overflow-x" : "hidden",
				"position"   : "absolute",
				"top"        : 0,
				"left"       : "-9999px"
			}
		}).setStyles(this.textarea.getStyles("font-size", "font-family", "width", "line-height", "padding")).inject(document.body);
		this.resize.periodical(this.options.interval, this);
	},

	resize: function() {
		var html = this.textarea.get('value').replace(/\n|\r\n/g, '<br>X');
		if (this.dummy.get("html").toLowerCase() != html.toLowerCase()){
			this.dummy.set("html", html);
			var triggerHeight = this.dummy.getSize().y + this.options.margin;
			if (this.textarea.clientHeight != triggerHeight)
			{
				var newHeight = Math.max(this.options.minHeight, triggerHeight);
				this.textarea.setStyle("height", newHeight);
			}
		}
	}

});// legacy getters and setters
Element.implement({
	SFUIset: function(prop, val) {
		return this.sfui(prop, val);
	},
	SFUIget: function(prop) {
		return this.sfui(prop);
	}
});

// new and simpler getter/setter
Element.implement({
	sfui: function(prop, val) {
		// init sfui element if not inited yet
		if(!SFUI.updateUIElement(this))
			return;
		
		var sfui = this.retrieve('SFUI');
		
		// return if type not found
		if (!sfui) 
			return arguments.length < 2 ? null : this;
		
		var propertyStub = prop.substr(0,1).toUpperCase()+prop.substr(1);
		
		if (arguments.length < 2) {
			var f = sfui['get'+propertyStub];
			if (!f)
				throw 'no getter defined';
			return f.call(sfui);

		} else {
			var f = sfui['set'+propertyStub];
			if (!f)
				throw 'no setter defined';
			return f.call(sfui,val);
		}
		return this;
	}
});

var SFUI_Element = new Class({
	Implements: [Options],
	element: undefined,
	options: {
	},
	ajaxindicator: null,
	initialize: function(element,options) {
		
		if ($type(element) != 'element') {
			options = element;
			element = undefined;
		}

		// extend element to generate uid
		if (element)
			element = $(element);

		// return if element already has an sfui instance
		if (element && element.retrieve('SFUI'))
			return;

		this.element = element;
		if (options)
			this.setOptions(options);
		
				
		if (this.element) {
			this.element.store('SFUI', this);
			this.setupElement();
		}
		
		return this;
	},
	getDom: function() {
		if (typeof this.element != 'undefined')
			return this.element;
		this.createElement();
		this.setupElement();
		return this.element;
	},
	createElement: function() {
		throw 'not implemented';
	},
	setupElement: $empty,
	getElement: $empty,
	setAjaxindicator: function(bool) {
	
		if (bool && !this.ajaxindicator) {
			var overlay;
			var indicator = new Element('div', {
				styles: {
					position: 'absolute',
					top: 0,
					left: 0,
					width: '100%',
					height: '100%',
					'z-index': '10005',
					cursor: 'default'
				}
			})
			.grab(overlay = new Element('div', {
				styles: {
					position: 'absolute',
					top: 0,
					left: 0,
					width: '100%',
					height: '100%',
					background: '#1a273f',
					opacity: 0.7
				}
			}))
			.grab(new Element('div', {
				'class': 'ajaxindicator small dark',
				styles: {
					position: 'absolute',
					top: '50%',
					left: '50%',
					margin: '-8px 0 0 -8px'
				}
			}));
			
			this.ajaxindicator = indicator;
			this.element.grab(indicator);
		} else {
			this.ajaxindicator.dispose();
			this.ajaxindicator = null;
		}
	}
});

var SFUI_Button = new Class({
	Extends: SFUI_Element,
	options: {
		'action': 'button',
		'label': 'Submit',
		'name': '',
		'href': '',
		'id': '',
		'class': ''
	},
	// elements: 
	outer: undefined,
	input: undefined,
	labelElement: undefined,
	href: undefined,
	initialize: function(element, options) {
		return this.parent(element,options);
	},
	setupElement: function() {
		var element = this.element;
		
		/* no select for IE */
		if (typeof element.onselectstart!="undefined")
			onselectstart=function(){return false;};

		element.addEvent('mousedown', this.mouseDown.bindWithEvent(this));
		this.boundMouseReleaseHandler = this.mouseRelease.bindWithEvent(this);
		
	},
	getOuter: function() {
		return typeof this.outer != 'undefined' ? this.outer : this.outer = this.element.getElement('.outer');
	},
	getInput: function() {
		return typeof this.input != 'undefined' ? this.input : this.input = this.element.getElement('input');
	},
	getLabelElement: function() {
		return typeof this.labelElement != 'undefined' ? this.labelElement : this.labelElement = this.element.getElement('.label');
	},
	getHref: function() {
		return typeof this.href != 'undefined' ? this.href : this.href = this.element.getElement('a');
	},
	boundMouseReleaseHandler: undefined,
	mouseRelease: function() {
		this.element.removeClass('active');
		$(document.body).removeEvent('mouseup', this.boundMouseReleaseHandler);
	},
	mouseDown: function() {
		$(document.body).addEvent('mouseup', this.boundMouseReleaseHandler);
		this.element.addClass('active');
	},
	// element creation
	createElement: function() {	

		var ret = this.element = new Element('span', { 'class': 'SFUI Button'+($chk(this.options['class']) ? ' '+this.options['class'] : '') });
		ret.setProperty('data-type', 'Button');
		
		$chk(this.options.id) && ret.set('id', this.options.id);
		$chk(this.options.name) && ret.addClass('name-'+this.options.name);

		var outer = this.outer = new Element('span', { 'class': 'outer' }).inject(ret);
		outer.grab(new Element('span', { 'class': 'tab', 'tabindex': 0}));
		outer.grab(new Element('span', { 'class': 'inner left' })).grab(new Element('div', { 'class': 'inner center' })).grab(new Element('div', { 'class': 'inner right' }));
		outer.grab(this.labelElement = new Element('span', { 'class': 'label'}).set('text', this.options.label));
		this.options.href && outer.grab(this.href = new Element('a', { href: this.options.href }));
		
		if (!this.options.href && !this.options.file && this.options.action == 'submit') {
			ret.grab(this.input = new Element('input', { type: 'submit', value: '', 'class': 'submit-overlay'}));
		}

	},
	// property functions
	getLabel: function() {
		return this.getLabelElement().get('text');
	},
	setLabel: function(text) {
		this.getLabelElement().set('text',text);
	}
});

var SFUI_File = new Class({
	Extends: SFUI_Button,
	fileInput: undefined,
	fileWrapper: undefined,
	options: {
		file: true
	},
	initialize: function(a,b) {
		return this.parent(a,b);
	},
	setupElement: function() {
		this.parent();
		/* file button handler */

		var wrapper = this.getFileWrapper();
		var fileinput = this.getFileInput();
		wrapper.setStyle('opacity', 0.000001);
		fileinput.setStyles({
			'display': 'inline',
			'position': 'relative'
		});
		
		if (Browser.Engine.trident) {
			fileinput.setStyle('filter', 'alpha(opacity=0)');
		}
		
		wrapper.grab(fileinput);
		wrapper.inject(this.element, 'top');

		this.element.addEvent('mousemove', this.mouseMove.bindWithEvent(this));
		
	},
	getFileInput: function() {
		return typeof this.fileInput != 'undefined' ? this.fileInput : this.fileInput = this.element.getElement('input[type=file]');
	},
	getFileWrapper: function() {
		return typeof this.fileWrapper != 'undefined' ? this.fileWrapper : this.fileWrapper = this.element.getElement('.file-wrapper');
	},
	mouseMove: function(event) {
		var fileinput = this.getFileInput();
		var fileinputsize = fileinput.getSize();
		var pos = this.element.getCoordinates();
		var offset = {x: event.page.x - pos.left, y: event.page.y - pos.top};
		fileinput.setStyles({
			top: offset.y - fileinputsize.y + 10,
			left: offset.x - fileinputsize.x + 10
		});		
	},
	createElement: function() {
		this.parent();

		this.element.addClass('File');
		this.fileWrapper = new Element('div', { 'class': 'file-wrapper' }).grab(this.fileInput = new Element('input', { type: 'file', 'class': 'file_input', size: 1, name: this.options.name })).inject(ret);

	}
});



var SFUI_Text = new Class({
	Extends: SFUI_Element,
	initialize: function(a,b) {
		this.parent(a,b);
	},
	labelElement: undefined,
	inputElement: undefined,
	labelText: undefined,
	isLabel: false,
	type: 'text',
	setupElement: function() {		
		var label = this.getLabelElement();
		if (label) {
			this.isLabel = this.element.hasClass('label');
			if (this.isLabel && this.getInputElement().get('value') != '') {
				this.element.removeClass('label');
				this.islabel = false;
			}
		}
		this.attachEventHandlers();
		if (this.options.focus) {
			this.focus();
		}
	},
	attachEventHandlers: function() {
		var input = this.getInputElement();
		var label = this.getLabelElement();
		if (label) {
			label.addEvent('click', this.focus.bindWithEvent(this));
			input.addEvent('focus', this.focus.bindWithEvent(this));		
			input.addEvent('blur', this.blur.bindWithEvent(this));
		}
		input.addEvent('keyup', this.keyup.bindWithEvent(this));
	},
	keyup: function(event) {
		if (event.key != 'enter')
			return;
		var f = this.element.getParent('form');
		if (f) {
			f.fireEvent('submit');
			f.submit();
		}
	},
	focus: function() {
		if (this.getInputElement().value == '') {
			this.getInputElement().value = '';
			this.element.removeClass('label');
			this.isLabel = false;
			(function() { this.getInputElement().focus(); }.bind(this)).delay(1);			
		}
	},
	blur: function() {
		if (this.getInputElement().value == '') {
			this.element.addClass('label');
			this.isLabel = true;
			//this.getInputElement().value = this.getLabel();
		}
	},
	getLabelElement: function() {
		return typeof this.labelElement != 'undefined' ? this.labelElement : this.labelElement = this.element.getElement('.text-label');
	},
	getInputElement: function() {
		return typeof this.inputElement != 'undefined' ? this.inputElement : this.inputElement = this.element.getElement('input');
	},
	setLabel: function(label) {
		if (typeof this.getLabelElement() == 'undefined')
			return;
		this.getLabelElement().set('text', label);
		this.labelText = label;	
		if (this.isLabel)
			this.getInputElement().set('value',label);
	},
	getLabel: function() {
		if (typeof this.labelText != 'undefined')
			return this.labelText;
		if (typeof this.getLabelElement() == 'undefined')
			return null;
		return this.labelText = this.getLabelElement().get('text');
	},
	setValue: function(value) {
		this.getInputElement().set('value', value);
		this.element.removeClass('label');
		this.isLabel = false;
	},
	getValue: function() {
		return this.inputElement.get('value');
	}
});

var SFUI_Password = new Class({
	Extends: SFUI_Text,
	initialize: function(a,b) {
		this.type = 'password';
		this.parent(a,b);
	}
});

var SFUI_ComboBox = new Class({
	Extends: SFUI_Element,
	// elements:
	outer: undefined,
	optionsContainer: undefined,
	optionsElement: undefined,
	dropdownHandle: undefined,
	labelElement: undefined,
	inputElement: undefined,
	newInputElement: undefined,
	optionsFx: undefined,
	// state attributes:
	optionsVisible: false,
	singleClick: false,
	selectedElement: undefined,
	value: undefined,
	
	initialize: function(a,b) {
		this.parent(a,b);
	},
	getOuter: function() {
		return typeof this.outer != 'undefined' ? this.outer : this.outer = this.element.getFirst();
	},
	getOptionsContainer: function() {
		return typeof this.optionsContainer != 'undefined' ? this.optionsContainer : this.optionsContainer = this.element.getChildren()[1];
	},
	getOptionsElement: function() {
		return typeof this.optionsElement != 'undefined' ? this.optionsElement : this.optionsElement = this.getOptionsContainer().getChildren()[1];
	},
	getDropdownHandle: function() {
		return typeof this.dropdownHandle != 'undefined' ? this.dropdownHandle : this.dropdownHandle = this.getOuter().getChildren()[5];
	},
	getLabelElement: function() {
		return typeof this.labelElement != 'undefined' ? this.labelElement : this.labelElement = this.getOuter().getChildren()[4].getFirst();
	},
	getInputElement: function() {
		return typeof this.inputElement != 'undefined' ? this.inputElement : this.inputElement = this.element.getChildren()[2];
	},
	getNewInputElement: function() {
		return typeof this.newInputElement != 'undefined' ? this.newInputElement : this.newInputElement = this.getLabelElement().getParent().getChildren()[1];
	},
	getOptionsFx: function() {
		return typeof this.optionsFx != 'undefined' ? this.optionsFx : this.optionsFx = new Fx.Elements([this.getOptionsElement(), this.getOptionsContainer()], {duration: 'short', transition: Fx.Transitions.Sine.easeOut});
	},
	newInputElementChange: function(event) {
		var newValue = this.getNewInputElement().get('value');
		if (this.getInputElement())
			this.getInputElement().set('value', newValue);
		this.element.store('value', newValue);
		this.element.fireEvent('change', newValue);
	},
	setInvisible: function() {
		this.optionsVisible = false;
		this.getOptionsContainer().setStyle('display', 'none');
		this.element.removeClass('active');
	},
	setVisible: function() {
		this.optionsVisible = true;
		var optionsContainer = this.getOptionsContainer();
		if (window.effectsDisabled) {
			optionsContainer.setStyle('display', 'block');
		} else {
			var fx = this.getOptionsFx();
			fx.set({'0':{overflow: 'hidden'},'1':{'opacity': 0,'display': 'block'}});
			fx.start({
				'0': {height: [0, this.getOptionsElement().getSize().y]},
				'1': {opacity: [0, 1]}
			}).chain(function(){
				fx.set({
					'0': {
						height: '',
						overflow: ''
					},
					'1': {opacity: ''}
				});
			});
		}
	
		this.element.addClass('active');
	},
	toggleVisible: function() {
		if (this.optionsVisible) {
			this.setVisible();
		} else {
			this.setInvisible();
		}
	},
	boundResetSingleClickFunc: undefined,
	resetSingleClickFunc: function(event) {
		var options = this.getOptionsElement();
		if (event && event.type == 'mousedown' && options.hasChild(event.target))
			return;
		this.setInvisible();
		this.getDropdownHandle().addEvent('mousedown', this.singleClickFunc.bindWithEvent(this));
		$(document.body).removeEvent('mouseup', this.boundResetSingleClickFunc);
		$(document.body).removeEvent('mousedown', this.boundResetSingleClickFunc);
	},
	disableSingleClickFunc: function() {
		this.getDropdownHandle().removeEvents('mouseup');
		$(document.body).removeEvent('mouseup', this.boundResetSingleClickFunc);
		$(document.body).addEvent('mousedown', this.boundResetSingleClickFunc);
	},
	singleClickFunc: function() {
		this.getOptionsElement().getElements('dt.selected').each(function(item) {
			item.removeClass('selected');
		});
		this.selectedElement.addClass('selected');
		this.getDropdownHandle().removeEvents('mousedown');
		this.singleClick = true;
		this.setVisible();
		$(document.body).addEvent('mouseup', this.boundResetSingleClickFunc);
		this.getDropdownHandle().addEvent('mouseup', this.disableSingleClickFunc.bindWithEvent(this));
	},
	/* option click and hover selection */
	updateValue: function() {
		var value;
		var inputElement = this.getInputElement();
		var selected = this.selectedElement;
		var next = selected.getNext();
		if (next && next.hasClass('option-data')) {
			value = next.get('text');
		} else {
			value = selected.get('text');
		}
		
		if (inputElement)
			inputElement.set('value', value);
		this.value = value;
		this.element.fireEvent('change', value);
	},
	outerClickEvent: function(event) {
		if (event.target == this.getDropdownHandle())
			return;
		this.getNewInputElement().focus();
	},
	optionClickFunc: function(event) {
		var type = $type(event);
		var that;
		if (type == 'element')
			that = event;
		else if (!event.target.hasClass('option'))
			return;
		else
			that = event.target;
	
		this.resetSingleClickFunc();
		
		var value;
		
		if (that.hasClass('new_option')) {
			this.element.addClass('text-input-active');
			value = '*';
			this.getOuter().addEvent('click', this.outerClickEvent.bindWithEvent(this));
			this.getNewInputElement().set('value', '');
			this.getNewInputElement().select();
		} else {
			this.element.removeClass('text-input-active');
			value = that.get('text');
			this.getOuter().removeEvents('click');
		}
		
		
		this.value = value;
		this.getLabelElement().set('text', value);
		
		/* shorten label text */
		
		
		this.selectedElement = that;
		this.getOptionsElement().getElements('.option.selected').each(function(item) {
			item.removeClass('selected');
		});
		that.addClass('selected');
		
		this.updateValue();
	},
	optionHoverFunc: function(event) {
		if (!$(event.target).hasClass('option'))
			return;
		this.getOptionsElement().getElements('.option.selected').each(function(item) {
			item.removeClass('selected');
		});
		$(event.target).addClass('selected');
	},
	setupElement: function() {
		var element = this.element;
		
		/* other elements */
		var outer = this.getOuter();
		var optionsContainer = this.getOptionsContainer();
		var options = this.getOptionsElement();
		var dropdownHandle = this.getDropdownHandle();
		var labelElement = this.getLabelElement();

		/* input element */
		var m = /name-([^ ]+)/.exec(element.className);
		var name = false;
		var inputElement = false;
		if (m){
			name = m[1];
		}
		if (name) {			
			var inputElement = this.getInputElement();
		}
		
		/* new input element */
		var newInputElement = this.getNewInputElement();
		if (newInputElement) {
			newInputElement.addEvent('change', this.newInputElementChange.bindWithEvent(this));
		}


		/* events */
		this.boundResetSingleClickFunc = this.resetSingleClickFunc.bindWithEvent(this);
	
		dropdownHandle.addEvent('mousedown', this.singleClickFunc.bindWithEvent(this));
		

		options.addEvent('mouseup', this.optionClickFunc.bindWithEvent(this));
		options.addEvent('mouseover', this.optionHoverFunc.bindWithEvent(this));
	
		/* check for selected option class */
		var preselectedElement = options.getElement('.selected');
		if (preselectedElement) {
			this.selectedElement = preselectedElement;
		} else {
			this.selectedElement = options.getFirst();
		}
		labelElement.set('text', this.selectedElement.get('text'));
	
		this.updateValue();

		var width = parseInt(this.element.getStyle('width'));

		optionsContainer.setStyle('min-width', width);
		
		if (newInputElement) {		
			new ResizableTextbox(newInputElement, { step: 9, max: width - 30, min: 50 });
		}
		
		// TODO: add keyboard handlers	

	},
	// property getter/setters
	setSelectedValue: function(value) {
		var input = this.getInputElement();
		var label = this.getLabelElement();
		var options = this.getOptionsElement();
		var $break = { '__act': 'BREAK' };
		var valueSet = false;
		
		var thisValue;
		
		var setValueFunc = function(element, newValue) {
			options.getElements('.option.selected').each(function(item) {
				item.removeClass('selected');
			});
			element.addClass('selected');
			this.selectedElement = element;
			label.set('text', element.get('text'));
			input.set('value', newValue);
			this.value = newValue;
		}.bind(this);
		
		try {
			options.getElements('.option-data').each(function(item){
				var prev;
				if (item.get('text') == value && (prev = item.getPrevious()).hasClass('option')) {
					setValueFunc(prev, item.get('text'));
					valueSet = true;
					thisValue = value;
					throw $break;
				}
			});
		} catch (e) {
			if (e != $break)
				throw e;
		}
		
		if (!valueSet) {
			try {
				options.getElements('.option').each(function(item){
					if (item.get('text') == value) {
						setValueFunc(item, item.get('text'));
						valueSet = true;
						var next = item.getNext();
						if (next && next.hasClass('option-data')) {
							thisValue = next.get('text');
						} else {
							thisValue = value;
						}
						throw $break;
					}
				});
			} catch (e) {
				if (e != $break)
					throw e;
			}
		}
		
		if (valueSet)		
			this.element.fireEvent('change', value);
	},
	getSelectedValue: function() {
		return this.value;
	},
	setOptions: function(options) {

		var type = $type(options);
		if (type != 'array' && type != 'object')
			return this;
		
		/* clear current elements */
		var optionsContainer = this.getOptionsElement();
		var optionElements = optionsContainer.getChildren();
		var insertPosition = null;
		var e;
		for (var i = 0; i < optionElements.length; i++) {
			e = optionElements[i];
			if (e.hasClass('new_option')) {
				insertPosition = e;
				i++;
				continue;
			}
			e.dispose();		
		}
		
		/* insert new elements */
		var firstElement = null;
		var firstValue = null;
		
		if (type == 'array') {
			options.each(function(item, i) {
				var newElement = new Element('span', { 'class': 'option', text: item });
				if (insertPosition != null) {
					newElement.inject(insertPosition, 'before');
				} else {
					optionsContainer.grab(newElement);
				}
				if (firstElement == null) {
					firstElement = newElement;
					firstValue = item;
				}
			}.bind(this));
		} else if (type == 'object') {
			var value;
			var newLabel;
			var newValue;
			for(var prop in options) {
				value = options[prop];
				newLabel = new Element('span', { 'class': 'option', text: value });
				newValue = new Element('span', { 'class': 'option-data', text: prop });
				if (insertPosition != null) {
					newLabel.inject(insertPosition, 'before');
					newValue.inject(insertPosition, 'before');
				} else {
					optionsContainer.adopt(newLabel, newValue);
				}
				if (firstElement == null) {
					firstElement = newLabel;
					firstValue = newValue;
				}
			}
		}
		
		this.selectedElement = firstElement;
		
		var inputElement = this.getInputElement();
		
		if (inputElement)
			inputElement.set('value', firstValue);
		this.value = firstValue;
		this.element.fireEvent('change', firstValue);
		
		this.optionClickFunc(firstElement);		
	}
});

var SFUI_Selector = new Class({
	Extends: SFUI_Element,
	// state attrs:
	active: undefined,
	activePos: undefined,
	elements: undefined,
	activeElement: undefined,
	getSelectorElements: function() {
		return typeof this.elements != 'undefined' ? this.elements : this.elements = this.element.getElements('.selector-element');
	},
	initialize: function(a,b) {
		this.parent(a,b);
	},
	setupElement: function() {
		
		var height = parseInt(this.element.getStyle('height'));
		var selectorElements = this.getSelectorElements();
		var activeElement = this.element.getElement('.selector-element.active');
//		if (!activeElement) {
//			activeElement = selectorElements[0];
//			activeElement.addClass('active');
//		}
		
		this.active = activeElement;
		if (activeElement) {
			this.activePos = activeElement.getAllPrevious().length;
		}
		
		selectorElements.each(function(item, i) {
			var m = /name-([^ ]+)/.exec(item.className);
			var name;
			if (m)
				name = m[1];
			else
				name = item.getElement('.label').get('text');
			item.addEvent('click', this.clickHandler.bindWithEvent(this, [item, i]));
		}.bind(this));

		
	},
	clickHandler: function(event, item, i) {
		if (item.hasClass('active'))
			return;
		this.element.getElements('.selector-element.active').each(function(item) {
			item.removeClass('active');
		});
		item.addClass('active');
		this.active = item;
		this.activePos = i;
		this.element.fireEvent('change', { name: name, index: i });
	},
	// sfui getters and setters
	setActive: function(i) {
		var newActive;
		var index;
		var name;
		if ($type(i) == 'string') {
			newActive = this.element.getElement('name-'+i);
			index = newActive.getAllPrevious().length;
			name = i;
		} else if ($type(i) == 'number') {
			newActive = this.element.getElements()[i];
			index = i;
			var m = /name-([^ ]+)/.exec(newActive.className);
			if (m)
				name = m[1];
			else
				name = newActive.getElement('.label').get('text');
		}
		if (!newActive)
			return { index: 0 };
		
		this.active.removeClass('active');
		this.active = newActive;
		this.activePos = index;
		
		this.element.fireEvent('change', { name: name, index: index });
	},
	getActive: function() {
		var active = this.active;
		var m = /name-([^ ]+)/.exec(active.className);
		var name;
		if (m)
			name = m[1];
		else
			name = active.getElement('.label').get('text');
		
		var activePos = this.activePos;
		
		return { name: name, index: activePos };
	}
	
});

var SFUI = new Class({
	initialize: function() {
	},
//	ComboBox: function(options) {
//		options = $merge({
//			'name': '',
//			'id': '',
//			'class': '',
//			'width': '',
//			'selected': '',
//			'new_label': 'new...',
//			'allow_new': false
//		}, options);
//		
//		// TODO: update latest changes from SFUI.class.php
//		var ret = new Element('span', { 'class': 'SFUI ComboBox'+($chk(options['class']) ? ' '+options['class'] : '') });
//		$chk(options.id) && ret.set('id', options.id);
//		$chk(options.name) && ret.addClass('name-'+options.name);
//
//		var outer = new Element('span', { 'class': 'outer' }).inject(ret);
//		outer.grab(new Element('span', { 'class': 'tab', 'tabindex': 0}));
//		outer.grab(new Element('span', { 'class': 'inner left' })).grab(new Element('div', { 'class': 'inner center' })).grab(new Element('div', { 'class': 'inner right' }));
//		
//		var labelContainer = labelContainer = new Element('span', { 'class': 'labelContainer'});
//		labelContainer.grab(new Element('div', { 'class': 'label'}).set('text', ' '));
//		if (options.allow_new) {
//			labelContainer.grab(new Element('input', { type: 'text', 'class': 'new-option-value'}));
//		}
//		outer.grab(labelContainer);
//		
//		var optionsContainer = new Element('div', { 'class': 'optionsContainer dropshadow'}).inject(ret);
//		optionsContainer.grab(
//				new Element('span', { 'class': 'dropshadows' })
//					.grab(new Element('span', {'class': 'tl'}))
//					.grab(new Element('span', {'class': 'bl'}))
//					.grab(new Element('span', {'class': 'tr'}))
//					.grab(new Element('span', {'class': 'br'}))
//				);
//		
//		var optionsElement = new Element('span', { 'class': 'options' }).inject(optionsContainer);
//		var optionsType = $type(options.options);
//		if (optionsType == 'array') {
//			options.options.each(function(item) {
//				var dt = new Element('span', {
//					'class': 'option'
//				}).set('text', item).inject(optionsElement);
//				if (options.selected == item)
//					dt.addClass('selected');
//			});
//		} else if (optionsType == 'object') {
//			for(var i in options.options) {
//				var val = options.options[i];
//				var dt = new Element('dt', {
//					'class': 'option'
//				}).set('text', val).inject(optionsElement);
//				var dd = new Element('dd', {
//					'class': 'option-data'
//				}).set('text', i).inject(optionsElement);
//				if (options.selected == i)
//					dt.addClass('selected');
//			}
//		}
//		
//		if (options.name)
//			new Element('input', { 'type': 'hidden', 'name': options.name }).inject(ret);
//				
//		this._combobox(ret);
//
//		return ret;
//	},
	Button: function(options) {		
		return new SFUI_Button(options).getDom();
	},
//	Text: function(options) {
//		options = $merge({
//			'password': false,
//			'value': '',
//			'label': '',
//			'name': '',
//			'size': '',
//			'id': '',
//			'class': ''
//		}, options);
//		
//		var ret = new Element('span', { 'class': 'SFUI Text'+($chk(options['class']) ? ' '+options['class'] : '') });
//		
//		var input = new Element('input', {'type': options.password ? 'password' : 'text', 'value': options.value });
//		options.size && input.setStyle('width', options.size);
//		
//		var outer = new Element('span', { 'class': 'outer' }).inject(ret);
//		outer.grab(new Element('span', { 'class': 'inner left'}));
//		outer.grab(new Element('span', { 'class': 'inner center'}));
//		outer.grab(new Element('span', { 'class': 'inner right'}));
//		outer.grab(new Element('span', { 'class': 'input' }).grab(input));
//		
//		if ($chk(options.button))
//			outer.grab(options.button);
//		
//		options.label && outer.grab(new Element('span', { 'class': 'text-label', 'text': options.label }));
//		
//		this._text(ret);
//		
//		return ret;
//	},
//	TextButton: function(options) {
//		options = $merge({
//			'action': 'submit',
//			'label': '',
//			'name': '',
//			'href': '',
//			'id': '',
//			'class': '',
//			'password': false,
//			'size': '',
//			'value': ''
//		}, options);
//		
//		options.button = this.Button({name: options['button_name'], label: options['button_label']});
//		
//		var ret = this.Text(options);
//		
//		return ret;		
//	},
//	File: function(options) {
//		options = $merge({
//			'name': 'input_file',
//			'label': '',
//			'id': '',
//			'class': '',
//			'file': true
//		}, options);
//		
//		var ret = this.Button(options);
//		
//		return ret;		
//	},
	updateUI: function(element) {
//		if (element.hasClass('.SFUI')) 
//			this.updateUIElement(element);
//
//		Profiler.start('get all sfui elements');
//		var elements = element.getElements('.SFUI');
//		elements.each(function(item, i) {
//			this.updateUIElement(item);
//		}.bind(this));
	},
	updateUIElement: function(item) {
		if (item.retrieve('SFUI'))
			return true;
		
		if (!item.hasClass('SFUI'))
			return false;
		
		var type = item.getProperty('data-type');
		switch(type) {
			case 'Button': this._button(item); break;
			case 'Text': this._text(item); break;
			case 'ComboBox': this._combobox(item); break;
			case 'Selector': this._selector(item); break;
			default: return false;
		}
		return true;
	},
	_selector: function(element) {
		new SFUI_Selector(element);
	},
	_combobox: function(element) {
		new SFUI_ComboBox(element);
	},
	_button: function(element) {
		new SFUI_Button(element);
	},
	_text: function(element) {
		new SFUI_Text(element);
	}
});
SFUI = new SFUI();


//window.addEvent('domready', function() {
//	Profiler.start('SFUI init');
//	
//	Profiler.stop('SFUI init');
//});

//Abstract class for quickbrowser and tabbar content area renderers
var ContentRenderer = new Class({
	Implements: [Options, Events],
	data: undefined,
	options: {},
	content: undefined,
	initialize: function(data, content, options) {
		this.setOptions(options);
		this.data = data;
		this.content = content;
	},
	renderContent: function() {
		this.render();
		return this;
	},
	render: function() {
	}
});

//Renderer for the main quickbrowser content area
var QuickbrowseRenderer = new Class({
	Extends: ContentRenderer,
	render: function() {
		console.log('QuickbrowseRenderer');
		var wrapper = new Element('div', { 'class': 'sitenews-content' });
        var header = new Element('span');
        wrapper.grab(header);
		var thumblayoutdiv = new Element('div', {'class': 'box-layout thumbs-text quickbrowser'});

        var icon;
		//Get the current page ID
		var pageid_content =  __SUBMISSION_DATA.id;

        this.data.items.each(function(item) {
			var itemstyle = 'box-link';
			//Highlight entry if it corresponds to the current page
			if (pageid_content == item.pid)
				itemstyle = itemstyle + ' active';
			//Parent element containing thumbnail div and description div
			var itemhtml = new Element('span', { 
				'class': itemstyle
			});
			itemhtml.grab(new Element('a',  {
				'class': itemstyle,
				'href': item.href
			}));	
			//Find out whether to show portrait or landscape thumbnail
			var thumbclass;
			if (item.width >= item.height)
				thumbclass = 'landscape-format';
			else
				thumbclass = 'portrait-format';			

			var imgdesc = item.name+' by '+item.authorName;
			//Thumbnail div
			var thumbhtml = new Element('span', { 'class': 'box-image' });
			var border = new Element('span', { 'class': 'box-image-border dropshadow ds-small' });
			border.set('html', '<span class="dropshadows">'+
					'<span class="tl"></span>'+
					'<span class="bl"></span>'+
					'<span class="tr"></span>'+
					'<span class="br"></span>'+
					'</span>');
			var img;
			var imgcont;
			new Element('span', { 'class': 'box-image-bg' }).grab(imgcont = new Element('span', { 'class': 'small-thumbs-img img-container'} ).grab(img = new Element('img', { 'class': thumbclass, src: item.thumb, title: imgdesc, alt: imgdesc } ))).inject(border);
			thumbhtml.grab(border);
		
			// only set size if it is known
			if (item.width && item.height) {
				img.set('width', item.width);
				img.set('height', item.height);
				imgcont.setStyles({
					width: item.width,
					height: item.height
				});
			}
		
			var descriptionhtml = new Element('span', { 'class': 'box-text' });
			var keywords = item.keywords;
			if (keywords == false)
				keywords = '';
			descriptionhtml.set('html', '<span class="title">'+item.name+'</span> <span class="author">by '+item.authorName+'</span><br/><span class="tags">'+keywords+'</span>');
			//Put thumb and description divs together under the common item a-element
			itemhtml.grab(thumbhtml);
			itemhtml.grab(descriptionhtml);
			thumblayoutdiv.grab(itemhtml);
        }.bind(this));
        wrapper.grab(thumblayoutdiv);
        updateBoxImages(wrapper);
        this.content.grab(wrapper);
	}
});

//Renderer for sidebar logbook browser on main page
var LogbookRenderer = new Class({
	Extends: ContentRenderer,
	render: function() {
		console.log('LogbookRenderer');
        var header = new Element('span');
        var list = new Element('ul');
        var icon;
        var lastDay = -1;
        this.data.items.each(function(item) {
            if (lastDay != item.day) {
                lastDay = item.day;
                list.grab(new Element('dt').set('html', '<br/>'+item.date));
            }
            var lbitem = new Element('li', { 'html': item.message });
            lbitem.appendChild(new Element('div', { 'class': 'rightdeletebtn' }));
            lbitem.addEvent('click', function(){
            	ajaxLogbookDelete(lbitem, item.id, true);
            });
            list.grab(lbitem);
        });
        var wrapper = new Element('div', { 'class': 'sitenews-content' });
        wrapper.grab(header).grab(list);
        this.content.grab(wrapper);
	}
});

//Renderer for site news in main page sidebar
var NewsRenderer = new Class({
	Extends: ContentRenderer,
	render: function() {
		console.log('NewsRenderer');
		var header = new Element('div');
		var list = new Element('div');
		this.data.items.each(function(item) {
			list.grab(new Element('div', { html: '<br/><b>'+item.subject+'</b><br/><hr/>' }));
			list.grab(new Element('div', { 'class': 'sitenews-body', html: item.message }));
		});
		list.grab(new Element('div', { 'class': 'sitenews-body', html: '<br/><hr/><a href="http://twitter.com/sofurrynews">Follow us on Twitter!</a>' }));
		var wrapper = new Element('div', { 'class': 'sitenews-content' });
		wrapper.grab(header).grab(list);
		this.content.grab(wrapper);
	}
});

// Renderer for chat users online in main page sidebar
var UsersOnlineRenderer = new Class({
	Extends: ContentRenderer,
	render: function() {
		console.log('UsersOnlineRenderer');
		var header = new Element('div', { html: '<h3>Users Online:</h3>' });
		var lhtml = '<ul id="chatusersonlinelist">';
		this.data.items.each(function(item) {
			if(item.name != undefined){
				var nstart = '';
				var nend = '';
				if(item.issmod == 'true' || item.isgmod == 'true'){
					nstart = '<span class="chatmodname">';
					nend = '</span>';
				}
				lhtml += '<li class="chatusersonlineli"><img src="'+item.avatar+'" border=0 width=30> <a href="index.php?uid='+item.uid+'">'+nstart+item.name+nend+'</a></li>';
			}
		});
		lhtml += '</ul>';

		var list = new Element('div', { id: 'chatusersonline', html: lhtml });
		var wrapper = new Element('div', { 'class': 'sitenews-content' });
		wrapper.grab(header).grab(list);
		this.content.innerHTML = '';
		this.content.grab(wrapper);
	}
});

// Renderer for the list of chat rooms
var ChatRoomsRenderer = new Class({
	Extends: ContentRenderer,
	render: function() {
		console.log('ChatRoomsRenderer');
		var list = new Element('div');
		var lhtml = '';
		var prevgroup = -1;
		updateRoomList(this.data.items); // Defined in chat.js
		this.data.items.each(function(item) {
			if(item.group != prevgroup){
				if(lhtml != ''){
					lhtml += '</ul>';
				}else{
					lhtml += '<br/>';
				}
				lhtml += '<b>' + item.groupname + '</b><ul>';
				prevgroup = item.group;
			}
			lhtml += '<li><a ';
			if(chatListMode == 'js'){
				lhtml += 'onclick="changeChatRoom(' + item.id + ');"';
			}else if(chatListMode == 'link'){
				lhtml += 'href="index.php?action=chat&room=' + item.id + '" ';
			}
			lhtml += 'title="' + item.desc + '">' + item.name + "</a> (Rated " + item.ratingtext + ")</li>";
		});
		if(lhtml != ''){
			lhtml += '</ul><span class="chatroomlistextratext">Hover over a rating to see a longer description.</span><br/>';
			lhtml = '<span class="chatroomlistextratext">To switch room, click on the name in the list below.<br/>Only rooms to which you have access are shown</span><br/>' + lhtml;
		}
		list.innerHTML = lhtml;
		var wrapper = new Element('div', { 'class': 'sitenews-content' });
		wrapper.grab(list);
		this.content.grab(wrapper);
	}
});

//Renderer for private messages on main page sidebar
var PmRenderer = new Class({
	Extends: ContentRenderer,
	render: function() {
		console.log('PmRenderer');
        var header = new Element('span');
        var list = new Element('ul');
        var icon;
        var lastDay = -1;
        this.data.items.each(function(item) {
            if (item.status == 1)
                icon = "mail1small.png";
            else if (item.status == 0)
                icon = "mail2small.png";
			else
                icon = "nomailsmall.png";

            if (lastDay != item.day) {
                lastDay = item.day;
                list.grab(new Element('dt').set('html', '<br/>'+item.date));
            }
            list.grab(new Element('div').set('html', '<img src="/images/'+icon+'"> &quot;<a href="?action=viewpm&id='+item.id+'">'+item.subject+'</a>&quot; from <a href="index.php?uid='+item.fromUserId+'">'+item.fromUserName+'</a>'));
        });
        var wrapper = new Element('div', { 'class': 'sitenews-content' });
        wrapper.grab(header).grab(list);
        this.content.grab(wrapper);
    }
});

//Renderer for "prev"/"next" pagination area in quickbrowser
var PaginationRenderer = new Class({
	Extends: ContentRenderer,
	pageContent: undefined,
	render: function() {
		console.log('PaginationRenderer');
		
		var paginationDiv;

		this.pageContent = new Element('div', {'class': 'page', styles: { clear: 'both' }});
		this.content.grab(this.pageContent);
		
		this.content.grab(
			new Element('div',{ 'class': 'bottom-panel' })
				.grab(paginationDiv = new Element('div', { 'class': 'bottom-panel-content' })
			)
		);
		
		var prev = new Element('span', { 
			styles: {
				'float': 'left'
			}, 
			text: 'Prev',
			'class': 'pagination-clickable'
		}).addEvent('click',function() {
			console.log('prev');
			this.fireEvent('prev'); 
		}.bind(this));
		paginationDiv.grab(prev);
	
		var next = new Element('span', { 
			styles: {
				'float': 'right'
			}, 
			text: 'Next',
			'class': 'pagination-clickable'
		}).addEvent('click',function() {
			console.log('next');
			this.fireEvent('next');
		}.bind(this));
		paginationDiv.grab(next);

	},
	setPageContent: function(div) {
		this.pageContent.set('html','');
		this.pageContent.grab(div);
	},
	setCurrentPage: function(num) {
		
	}
});

// Abstact class for sub controllers
var TabbarSubController = new Class({
	Implements: [Options, Events],
	options: {
		tabName: '',
		tabUrl: ''
	},
	data: undefined,
	content: undefined,
	tabContent: undefined,
	initialize: function(data, content, options) {
		this.setOptions(options);
		this.data = data;
		this.content = content;
	},
	start: function() {
		var tab = this.tab;
		this.callRenderer();
		
		this.content.getChildren().each(function(item) {
			item.dispose();
		});
		this.content.grab(this.tabContent);
		this.setupContent(tab, this.tabContent);
	},
	callRenderer: function() {
		throw "not implemented";
	},
	createRenderer: function(type, content) {
		throw "not implemented";
	},
	setupContent: function(tab, content) {
	},
	activate: function() {
	},
	deactivate: function() {
	}
});

// A simple controller that just displays a list of items using one of several renderers depending on content type
var SimpleSubController = new Class({
	Extends: TabbarSubController,
	createRenderer: function(type, content) {
		console.log("SimpleSubController.createRenderer "+type);
		switch (type) {
			case 'logbook': return new LogbookRenderer(this.data, content);
			case 'news': return new NewsRenderer(this.data, content);
			case 'pm': return new PmRenderer(this.data, content);
			case 'chatrooms': return new ChatRoomsRenderer(this.data, content);
		}
		return null;
	},
	callRenderer: function() {
		if (typeof this.tabContent == 'undefined') {
			this.tabContent = new Element('div', { 'class': 'tabbar-content' });
			this.createRenderer(AJAX_MESSAGETYPE[this.data.messageType], this.tabContent)
				.setOptions(this.options)
				.renderContent();
		}
	}
});

// A controller that displays and refreshes a list of items
var RefreshingSubController = new Class({
	Extends: TabbarSubController,
	refreshThread: undefined,
	regenUrl: false,
	custUrl: undefined,
	oldUrl: '',
	ajax: undefined,
	createRenderer: function(type, content) {
		console.log("RefreshingSubController createRenderer " + type);
		switch (type) {
			case 'usersonline': return new UsersOnlineRenderer(this.data, content);
		}
		return null;
	},
	callRenderer: function() {
		if(AJAX_MESSAGETYPE[this.data.messageType] == 'usersonline'){
			this.regenUrl = true;
		}
		console.log('RefreshingSubController: url at render '+ this.ajaxUrl);
		var urlChanged = false;
		if(this.regenUrl){
			console.log("regenurl");
			switch(AJAX_MESSAGETYPE[this.data.messageType]){
			case 'usersonline': 
				var ridpos = this.options.tabUrl.indexOf('roomid=');
				if(ridpos == -1){
					this.options.tabUrl += '&roomid=' + currentRoomId;
				}else{
					this.options.tabUrl = this.options.tabUrl.replace(this.options.tabUrl.substr(ridpos), '&roomid=' + currentRoomId);
				}
			}
			if(this.options.tabUrl != this.oldUrl){
				urlChanged = true;
			}
			this.oldUrl = this.options.tabUrl;
		}
		console.log("urlChanged: " + urlChanged);
		if (typeof this.ajax == 'undefined' || urlChanged) {
			console.log("Rebuild AJAX");
			this.ajax = new Request.JSON({url: this.options.tabUrl, 'link': 'ignore', onSuccess: function(data) {
				console.log("RefreshingSubController received ajax success");
				this.fireEvent('load');
				this.handleAjax(data);
				this.callRenderer();
			}.bind(this)});
			console.log('Ajax url set1: '+this.ajax.options.url);
			console.log("ajaxurl: "+this.options.ajaxUrl);
			console.log("taburl: "+this.options.tabUrl);
		}
	
		this.tabContent = new Element('div', { 'class': 'tabbar-content' });
		this.createRenderer(AJAX_MESSAGETYPE[this.data.messageType], this.tabContent)
			.setOptions(this.options)
			.renderContent();
		this.activate();
	},
	refreshFunc: function () { 
		this.ajax.get();
	},
	activate: function () {
		$clear(this.refreshThread);
		this.refreshThread = (function(){this.refreshFunc();}.bind(this)).periodical(5000);
	},
	deactivate: function() {
		$clear(this.refreshThread);
	}
	
});


//Quickbrowser controller with support for pagination
var PaginatedSubController = new Class({
	Extends: TabbarSubController,
	pageContents: [],
	currentPage: undefined,
	paginationRenderer: undefined,
	precachedData: {},
	loadPages: [],
	ajax: undefined,
	createRenderer: function(type, content, data) {
		switch (type) {
			case 'pagination': return new PaginationRenderer(data, content); 
			case 'quickbrowser': return new QuickbrowseRenderer(data, content);
		}
		return null;
	},
	handleAjax: function(response) {
		console.log("handleAjax: ",response);
		if ($type(response.pagecontents) == 'array') {
			response.pagecontents.each(function(item) {
				console.log("saving response data for page "+item.currentpage);
				this.precachedData[item.currentpage] = item;
			}.bind(this));
		}
		if (typeof this.currentPage == 'undefined')
			this.currentPage = response.currentpage;
		this.messageType = AJAX_MESSAGETYPE[response.messageType];
		this.totalPages = response.totalpages;
	},
	callRenderer: function(fromAjax) {
		console.log("callRenderer: ",fromAjax);
		if (this.data) {
			this.handleAjax(this.data);
			this.data = null;
		}
		if (typeof this.ajax == 'undefined') {
			this.ajax = new Request.JSON({'link': 'ignore', onSuccess: function(data) {
				console.log("Ajax call returned: ",data);
				this.fireEvent('load');
/* NOTE: Disabled to fix bug when pressing "next" while another ajax request is already pending */
/*				var existsBeforeHandle = typeof this.precachedData[this.currentPage] == 'undefined'; */
				this.handleAjax(data);
/*				var existsAfterHandle = typeof this.precachedData[this.currentPage] == 'undefined'; 
				if (!existsBeforeHandle && existsAfterHandle) { */
				this.callRenderer(true);
/*				{*/
			}.bind(this)});
		}
		
		console.log("Precaching pages before cleanup: ",this.loadPages, Object.keys(this.precachedData));
		this.cleanupLoadPages();
		console.log("Precaching pages: ",this.loadPages);
		if (this.loadPages.length > 0) {
			if (fromAjax) {
				return;
			}

			this.ajax.options.url = this.options.tabUrl + '&page=' + this.currentPage + '&loadPages=' + this.loadPages.join(',');
			// TODO: do this not only when the current page hasn't been rendered (typeof this.pageContents[this.currentPage] == 'undefined'), but also when someone clicks prev/next
			this.ajax.get();		
		}
		
		//On initial call, create the required html elements
		if (typeof this.tabContent == 'undefined' || typeof this.paginationRenderer == 'undefined') {
			this.tabContent = new Element('div', { 'class': 'tabbar-content' });
			this.paginationRenderer = this.createRenderer('pagination', this.tabContent)
				.setOptions(this.options)
				.renderContent();
			this.paginationRenderer.addEvent('prev', this.setPageEvent.bindWithEvent(this, ['prev']) );
			this.paginationRenderer.addEvent('next', this.setPageEvent.bindWithEvent(this, ['next']) );
		}
		
		//Send page ajax request only if we don't have this page cached yet
		if (typeof this.pageContents[this.currentPage] == 'undefined') {
		
			if (typeof this.precachedData[this.currentPage] == 'undefined') {
				this.fireEvent('loading');
				return;
			}
			this.pageContents[this.currentPage] = new Element('div', { 'class': 'page-content' });
			
			this.createRenderer(this.messageType, this.pageContents[this.currentPage], this.precachedData[this.currentPage])
				.setOptions(this.options)
				.renderContent();
		}
		this.paginationRenderer.setPageContent(this.pageContents[this.currentPage]);
		this.paginationRenderer.setCurrentPage(this.currentPage);
	},
	cleanupLoadPages: function() {
		console.log("cleanupLoadPages");
		/* clear dupes */
		var existing = {};
		this.loadPages = this.loadPages.filter(function(item, i) {
			if (existing[item])
				return false;
			existing[item] = true;
			return true;
		});
		var page;
		
		this.loadPages.slice(0).each(function (page) {
			console.log('CHECK',page);
			/* clear cached pages */
			if (typeof this.precachedData[page] != 'undefined') {
				console.log('DATA HIT',page);
				this.loadPages.erase(page);
				return;
			}
			/* clear rendererd pages */
			if (typeof this.pageContents[page] != 'undefined') {
				console.log('CONTENT HIT',page);
				this.loadPages.erase(page);
			}
		}.bind(this));
	},
	setPageEvent: function(event, dir) {
		if (this.ajax)
			this.ajax.cancel();
			
		console.log("setPageEvent: ",dir);
        //Don't allow browsing beyond the available pages
		if (dir == 'prev') {
			this.currentPage--;
			if (this.currentPage < 0) {
				this.currentPage = 0;
			}
		} else if (dir == 'next') {
			if (this.totalPages - 1 > this.currentPage) {
				this.currentPage++;
			}
		}

		// TODO: optimize this a little, load only clusters of two or so
		for(var i = 0; i <= 2; i++) {
			if (dir == 'next') {
				if (this.currentPage+i <= (this.totalPages-1)) {
					this.loadPages.push(this.currentPage+i);
				}
			} else {
				if (this.currentPage-i >= 0) {
					this.loadPages.push(this.currentPage-i);
				}
			}
		}

		this.callRenderer();
	}
});


var Tabbar = new Class({
	Implements : Options,
	options : {
		tabContainer : undefined,
		content : undefined,
		tabs : {},
		tabSelector: 'a'
	},
	subControllers: {},
	ajax: undefined,
	initialize : function(options) {
		this.setOptions(options);
		
		// create an indexed array from the map
		var tabsIndex = [];
		for(var i in this.options.tabs) {
			tabsIndex.push({name: i, url: this.options.tabs[i]});
		}
	
		// create the ajax request
		this.ajax = new Request.JSON({ url: '', link: 'cancel' });
	
		/* if a url hash is set with a valid tab name, activate that tab */
		var hash = location.hash.substring(1);
		var t = this.options.tabs[hash];
		var overrideActive;
		if (typeof t != 'undefined') {
			overrideActive = hash;
		}
		
		var tabs = this.options.tabContainer;
		// loop through all tabs and set them up
		tabs.getElements(this.options.tabSelector).each(function(item, i) {
			var tabName = tabsIndex[i].name;
			item.store('fx', new Fx.BGAnim(item));
			if (!tabsIndex[i])
				return;
			if (item.hasClass('active') || overrideActive == tabName)
				this.setActive(tabName, item);			
			item.addEvent('click', this.clickHandler.bindWithEvent(this, [tabName, item]));
		}.bind(this));
		
	},
	ajaxHandler: function(response, tabName, tab) {
		try {
			tab.retrieve('fx').cancel();
			tab.removeClass('ajax');
			var type = response.messageType;
			if (typeof type == 'undefined' || typeof AJAX_MESSAGETYPE[type] == 'undefined') {
				console.log("ajaxHandler can't handle: "+type);
				console.log(AJAX_MESSAGETYPE);
				// TODO: errorhandling
				return;
			}
			
			var stype = AJAX_MESSAGETYPE[type];
			
			// create controller
			var controller = this.subControllers[tabName] = this.createController(stype, response);
			
			// set options
			controller.setOptions( {
				tabName: tabName,
				tabUrl: this.options.tabs[tabName]
			});
			
			// add event handlers for ajax loading anim
			controller.addEvent('loading', function() { 
				tab.addClass('ajax');
				this.setContentActive(false);
				var fx = tab.retrieve('fx');
				fx.start();
			}.bind(this));
			controller.addEvent('load', function() { 
				tab.removeClass('ajax');
				this.setContentActive(true); 
				var fx = tab.retrieve('fx');
				fx.cancel();
			}.bind(this));
			
			// start the controller
			controller.start();
		} catch (e) {
			console.log(e);
		}
	},
	createController : function(type, response) {
		switch (type) {
			case 'logbook': return new SimpleSubController(response,this.options.content);
			case 'news': return new SimpleSubController(response,this.options.content);
			case 'pm': return new SimpleSubController(response,this.options.content);
			case 'usersonline': return new RefreshingSubController(response,this.options.content);
			case 'chatrooms': return new SimpleSubController(response,this.options.content);
			case 'quickbrowser': return new PaginatedSubController(response,this.options.content);
		}
		console.log("CreateController: Could not find controller for "+type);
		return null;
	},
	clickHandler: function(event, tabName, tab) {
		event && event.stop();
		tab.blur();
		//location.hash = tabName;
		this.setActive(tabName, tab);
	},
	setActive: function(tabName, tab) {
		this.options.tabContainer.getElements(this.options.tabSelector+'.active').each(function(item) {
			item.removeClass('active');
			item.removeClass('ajax');
			var fx = item.retrieve('fx');
			fx && fx.cancel();
		});
		tab.addClass('active');
		if (typeof this.subControllers[tabName] != 'undefined') {
			$each(this.subControllers, function(controller, index) {
					controller.deactivate(); //TODO: deactivate is currenly only called the second time you switch to another tab... Think we need to add something to how we generate the first tab loaded (which is not done in JS)
			});
			this.subControllers[tabName].activate();
			this.setContentActive(true);
			var url = this.options.tabs[tabName];	
			this.subControllers[tabName].start();
			return;
		}
		if (typeof __INLINEJSON != 'undefined' && typeof __INLINEJSON[tabName] != 'undefined') {
			this.setContentActive(true);
			this.ajaxHandler( __INLINEJSON[tabName], tabName, tab);
			return;
		}

		tab.addClass('ajax');
		tab.retrieve('fx').start();
		this.setContentActive(false);
		var url = this.options.tabs[tabName];	
		this.ajax.options.url = url;
		this.ajax.removeEvents('complete');
		this.ajax.addEvent('complete', this.ajaxHandler.bindWithEvent(this, [tabName, tab]));
		this.ajax.send();
	},
	setContentActive: function(bool) {
		var c = this.options.content;
		if (!bool) {
			c.setStyle('position', 'relative');
			var bgcolor;
			// get background-color from parent element
			for (var p = c; p && (bgcolor = p.getStyle('background-color')) == 'transparent'; p = p.getParent()); 
			c.grab(this.contentOverlay = new Element('div', { 
				styles: { 
					background: bgcolor, 
					opacity: 0.5,
					width: '100%',
					height: '100%',
					position: 'absolute',
					top: 0,
					left: 0
				}
			}));
			//this.contentOverlay.store('fx', new Fx.Tween(this.contentOverlay).start('opacity', 0.5));
		} else {
			if (this.contentOverlay) {
				/*var fx = this.contentOverlay.retrieve('fx');
				if (fx && fx.cancel)
					fx.cancel();*/
				this.contentOverlay.dispose();
				this.contentOverlay = null;
			}
		}
	}
});

Fx.BGAnim = new Class({
	Extends: Fx,
	pos: undefined,
	element: undefined,
	options: {

	},
	initialize: function(element,options) {
		this.element = element;
		return this.parent(options);
	},
	start: function() {
		var bgpos = this.element.getStyle('background-position');
		if (bgpos) bgpos = bgpos.split(' ');
		else bgpos = [0,0];
		this.pos = parseInt(bgpos[0]);
		this.otherPos = bgpos[1];
		return this.parent();
	},
	step: function() {
		var time = $time();
		var thisLastTime = $chk(this.lastTime) ? this.lastTime : this.time;
		var diff = time - thisLastTime;
		this.lastTime = time;

		this.pos += diff/10;
			
		this.set(this.pos);
	},
	cancel: function() {
		this.element.setStyle('background-position', '');
		this.parent();
	},
	set: function(pos) {
		this.element.setStyle('background-position', pos+'px '+this.otherPos);
	},
	startTimer: function() {
		this.lastTime = $time();
		return this.parent();
	}
});

new Element('img', {src: SF.app.staticUrl+'/layouts/sofurry/images/tab-light-activity.png' });
if (typeof console == 'undefined') console = {};
if (typeof console.log == 'undefined') console.log = $empty;
if (typeof console.info == 'undefined') console.info = $empty;
if (typeof console.debug == 'undefined') console.debug = $empty;
if (typeof console.warn == 'undefined') console.warn = $empty;
if (typeof console.error == 'undefined') console.error = $empty;
if (typeof console.time == 'undefined') console.time = $empty;
if (typeof console.timeEnd == 'undefined') console.timeEnd = $empty;
if (typeof console.profile == 'undefined') console.profile = $empty;
if (typeof console.profileEnd == 'undefined') console.profileEnd = $empty;
if (typeof console.trace == 'undefined') console.trace = $empty;
if (typeof console.group == 'undefined') console.group = $empty;
if (typeof console.groupEnd == 'undefined') console.groupEnd = $empty;
if (typeof console.dir == 'undefined') console.dir = $empty;
if (typeof console.dirxml == 'undefined') console.dirxml = $empty;


new Element('img', {src: SF.app.staticUrl+'/images/ajaxindicator_large.gif' });
new Element('img', {src: SF.app.staticUrl+'/images/ajaxindicator_small.gif' });
new Element('img', {src: SF.app.staticUrl+'/images/ajaxindicator_large_darkbg.gif' });
new Element('img', {src: SF.app.staticUrl+'/images/ajaxindicator_small_darkbg.gif' });
var sidebarprogressindicator = '<center><img src="'+SF.app.staticUrl+'/images/ajaxindicator_large.gif"></center>';

var FancyUserMenu = new Class({
	initialize: function(element) {
		this.element = element;
		this.timeout = null;
		
		this.dropshadow = new DropShadow(this.element);
		
		new DropShadow(this.getList()).show();
		
		this.element.addEvent('mouseenter', function() {
			$clear(this.timeout);
			this.timeout = null;
			if (FancyUserMenu.activeMenu) {
				FancyUserMenu.activeMenu.hide();
			}
			this.show();
			FancyUserMenu.activeMenu = this;
		}.bindWithEvent(this));
		this.element.addEvent('mouseleave', function() {
			this.timeout = (function() {
				this.hide();
				FancyUserMenu.activeMenu = null;				
			}).delay(400, this);			
		}.bindWithEvent(this));
	},
	hide: function() {
		$clear(this.timeout);
		this.timeout = null;
		this.element.removeClass('active');
		this.dropshadow.hide();
	},
	show: function() {
		this.element.addClass('active');
		this.dropshadow.show();
	},
	getList: function() {
		if (!this.list)
			this.list = this.element.getElement('ul');
		if(this.list == null) { this.list = this.element }
		return this.list;
	},
	getListContainer: function() {
		if (!this.listContainer)
			this.listContainer = this.element.getElement('div');
		return this.listContainer;
	}
});
FancyUserMenu.activeMenu = null;

var DropShadow = new Class({
	initialize: function(element) {
		this.element = element;
		return this;
	},
	show: function() {
		if (this.dropshadow) {
			this.dropshadow.addClass('dropshadow');
			return;
		}
		
		this.dropshadow = new Element('span', { 'class': 'dropshadow', styles: { display: 'inline-block' } });
		this.dropshadow.wraps(this.element);
		new Element('span', { 'class': 'dropshadows' })
		.set('html', '<span class="tl"></span>'+
			'<span class="bl"></span>'+
			'<span class="tr"></span>'+
			'<span class="br"></span>').inject(this.dropshadow, 'top');
		return this;
	},
	hide: function() {
		if (!this.dropshadow)
			return;
		this.dropshadow.removeClass('dropshadow');
		return this;
	}
});


window.addEvent('domready', function() {
	$$('.fancyusermenu').each(function(item) {
		new FancyUserMenu(item);
	});	
});
 
var Flashmessage = new Class({
	initialize: function(element) {
		this.container = element;
		this.bg = element.getElement('.fm-bg');
		this.message = element.getElement('.fm');
		this.error = element.hasClass('error');
		this.bg.setStyle('opacity', 0.5);
		
		this.slide = new Fx.Slide(this.container, { transition: Fx.Transitions.Bounce.easeOut, duration: 1000 }).hide();

	},
	flash: function() {
		var slide = this.slide;
		slide.slideIn();
		if(this.error)
			new Fx.Tween(this.bg, {duration: 3000, property: 'background-color' }).set('#f00').start('#000');
		
		(function() {
			slide.options.duration = 500;
			slide.options.transition = Fx.Transitions.Back.easeIn;
			slide.slideOut();
		}).delay(5000);
	}
});

if (Browser.Platform.ipod) {
	window.effectsDisabled = true;
}

Element.implement({
	fireDOMEvent: function(evt) {
		if (document.createEvent) {
			var evObj = document.createEvent("MouseEvents");
			evObj.initEvent(evt, true, false);
			this.dispatchEvent(evObj);
		} else if (document.createEventObject) {
			this.fireEvent("on" + evt);
		}
	}	
});

if (!Browser.Engine.trident && !window.effectsDisabled) {
	document.write('<style type="text/css">.box-layout .box-image img { visibility: hidden; }</style>');
	
	var updateBoxImages = function(parent) {
		Profiler.start('Image FadeIn');
		if (typeof parent == 'undefined')
			parent = $(document.body);
		
		Profiler.start('Image FadeIn::getElements');
		var elements = parent.getElements('.box-layout .box-image');
		Profiler.stop('Image FadeIn::getElements');
		
		var i;
		elements.each(function(item) {
			Profiler.start('Image FadeIn::getImages');
			var images = item.getElements('img');
			Profiler.stop('Image FadeIn::getImages');
			images.each(function(img) {
				Profiler.start('Image FadeIn::handleImage');
				if (img.retrieve('fadein'))
					return;
				img.store('fadein', true);
				var limg = new Image();
				var fx = new Fx.Tween(img, { duration: 'short' });
				var start = $time();
				limg.addEvent('load', function(event) {
					//console.log('FADEIN:LOAD',img);
					if ($time() - start > 250 && img.getStyle('display') != 'none')
						fx.set('opacity', 0).start('opacity', 1); 
					img.setStyle('visibility', 'visible');
				});
				//(function() {
				limg.src = img.src;
				//}).delay(500+(Math.random()*1000));
				Profiler.stop('Image FadeIn::handleImage');
				i++;
			});
		});
		Profiler.stop('Image FadeIn');
	};
	
	window.addEvent('domready', updateBoxImages);
} else {
	var updateBoxImages = function() {};
}

window.addEvent('domready', function() {
	
	/* make tabbar tabs switch activity really quick */
	/* but only middle and left since right is most likely ajaxified */
	$$('.tabbar.middle .top, .tabbar.left .top').each(function(item) {
		var tabs = item.getElements('a');
		tabs.each(function(tab) {
			tab.addEvent('click', function(event) {
				item.getElements('a.active').each(function(active) { active.removeClass('active'); });
				tab.addClass('active');
			});
		});
	});
	

	return;

});

window.addEvent('domready', function() {

	$$('.highlight .box-link').addEvent('mouseenter', function() {
		var mainpic = document.getElementById('highlight_mainpic');
		var mainpic_a = document.getElementById('highlight_mainpic_a');
		var link = this.getChildren()[0].href;
		var box = this.getChildren('.box-image')[0].getChildren('.box-image-border')[0].getChildren('.box-image-bg')[0];
		var img = box.getChildren('.small-thumbs-img').getChildren()[0][0];
		var that = img.src;

		new Fx.Tween(mainpic).start('opacity',0).chain(function() {
			mainpic.src = that;
			mainpic_a.href = link;
			this.start('opacity',1);
		});
	});
	
});



var Console = new Class({
	consoleDiv: undefined,
	watches: [],
	logMessages: [],
	initialize: function() {
		
	},
	enable: function() {
		this.consoleDiv = new Element('div', {
			styles: {
				background: '#fff',
				color: '#000',
				position: 'fixed',
				top: 0,
				left: 0
			}
		});
		if (Browser.Engine.trident) {
			this.consoleDiv.setStyles({
				position: 'absolute'
			});
		}
		$(document.body).grab(this.consoleDiv);
	},
	disable: function() {
		this.consoleDiv.destroy();
		this.watches = [];
		this.log = [];
	},
	addWatch: function(expr) {
		this.watches.push(expr);
	},
	log: function(expr) {
		this.log.push(expr);
		while (log.count > 10)
			this.log.shift();
	},
	updateWatches: function() {
		
	}
});


function ajaxLogbookDelete(lbitem, ldid, dotransition){
	var ajaxrmlogbook = new Request.JSON({ url: SF.app.homeUrl+'/ajaxfetch.php', link: 'ignore', onSuccess: function() {
		if(dotransition){
			new Fx.Slide(lbitem).slideOut().chain(function() { lbitem.dispose(); });
		}else{
			lbitem.dispose();
		}
		
	}});
	ajaxrmlogbook.send('f=rmlogbook&lbid='+ldid);
}

window.addEvent('domready', function() {
	var sidebar = $('sidebar-cont');
	if (sidebar) {
		var sidebarPos = sidebar.getPosition();
		var scroll;
		var hooked = false;
		var f = function() {
			scroll = Page.getScroll();
			if (scroll.y > sidebarPos.y-12) {
				if (!hooked) {
					hooked = true;
					sidebar.setStyle('position','fixed');
				}
				sidebar.setStyles({
					top: 0,
					left: sidebarPos.x
				});
			} else if (hooked) {
				hooked = false;
				sidebar.setStyles({
					position: '',
					top: '0',
					left: ''
				});
			}
		};
		window.addEvent('scroll', f);		
		window.addEvent('resize', function() {
			if (hooked) {
				sidebar.setStyles({
					position: '',
					top: '',
					left: ''
				});
				sidebarPos = sidebar.getPosition();
				sidebar.setStyle('position','fixed');
			} else {
				sidebarPos = sidebar.getPosition();
			}
			f();
		});
		f();
	}
});

var PictureZoomer = new Class({
	Implements: [ Options ],
	options: {
		width: undefined,
		height: undefined
	},
	fullsizeUrl: undefined,
	isZoomed: false,
	overlay: undefined,
	zoomImage: undefined,
	zoomImageContainer: undefined,
	page: undefined,
	initialize: function(anchor, options) {
		this.setOptions(options);
		
		this.fullsizeUrl = anchor.get('href');

		anchor.addEvent('click', function(event) { 
			anchor.blur(); 
			event.stop();
			this.zoom();
		}.bindWithEvent(this));
		
	},
	zoom: function() {
		if (this.isZoomed)
			return;
		this.isZoomed = true;
		
		if (!this.page)
			this.page = $('page');
		
		this.page.addClass('noscroll');
		
		if (!this.overlay) {
			this.overlay = new Overlay({ color: '#000', opacity: 0.85 });
			this.overlay.addEvent('click', function() {
				this.unzoom();
			}.bindWithEvent(this));
		}
		this.overlay.show();
		
		if (!this.zoomImage) {
			var img;
			this.zoomImage = new Element('div', {'class': 'zoomimage-container'})
				.grab(new Element('div', {'class': 'zoomimage-container-inner'})
					.grab(img = new Element('img', { 
						src: this.fullsizeUrl,
						styles: {
							'border': '1em solid #fff'
						}
					})
				)
			);
			if (this.options.width && this.options.height) {
				img.setStyles({
					width: this.options.width,
					height: this.options.height					
				});
				var windowHeight = Page.getSize().height;
				console.log(this.options.height, windowHeight);
				if (this.options.height+30 < windowHeight) {
					console.log(Math.floor((windowHeight - this.options.height)/2));
					img.setStyle('margin-top', (Math.floor((windowHeight - this.options.height)/2)-30)+'px');
				}
			}
			this.zoomImage.addEvent('click', function() {
				this.unzoom();
			}.bindWithEvent(this));
		}

		document.body.appendChild(this.zoomImage);
	},
	unzoom: function() {
		if (!this.isZoomed)
			return;
		this.isZoomed = false;
		
		if (!this.page)
			this.page = $('page');
		
		this.page.removeClass('noscroll');
		
		this.overlay.remove();
		
		this.zoomImage.dispose();
	}
});

var windowIsFocused = '1';

function onFocus(){ 
	windowIsFocused = '1'; 
} 

function onBlur() { 
	windowIsFocused = '0';  
} 


if (!+"\v1") { // check for Internet Explorer 
	document.onfocusin = onFocus; 
	document.onfocusout = onBlur; 
} else { 
	window.onfocus = onFocus; 
	window.onblur = onBlur; 
	document.onfocus = onFocus; 
	document.onblur = onBlur; 
}

var queuedNotifications = false;

window.addEvent('domready', function() {
	statusUpdate();
});

function statusUpdate(){
	if(queuedNotifications != false){
		handleStatusUpdateResponse(queuedNotifications);
	}
}

function handleStatusUpdateResponse(response){
	if(response.notificationcount != undefined){
		console.log('Got '+response.notificationcount+' notifications to deliver!');
		if(response.notificationcount > 0){
			for(var i = 0; i < response.notificationcount; i++){
				console.log('Got ' + response.notifications[i].header);
				showNotification(response.notifications[i]);
			}
		}
	}
}

var isNotboxCreated = false;

var notboxDisappear;

function showNotification(data){
	if(data == undefined || data == null){
		data = new Object();
		data.header = 'Header';
		data.message = 'Message';
		data.color = '#FF0000';
		data.image = '/layouts/sofurry/images/icons/report.png';
	}
	console.log(data);
	if(!isNotboxCreated){
		document.body.innerHTML += '<div id="notificationbox"><div id="notboxtitle"></div><div id="notboxcontent"><div id="notboximage"></div><div id="notboxmessage"></div></div></div>';
		isNotboxCreated = true;
	}else{
		clearTimeout(notboxDisappear);
	}
	document.getElementById('notificationbox').style.display = 'block';
	document.getElementById('notboxtitle').innerHTML = data.header;
	document.getElementById('notboxmessage').innerHTML = data.message;
	document.getElementById('notboxcontent').style.background = data.color;
	if(data.image != false && data.image != 'false'){
		document.getElementById('notboximage').innerHTML = '<img src="' + data.image + '" alt="" width="34" height="34"/>';
	}
	notboxDisappear = setTimeout('document.getElementById(\'notificationbox\').style.display = \'none\';', 5000);
}

function Nuke_Checkall()
{
	void(d=document);
	void(el=d.getElementsByName('nukeSelection[]'));
	var allChecked = 1;
	for(i=0;i<el.length;i++)
		if (el[i].checked==0) {
			allChecked = 0;
		}
	for(i=0;i<el.length;i++)
		void(el[i].checked=(1-allChecked))
}
function Nuke_Invert(){
	void(d=document);
	void(el=d.getElementsByName('nukeSelection[]'));
	for(i=0;i<el.length;i++)
		void(el[i].checked=(1-el[i].checked))
}

window.addEvent('domready', function() {
	if ($('nukeselectallbutton'))
	$('nukeselectallbutton').addEvent('click', function(){
		Nuke_Checkall();
	});
	if ($('nukeinvertbutton'))
	$('nukeinvertbutton').addEvent('click', function(){
		Nuke_Invert();
	});
	return
});
/*
  Moogets - TextboxList 0.2
  - MooTools version required: 1.2
  - MooTools components required: Element.Event, Element.Style and dependencies.
  
  Credits:
  - Idea: Facebook + Apple Mail
  - Caret position method: Diego Perini <http://javascript.nwbox.com/cursor_position/cursor.js>
  
  Changelog:
  - 0.1: initial release
  - 0.2: code cleanup, small blur/focus fixes
*/

/* Copyright: Guillermo Rauch <http://devthought.com/> - Distributed under MIT - Keep this message! */

/*
requirements unclear. what elements does it affect? can it affect more than one input field?
*/

Element.implement({
  
  getCaretPosition: function() {
    if (this.createTextRange) {
      var r = document.selection.createRange().duplicate();
    	r.moveEnd('character', this.value.length);
    	if (r.text === '') return this.value.length;
    	return this.value.lastIndexOf(r.text);
    } else return this.selectionStart;
  }
  
});

var ResizableTextbox = new Class({
  
  Implements: Options,
  
  options: {
    min: 50,
    max: 500,
    step: 12
  },
  
  initialize: function(element, options) {
    var that = this;
    this.setOptions(options);
    this.el = $(element);
    this.width = this.el.offsetWidth;
    this.el.addEvents({
      'keydown': function() {
        this.store('rt-value', this.get('value').length);
      },
      'keyup': function() {
        var newsize = that.options.step * this.get('value').length;
        if(newsize <= that.options.min) newsize = that.width;
        if(! (this.get('value').length == this.retrieve('rt-value') || newsize <= that.options.min || newsize >= that.options.max))
          this.setStyle('width', newsize);
      }
    });
  }
  
});

var TextboxList = new Class({
  
  Implements: [Events, Options],

  options: {/*
    onFocus: $empty,
    onBlur: $empty,
    onInputFocus: $empty,
    onInputBlur: $empty,
    onBoxFocus: $empty,
    onBoxBlur: $empty,
    onBoxDispose: $empty,*/
    resizable: {},
    className: 'bit',
    separator: '###',
    extrainputs: true,
    startinput: true,
    hideempty: true
  },
  
  initialize: function(element, options) {
    this.setOptions(options);
    this.element = $(element).setStyle('display', 'none');    
    this.bits = new Hash;
    this.events = new Hash;
    this.count = 0;
    this.current = false;
    this.maininput = this.createInput({'class': 'maininput'});
    this.holder = new Element('ul', {
      'class': 'holder', 
      'events': {
        'click': function(e) { 
          e = new Event(e).stop();
          if(this.maininput != this.current) this.focus(this.maininput); 
        }.bind(this)
      }
    }).inject(this.element, 'before').adopt(this.maininput);
    this.makeResizable(this.maininput);
    this.setEvents();
  },
  
  setEvents: function() {
    document.addEvent(Browser.Engine.trident ? 'keydown' : 'keypress', function(e) {    
      if(! this.current) return;
      if(this.current.retrieve('type') == 'box' && e.code == Event.Keys.backspace) new Event(e).stop();
    }.bind(this));      
         
    document.addEvents({
      'keyup': function(e) { 
        e = new Event(e).stop();
        if(! this.current) return;
        switch(e.code){
          case Event.Keys.left: return this.move('left');
          case Event.Keys.right: return this.move('right');
          case Event.Keys.backspace: return this.moveDispose();
        }
      }.bind(this),
      'click': function() { this.fireEvent('onBlur').blur(); }.bind(this)
    });
  },
  
  update: function() {
    this.element.set('value', this.bits.getValues().join(this.options.separator));
    return this;
  },
  
  add: function(text, html) {
    var id = this.options.className + '-' + this.count++;
    var el = this.createBox($pick(html, text), {'id': id}).inject(this.current || this.maininput, 'before');
    el.addEvent('click', function(e) {
      e = new Event(e).stop();
      this.focus(el);
    }.bind(this));
    this.bits.set(id, text);    
    if(this.options.extrainputs && (this.options.startinput || el.getPrevious())) this.addSmallInput(el, 'before');
    return el;
  },
  
  addSmallInput: function(el, where) {
    var input = this.createInput({'class': 'smallinput'}).inject(el, where);
    input.store('small', true);
    this.makeResizable(input);
    if(this.options.hideempty) input.setStyle('display', 'none');
    return input;
  },
  
  dispose: function(el) {
    this.bits.erase(el.id);
    if(el.getPrevious().retrieve('small')) el.getPrevious().destroy();
    if(this.current == el) this.focus(el.getNext());
    if(el.retrieve('type') == 'box') this.fireEvent('onBoxDispose', el);
    el.destroy();    
    return this;
  },
  
  focus: function(el, nofocus) {
    if(! this.current) this.fireEvent('onFocus', el);
    else if(this.current == el) return this;
    this.blur();
    el.addClass(this.options.className + '-' + el.retrieve('type') + '-focus');
    if(el.retrieve('small')) el.setStyle('display', 'block');
    if(el.retrieve('type') == 'input') {
      this.fireEvent('onInputFocus', el);      
      if(! nofocus) this.callEvent(el.retrieve('input'), 'focus');
    }
    else this.fireEvent('onBoxFocus', el);
    this.current = el;    
    return this;
  },
  
  blur: function(noblur) {
    if(! this.current) return this;
    if(this.current.retrieve('type') == 'input') {
      var input = this.current.retrieve('input');
      if(! noblur) this.callEvent(input, 'blur');   
      this.fireEvent('onInputBlur', input);
    }
    else this.fireEvent('onBoxBlur', this.current);
    if(this.current.retrieve('small') && ! input.get('value') && this.options.hideempty) 
      this.current.setStyle('display', 'none');
    this.current.removeClass(this.options.className + '-' + this.current.retrieve('type') + '-focus');
    this.current = false;
    return this;
  },
  
  createBox: function(text, options) {
    return new Element('li', $extend(options, {'class': this.options.className + '-box'})).set('html', text).store('type', 'box');
  },
  
  createInput: function(options) {
    var li = new Element('li', {'class': this.options.className + '-input'});
    var el = new Element('input', $extend(options, {
      'type': 'text', 
      'events': {
        'click': function(e) { e = new Event(e).stop(); },
        'focus': function(e) { if(! this.isSelfEvent('focus')) this.focus(li, true); }.bind(this),
        'blur': function() { if(! this.isSelfEvent('blur')) this.blur(true); }.bind(this),
        'keydown': function(e) { this.store('lastvalue', this.value).store('lastcaret', this.getCaretPosition()); }
      }
    }));
    return li.store('type', 'input').store('input', el).adopt(el);
  },
  
  callEvent: function(el, type) {
    this.events.set(type, el);
    el[type]();
  },
  
  isSelfEvent: function(type) {
    return (this.events.get(type)) ? !! this.events.erase(type) : false;
  },
  
  makeResizable: function(li) {
    var el = li.retrieve('input');
    el.store('resizable', new ResizableTextbox(el, $extend(this.options.resizable, {min: 12, max: this.element.getStyle('width').toInt()})));
    return this;
  },
  
  checkInput: function() {
    var input = this.current.retrieve('input');
    return (! input.retrieve('lastvalue') || (input.getCaretPosition() === 0 && input.retrieve('lastcaret') === 0));
  },
  
  move: function(direction) {
    var el = this.current['get' + (direction == 'left' ? 'Previous' : 'Next')]();
    if(el && (! this.current.retrieve('input') || ((this.checkInput() || direction == 'right')))) this.focus(el);
    return this;
  },
  
  moveDispose: function() {
    if(this.current.retrieve('type') == 'box') return this.dispose(this.current);
    if(this.checkInput() && this.bits.getKeys().length && this.current.getPrevious()) return this.focus(this.current.getPrevious());
  }
  
});var TaglistLoader = new Class({
	response: null,
	url: 'ajaxtags.php',
	ajax: null,
	listeners: [],
	initialize: function() {
	},
	get: function(f) {
		if ($type(f) != 'function')
			return;
		
		if (this.response == null) {
			if (this.ajax == null) {
				this.ajax = new Request.JSON({'url': SF.app.homeUrl+'/'+this.url, 'onComplete': function(j) {
					this.response = j
					f.call(this, this.response);
					this.listeners.each(function(fn){
						fn.call(this, this.response);
					}.bind(this));
					this.listeners = [];
				}.bind(this)}).get(); 
			} else {
				this.listeners.push(f);
			}
		} else {
			f.call(this, this.response);
		}
	},
	reset: function() {
		if (this.ajax) {
			this.ajax.cancel();
		}
		this.ajax = null;
		this.response = null;
	}
});
TaglistLoader = new TaglistLoader();/*
  Moogets - TextboxList + Autocomplete 0.2
  - MooTools version required: 1.2
  - MooTools components required: Element.Event, Element.Style, Selectors, Request.JSON and dependencies.
  
  Credits:
  - Idea: Facebook
  
  Changelog:
  - 0.1: initial release
  - 0.2: added click support, removed $attributes use, code cleanup
*/

/* Copyright: Guillermo Rauch <http://devthought.com/> - Distributed under MIT - Keep this message! */

/* requires textboxlist.js */

var FacebookList = new Class({
  
  Extends: TextboxList,
  
  options: {    
    onBoxDispose: function(item) { this.autoFeed(item.retrieve('text')); },
    onInputFocus: function() { this.autoShow(); },    
    onInputBlur: function(el) { 
      this.lastinput = el;
      this.blurhide = this.autoHide.delay(200, this);
    },
    autocomplete: {
      'opacity': 1,
      'maxresults': 10,
      'minchars': 1
    }
  },
  
  initialize: function(element, autoholder, options) {
    this.parent(element, options);
    this.data = [];
		this.autoholder = $(autoholder).set('opacity', this.options.autocomplete.opacity);
		this.autoresults = this.autoholder.getElement('ul');
		var children = this.autoresults.getElements('li');
    children.each(function(el) { this.add(el.innerHTML); }, this); 
  },
  
  autoShow: function(search) {
    this.autoholder.setStyle('display', 'block');
    this.autoholder.getChildren().setStyle('display', 'none');
    if(! search || ! search.trim() || (! search.length || search.length < this.options.autocomplete.minchars)) 
    {
      this.autoholder.getElement('.default').setStyle('display', 'block');
      this.resultsshown = false;
    } else {
      this.resultsshown = true;
      this.autoresults.setStyle('display', 'block').empty();
      
      //Changed by Toumal: Copy the data array with the available keywords and append the search term to it
      //This allows new keywords to be added.
      var tempdata = [];
      tempdata = this.data.slice();
      tempdata[this.data.length] = search; 
      tempdata = tempdata.filter(function(str) { return str ? str.test(search, 'i') : false; });
	  tempdata = tempdata.sort( function (a,b) { return levenshtein(a,search)-levenshtein(b,search) });
	  tempdata.each(function(result, ti) {
        if(ti >= this.options.autocomplete.maxresults) return;
        var that = this;
        var el = new Element('li').addEvents({
          'mouseenter': function() { that.autoFocus(this); },
          'click': function(e) { 
            new Event(e).stop();
            that.autoAdd(this); 
          }
        }).set('html', this.autoHighlight(result, search)).inject(this.autoresults);
        el.store('result', result);
        if(ti == 0) this.autoFocus(el);
      }, this);
    }
    return this;
  },
  
  autoHighlight: function(html, highlight) {
    return html.replace(new RegExp(highlight, 'gi'), function(match) {
      return '<em>' + match + '</em>';
    });
  },
  
  autoHide: function() {    
    this.resultsshown = false;
    this.autoholder.setStyle('display', 'none');    
    return this;
  },
  
  autoFocus: function(el) {
    if(! el) return;
    if(this.autocurrent) this.autocurrent.removeClass('auto-focus');
    this.autocurrent = el.addClass('auto-focus');
    return this;
  },
  
  autoMove: function(direction) {    
    if(!this.resultsshown) return;
    this.autoFocus(this.autocurrent['get' + (direction == 'up' ? 'Previous' : 'Next')]());
    return this;
  },
  
  autoFeed: function(text) {
    this.data.include(text);    
    return this;
  },
  
  autoAdd: function(el) {
    if(!el || ! el.retrieve('result')) return;
    this.add(el.retrieve('result'));
    delete this.data[this.data.indexOf(el.retrieve('result'))];
    this.autoHide();
    var input = this.lastinput || this.current.retrieve('input');
    input.set('value', '').focus();
    return this;
  },
  
  createInput: function(options) {
    var li = this.parent(options);
    var input = li.retrieve('input');
    input.addEvents({
      'keydown': function(e) {
        this.dosearch = false;
        switch(new Event(e).code) {
          case Event.Keys.up: return this.autoMove('up');
          case Event.Keys.down: return this.autoMove('down');
          case 188: 
            if(! this.autocurrent) break;
            this.autoAdd(this.autocurrent);
            this.autocurrent = false;
            this.autoenter = true;
            break;
          case Event.Keys.enter: 
            if(! this.autocurrent) break;
            this.autoAdd(this.autocurrent);
            this.autocurrent = false;
            this.autoenter = true;
            break;
          case Event.Keys.esc: 
            this.autoHide();
            if(this.current && this.current.retrieve('input'))
              this.current.retrieve('input').set('value', '');
            break;
          default: this.dosearch = true;
        }
      }.bind(this),
      'keyup': function() {
        if(this.dosearch) this.autoShow(input.value);
      }.bind(this)
    });
    input.addEvent(Browser.Engine.trident ? 'keydown' : 'keypress', function(e) { 
      if(this.autoenter) new Event(e).stop()
      this.autoenter = false;
    }.bind(this));
    return li;
  },
  
  createBox: function(text, options) {
    var li = this.parent(text, options);
    return li.addEvents({
      'mouseenter': function() { this.addClass('bit-hover') },
      'mouseleave': function() { this.removeClass('bit-hover') }
    }).adopt(new Element('a', {
      'href': '#',
      'class': 'closebutton',
      'events': {
        'click': function(e) {
          new Event(e).stop();
          if(! this.current) this.focus(this.maininput);
          this.dispose(li);
        }.bind(this)
      }
    })).store('text', text);
  }

});


  // Calculate Levenshtein distance between two strings  
  function levenshtein(s1, s2) {
    if (s1 == s2) {
        return 0;
    } 
	if (!s1 || !s2)
		return 100;
		
    var s1_len = s1.length;
    var s2_len = s2.length;
    if (s1_len === 0) {
        return s2_len;    
	}
    if (s2_len === 0) {
        return s1_len;
    }
     // BEGIN STATIC
    var split = false;
    try{
        split=!('0')[0];
    } catch (e){
		split=true; // Earlier IE may not support access by string index
    }
    // END STATIC
    if (split){
        s1 = s1.split('');
        s2 = s2.split('');
    }
 
    var v0 = new Array(s1_len+1);
    var v1 = new Array(s1_len+1); 
    var s1_idx=0, s2_idx=0, cost=0;
    for (s1_idx=0; s1_idx<s1_len+1; s1_idx++) {
        v0[s1_idx] = s1_idx;
    }    var char_s1='', char_s2='';
    for (s2_idx=1; s2_idx<=s2_len; s2_idx++) {
        v1[0] = s2_idx;
        char_s2 = s2[s2_idx - 1];
         for (s1_idx=0; s1_idx<s1_len;s1_idx++) {
            char_s1 = s1[s1_idx];
            cost = (char_s1 == char_s2) ? 0 : 1;
            var m_min = v0[s1_idx+1] + 1;
            var b = v1[s1_idx] + 1;
            var c = v0[s1_idx] + cost;
            if (b < m_min) {
                m_min = b; 
			}
            if (c < m_min) {
                m_min = c; 
			}
            v1[s1_idx+1] = m_min;
        }
        var v_tmp = v0;
        v0 = v1;
        v1 = v_tmp;
	}
    return v0[s1_len];
  }
