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

/*
---

script: More.js

description: MooTools More

license: MIT-style license

authors:
- Guillermo Rauch
- Thomas Aylott
- Scott Kyle

requires:
- core:1.2.4/MooTools

provides: [MooTools.More]

...
*/

MooTools.More = {
	'version': '1.2.4.2',
	'build': 'bd5a93c0913cce25917c48cbdacde568e15e02ef'
};

/*
---

script: Log.js

description: Provides basic logging functionality for plugins to implement.

license: MIT-style license

authors:
- Guillermo Rauch
- Thomas Aylott
- Scott Kyle

requires:
- core:1.2.4/Class
- /MooTools.More

provides: [Log]

...
*/

(function(){

var global = this;

var log = function(){
	if (global.console && console.log){
		try {
			console.log.apply(console, arguments);
		} catch(e) {
			console.log(Array.slice(arguments));
		}
	} else {
		Log.logged.push(arguments);
	}
	return this;
};

var disabled = function(){
	this.logged.push(arguments);
	return this;
};

this.Log = new Class({
	
	logged: [],
	
	log: disabled,
	
	resetLog: function(){
		this.logged.empty();
		return this;
	},

	enableLog: function(){
		this.log = log;
		this.logged.each(function(args){
			this.log.apply(this, args);
		}, this);
		return this.resetLog();
	},

	disableLog: function(){
		this.log = disabled;
		return this;
	}
	
});

Log.extend(new Log).enableLog();

// legacy
Log.logger = function(){
	return this.log.apply(this, arguments);
};

})();

/*
---

script: Class.Refactor.js

description: Extends a class onto itself with new property, preserving any items attached to the class's namespace.

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Class
- /MooTools.More

provides: [Class.refactor]

...
*/

Class.refactor = function(original, refactors){

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

	return original;

};

/*
---

script: Class.Binds.js

description: Automagically binds specified methods in a class to the instance of the class.

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Class
- /MooTools.More

provides: [Class.Binds]

...
*/

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

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


/*
---

script: Array.Extras.js

description: Extends the Array native object to include useful methods to work with arrays.

license: MIT-style license

authors:
- Christoph Pojer

requires:
- core:1.2.4/Array

provides: [Array.Extras]

...
*/
Array.implement({

	min: function(){
		return Math.min.apply(null, this);
	},

	max: function(){
		return Math.max.apply(null, this);
	},

	average: function(){
		return this.length ? this.sum() / this.length : 0;
	},

	sum: function(){
		var result = 0, l = this.length;
		if (l){
			do {
				result += this[--l];
			} while (l);
		}
		return result;
	},

	unique: function(){
		return [].combine(this);
	}

});

/*
---

script: Hash.Extras.js

description: Extends the Hash native object to include getFromPath which allows a path notation to child elements.

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Hash.base
- /MooTools.More

provides: [Hash.Extras]

...
*/

Hash.implement({

	getFromPath: function(notation){
		var source = this.getClean();
		notation.replace(/\[([^\]]+)\]|\.([^.[]+)|[^[.]+/g, function(match){
			if (!source) return null;
			var prop = arguments[2] || arguments[1] || arguments[0];
			source = (prop in source) ? source[prop] : null;
			return match;
		});
		return source;
	},

	cleanValues: function(method){
		method = method || $defined;
		this.each(function(v, k){
			if (!method(v)) this.erase(k);
		}, this);
		return this;
	},

	run: function(){
		var args = arguments;
		this.each(function(v, k){
			if ($type(v) == 'function') v.run(args);
		});
	}

});

/*
---

script: String.Extras.js

description: Extends the String native object to include methods useful in managing various kinds of strings (query strings, urls, html, etc).

license: MIT-style license

authors:
- Aaron Newton
- Guillermo Rauch

requires:
- core:1.2.4/String
- core:1.2.4/$util
- core:1.2.4/Array

provides: [String.Extras]

...
*/

(function(){
  
var special = ['À','à','Á','á','Â','â','Ã','ã','Ä','ä','Å','å','Ă','ă','Ą','ą','Ć','ć','Č','č','Ç','ç', 'Ď','ď','Đ','đ', 'È','è','É','é','Ê','ê','Ë','ë','Ě','ě','Ę','ę', 'Ğ','ğ','Ì','ì','Í','í','Î','î','Ï','ï', 'Ĺ','ĺ','Ľ','ľ','Ł','ł', 'Ñ','ñ','Ň','ň','Ń','ń','Ò','ò','Ó','ó','Ô','ô','Õ','õ','Ö','ö','Ø','ø','ő','Ř','ř','Ŕ','ŕ','Š','š','Ş','ş','Ś','ś', 'Ť','ť','Ť','ť','Ţ','ţ','Ù','ù','Ú','ú','Û','û','Ü','ü','Ů','ů', 'Ÿ','ÿ','ý','Ý','Ž','ž','Ź','ź','Ż','ż', 'Þ','þ','Ð','ð','ß','Œ','œ','Æ','æ','µ'];

var standard = ['A','a','A','a','A','a','A','a','Ae','ae','A','a','A','a','A','a','C','c','C','c','C','c','D','d','D','d', 'E','e','E','e','E','e','E','e','E','e','E','e','G','g','I','i','I','i','I','i','I','i','L','l','L','l','L','l', 'N','n','N','n','N','n', 'O','o','O','o','O','o','O','o','Oe','oe','O','o','o', 'R','r','R','r', 'S','s','S','s','S','s','T','t','T','t','T','t', 'U','u','U','u','U','u','Ue','ue','U','u','Y','y','Y','y','Z','z','Z','z','Z','z','TH','th','DH','dh','ss','OE','oe','AE','ae','u'];

var tidymap = {
	"[\xa0\u2002\u2003\u2009]": " ",
	"\xb7": "*",
	"[\u2018\u2019]": "'",
	"[\u201c\u201d]": '"',
	"\u2026": "...",
	"\u2013": "-",
	"\u2014": "--",
	"\uFFFD": "&raquo;"
};

var getRegForTag = function(tag, contents) {
	tag = tag || '';
	var regstr = contents ? "<" + tag + "[^>]*>([\\s\\S]*?)<\/" + tag + ">" : "<\/?" + tag + "([^>]+)?>";
	reg = new RegExp(regstr, "gi");
	return reg;
};

String.implement({

	standardize: function(){
		var text = this;
		special.each(function(ch, i){
			text = text.replace(new RegExp(ch, 'g'), standard[i]);
		});
		return text;
	},

	repeat: function(times){
		return new Array(times + 1).join(this);
	},

	pad: function(length, str, dir){
		if (this.length >= length) return this;
		var pad = (str == null ? ' ' : '' + str).repeat(length - this.length).substr(0, length - this.length);
		if (!dir || dir == 'right') return this + pad;
		if (dir == 'left') return pad + this;
		return pad.substr(0, (pad.length / 2).floor()) + this + pad.substr(0, (pad.length / 2).ceil());
	},

	getTags: function(tag, contents){
		return this.match(getRegForTag(tag, contents)) || [];
	},

	stripTags: function(tag, contents){
		return this.replace(getRegForTag(tag, contents), '');
	},

	tidy: function(){
		var txt = this.toString();
		$each(tidymap, function(value, key){
			txt = txt.replace(new RegExp(key, 'g'), value);
		});
		return txt;
	}

});

})();

/*
---

script: String.QueryString.js

description: Methods for dealing with URI query strings.

license: MIT-style license

authors:
- Sebastian Markbåge, Aaron Newton, Lennart Pilon, Valerio Proietti

requires:
- core:1.2.4/Array
- core:1.2.4/String
- /MooTools.More

provides: [String.QueryString]

...
*/

String.implement({

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

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

});

/*
---

script: URI.js

description: Provides methods useful in managing the window location and uris.

license: MIT-style license

authors:
- Sebastian Markbge
- Aaron Newton

requires:
- core:1.2.4/Selectors
- /String.QueryString

provides: URI

...
*/

var URI = new Class({

	Implements: Options,

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

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

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

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

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

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

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

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

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

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

	toURI: function(){
		return this;
	},

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

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

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

});

URI.prototype.toString = URI.prototype.valueOf = function(){
	return this.get('value');
};

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

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

String.implement({

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

});

/*
---

script: Elements.From.js

description: Returns a collection of elements from a string of html.

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Element
- /MooTools.More

provides: [Elements.from]

...
*/

Elements.from = function(text, excludeScripts){
	if ($pick(excludeScripts, true)) text = text.stripScripts();

	var container, match = text.match(/^\s*<(t[dhr]|tbody|tfoot|thead)/i);

	if (match){
		container = new Element('table');
		var tag = match[1].toLowerCase();
		if (['td', 'th', 'tr'].contains(tag)){
			container = new Element('tbody').inject(container);
			if (tag != 'tr') container = new Element('tr').inject(container);
		}
	}

	return (container || new Element('div')).set('html', text).getChildren();
};

/*
---

script: Element.Delegation.js

description: Extends the Element native object to include the delegate method for more efficient event management.

credits:
- "Event checking based on the work of Daniel Steigerwald. License: MIT-style license.	Copyright: Copyright (c) 2008 Daniel Steigerwald, daniel.steigerwald.cz"

license: MIT-style license

authors:
- Aaron Newton
- Daniel Steigerwald

requires:
- core:1.2.4/Element.Event
- core:1.2.4/Selectors
- /MooTools.More

provides: [Element.Delegation]

...
*/
(function(){
	
	var match = /(.*?):relay\(([^)]+)\)$/,
		combinators = /[+>~\s]/,
		splitType = function(type){
			var bits = type.match(match);
			return !bits ? {event: type} : {
				event: bits[1],
				selector: bits[2]
			};
		},
		check = function(e, selector){
			var t = e.target;
			if (combinators.test(selector = selector.trim())){
				var els = this.getElements(selector);
				for (var i = els.length; i--; ){
					var el = els[i];
					if (t == el || el.hasChild(t)) return el;
				}
			} else {
				for ( ; t && t != this; t = t.parentNode){
					if (Element.match(t, selector)) return document.id(t);
				}
			}
			return null;
		};

	var oldAddEvent = Element.prototype.addEvent,
		oldRemoveEvent = Element.prototype.removeEvent;
		
	Element.implement({

		addEvent: function(type, fn){
			var splitted = splitType(type);
			if (splitted.selector){
				var monitors = this.retrieve('$moo:delegateMonitors', {});
				if (!monitors[type]){
					var monitor = function(e){
						var el = check.call(this, e, splitted.selector);
						if (el) this.fireEvent(type, [e, el], 0, el);
					}.bind(this);
					monitors[type] = monitor;
					oldAddEvent.call(this, splitted.event, monitor);
				}
			}
			return oldAddEvent.apply(this, arguments);
		},

		removeEvent: function(type, fn){
			var splitted = splitType(type);
			if (splitted.selector){
				var events = this.retrieve('events');
				if (!events || !events[type] || (fn && !events[type].keys.contains(fn))) return this;

				if (fn) oldRemoveEvent.apply(this, [type, fn]);
				else oldRemoveEvent.apply(this, type);

				events = this.retrieve('events');
				if (events && events[type] && events[type].length == 0){
					var monitors = this.retrieve('$moo:delegateMonitors', {});
					oldRemoveEvent.apply(this, [splitted.event, monitors[type]]);
					delete monitors[type];
				}
				return this;
			}

			return oldRemoveEvent.apply(this, arguments);
		},

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

	});

})();

/*
---

script: Element.Measure.js

description: Extends the Element native object to include methods useful in measuring dimensions.

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

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Element.Style
- core:1.2.4/Element.Dimensions
- /MooTools.More

provides: [Element.Measure]

...
*/

Element.implement({

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

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

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

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

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

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

		return $extend(styles, size);
	}

});

/*
---

script: Element.Shortcuts.js

description: Extends the Element native object to include some shortcut methods.

license: MIT-style license

authors:
- Aaron Newton

requires:
- core:1.2.4/Element.Style
- /MooTools.More

provides: [Element.Shortcuts]

...
*/

Element.implement({

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

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

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

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

	show: function(display){
		return this.setStyle('display', display || this.retrieve('originalDisplay') || 'block');
	},

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

});


/*
---

script: Request.JSONP.js

description: Defines Request.JSONP, a class for cross domain javascript via script injection.

license: MIT-style license

authors:
- Aaron Newton
- Guillermo Rauch

requires:
- core:1.2.4/Element
- core:1.2.4/Request
- /Log

provides: [Request.JSONP]

...
*/

Request.JSONP = new Class({

	Implements: [Chain, Events, Options, Log],

	options: {/*
		onRetry: $empty(intRetries),
		onRequest: $empty(scriptElement),
		onComplete: $empty(data),
		onSuccess: $empty(data),
		onCancel: $empty(),
		log: false,
		*/
		url: '',
		data: {},
		retries: 0,
		timeout: 0,
		link: 'ignore',
		callbackKey: 'callback',
		injectScript: document.head
	},

	initialize: function(options){
		this.setOptions(options);
		if (this.options.log) this.enableLog();
		this.running = false;
		this.requests = 0;
		this.triesRemaining = [];
	},

	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 (!$chk(arguments[1]) && !this.check(options)) return this;

		var type = $type(options), 
				old = this.options, 
				index = $chk(arguments[1]) ? arguments[1] : this.requests++;
		if (type == 'string' || type == 'element') options = {data: options};

		options = $extend({data: old.data, url: old.url}, options);

		if (!$chk(this.triesRemaining[index])) this.triesRemaining[index] = this.options.retries;
		var remaining = this.triesRemaining[index];

		(function(){
			var script = this.getScript(options);
			this.log('JSONP retrieving script with url: ' + script.get('src'));
			this.fireEvent('request', script);
			this.running = true;

			(function(){
				if (remaining){
					this.triesRemaining[index] = remaining - 1;
					if (script){
						script.destroy();
						this.send(options, index).fireEvent('retry', this.triesRemaining[index]);
					}
				} else if(script && this.options.timeout){
					script.destroy();
					this.cancel().fireEvent('failure');
				}
			}).delay(this.options.timeout, this);
		}).delay(Browser.Engine.trident ? 50 : 0, this);
		return this;
	},

	cancel: function(){
		if (!this.running) return this;
		this.running = false;
		this.fireEvent('cancel');
		return this;
	},

	getScript: function(options){
		var index = Request.JSONP.counter,
				data;
		Request.JSONP.counter++;

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

		var src = options.url + 
			 (options.url.test('\\?') ? '&' :'?') + 
			 (options.callbackKey || this.options.callbackKey) + 
			 '=Request.JSONP.request_map.request_'+ index + 
			 (data ? '&' + data : '');
		if (src.length > 2083) this.log('JSONP '+ src +' will fail in Internet Explorer, which enforces a 2083 bytes length limit on URIs');

		var script = new Element('script', {type: 'text/javascript', src: src});
		Request.JSONP.request_map['request_' + index] = function(data){ this.success(data, script); }.bind(this);
		return script.inject(this.options.injectScript);
	},

	success: function(data, script){
		if (script) script.destroy();
		this.running = false;
		this.log('JSONP successfully retrieved: ', data);
		this.fireEvent('complete', [data]).fireEvent('success', [data]).callChain();
	}

});

Request.JSONP.counter = 0;
Request.JSONP.request_map = {};

/*
---

script: Keyboard.js

description: KeyboardEvents used to intercept events on a class for keyboard and format modifiers in a specific order so as to make alt+shift+c the same as shift+alt+c.

license: MIT-style license

authors:
- Perrin Westrich
- Aaron Newton
- Scott Kyle

requires:
- core:1.2.4/Events
- core:1.2.4/Options
- core:1.2.4/Element.Event
- /Log

provides: [Keyboard]

...
*/

(function(){

	var parsed = {};
	var modifiers = ['shift', 'control', 'alt', 'meta'];
	var regex = /^(?:shift|control|ctrl|alt|meta)$/;
	
	var parse = function(type, eventType){
		type = type.toLowerCase().replace(/^(keyup|keydown):/, function($0, $1){
			eventType = $1;
			return '';
		});
		
		if (!parsed[type]){
			var key = '', mods = {};
			type.split('+').each(function(part){
				if (regex.test(part)) mods[part] = true;
				else key = part;
			});
		
			mods.control = mods.control || mods.ctrl; // allow both control and ctrl
			var match = '';
			modifiers.each(function(mod){
				if (mods[mod]) match += mod + '+';
			});
			
			parsed[type] = match + key;
		}
		
		return eventType + ':' + parsed[type];
	};

	this.Keyboard = new Class({

		Extends: Events,

		Implements: [Options, Log],

		options: {
			/*
			onActivate: $empty,
			onDeactivate: $empty,
			*/
			defaultEventType: 'keydown',
			active: false,
			events: {}
		},

		initialize: function(options){
			this.setOptions(options);
			//if this is the root manager, nothing manages it
			if (Keyboard.manager) Keyboard.manager.manage(this);
			this.setup();
		},

		setup: function(){
			this.addEvents(this.options.events);
			if (this.options.active) this.activate();
		},

		handle: function(event, type){
			//Keyboard.stop(event) prevents key propagation
			if (!this.active || event.preventKeyboardPropagation) return;
			
			var bubbles = !!this.manager;
			if (bubbles && this.activeKB){
				this.activeKB.handle(event, type);
				if (event.preventKeyboardPropagation) return;
			}
			this.fireEvent(type, event);
			
			if (!bubbles && this.activeKB) this.activeKB.handle(event, type);
		},

		addEvent: function(type, fn, internal) {
			return this.parent(parse(type, this.options.defaultEventType), fn, internal);
		},

		removeEvent: function(type, fn) {
			return this.parent(parse(type, this.options.defaultEventType), fn);
		},

		activate: function(){
			this.active = true;
			return this.enable();
		},

		deactivate: function(){
			this.active = false;
			return this.fireEvent('deactivate');
		},

		toggleActive: function(){
			return this[this.active ? 'deactivate' : 'activate']();
		},

		enable: function(instance){
			if (instance) {
				//if we're stealing focus, store the last keyboard to have it so the relenquish command works
				if (instance != this.activeKB) this.previous = this.activeKB;
				//if we're enabling a child, assign it so that events are now passed to it
				this.activeKB = instance.fireEvent('activate');
			} else if (this.manager) {
				//else we're enabling ourselves, we must ask our parent to do it for us
				this.manager.enable(this);
			}
			return this;
		},

		relenquish: function(){
			if (this.previous) this.enable(this.previous);
		},

		//management logic
		manage: function(instance) {
			if (instance.manager) instance.manager.drop(instance);
			this.instances.push(instance);
			instance.manager = this;
			if (!this.activeKB) this.enable(instance);
			else this._disable(instance);
		},

		_disable: function(instance) {
			if (this.activeKB == instance) this.activeKB = null;
		},

		drop: function(instance) {
			this._disable(instance);
			this.instances.erase(instance);
		},

		instances: [],

		trace: function(){
			this.enableLog();
			var item = this;
			this.log('the following items have focus: ');
			while (item) {
				this.log(document.id(item.widget) || item.widget || item, 'active: ' + this.active);
				item = item.activeKB;
			}
		}

	});

	Keyboard.stop = function(event) {
		event.preventKeyboardPropagation = true;
	};

	Keyboard.manager = new this.Keyboard({
		active: true
	});
	
	Keyboard.trace = function(){
		Keyboard.manager.trace();
	};
	
	var handler = function(event){
		var mods = '';
		modifiers.each(function(mod){
			if (event[mod]) mods += mod + '+';
		});
		Keyboard.manager.handle(event, event.type + ':' + mods + event.key);
	};
	
	document.addEvents({
		'keyup': handler,
		'keydown': handler
	});

	Event.Keys.extend({
		'pageup': 33,
		'pagedown': 34,
		'end': 35,
		'home': 36,
		'capslock': 20,
		'numlock': 144,
		'scrolllock': 145
	});

})();


/* hide firebug console from non-capable */
if(typeof(window.loadFirebugConsole) == 'undefined' || typeof(window.console) == 'undefined' ) {
  var names = ['log', 'debug', 'info', 'warn', 'error', 'assert', 'dir', 'dirxml', 'group', 'groupEnd', 'time', 'timeEnd', 'count', 'trace', 'profile', 'profileEnd'];
  window.console = {};
  
  var length = names.length;
  
  for(var i = 0; i < length; ++i) {
    window.console[names[i]] = function() {};
  }
}



/* All classes from MooTools are combined in kw_mootools-{version}.js.
 * For better performance you can load this file from google:
 * http://ajax.googleapis.com/ajax/libs/mootools/{version}/mootools.js
 * 
 * Some extensions require MooTools More classes. These required and
 * other useful classes from MooTools More are combined in
 * kw_mootools_more-{version}.js:
 *  - More
 *  - Class.Refactor
 *  - Class.Binds
 *  - Array.Extras
 *  - Hash.Extras
 *  - String.Extras
 *  - String.QueryString
 *  - URI
 *  - Elements.From
 *  - Element.Delegation (make sure you have read the documentation -
 *    you can dramatically increase your sites efficiency!)
 *  - Element.Measure
 *  - Element.Shortcuts
 *  - Request.JSONP
 *  - Keyboard
 * 
 * Take care that these two scripts are loaded before you load
 * kw_mootools_extensions-{version}.js
 */



/* Class: Request
 * ==============
 */

/* Refactoring the Request Class to allow custom status codes
 * Requires: Class.Refactor
 */
Request = Class.refactor(Request, {
  
  /* Description of success status codes:
   * 409: Validation errors
   */
  additional_success_status_codes: [409],
  
  /* Check if the returned success code is included in our custom ones
   * or call the original function which handles all the default stuff
   */
  isSuccess: function() {
    return (this.additional_success_status_codes.contains(this.status) || this.previous());
  },
  
  /* Fire custom status event if returning status code is included in
   * our custom status codes or fire the default success event
   */
  onSuccess: function() {
    if(this.additional_success_status_codes.contains(this.status))
      this.fireEvent('complete', arguments).fireEvent('status' + this.status, arguments).callChain();
    else
      this.fireEvent('complete', arguments).fireEvent('success', arguments).callChain();
  }

});

/* Uncached Ajax-Request (adds timestamp to request)
 */
Request.Uncached = new Class({
  
  Extends: Request,
  
  options: {
    noCache: true
  },
  
  initialize: function(options) {
    this.parent(options);
  }
  
});

/* Uncached Ajax-Request which executes JS in responseText AT THE END
 * (due to errors if you reference html elements in executed JS and
 * the DOM is not yet up to date)
 */
Request.UncachedHTML = new Class({
  
  Extends: Request.HTML,
  
  options: {
    noCache: true,
    evalScripts: false
  },
  
  initialize: function(options) {
    this.parent(options);
  },
  
  onSuccess: function(responseTree, responseElements, responseHTML, responseJavaScript) {
    this.parent(responseTree, responseElements, responseHTML, responseJavaScript);
    $exec(responseJavaScript);
  }
  
});



/* Class: Selectors.Pseudo
 * =======================
 * 
 * Requires: Element.Shortcuts
 */

// for recognizing html5 data-* attributes
Selectors.RegExps.combined = (/\.([\w-]+)|\[([\w-]+)(?:([!*^$~|]?=)(["']?)([^\4]*?)\4)?\]|:([\w-]+)(?:\( ["']?(.*?)?["']?\)|$)/g);

Selectors.Pseudo.visible = function() {
  return $(this).isDisplayed();
};

Selectors.Pseudo.hidden = function() {
  return !$(this).isDisplayed();
};

Selectors.Pseudo.unchecked = function() {
  return !$(this).checked;
};

Selectors.Pseudo.writeable = function() {
  return !$(this).get('readonly');
};

/* Enhanced enabled pseudo selector which now matches also elements
 * with class 'disabled'
 */
Selectors.Pseudo.enabled = function() {
  return (!$(this).hasClass('disabled') && !$(this).get('disabled'));
};

/* Some selectors for new html5 input attributes
 */
 
Selectors.Pseudo.required = function() {
  return !!$(this).get('required');
};

Selectors.Pseudo.optional = function() {
  return !$(this).get('required');
};

Selectors.Pseudo.placeholder = function() {
  return !!$(this).get('placeholder');
};



/* Class: String
 * =============
 */
 
String.implement({
  
  isEmpty: function() {
    return this.length === 0;
  },
  
  /* Calls String.trim before emptiness check
   */
  isBlank: function() {
    return this.trim().isEmpty();
  },
  
  /* As of version 1.2.4 MooTools provides an own method to create
   * elements from an string (Elements.From)
   */
  toElements: function() {
    var div = document.createElement('div');
    div.innerHTML = this;
    return $$(div.childNodes);
  },
  
  /* Returns a ruby-like class formated string
   */
  classify: function(first_to_uppercase) {
    var stirng = this;
    
    if(first_to_uppercase)
      string = string.firstToUppercase();
    
    return string.replace(/_\D/g, function(match){
      return match.charAt(1).toUpperCase();
    });
  },
  
  firstToUppercase: function() {
    return this.replace(/^\D/g, function(match){
      return match.charAt(0).toUpperCase();
    });
  },
  
  createUUID: function(options) {
	  options = options || {};
	  
    var uuid = [];
    var hex = '0123456789ABCDEF';

    for(var i = 0; i < 36; i++)
      uuid[i] = Math.floor(Math.random() * 0x10);

    // Conform to RFC-4122, section 4.4
    uuid[14] = 4;  // Set 4 high bits of time_high field to version
    uuid[19] = (uuid[19] & 0x3) | 0x8;  // Specify 2 high bits of clock sequence

    // Convert to hex chars
    for(i = 0; i <36; i++)
      uuid[i] = hex[uuid[i]];

    uuid[8] = uuid[13] = uuid[18] = uuid[23] = '-';

    return [options.prefix, uuid.join(''), options.sufix].clean().join('-');
  }
  
});



/* Class: Element
 * ==============
 */
 
var oldToggleClass = Element.prototype.toggleClass;
Element.implement({
  
  getFormElementHash: function() {
    var hash = {};
    
    this.getElements('input, select, textarea').each(function(element) {
      if(!element || ['submit', 'reset', 'file'].contains(element.get('type')) || !element.get('name') || element.get('disabled') || element.hasClass('default'))
        return;
        
      var value = (element.tagName.toLowerCase() == 'select') ? Element.getSelected(element).map(function(option) {
        return option.value;
      }) : ((element.type == 'radio' || element.type == 'checkbox') && !element.checked) ? null : element.value;
      
      $splat(value).each(function(value) {
        if(typeof value != 'undefined')
          hash[element.name] = value;
      });
    });
    
    return hash;
  },
  
  unenabled: function() {
    return ((this.hasClass('button') && this.hasClass('button_disabled')) || this.hasClass('disabled'));
  },
  
  enabled: function() {
    return !this.unenabled();
  },
  
  /* Clones the element and all its events
   * Shortcut if you work on cached elements for performance reasons
   * TODO:
   * Benchmark event deep cloning
   */
  cacheClone: function(options) {
    options = $extend({
      contents: true,
      ids: true,
      events: true
    }, (options || {}));

    var clone = this.clone(options.contents, options.ids);
    if(options.events) clone = clone.cloneEvents(this);
    if(options.events && options.contents) {
      var original_elements = this.getElements('*');
      var cloned_elements = clone.getElements('*');
      var length = original_elements.length;
      for(var i = 0; i < length; i++) {
        cloned_elements[i] = cloned_elements[i].cloneEvents(original_elements[i]);
      }
    }
    
    return clone;
  },
  
  createUUID: function(options) {
	  if(this.get('id') === null || this.get('id').isBlank())
	    this.set('id', this.createUUID(options));
	    
	  return this;
  },
  
  toggleClass: function(class_1, class_2) {
    if(!class_2) return oldToggleClass.apply(this, class_1);
    
    if(this.hasClass(class_1))
      this.removeClass(class_1).addClass(class_2);
    else if(this.hasClass(class_2))
      this.removeClass(class_2).addClass(class_1);
    else if(!this.hasClass(class_1))
      this.addClass(class_1);
    else if(!this.hasClass(class_2))
      this.addClass(class_2);
      
    return this;
  },
  
  disableInputs: function() {
    this.getElements('input, textarea, select').each(function(element) {
      element.set('disabled', true);
    });
    
    return this;
  },
  
  enableInputs: function() {
    this.getElements('input, textarea, select').each(function(element) {
      element.set('disabled', false);
    });
    
    return this;
  },
  
  addEventIfExists: function(selector, event, callback) {
    var element = this.getElement(selector);
    if(element) element.addEvent(event, callback);
    
    return this;
  }
  
});



/* Class: Number
 * =============
 */
 
Number.implement({

  minimum: function(min) {
    return this.toInt() < min ? min : this.toInt();
  },
  
  maximum: function(max) {
    return this.toInt() > max ? max : this.toInt();
  }

});



/* Class: Array
 * ============
 */
 
Array.implement({

  first: function() {
    return this[0];
  },
  
  second: function() {
    return this[1];
  }
  
});
Array.alias('getLast', 'last');



/* Class: Chain
 * ============
 */
 
Chain.implement({

  chainTop: function() {
    this.$chain = Array.flatten(arguments).extend(this.$chain);
  }

});



/* Class: Form.Utilities
 * =====================
 * 
 * Requires MooTools Form Class or creates its own Namespace
 */
 
if(!$defined(Form)) var Form = {};
Form.Utilities = {
  
  /* Calling this method will cause all checkboxes to stay in its checked
   * state. If scope is undefined all checkboxes in document are affected
   */
  inputStaysChecked: function(scope) {
    var elements;

    switch($type(scope)) {
      case 'array': elements = $$(scope); break;
      case 'element': elements = scope; break;
      default: elements = ($(scope) || $(document.body)).getElements('input[type=checkbox]:stay_checked'); break;
    }
    
    elements.addEvent('click', function(event) {
      event.stop();
      
      this.set('checked', true);
    });
  }
  
};



/* Class: K
 * ====================
 */
 
if(!$defined(K)) var K = {};



/* Class: K.Placeholder
 * ====================
 * 
 * Add the behaviour of the new html5 input attribute 'placeholder'
 * for all inputs with this attribute.
 * Unless kw_modernizr-{version}.js is loaded every browser is
 * affected so you have to take care by yourself that browsers which
 * currently support this new attribute are not messed up with this
 * (When this was created only WebKit browsers are supporting this attribute).
 */
 
K.Placeholder = new Class({
  
  Implements: [Options, Events],
  
  options: {
    scope: undefined,
    placeholder_class: 'placeholder',
    hide_on_submit: false,
    hide_labels: false,
    skip_check: false
  },
  
	initialize: function(options) {
	  this.setOptions(options);
	  this.options.scope = $(this.options.scope || document.body);
	  
    // Catch all input elements with placeholder attribute
	  this.options.scope.getElements('input:placeholder, textarea:placeholder').each(function(input) {
	    // Return and do nothing if browser supports placeholder attribute
  	  if(!this.options.skip_check && input.tagName.toLowerCase() == 'input' && ($type(window.Modernizr) && Modernizr.input['placeholder'])) return;
  	  
      // Add focus and blur event
      input.addEvents({
        focus: this.unsetInputDefaultValue.bind(this, input),
        blur: this.setInputDefaultValue.bind(this, input)
      }).set('data-default_name', input.get('name'));
      
      var id = input.get('id');
      if(this.options.hide_labels && !id.isBlank())
        this.options.scope.getElements('label[for={id}]'.substitute({
          id: id
        })).hide();
      
      this.setInputDefaultValue(input);
	  }.bind(this));

    return this;
	},

	setInputDefaultValue: function(input) {
	  if(input.get('value').isBlank()) {
	    this.fireEvent('show_placeholder', input);
      input.addClass(this.options.placeholder_class).set('value', input.get('placeholder').replace(/\\n/g, "\n"));
      if(this.options.hide_on_submit) input.set('name', '');
    }
	  return input;
	},

	unsetInputDefaultValue: function(input) {
	  if(input.hasClass(this.options.placeholder_class)) {
	    this.fireEvent('hide_placeholder', input);
	    input.removeClass(this.options.placeholder_class).set('value', '');
      if(this.options.hide_on_submit) {
        var default_name = input.get('data-default_name');
        if(default_name && !default_name.isBlank()) input.set('name', default_name);
      }
    }

	  return input;
	}
  
});



/* Class: K.RequestOverlay
 * =======================
 * 
 * Another attemp to create an abstract overlay class -
 * seems to be very difficult ;)
 * Only tested in Ruflotse but i thought i let it stay in here
 */
 
K.RequestOverlay = new Class({
  
  Implements: [Options, Events],
  
  options: {
    container: 'overlay_container'
  },
  
  initialize: function(url, options) {
    this.setOptions(options);
    
    this.overlay_container = $(this.options.container);
    this.overlay = this.overlay_container.getElement('.overlay');
    
    if(this.overlay_container.getStyle('height') == '100%') {
      var height, parent = this.overlay_container.getParent();
      if(parent.get('tag') == 'body')
        height = window.getScrollSize().y;
      else
        height = this.overlay_container.getParent().getSize().y;
      this.overlay_container.setStyle('height', height);
    }
    
    if(!url || url.isBlank())
      return false;
      
    var request = new Request.UncachedHTML({
      method: 'get',
      onRequest: function() {
        this.fireEvent('request');
      }.bind(this),
      onComplete: function() {
        this.fireEvent('complete');
      }.bind(this),
      onSuccess: function(responseTree, responseElements, responseHTML, responseJavaScript) {
        this.updateOverlay(responseHTML);
        this.positionOverlay();
        this.initializeOverlay();
        this.showOverlay();
        new __defaultEvents(this.overlay_container);
        this.fireEvent('success', [this.overlay, responseTree, responseElements, responseHTML, responseJavaScript]);
      }.bind(this),
      onFailure: function() {
        this.hideOverlay();
        this.fireEvent('failure');
      }.bind(this)
    });
    
    this.overlay.removeEvents('update').addEvent('update', function() {
      this.overlay.store('request', request.send({
        url: url,
        data: this.options.data
      }));
    }.bind(this));
      
    this.showRequestOverlay();
    
    return this;
  },

  updateOverlay: function(content) {
    this.overlay.getElement('.overlay_inner_container').set('html', content);
    
    return this;
  },
  
  positionOverlay: function() {
    // this.overlay.position({
    //   position: 'centertop',
    //   edge: 'centertop'
    // });
    
    return this;
  },
  
  initializeOverlay: function(request) {
    if(!request)
      this.fireEvent('show', this.overlay);
    else
      this.fireEvent('requestShow', this.overlay);
    
    this.overlay.getElements('.cancel').addEvent('click', function(event) {
      event.stop();
      
      this.overlay.retrieve('request').cancel();
      
      if(!request)
        this.fireEvent('hide', this.overlay);
      else
        this.fireEvent('requestHide', this.overlay);
        
      this.hideOverlay();
    }.bind(this));
    
    return this;
  },
  
  showRequestOverlay: function() {
    if(!this.overlay.isDisplayed()) {
      try {
        this.overlay.getElement('h2').set('html', this.overlay.get('loading_headline'));
        this.overlay.getElements('.overlay_section').erase(this.overlay.getElement('.overlay_section').set('html', this.overlay.get('loading_message'))).hide();
        this.overlay.getElements('.button_container a.button').hide();
      } catch(error) {};
      
      this.positionOverlay();
      this.initializeOverlay(true);
      this.showOverlay();
    }
    
    this.overlay.fireEvent('update');

    return this;
  },
    
  showOverlay: function() {
    $try(function() {
      this.overlay_container.show();
    }.bind(this));
    
    // In IE6 select elements are always on top of the window
    // Lets hide them when we will show an overlay
    if(Browser.Engine.name == 'trident' && Browser.Engine.version == 4)
      $(document.body).getElements('select').hide();
    
    this.overlay.show();
    
    return this;
  },
  
  hideOverlay: function() {
    // In IE6 select elements are always on top of the window
    // We hid them when showing an overlay, no we have to show them
    if(Browser.Engine.name == 'trident' && Browser.Engine.version == 4)
      $(document.body).getElements('select').show();
      
    this.overlay.hide();
    $try(function() {
      this.overlay_container.hide();
    }.bind(this));
    
    return this;
  }
  
});



/* Class: K.PostRequest
 * ====================
 * 
 * Post data by injecting a hidden form with inputs
 * filled with the provided data
 */
 
K.PostRequest = new Class({
  
  Implements: Options,
  
  options: {
    method: 'post',
    enctype: 'multipart/form-data',
    'accept-charset': 'utf-8'
  },
  
  initialize: function(action, options) {
    this.setOptions(options);
    
    form = new Element('form', {
      action: action,
      method: this.options.method,
      enctype: this.options.enctype,
      'accept-charset': this.options['accept-charset']
    });
    
    switch($type(this.options.data)) {
      case 'array':
        this.options.data.each(function(hash) {
          if($type(object) != 'object')
            return false;
            
          return new Hash(object).each(function(value, key) {
            new Element('input', {
              type: 'hidden',
              name: key,
              value: value
            }).inject(form);
          });
        });
        break;
      case 'object':
        new Hash(this.options.data).each(function(value, key) {
          return new Element('input', {
            type: 'hidden',
            name: key,
            value: value
          }).inject(form);
        });
        break;
    };
    
    return form.inject($(document.body)).submit();
  }
  
});



/* Class: K.Slideshow
 * ==================
 * 
 * Add Slideshow for an ul
 * 
 * Options:
 * name, values, default value
 * 
 * - teaser_container: id|element, 'teaser'
 *     Add slideshow behaviour for this uls.
 *     If teaser_container is undefined, behaviour is added to all .slideshow
 * - autoplay: true|false, true
 * - view: 'stack|list', 'stack'
 * - stack_offset: number in px, 0
 * - pause_delay: number in ms, 30000
 * - slide_delay: number in ms, 2000
 *     Minimum is 1000
 * - duration: number in ms, 1000
 * - stack_duration_delay: number in ms, 250
 *     Delay between item collection slides
 * - transition_in: transition|function, expo:out
 * - transition_out: transition|function, expo:out
 * - onInitializeTeaser: function(item, position)
 * - onKeyboardLeft: function(event)
 * - onKeyboardRight: function(event)
 * - onSlideComplete: function(item)
 * - getTeaserCatch: function(item), first a inside an item
 *     returns the catch for item
 */

K.Slideshow = new Class({
  
  Implements: [Options, Events],
  
  options: {
    teaser_container: 'teaser',
    autoplay: true,
    view: 'stack',
    stack_offset: 0,
    pause_delay: 30000,
    slide_delay: 5000,
    duration: 1000,
    stack_duration_delay: 250,
    transition_in: 'expo:out',
    transition_out: 'expo:out',
    onInitializeTeaser: $empty,
    onKeyboardLeft: $empty,
    onKeyboardRight: $empty,
    onSlideComplete: $empty,
    
    getTeaserCatch: function(teaser_item) {
      return teaser_item.getElement('a');
    }
  },
  
  teaser_catches: [],
  teaser_items: [],
  teaser_items_length: 0,
  teaser_item_width: 0,
  teaser_container_width: 0,
  current_teaser: 0,
  pause: 0,
  fx_stack: [],
  
  /* Internal events
   * 
   * item: event name, parameter
   * 
   * teaser_item: 'slide', teaser_item, position
   * teaser_catch: 'click', teaser_item, position
   * teaser_item: 'slide', teaser_item, position
   */
  
  initialize: function(options) {
    this.setOptions(options);
    this.options.teaser_container = $(this.options.teaser_container);
    
    if(!this.options.teaser_container) return this;
    
    this.teaser_container_width = this.options.teaser_container.getWidth();
    this.teaser_items = this.options.teaser_container.getElements('li');
    this.teaser_item_width = this.teaser_items.first().getWidth();
    this.teaser_items_length = this.teaser_items.length;
    
    // Bind events to teaser
    for(var i = 0; i < this.teaser_items_length; i++) {
      var teaser_item = this.teaser_items[i];
      if(this.options.view == 'stack') teaser_item.show();
      var teaser_catch = $lambda(this.options.getTeaserCatch)(teaser_item);
      
      teaser_item.addEvent('slide', this.slide.bindWithEvent(this, [teaser_item, i]));
      
      if(teaser_catch) {
        teaser_item.store('catch', teaser_catch);
        this.teaser_catches.push(teaser_catch);
      
        teaser_catch.addEvent('click', function(event, teaser_item, i) {
          event.stop();

          this.pause = this.options.pause_delay;
          this.current_teaser = i;
          teaser_item.fireEvent('slide', teaser_item, i);
        }.bindWithEvent(this, [teaser_item, i]));
      }
      
      this.fireEvent('initializeTeaser', [teaser_item.toggleClass('open', 'closed'), i]);
    };
    
    // Auto sliding
    if(this.options.autoplay) this.autoSlide.periodical([this.options.slide_delay, 1000].max(), this);
    
    // Keyboard Events
    new Keyboard({
      events: {
        'left': function(event) {
          this.pause = this.options.pause_delay;
          this.fireEvent('keyboardLeft', event);
          this.autoSlide({
            direction: -1,
            force: true
          });
        }.bind(this),
        'right': function(event) {
          this.pause = this.options.pause_delay;
          this.fireEvent('keyboardRight', event);
          this.autoSlide({
            force: true
          });
        }.bind(this)
      }
    }).activate();
    
    return this;
  },
  
  autoSlide: function(options) {
    options = $merge({
      direction: 1,
      force: false
    }, (options || {}));
    
    if(!options.force && this.pause > 0) {
      this.pause = this.pause - this.options.slide_delay;
      
      return false;
    }
    
    this.current_teaser = this.current_teaser + options.direction;
    var next_teaser = this.teaser_items[this.current_teaser];
    
    if(!next_teaser) {
      if(options.direction > 0) {
        this.current_teaser = 0;
        next_teaser = this.teaser_items.first();
      } else if(options.direction < 0) {
        this.current_teaser = this.teaser_items_length;
        next_teaser = this.teaser_items.last();
      }
    }
    
    next_teaser.fireEvent('slide', next_teaser, this.current_teaser);
    
    return this;
  },
  
  slide: function(event, teaser_item, position) {
    if(event) this.fx_stack = [];
    
    this['slide' + this.options.view.capitalize()](event, teaser_item, position);
    
    if(event) this.fireEvent('slideComplete', teaser_item);
    
    return this;
  },
  
  slideStack: function(event, teaser_item, position) {
    var tween;
    var tween_options = {
      unit: 'px',
      property: 'left',
      transition: this.options.transition_out,
      duration: this.options.duration
    };
    
    if(teaser_item.hasClass('open')) {
      var next = teaser_item.getNext('.open');
      if(next) this.slide(undefined, next, position + 1);
      if(!event) {
        this.fx_stack.push([teaser_item.set('tween', $merge(tween_options, {
          onStart: function(item) {
            item.removeClass('open');
          },
          onComplete: function(item) {
            item.addClass('closed');
          }
        })), position]);
      } else {
        this.fx_stack.each(function(array, i) {
          array[0].tween.delay(i * this.options.stack_duration_delay, array[0], this.teaser_container_width - (this.options.stack_offset * (this.teaser_items_length - array[1])));
        }.bind(this));
      }
    } else if(teaser_item.hasClass('closed')) {
      var previous = teaser_item.getPrevious('.closed');
      if(previous) this.slide(undefined, previous, position - 1);
      
      this.fx_stack.push([teaser_item.set('tween', $merge(tween_options, {
        onStart: function(item) {
          item.removeClass('closed');
        },
        onComplete: function(item) {
          item.addClass('open');
        }
      })), position]);
      
      if(event) {
        this.fx_stack.each(function(array, i) {
          array[0].tween.delay(i * this.options.stack_duration_delay, array[0], this.options.stack_offset * array[1]);
        }.bind(this));
      }
    }
    
    return this;
  },
  
  slideList: function(event, teaser_item, position) {
    this.options.teaser_container.set('tween', {
      unit: 'px',
      property: 'left',
      transition: this.options.transition_in,
      duration: this.options.duration
    }).tween(teaser_item.getAllPrevious('li').length * -this.teaser_item_width);
    
    return this;
  }
  
});



/* Class: K.TemplateRenderer
 * =========================
 * 
 * Simple class to fill HTML Templates with substitutions (compare to mootools string.substitute()). 
 * Supports some nice features like conditional subtemplates. You need and should only initialize one
 * TemplateRenderer per page (there is a caching mechanism to speed up template load).
 *
 * The Following Substitutions are recognized:
 *
 *   {var}: Will be replaced with the value from the substitution object
 *   {obj.var}: Same as before, works for subobjects of the substitution object
 *   {template:name}: Renders the named subtemplate
 *   {if:condition:template1:template2}: If the replacement {condition} is not null, template1 will be rendered, otherwise template2
 *   {unless:condition:template1}: If the replacement {condition} is null, template1 will be rendered
 *   {each:var:template}: Renders template for each entry in the array var
 *
 * Usage Example:
 *
 *   In your HTML:
 *    <div id="template-sub-template-1">
 *      sub1: {sub1.text}
 *    </div>
 *    
 *    <div id="template-sub-template-2">
 *      sub2: {sub2.text}
 *    </div>
 *    
 *    <ul id="template-main">
 *      <li>
 *        {template:sub-template-1}
 *        {if:sub2:sub-template-2}
 *        {text}
 *      </li>
 *    </ul>
 * 
 *  In your JS
 * 
 *    var renderer = new TemplateRenderer();
 *    alert(renderer.render('main', {text: 'hello', sub1: {text: 'hello from sub1'}, sub2: {'hello from sub2'}}}))
 * 
 */
 
K.TemplateRenderer = new Class({
  
  Implements: Options,
  
  options: {
    return_element: false
  },
  
  initialize: function(options) {
    this.setOptions(options);
    if(!document.__k_tempplate_renderer) document.__k_tempplate_renderer = new Hash();
    
    return this;
  },
  
  templateSubstitute: function(template, object){
		return template.replace((/\\?\{([^{}]+)\}/g), function(match, name) {
			if (match.charAt(0) == '\\') return match.slice(1);
			
			var fparts = name.split(':');
			if(fparts.length == 1) {
			  return this.computeTemplateSubstitution(name, object);
			} else {
			  var fcall = fparts.shift();
			  switch(fcall) {
			    case 'template':
			      return this.render(fparts[0], object);
			    case 'if':
			      return this.computeTemplateSubstitution(fparts[0], object) ? this.render(fparts[1], object) : this.render(fparts[2], object);
			    case 'unless':
			      return this.computeTemplateSubstitution(fparts[0], object) ? '' : this.render(fparts[1], object);
			    case 'each':
			      var arr = this.computeTemplateSubstitution(fparts[0], object);
			      var retval = '';
			      if(arr) {
			        arr.each(function(item) {
			          retval += this.render(fparts[1], item);
			        }, this);
			      }
			      return retval;
		      case 'document-var':
			      return this.computeTemplateSubstitution(fparts[0], document);
		      case 'document-call':
		        return this.computeTemplateSubstitution(fparts[0], document)();
			    default:
			      return 'unknown fcall: ' + fcall;
			  }
			  
			}
		}.bind(this));
	},

  computeTemplateSubstitution: function(name, object) {
    var cObject = object;
    var parts = name.split('.');
    
    if(cObject != undefined) {
      var length = parts.length;
      
      for(i = 0; i < length; i++) {
        var cObjectPart = cObject[parts[i]];
        
        if(cObjectPart != undefined) {
          cObject = cObjectPart;
        } else {
          cObject = '';
          break;
        }
      }
      return cObject;
    } else {
      return '';
    }
  },
  
  render: function(name, object) {
    if(name) {
      var idname = 'template-' + name.hyphenate().replace(/::/g, '-');
      
      if(!document.__k_tempplate_renderer.has(idname)) {
        var templateElement = $(idname);
        if(templateElement) {
          document.__k_tempplate_renderer.set(idname, templateElement.innerHTML);
          templateElement.destroy();
        } else {
          return console.warn("Template not found: " + idname);
        }
      }
      
      var string = this.templateSubstitute(document.__k_tempplate_renderer.get(idname), object);

      return this.options.return_element ? Elements.from(string) : string;
    } else {
      return console.warn('Please specify an element');
    }
  }
  
});



/* Class: K.DatePeriodSelector
 * ====================
 * 
 * Shows a date period selector (google analytics style) for input
 * fields with type "date_period"
 */

K.DatePeriodSelector = new Class({
  
  Implements: Options,
  
  options: {
    start_month: false,
    start_date: false,
    end_date: false,
    template_layer: '',
    template_day: '',
    glue: ' – '
  },
  
  initialize: function(options) {
    this.setOptions(options);
    this.options.template_layer = Elements.from(this.options.template_layer)[0];

    $$('input[data-special_type=date_period]').each(function(input) {
      var layer = this.options.template_layer.clone();
      layer.addClass('date_period_layer').addEvents({
        'click:relay(a.back)': function(event) {
          event.stop();
          
          this.initLayerWithDate(input, -1);
        }.bind(this),
        'click:relay(a.forward)': function(event) {
          event.stop();
          
          this.initLayerWithDate(input, 1);
        }.bind(this),
        'click:relay(a.today)': function(event) {
          event.stop();
          
          this.initLayerWithDate(input);
        }.bind(this),
        'click:relay(li.month a)': function(event) {
          event.stop();
          
          this.setDateFrame($(event.target), layer);
        }.bind(this),
        'click:relay(a.button)': function(event) {
          event.stop();
          
          this.getParent('form').submit();
        },
        'click:relay(a.close)': function(event) {
          event.stop();
          
          layer.hide();
        }
      }).inject(input, 'after');
    }.bind(this));

    $$('input[data-special_type=date_period]').addEvents({
      'click': function(event) {
        event.stop();

        this.initLayerWithDate($(event.target)).show();
      }.bind(this)
    });

    return this;
  },
  
  initLayerWithDate: function(input, start_offset) {
    var layer = input.getNext('.date_period_layer');
    var value = input.get('value');
    var dates = value.split(this.options.glue);
    var date = new Date();
    this.options.start_date = dates[0] ? date.clone().parse(dates[0]) : date.clone();
    this.options.end_date = dates[1] ? date.clone().parse(dates[1]) : date.clone();

    switch ($type(start_offset)){
      case 'date':
        this.options.start_month = start_offset.clone().beginningOfMonth();
        break;
      case 'string':
        this.options.start_month = date.clone().parse(start_offset);
        break;
      case 'number':
        this.options.start_month = (this.options.start_month || this.options.start_date.beginningOfMonth()).increment('month', start_offset);
        break;
      default:
        this.options.start_month = new Date().beginningOfMonth();
    }
    
    layer.getElements('ul').each(function(list, i) {
      var current_month = this.options.start_month.clone().increment('month', i);
      var start_week_day;
      var days = 1;
      
      list.getPrevious('strong').set('text', current_month.format('%B %Y'));
      var new_list = document.createDocumentFragment();
      // Last days of previous month
      var start_week = current_month.clone().beginningOfWeek();
      var current_day = start_week.clone().decrement('day', 1);
      start_week_day = start_week.get('date');
      if(start_week_day > 1) {
        var previous_month_days = current_month.clone().decrement('month', 1).getLastDayOfMonth();
        while(start_week_day <= previous_month_days) {
          this.addDayToList(new_list, current_day.increment('day', 1), start_week_day, 'previous_month');
          
          start_week_day++;
          days++;
        }
      }
      
      // Days of current month
      start_week_day = 1;
      var sum_of_days = current_month.getLastDayOfMonth();
      while(start_week_day <= sum_of_days) {
        this.addDayToList(new_list, current_day.increment('day', 1), start_week_day, 'current_month');
        
        start_week_day++;
        days++;
      }
      
      // First days of next month
      var end_week = current_month.clone().endOfMonth().endOfWeek();
      var end_week_day = end_week.get('date');
      start_week_day = 1;
      if(end_week_day < 7) {
        while(start_week_day <= end_week_day) {
          this.addDayToList(new_list, current_day.increment('day', 1), start_week_day, 'next_month');
          
          start_week_day++;
          days++;
        }
      }
      
      while(days <= 42) {
        this.addDayToList(new_list, current_day.increment('day', 1), start_week_day, 'next_month');
        
        start_week_day++;
        days++;
      }
      
      list.getElements('li.month').destroy();
      list.appendChild(new_list);
    }.bind(this));
    
    return layer;
  },
  
  addDayToList: function(list, date, day, classes) {
    if(date.diff(this.options.start_date) <= 0 && date.diff(this.options.end_date) >= 0)
      classes = [classes, 'active'];
    
    return list.appendChild(Elements.from(this.options.template_day.substitute({
      klass: 'month ' + ($type(classes) == 'array' ? classes.flatten().join(' ') : classes),
      day: day
    }))[0].set('date', date.format('%x')));
  },
  
  setDateFrame: function(day, layer) {
    day = day.getParent('li');
    var date = new Date().parse(day.get('date'));
    var input = day.getParent('.date_period_layer').getPrevious('input[data-special_type=date_period]');
    
    if(this.options.start_date && (this.options.end_date && !this.skip_end_date)) {
      layer.getElements('li.active').removeClass('active');
      this.options.end_date = false;
      this.options.start_date = date;
      this.skip_end_date = true;
      input.set('value', [date.format('%x'), date.format('%x')].join(this.options.glue));
      day.addClass('active');
    } else {
      if(this.options.start_date.diff(date) >= 0) {
        this.options.end_date = date;
      } else {
        this.options.end_date = this.options.start_date;
        this.options.start_date = date;
      }
      this.skip_end_date = false;
      input.set('value', [this.options.start_date.format('%x'), this.options.end_date.format('%x')].join(this.options.glue));
      this.initLayerWithDate(input, this.options.start_date);
    }

    return this;
  }
  
});



/* Class: K.InfoBubble
 * ===================
 */
 
Selectors.Pseudo.k_infobubble_headline = function() {
  return !!$(this).get('data-k-infobubble-headline');
};

Selectors.Pseudo.k_infobubble_content = function() {
  return !!$(this).get('data-k-infobubble-content');
};

K.InfoBubble = new Class({
  
  Implements: Options,
  
  renderer: undefined,
  
  options: {
    scope: undefined
  },
  
  initialize: function(options) {
    this.setOptions(options);
    this.options.scope = $(this.options.scope || document.body);
    
    this.initializeRenderer();
    
    this.options.scope.getElements('*:k_infobubble_content, *:k_infobubble_headline').addEvents({
      'mouseenter': function(event) {
        this.showInfoBubble.delay(250, this, event);
      }.bind(this),
      'mouseleave': function(event) {
        this.hideInfoBubble.delay(300, this, event);
      }.bind(this)
    });
    
    return this;
  },
  
  initializeRenderer: function() {
    this.renderer = this.renderer || new K.TemplateRenderer({
      return_element: true
    });
    
    return this.renderer;
  },
  
  initializeInfoBubble: function(element, x, y) {
    var bubble = element.retrieve('k-infobubble');
    
    if(!bubble) {
      bubble = this.renderer.render('k-infobubble', {
        headline: element.get('data-k-infobubble-headline'),
        content: element.get('data-k-infobubble-content')
      })[0];
      
      element.store('k-infobubble', bubble.inject(document.body, 'bottom'));
      
      var offset = parseInt((element.get('data-k-infobubble-offset') || 0), 10);
      var element_width = element.getWidth();
      var bubble_width = bubble.show().getWidth();
      var left = y + element_width + offset;
      
      bubble.hide();
      
      if(left + bubble_width >= $(document.body).getWidth())
        left = y - (bubble_width + offset);
      
      bubble.setStyles({
        top: x,
        left: left
      });
      
      return bubble;
    } else {
      return bubble;
    }
  },
  
  showInfoBubble: function(event) {
    var element = $(event.target);
    if(element.get('data-k-infobubble-headline') || element.get('data-k-infobubble-content')) {
      var coordinates = element.getCoordinates();
      var info_bubble = this.initializeInfoBubble(element, coordinates.top, coordinates.left);
    }
    
    if(info_bubble) info_bubble.show();
    
    return this;
  },
  
  hideInfoBubble: function(event) {
    var element = $(event.target).retrieve('k-infobubble');
    if(element) element.hide();
    
    return this;
  }
  
});



/* Class: K.UIDCreator
 * ===================
 */
K.UIDCreator = new Class({
  
  getNextUuid: function() {
    document._uid_creator_current_id = (document._uid_creator_current_id || 0) + 1;
    return document._uid_creator_current_id;
  },
  
  getNextUid: function() {
    this._uid_creator_current_id = (this._uid_creator_current_id || 0) + 1;
    return this._uid_creator_current_id;
  }
  
});



/* Class: K.RestfulLinks
 * =====================
 * 
 * Elements with data-rest-method and href or data-rest-url set
 * will be submitting an +data-rest-method+ request when clicking
 * 
 * Options:
 * name: values, default value
 * 
 * - scope: id|element, undefined
 * - methods: array, ['post', 'put', 'delete']
 * - token: string, ''
 */
 
K.RestfulLinks = new Class({
  
  Implements: [Options],
  
  options: {
    scope: undefined,
    methods: ['post', 'put', 'delete'],
    token: ''
  },
  
  initialize: function(options) {
    this.setOptions(options);
    this.scope = $(this.scope || document.body);
    
    this.scope.addEvents({
      'click:relay(*:data-rest-method)': function(event, element) {
        event.stop();
        
        var url = element.get('data-rest-url') || element.get('href');
        var method = element.get('data-rest-method');

        if(!url || !this.options.methods.contains(method)) return;

        var form = new Element('form', {
          action: url,
          method: 'post',
          styles: {
            display: 'none'
          }
        });
        
        new Element('input', {
          type: 'hidden',
          name: 'authenticity_token',
          value: this.options.token
        }).inject(form);
        
        if(method != 'post') 
          new Element('input', {
            type: 'hidden',
            name: '_method',
            value: method
          }).inject(form);
          
        form.inject(element, 'before').submit();
      }.bind(this)
    });
  }
  
});

window.addEvent('domready', function() {
  
  new K.Placeholder({
    skip_check: true,
    hide_on_submit: true
  });

  new K.Slideshow({
    stack_offset: 30,
    onInitializeTeaser: function(teaser_item, i) {
      if(i == 0) return teaser_item.toggleClass('open', 'closed');

      return teaser_item.setStyle('left', parseInt(teaser_item.getStyle('left'), 10) - (this.options.stack_offset * (this.teaser_items_length - i)));
    }
  });
  
  new K.Slideshow({
    teaser_container: 'images',
    stack_duration_delay: 0,
    view: 'list',
    onInitializeTeaser: function(teaser_item, i) {
      if(i == 0) teaser_item.retrieve('catch').addClass('active');
    },
    onSlideComplete: function(teaser_item) {
      this.teaser_catches.each(function(element) {
        element.removeClass('active');
      });
      
      return teaser_item.retrieve('catch').addClass('active');
    },
    getTeaserCatch: function(teaser_item) {
      return $('slide_navigation').getElement('li[teaser={id}]'.substitute({
        id: teaser_item.get('teaser')
      }));
    }
  });
  
});

Website = new Class({
  
  initializeProjectList: function() {
    var list = $('teaser_projects').getElement('ul');
    list.getElements('li').each(function(li) {
      var hover_element = li.getElement('a div.hover');
      hover_element.fade('hide');
      hover_element.show();
      hover_element.set('tween', {duration: 250 });
      li.getElement('a.teaser').addEvent('mouseover', function(e){
        hover_element.tween('opacity', 1);
      });
      li.getElement('a.teaser').addEvent('mouseout', function(e){
        hover_element.tween('opacity', 0);
      });
    });
  },
  
  initializeDefaultForm: function(form_list) {   
    form_list.getElements('li.text, li.string').each(function(li) {
      var form_element = li.getElement('input') || li.getElement('textarea');
      var form_hint = li.getElement('.form_hint');
      var form_error = li.getElement('.form_error');
      
      form_element.addEvent('focus', function(e){
        if(form_error && form_error.isVisible()) {
          form_error.hide();
        };
        form_hint.show();
      });
      
      form_element.addEvent('blur', function(e){
        form_hint.hide();
      });
    });
    var success_overlay = form_list.getPrevious('.success_overlay');
    var form = form_list.getParent('form');
    if (success_overlay) {
      success_overlay.getElement('.close').addEvent('click', function(e) {
        e.stop();
        success_overlay.fade(0);
        form_list.getElements('li.text, li.string').each(function(li) {
          var form_element = li.getElement('input') || li.getElement('textarea');
          form_element.value = form_element.getAttribute('placeholder');
          form_element.addClass('placeholder');
        });
      });
    } else {
      console.log('no success overlay found');
    };
  },
  
  initializeContactForm: function(contact_path) {
    var form = $('contact_form');
    var submit = $('contact_form_submit');
    var spinner = form.getElement('.spinner');
    submit.set('disabled', false);
    var success_box = form.getElement('#contact_success');
    
    this.initializeDefaultForm(form.getElement('ul.form_list'));
    
    form.addEvent('submit', function(e) {
      e.stop();
      if (!submit.get('disabled')) {
        submit.set('disabled',true);
        var request = new Request({
          url: contact_path,
          method: 'post',
          data: form.toQueryString(),
          onRequest: function() {
            spinner.show();
          },
          onSuccess: function(response) {
            spinner.hide();

            submit.set('disabled', false);

            success_box.setStyle('opacity', 0);
            success_box.show();
            success_box.fade(.85);
            submit.set('disabled', false);
          },
          onFailure: function(response) {
            spinner.hide();
            
            var errors = JSON.decode(response.responseText);
            
            var email_container = $('contact_email_error');
            var text_container  = $('contact_text_error');
            
            errors.each(function(error){
              if(error[0] == 'email'){ error_container = email_container; email_container = null;};
              if(error[0] == 'text') { error_container = text_container;  text_container  = null;};

              error_container.set('html', error[1]);

              if (error_container.isHidden()) {
                error_container.show();
                error_container.fade(1);
              } else {
                error_container.flash('#D61E21', '#871113', 3);
              }; 
            });
            
            [email_container, text_container].clean().each(function(li){
              li.hide();
            });
                        
            submit.set('disabled', false);
          }
        }).send();
      };
    });
  },
  
  initializeNewsletterForm: function() {
    var form = $('newsletter_form');
    var submit = form.getElement('.buttons button');
    var spinner = form.getElement('.spinner');
    var success_box = $('newsletter_success');
    submit.set('disabled', false);
    
    this.initializeDefaultForm(form.getElement('ul.form_list'));
    
    form.addEvent('submit', function(e) {
      e.stop();
      
      if (!submit.get('disabled')) {
        submit.set('disabled', true);
        var request = new Request({
          url: form.get('action'),
          method: 'post',
          data: form.toQueryString(),
          onRequest: function() {
            spinner.show();
          },
          onSuccess: function(response) {
            spinner.hide();

            submit.set('disabled', false);
            
            success_box.setStyle('opacity', 0);
            success_box.show();
            success_box.fade(.85);
          },
          onFailure: function(response, text) {
            spinner.hide();
            
            var errors = JSON.decode(response.responseText);
            var first_name_li = $('newsletter_firstname_error');
            var last_name_li  = $('newsletter_lastname_error');
            var email_li      = $('newsletter_email_error');

            errors.each(function(error){
              if(error[0] == 'first_name'){ li = first_name_li; first_name_li = null;};
              if(error[0] == 'last_name') { li = last_name_li;  last_name_li  = null;};
              if(error[0] == 'email')     { li = email_li;      email_li      = null;};

              li.set('html', error[1]);

              if (li.isHidden()) {
                li.show();
                li.fade(1);
              } else {
                li.flash('#D61E21', '#871113', 3);
              }; 
            });

            [first_name_li, last_name_li, email_li].clean().each(function(li){
              li.hide();
            });
            submit.set('disabled', false);
          }
        }).send();
      };
    });
  }
  
});

Element.implement({
  isHidden: function(){
    var w = this.offsetWidth, h = this.offsetHeight,
    force = (this.tagName === 'TR');
    return (w===0 && h===0 && !force) ? true : (w!==0 && h!==0 && !force) ? false : this.getStyle('display') === 'none';
  },
  isVisible: function(){
    return !this.isHidden();
  }
});

Element.implement({
	flash: function(to,from,reps,prop,dur) {
		
		//defaults
		if(!reps) { reps = 1; }
		if(!prop) { prop = 'background-color'; }
		if(!dur) { dur = 250; }
		
		//create effect
		var effect = new Fx.Tween(this, {
				duration: dur,
				link: 'chain'
			});
		
		//do it!
		for(x = 1; x <= reps; x++)
		{
			effect.start(prop,from,to);
		}
	}
});

//when the dom is ready...
window.addEvent('domready', function() {
	//time to implement fancy show / hide
	Element.implement({
		//implement show
		fancyShow: function() {
			this.fade('in');
		},
		//implement hide
		fancyHide: function() {
			this.fade('out');
		}
	});
});
