var Joof = {
	makePrototype: function(proto, object){
		$parse(object, function(method, fnc){
			proto[method] = function(){
				return fnc.apply(this, [this].concat($A(arguments)));
			}
		});
	},
	
	log: function(msg){
		if(window.console && console.log) console.log(msg);
	},
	
	extend: function(object, donor, overwrite){
		for(var method in donor){
			if(donor.hasOwnProperty(method)){
				if(!!overwrite) object[method] = donor[method];
				else if(!object[method]) object[method] = donor[method];
			}
		}
		return object;
	},
	
	parse: function(object, fnc, bind){
		for(var method in object) 
			if(object.hasOwnProperty(method))
				fnc.call(bind, method, object[method]);
	},
	
	get: function(element){
		if(typeof element == "string") element = document.getElementById(element) || null;		
		if(element && typeof element.hide !== "function") return $extend(element, Element);
		else return element;
		//return element;
	},
	
	select: function(query, context){
		return Sizzle(query, context || null);
	},
	
	getBody: function(){
		return document.body || document.documentElement;
	},
	
	range: function(start, end){
		var data = [];
		if(start <= end) 
			for(var index = start; index <= end; index++)	data.push(index);
		else
			for(var index = start; index >= end; index--) data.push(index);
		return data;
	},
	
	loop: function(nbLoops, fnc, bind){
		for(var index = 0; index < nbLoops; index++){
			fnc.call(bind, index);
		}
	},
	
	toArray: function(collection){
		if(collection.constructor == String) return collection.split("");
		var data = [];
		if(collection.length){
			for(var index = 0; index < collection.length; index++){
				data.push(collection[index]);
			}
			return data;
		}
		try{
			$parse(collection, function(key, value){
				data.push(value);
			});
			return data;
		}catch(e){ 
			return data;
		}
	},
	
	rand: function(min, max){
		return Math.floor(Math.random() * (max - min + 1) + min);
	},
	
	genUID: function(length){
		var charset = [
			String("ABCDEFGHIJKLMNOPQRSTUVWXYZ").split(""), 
			String("abcdefghijklmnopqrstuvwxyz").split(""),
			String("0123456789").split("")
		];
		var uid = ""; length = (length || 10);
		$loop(length, function(){
			uid += charset[$rand(0, 2)].getRandomItem();
		});
		return uid;
	},
	
	importScript: function(src){
		Element.insert(new Element("script", {type: "text/javascript", src: src}), Joof.getBody());
	},
	
	support: function(cssRule){
		var div = new Element('div');
		var vendors = "Khtml Ms O Moz Webkit".split(" ");
		var len = vendors.length;
		return function(prop){
			if(prop in div.style) return true;
			prop = prop.replace(/^[a-z]/, function(val){
				return val.toUpperCase();
			});
			while(len--){
				if(vendors[len] + prop in div.style) return true;
			}
			return false;
		};
	}
}

var $break = function(){};

(function(){	
	document.onDOMReady = function(fnc){
		if(Browser.isWebKit){
			if(["loaded", "complete"].inArray(document.readyState)) fnc.call(null);
			else document.onDOMReady.delay(50, fnc);
		}else if(Browser.isGecko || Browser.isOpera)
			Event.add(document, "DOMContentLoaded", fnc);
		else if(Browser.isIE){
			document.write("<script id=\"__ie_dom_loaded\" defer=\"defer\" src=\"javascript: void(0)\"><\/script>");
			$("__ie_dom_loaded").onreadystatechange = function(){
				if(this.readyState == "complete") fnc.call(null); 
			};
		}else{
			Event.add(window, "load", fnc);
		}
	}
	
	var aliasHash = {
		"$": "get",
		"$$": "select",
		"$A": "toArray",
		"$R": "range",
		"$extend": "extend",
		"$genUID": "genUID",
		"$loop": "loop",
		"$rand": "rand",
		"$parse": "parse",
		"$log": "log",
		"$importScript": "importScript",
		"$getBody": "getBody",
		"$support": "support"
	};
	
	Joof.parse(aliasHash, function(alias, fnc){
		window[alias] = Joof[fnc];
	});
})();

var Browser = {};
(function(){
	var versionSearchString = "";
	var searchString = function(data){
		for (var i=0; i<data.length; i++){
			var dataString = data[i].string;
			var dataProp = data[i].prop;
			versionSearchString = data[i].versionSearch || data[i].identity;
			if (dataString){
				if(dataString.indexOf(data[i].subString) != -1)
					return data[i].identity;
			}
			else if (dataProp)
				return data[i].identity;
		}
	}
	var searchVersion = function(dataString){
		var index = dataString.indexOf(versionSearchString);
		if(index == -1) return;
		return parseFloat(dataString.substring(index+versionSearchString.length+1));
	}
	var dataBrowser = [
		{
			string: navigator.userAgent,
			subString: "Chrome",
			identity: "Chrome"
		},
		{ 	string: navigator.userAgent,
			subString: "OmniWeb",
			versionSearch: "OmniWeb/",
			identity: "OmniWeb"
		},
		{
			string: navigator.vendor,
			subString: "Apple",
			identity: "Safari",
			versionSearch: "Version"
		},
		{
			prop: window.opera,
			identity: "Opera"
		},
		{
			string: navigator.vendor,
			subString: "iCab",
			identity: "iCab"
		},
		{
			string: navigator.vendor,
			subString: "KDE",
			identity: "Konqueror"
		},
		{
			string: navigator.userAgent,
			subString: "Firefox",
			identity: "Firefox"
		},
		{
			string: navigator.vendor,
			subString: "Camino",
			identity: "Camino"
		},
		{
			string: navigator.userAgent,
			subString: "Netscape",
			identity: "Netscape"
		},
		{
			string: navigator.userAgent,
			subString: "MSIE",
			identity: "MSIE",
			versionSearch: "MSIE"
		},
		{
			string: navigator.userAgent,
			subString: "Gecko",
			identity: "Mozilla",
			versionSearch: "rv"
		},
		{ 
			string: navigator.userAgent,
			subString: "Mozilla",
			identity: "Netscape",
			versionSearch: "Mozilla"
		}
	]
		
	Browser.name = searchString(dataBrowser) || "Unknown";
	Browser.version = searchVersion(navigator.userAgent) || searchVersion(navigator.appVersion) || "Unknown";
	Browser.isIE = !!(Browser.name == "MSIE");
	Browser.isGecko = !!(Browser.name == "Firefox" || Browser.name == "Camino" || Browser.name == "Netscape" || Browser.name == "Mozilla");
	Browser.isWebKit = !!(Browser.name == "Chrome" || Browser.name == "Safari");
	Browser.isOpera = !!(Browser.name == "Opera");
})();

Joof["class"] = function(properties){
	var subClass = function(){
		return this.initialize ? this.initialize.apply(this, arguments) : this;
	};
	$extend(subClass, this);
	subClass.prototype = properties;
	subClass.constructor = Joof.Class;
	return subClass;
};

window["Class"] = Joof["class"];

/*!
 * Sizzle CSS Selector Engine - v1.0
 *  Copyright 2009, The Dojo Foundation
 *  Released under the MIT, BSD, and GPL Licenses.
 *  More information: http://sizzlejs.com/
 */
(function(){

var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g,
	done = 0,
	toString = Object.prototype.toString,
	hasDuplicate = false,
	baseHasDuplicate = true;

// Here we check if the JavaScript engine is using some sort of
// optimization where it does not always call our comparision
// function. If that is the case, discard the hasDuplicate value.
//   Thus far that includes Google Chrome.
[0, 0].sort(function(){
	baseHasDuplicate = false;
	return 0;
});

var Sizzle = function(selector, context, results, seed) {
	results = results || [];
	context = context || document;

	var origContext = context;

	if ( context.nodeType !== 1 && context.nodeType !== 9 ) {
		return [];
	}
	
	if ( !selector || typeof selector !== "string" ) {
		return results;
	}

	var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context),
		soFar = selector, ret, cur, pop, i;
	
	// Reset the position of the chunker regexp (start from head)
	do {
		chunker.exec("");
		m = chunker.exec(soFar);

		if ( m ) {
			soFar = m[3];
		
			parts.push( m[1] );
		
			if ( m[2] ) {
				extra = m[3];
				break;
			}
		}
	} while ( m );

	if ( parts.length > 1 && origPOS.exec( selector ) ) {
		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
			set = posProcess( parts[0] + parts[1], context );
		} else {
			set = Expr.relative[ parts[0] ] ?
				[ context ] :
				Sizzle( parts.shift(), context );

			while ( parts.length ) {
				selector = parts.shift();

				if ( Expr.relative[ selector ] ) {
					selector += parts.shift();
				}
				
				set = posProcess( selector, set );
			}
		}
	} else {
		// Take a shortcut and set the context if the root selector is an ID
		// (but not if it'll be faster if the inner selector is an ID)
		if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML &&
				Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) {
			ret = Sizzle.find( parts.shift(), context, contextXML );
			context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0];
		}

		if ( context ) {
			ret = seed ?
				{ expr: parts.pop(), set: makeArray(seed) } :
				Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML );
			set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set;

			if ( parts.length > 0 ) {
				checkSet = makeArray(set);
			} else {
				prune = false;
			}

			while ( parts.length ) {
				cur = parts.pop();
				pop = cur;

				if ( !Expr.relative[ cur ] ) {
					cur = "";
				} else {
					pop = parts.pop();
				}

				if ( pop == null ) {
					pop = context;
				}

				Expr.relative[ cur ]( checkSet, pop, contextXML );
			}
		} else {
			checkSet = parts = [];
		}
	}

	if ( !checkSet ) {
		checkSet = set;
	}

	if ( !checkSet ) {
		Sizzle.error( cur || selector );
	}

	if ( toString.call(checkSet) === "[object Array]" ) {
		if ( !prune ) {
			results.push.apply( results, checkSet );
		} else if ( context && context.nodeType === 1 ) {
			for ( i = 0; checkSet[i] != null; i++ ) {
				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) {
					results.push( set[i] );
				}
			}
		} else {
			for ( i = 0; checkSet[i] != null; i++ ) {
				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
					results.push( set[i] );
				}
			}
		}
	} else {
		makeArray( checkSet, results );
	}

	if ( extra ) {
		Sizzle( extra, origContext, results, seed );
		Sizzle.uniqueSort( results );
	}

	return results;
};

Sizzle.uniqueSort = function(results){
	if ( sortOrder ) {
		hasDuplicate = baseHasDuplicate;
		results.sort(sortOrder);

		if ( hasDuplicate ) {
			for ( var i = 1; i < results.length; i++ ) {
				if ( results[i] === results[i-1] ) {
					results.splice(i--, 1);
				}
			}
		}
	}

	return results;
};

Sizzle.matches = function(expr, set){
	return Sizzle(expr, null, null, set);
};

Sizzle.find = function(expr, context, isXML){
	var set;

	if ( !expr ) {
		return [];
	}

	for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
		var type = Expr.order[i], match;
		
		if ( (match = Expr.leftMatch[ type ].exec( expr )) ) {
			var left = match[1];
			match.splice(1,1);

			if ( left.substr( left.length - 1 ) !== "\\" ) {
				match[1] = (match[1] || "").replace(/\\/g, "");
				set = Expr.find[ type ]( match, context, isXML );
				if ( set != null ) {
					expr = expr.replace( Expr.match[ type ], "" );
					break;
				}
			}
		}
	}

	if ( !set ) {
		set = context.getElementsByTagName("*");
	}

	return {set: set, expr: expr};
};

Sizzle.filter = function(expr, set, inplace, not){
	var old = expr, result = [], curLoop = set, match, anyFound,
		isXMLFilter = set && set[0] && Sizzle.isXML(set[0]);

	while ( expr && set.length ) {
		for ( var type in Expr.filter ) {
			if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) {
				var filter = Expr.filter[ type ], found, item, left = match[1];
				anyFound = false;

				match.splice(1,1);

				if ( left.substr( left.length - 1 ) === "\\" ) {
					continue;
				}

				if ( curLoop === result ) {
					result = [];
				}

				if ( Expr.preFilter[ type ] ) {
					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );

					if ( !match ) {
						anyFound = found = true;
					} else if ( match === true ) {
						continue;
					}
				}

				if ( match ) {
					for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
						if ( item ) {
							found = filter( item, match, i, curLoop );
							var pass = not ^ !!found;

							if ( inplace && found != null ) {
								if ( pass ) {
									anyFound = true;
								} else {
									curLoop[i] = false;
								}
							} else if ( pass ) {
								result.push( item );
								anyFound = true;
							}
						}
					}
				}

				if ( found !== undefined ) {
					if ( !inplace ) {
						curLoop = result;
					}

					expr = expr.replace( Expr.match[ type ], "" );

					if ( !anyFound ) {
						return [];
					}

					break;
				}
			}
		}

		// Improper expression
		if ( expr === old ) {
			if ( anyFound == null ) {
				Sizzle.error( expr );
			} else {
				break;
			}
		}

		old = expr;
	}

	return curLoop;
};

Sizzle.error = function( msg ) {
	throw "Syntax error, unrecognized expression: " + msg;
};

var Expr = Sizzle.selectors = {
	order: [ "ID", "NAME", "TAG" ],
	match: {
		ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
		CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/,
		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/,
		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
		TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/,
		CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/,
		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/,
		PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/
	},
	leftMatch: {},
	attrMap: {
		"class": "className",
		"for": "htmlFor"
	},
	attrHandle: {
		href: function(elem){
			return elem.getAttribute("href");
		}
	},
	relative: {
		"+": function(checkSet, part){
			var isPartStr = typeof part === "string",
				isTag = isPartStr && !/\W/.test(part),
				isPartStrNotTag = isPartStr && !isTag;

			if ( isTag ) {
				part = part.toLowerCase();
			}

			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
				if ( (elem = checkSet[i]) ) {
					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}

					checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ?
						elem || false :
						elem === part;
				}
			}

			if ( isPartStrNotTag ) {
				Sizzle.filter( part, checkSet, true );
			}
		},
		">": function(checkSet, part){
			var isPartStr = typeof part === "string",
				elem, i = 0, l = checkSet.length;

			if ( isPartStr && !/\W/.test(part) ) {
				part = part.toLowerCase();

				for ( ; i < l; i++ ) {
					elem = checkSet[i];
					if ( elem ) {
						var parent = elem.parentNode;
						checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false;
					}
				}
			} else {
				for ( ; i < l; i++ ) {
					elem = checkSet[i];
					if ( elem ) {
						checkSet[i] = isPartStr ?
							elem.parentNode :
							elem.parentNode === part;
					}
				}

				if ( isPartStr ) {
					Sizzle.filter( part, checkSet, true );
				}
			}
		},
		"": function(checkSet, part, isXML){
			var doneName = done++, checkFn = dirCheck, nodeCheck;

			if ( typeof part === "string" && !/\W/.test(part) ) {
				part = part.toLowerCase();
				nodeCheck = part;
				checkFn = dirNodeCheck;
			}

			checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
		},
		"~": function(checkSet, part, isXML){
			var doneName = done++, checkFn = dirCheck, nodeCheck;

			if ( typeof part === "string" && !/\W/.test(part) ) {
				part = part.toLowerCase();
				nodeCheck = part;
				checkFn = dirNodeCheck;
			}

			checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
		}
	},
	find: {
		ID: function(match, context, isXML){
			if ( typeof context.getElementById !== "undefined" && !isXML ) {
				var m = context.getElementById(match[1]);
				// Check parentNode to catch when Blackberry 4.6 returns
				// nodes that are no longer in the document #6963
				return m && m.parentNode ? [m] : [];
			}
		},
		NAME: function(match, context){
			if ( typeof context.getElementsByName !== "undefined" ) {
				var ret = [], results = context.getElementsByName(match[1]);

				for ( var i = 0, l = results.length; i < l; i++ ) {
					if ( results[i].getAttribute("name") === match[1] ) {
						ret.push( results[i] );
					}
				}

				return ret.length === 0 ? null : ret;
			}
		},
		TAG: function(match, context){
			return context.getElementsByTagName(match[1]);
		}
	},
	preFilter: {
		CLASS: function(match, curLoop, inplace, result, not, isXML){
			match = " " + match[1].replace(/\\/g, "") + " ";

			if ( isXML ) {
				return match;
			}

			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
				if ( elem ) {
					if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) {
						if ( !inplace ) {
							result.push( elem );
						}
					} else if ( inplace ) {
						curLoop[i] = false;
					}
				}
			}

			return false;
		},
		ID: function(match){
			return match[1].replace(/\\/g, "");
		},
		TAG: function(match, curLoop){
			return match[1].toLowerCase();
		},
		CHILD: function(match){
			if ( match[1] === "nth" ) {
				// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
				var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
					match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" ||
					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);

				// calculate the numbers (first)n+(last) including if they are negative
				match[2] = (test[1] + (test[2] || 1)) - 0;
				match[3] = test[3] - 0;
			}

			// TODO: Move to normal caching system
			match[0] = done++;

			return match;
		},
		ATTR: function(match, curLoop, inplace, result, not, isXML){
			var name = match[1].replace(/\\/g, "");
			
			if ( !isXML && Expr.attrMap[name] ) {
				match[1] = Expr.attrMap[name];
			}

			if ( match[2] === "~=" ) {
				match[4] = " " + match[4] + " ";
			}

			return match;
		},
		PSEUDO: function(match, curLoop, inplace, result, not){
			if ( match[1] === "not" ) {
				// If we're dealing with a complex expression, or a simple one
				if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) {
					match[3] = Sizzle(match[3], null, null, curLoop);
				} else {
					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
					if ( !inplace ) {
						result.push.apply( result, ret );
					}
					return false;
				}
			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
				return true;
			}
			
			return match;
		},
		POS: function(match){
			match.unshift( true );
			return match;
		}
	},
	filters: {
		enabled: function(elem){
			return elem.disabled === false && elem.type !== "hidden";
		},
		disabled: function(elem){
			return elem.disabled === true;
		},
		checked: function(elem){
			return elem.checked === true;
		},
		selected: function(elem){
			// Accessing this property makes selected-by-default
			// options in Safari work properly
			elem.parentNode.selectedIndex;
			return elem.selected === true;
		},
		parent: function(elem){
			return !!elem.firstChild;
		},
		empty: function(elem){
			return !elem.firstChild;
		},
		has: function(elem, i, match){
			return !!Sizzle( match[3], elem ).length;
		},
		header: function(elem){
			return (/h\d/i).test( elem.nodeName );
		},
		text: function(elem){
			return "text" === elem.type;
		},
		radio: function(elem){
			return "radio" === elem.type;
		},
		checkbox: function(elem){
			return "checkbox" === elem.type;
		},
		file: function(elem){
			return "file" === elem.type;
		},
		password: function(elem){
			return "password" === elem.type;
		},
		submit: function(elem){
			return "submit" === elem.type;
		},
		image: function(elem){
			return "image" === elem.type;
		},
		reset: function(elem){
			return "reset" === elem.type;
		},
		button: function(elem){
			return "button" === elem.type || elem.nodeName.toLowerCase() === "button";
		},
		input: function(elem){
			return (/input|select|textarea|button/i).test(elem.nodeName);
		}
	},
	setFilters: {
		first: function(elem, i){
			return i === 0;
		},
		last: function(elem, i, match, array){
			return i === array.length - 1;
		},
		even: function(elem, i){
			return i % 2 === 0;
		},
		odd: function(elem, i){
			return i % 2 === 1;
		},
		lt: function(elem, i, match){
			return i < match[3] - 0;
		},
		gt: function(elem, i, match){
			return i > match[3] - 0;
		},
		nth: function(elem, i, match){
			return match[3] - 0 === i;
		},
		eq: function(elem, i, match){
			return match[3] - 0 === i;
		}
	},
	filter: {
		PSEUDO: function(elem, match, i, array){
			var name = match[1], filter = Expr.filters[ name ];

			if ( filter ) {
				return filter( elem, i, match, array );
			} else if ( name === "contains" ) {
				return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0;
			} else if ( name === "not" ) {
				var not = match[3];

				for ( var j = 0, l = not.length; j < l; j++ ) {
					if ( not[j] === elem ) {
						return false;
					}
				}

				return true;
			} else {
				Sizzle.error( "Syntax error, unrecognized expression: " + name );
			}
		},
		CHILD: function(elem, match){
			var type = match[1], node = elem;
			switch (type) {
				case 'only':
				case 'first':
					while ( (node = node.previousSibling) )	 {
						if ( node.nodeType === 1 ) { 
							return false; 
						}
					}
					if ( type === "first" ) { 
						return true; 
					}
					node = elem;
				case 'last':
					while ( (node = node.nextSibling) )	 {
						if ( node.nodeType === 1 ) { 
							return false; 
						}
					}
					return true;
				case 'nth':
					var first = match[2], last = match[3];

					if ( first === 1 && last === 0 ) {
						return true;
					}
					
					var doneName = match[0],
						parent = elem.parentNode;
	
					if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
						var count = 0;
						for ( node = parent.firstChild; node; node = node.nextSibling ) {
							if ( node.nodeType === 1 ) {
								node.nodeIndex = ++count;
							}
						} 
						parent.sizcache = doneName;
					}
					
					var diff = elem.nodeIndex - last;
					if ( first === 0 ) {
						return diff === 0;
					} else {
						return ( diff % first === 0 && diff / first >= 0 );
					}
			}
		},
		ID: function(elem, match){
			return elem.nodeType === 1 && elem.getAttribute("id") === match;
		},
		TAG: function(elem, match){
			return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match;
		},
		CLASS: function(elem, match){
			return (" " + (elem.className || elem.getAttribute("class")) + " ")
				.indexOf( match ) > -1;
		},
		ATTR: function(elem, match){
			var name = match[1],
				result = Expr.attrHandle[ name ] ?
					Expr.attrHandle[ name ]( elem ) :
					elem[ name ] != null ?
						elem[ name ] :
						elem.getAttribute( name ),
				value = result + "",
				type = match[2],
				check = match[4];

			return result == null ?
				type === "!=" :
				type === "=" ?
				value === check :
				type === "*=" ?
				value.indexOf(check) >= 0 :
				type === "~=" ?
				(" " + value + " ").indexOf(check) >= 0 :
				!check ?
				value && result !== false :
				type === "!=" ?
				value !== check :
				type === "^=" ?
				value.indexOf(check) === 0 :
				type === "$=" ?
				value.substr(value.length - check.length) === check :
				type === "|=" ?
				value === check || value.substr(0, check.length + 1) === check + "-" :
				false;
		},
		POS: function(elem, match, i, array){
			var name = match[2], filter = Expr.setFilters[ name ];

			if ( filter ) {
				return filter( elem, i, match, array );
			}
		}
	}
};

var origPOS = Expr.match.POS,
	fescape = function(all, num){
		return "\\" + (num - 0 + 1);
	};

for ( var type in Expr.match ) {
	Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) );
	Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) );
}

var makeArray = function(array, results) {
	array = Array.prototype.slice.call( array, 0 );

	if ( results ) {
		results.push.apply( results, array );
		return results;
	}
	
	return array;
};

// Perform a simple check to determine if the browser is capable of
// converting a NodeList to an array using builtin methods.
// Also verifies that the returned array holds DOM nodes
// (which is not the case in the Blackberry browser)
try {
	Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType;

// Provide a fallback method if it does not work
} catch(e){
	makeArray = function(array, results) {
		var ret = results || [], i = 0;

		if ( toString.call(array) === "[object Array]" ) {
			Array.prototype.push.apply( ret, array );
		} else {
			if ( typeof array.length === "number" ) {
				for ( var l = array.length; i < l; i++ ) {
					ret.push( array[i] );
				}
			} else {
				for ( ; array[i]; i++ ) {
					ret.push( array[i] );
				}
			}
		}

		return ret;
	};
}

var sortOrder, siblingCheck;

if ( document.documentElement.compareDocumentPosition ) {
	sortOrder = function( a, b ) {
		if ( a === b ) {
			hasDuplicate = true;
			return 0;
		}

		if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) {
			return a.compareDocumentPosition ? -1 : 1;
		}

		return a.compareDocumentPosition(b) & 4 ? -1 : 1;
	};
} else {
	sortOrder = function( a, b ) {
		var ap = [], bp = [], aup = a.parentNode, bup = b.parentNode,
			cur = aup, al, bl;

		// The nodes are identical, we can exit early
		if ( a === b ) {
			hasDuplicate = true;
			return 0;

		// If the nodes are siblings (or identical) we can do a quick check
		} else if ( aup === bup ) {
			return siblingCheck( a, b );

		// If no parents were found then the nodes are disconnected
		} else if ( !aup ) {
			return -1;

		} else if ( !bup ) {
			return 1;
		}

		// Otherwise they're somewhere else in the tree so we need
		// to build up a full list of the parentNodes for comparison
		while ( cur ) {
			ap.unshift( cur );
			cur = cur.parentNode;
		}

		cur = bup;

		while ( cur ) {
			bp.unshift( cur );
			cur = cur.parentNode;
		}

		al = ap.length;
		bl = bp.length;

		// Start walking down the tree looking for a discrepancy
		for ( var i = 0; i < al && i < bl; i++ ) {
			if ( ap[i] !== bp[i] ) {
				return siblingCheck( ap[i], bp[i] );
			}
		}

		// We ended someplace up the tree so do a sibling check
		return i === al ?
			siblingCheck( a, bp[i], -1 ) :
			siblingCheck( ap[i], b, 1 );
	};

	siblingCheck = function( a, b, ret ) {
		if ( a === b ) {
			return ret;
		}

		var cur = a.nextSibling;

		while ( cur ) {
			if ( cur === b ) {
				return -1;
			}

			cur = cur.nextSibling;
		}

		return 1;
	};
}

// Utility function for retreiving the text value of an array of DOM nodes
Sizzle.getText = function( elems ) {
	var ret = "", elem;

	for ( var i = 0; elems[i]; i++ ) {
		elem = elems[i];

		// Get the text from text nodes and CDATA nodes
		if ( elem.nodeType === 3 || elem.nodeType === 4 ) {
			ret += elem.nodeValue;

		// Traverse everything else, except comment nodes
		} else if ( elem.nodeType !== 8 ) {
			ret += Sizzle.getText( elem.childNodes );
		}
	}

	return ret;
};

// Check to see if the browser returns elements by name when
// querying by getElementById (and provide a workaround)
(function(){
	// We're going to inject a fake input element with a specified name
	var form = document.createElement("div"),
		id = "script" + (new Date()).getTime();
	form.innerHTML = "<a name='" + id + "'/>";

	// Inject it into the root element, check its status, and remove it quickly
	var root = document.documentElement;
	root.insertBefore( form, root.firstChild );

	// The workaround has to do additional checks after a getElementById
	// Which slows things down for other browsers (hence the branching)
	if ( document.getElementById( id ) ) {
		Expr.find.ID = function(match, context, isXML){
			if ( typeof context.getElementById !== "undefined" && !isXML ) {
				var m = context.getElementById(match[1]);
				return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
			}
		};

		Expr.filter.ID = function(elem, match){
			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
			return elem.nodeType === 1 && node && node.nodeValue === match;
		};
	}

	root.removeChild( form );
	root = form = null; // release memory in IE
})();

(function(){
	// Check to see if the browser returns only elements
	// when doing getElementsByTagName("*")

	// Create a fake element
	var div = document.createElement("div");
	div.appendChild( document.createComment("") );

	// Make sure no comments are found
	if ( div.getElementsByTagName("*").length > 0 ) {
		Expr.find.TAG = function(match, context){
			var results = context.getElementsByTagName(match[1]);

			// Filter out possible comments
			if ( match[1] === "*" ) {
				var tmp = [];

				for ( var i = 0; results[i]; i++ ) {
					if ( results[i].nodeType === 1 ) {
						tmp.push( results[i] );
					}
				}

				results = tmp;
			}

			return results;
		};
	}

	// Check to see if an attribute returns normalized href attributes
	div.innerHTML = "<a href='#'></a>";
	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
			div.firstChild.getAttribute("href") !== "#" ) {
		Expr.attrHandle.href = function(elem){
			return elem.getAttribute("href", 2);
		};
	}

	div = null; // release memory in IE
})();

if ( document.querySelectorAll ) {
	(function(){
		var oldSizzle = Sizzle, div = document.createElement("div");
		div.innerHTML = "<p class='TEST'></p>";

		// Safari can't handle uppercase or unicode characters when
		// in quirks mode.
		if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
			return;
		}
	
		Sizzle = function(query, context, extra, seed){
			context = context || document;

			// Only use querySelectorAll on non-XML documents
			// (ID selectors don't work in non-HTML documents)
			if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) {
				try {
					return makeArray( context.querySelectorAll(query), extra );
				} catch(e){}
			}
		
			return oldSizzle(query, context, extra, seed);
		};

		for ( var prop in oldSizzle ) {
			Sizzle[ prop ] = oldSizzle[ prop ];
		}

		div = null; // release memory in IE
	})();
}

(function(){
	var div = document.createElement("div");

	div.innerHTML = "<div class='test e'></div><div class='test'></div>";

	// Opera can't find a second classname (in 9.6)
	// Also, make sure that getElementsByClassName actually exists
	if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) {
		return;
	}

	// Safari caches class attributes, doesn't catch changes (in 3.2)
	div.lastChild.className = "e";

	if ( div.getElementsByClassName("e").length === 1 ) {
		return;
	}
	
	Expr.order.splice(1, 0, "CLASS");
	Expr.find.CLASS = function(match, context, isXML) {
		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
			return context.getElementsByClassName(match[1]);
		}
	};

	div = null; // release memory in IE
})();

function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
		var elem = checkSet[i];
		if ( elem ) {
			elem = elem[dir];
			var match = false;

			while ( elem ) {
				if ( elem.sizcache === doneName ) {
					match = checkSet[elem.sizset];
					break;
				}

				if ( elem.nodeType === 1 && !isXML ){
					elem.sizcache = doneName;
					elem.sizset = i;
				}

				if ( elem.nodeName.toLowerCase() === cur ) {
					match = elem;
					break;
				}

				elem = elem[dir];
			}

			checkSet[i] = match;
		}
	}
}

function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
		var elem = checkSet[i];
		if ( elem ) {
			elem = elem[dir];
			var match = false;

			while ( elem ) {
				if ( elem.sizcache === doneName ) {
					match = checkSet[elem.sizset];
					break;
				}

				if ( elem.nodeType === 1 ) {
					if ( !isXML ) {
						elem.sizcache = doneName;
						elem.sizset = i;
					}
					if ( typeof cur !== "string" ) {
						if ( elem === cur ) {
							match = true;
							break;
						}

					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
						match = elem;
						break;
					}
				}

				elem = elem[dir];
			}

			checkSet[i] = match;
		}
	}
}

Sizzle.contains = document.compareDocumentPosition ? function(a, b){
	return !!(a.compareDocumentPosition(b) & 16);
} : function(a, b){
	return a !== b && (a.contains ? a.contains(b) : true);
};

Sizzle.isXML = function(elem){
	// documentElement is verified for cases where it doesn't yet exist
	// (such as loading iframes in IE - #4833) 
	var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement;
	return documentElement ? documentElement.nodeName !== "HTML" : false;
};

var posProcess = function(selector, context){
	var tmpSet = [], later = "", match,
		root = context.nodeType ? [context] : context;

	// Position selectors must be done after the filter
	// And so must :not(positional) so we move all PSEUDOs to the end
	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
		later += match[0];
		selector = selector.replace( Expr.match.PSEUDO, "" );
	}

	selector = Expr.relative[selector] ? selector + "*" : selector;

	for ( var i = 0, l = root.length; i < l; i++ ) {
		Sizzle( selector, root[i], tmpSet );
	}

	return Sizzle.filter( later, tmpSet );
};

// EXPOSE

window.Sizzle = Sizzle;

})();

Joof.hash = new Class({
	initialize: function(object){
		if(!object)
			var object = {};
		if(object.getLength)
			return object;
		if(object.constructor == String){
			object.split("&").each(function(parameters){
				var values = parameters.split("=");
				this[values[0]] = values[1];
			}, this);
		}else{
			$parse(object, function(key, value){
				this[key] = value;
			}, this);
		}
		return this;
	},
	each: function(fnc, bind){
		var index = 0;
		try{
			$parse(this, function(key, value){
				fnc.call(bind, key, value, index);
				index++;
			}, this);
		}catch(e){
			if(e != $break)
				throw e;
		}
	},
	getLength: function(){
		var length = 0;
		this.each(function(){
			length++;
		});
		return length;
	},
	hasKey: function(key){
		if(this[key]) return true;
		else return false;
	},
	serialize: function(){
		return Json.serialize(this);
	},
	toQueryString: function(){
		var encodedString = "";
		$parse(this, function(key, value){
			if(encodedString.length > 0) encodedString += "&";
			encodedString += key + "=" + encodeURIComponent(value);
		}, this);
		return encodedString;
	}
});

window["Hash"] = Joof.hash;
window["$H"] = function(object){
	return new Hash(object || {});
};

Joof.json = {
	serialize: function(object){
		var type = typeof object;
		if(type == "number" || type == "boolean") return object + "";
		else if(object === null) return "null";
		else if(type == "string"){
			var data = "";
			for(var index = 0; index < object.length; index++){
				var C = object.charAt(index);
				if(C == '\"') data += '\\"';
				else if(C == '\\') data += '\\\\';
				else if(C == '\b') data += '\\b';
				else if(C == '\f') data += '\\f';
				else if(C == '\n') data += '\\n';
				else if(C == '\r') data += '\\r';
				else if(C == '\t') data += '\\t';
				else if(object.charCodeAt(index) <= 0x1F){
					var hex = object.charCodeAt(index).toString(16);
					if(hex.length < 2) hex = '0' + hex;
					data += '\\u00' + hex.toUpperCase();
				}else data += C;
			}
			return '"' + data + '"';
		}
		var self = arguments.callee;
		if(object.constructor === Array){
			var data = [];
			for(var index = 0; index < object.length; index++){
				var val = self(object[index]);
				if (typeof(val) != "string") continue;
				data.push(val);
			}
			return "[" + data.join(", ") + "]";
		}
		if(type == "function") return null;
		var data = [];
		for(var key in object){
			var useKey;
			if(typeof(key) == "number") useKey = '"' + key + '"';
			else if(typeof(key) == "string") useKey = self(key);
			else continue;
			val = self(object[key]);
			if(typeof(val) != "string") continue;
			data.push(useKey + ":" + val);
		}
		return "{" + data.join(", ") + "}";
	},
	eval: function(json){
		var data = json.match(/^\s*\/\*(.*)\*\/\s*$/);
		if(data) json = data[1];
		return eval("(" + json + ")");
	}
};

window["Json"] = Joof.json;

Joof.text = function(txt){
	return document.createTextNode(txt || "");
};

window["Text"] = Joof.text;

Joof.array = {
	each: function(array, fnc, bind){
		try{
			for(var index = 0; index < array.length; index++)
				fnc.call(bind, array[index], index);
		}catch(e){
			if(e != $break) throw e;
		}
	},
	
	map: function(array, fnc, bind){
		var mapped = new Array(array.length);
		for(var entry in array){
			if(array.hasOwnProperty(entry))
				mapped[entry] = fnc.call(bind, array[entry]);
		}
		return mapped;
	},
	
	indexOf: function(array, value){
		var index = -1;
		for(var entry in array){
			if(array[entry] == value){
				index = entry;
				break;
			}
		}
		return index.toInt();
	},
	
	inArray: function(array, value){
		return array.indexOf(value) == -1 ? false : true;
	},
	
	remove: function(array, item){
		if(!array.inArray(item))
			return array;
		else
			return array.splice(array.indexOf(item), 1);
	},
	
	getRandomItem:  function(array){
		return array[$rand(0, array.length - 1)] || null;
	},
	
	join: function(array, spacer){
		var data = "", spacer = spacer === undefined ? ", " : spacer;
		for(var index = 0; index < array.length; index++){
			if(data.length > 0) data += spacer;
			data += array[index];
		}
		return data;
	},
	
	first: function(array){
		return array[0] || null;
	},
	
	last: function(array){
		return array[array.length - 1] || null;
	}
};

Joof.makePrototype(Array.prototype, Joof.array);

Joof.string = {
	isEmpty: function(string){
		return /^\s*$/.test(string);
	},
	isEmail: function(string){
		var reg = new RegExp("^[a-z0-9]+([_|\.|-]{1}[a-z0-9]+)*@[a-z0-9]+([_|\.|-]{1}[a-z0-9]+)*[\.]{1}[a-z]{2,6}$", "i");
		return (reg.test(string));
	},
	toInt: function(string, base){
		return parseInt(string, base || 10);
	},
	toFloat: function(string){
		return parseFloat(string);
	},
	truncate: function(string, truncLength, truncChar){
		var truncLength = truncLength || 30;
		return string.length > truncLength ? string.substr(0, truncLength) + (truncChar || "...") : String(string);
	},
	stripTags: function(string){
		return string.replace(/<\/?[^>]+>/gi, "");
	},
	stripScripts: function(string){
		return string.replace(new RegExp("<script[^>]*>([\\S\\s]*?)<\/script>", "img"), "");
	},
	capitalize: function(string){
		return string.substring(0, 1).toUpperCase() + string.substring(1).toLowerCase();
	},
	trim: function(string){
		return string.replace(/(^\s*)|(\s*$)/g, "");
	},
	camelize: function(string){
		return string.replace(/-\D/g, function(match){
			return match.charAt(1).toUpperCase();
		});
	},
	dash: function(string){
		return string.replace(/[A-Z]/g, function(match){
			return ("-" + match.charAt(0).toLowerCase());
		});
	},
	toQueryString: function(string){
		var encodedParams = "";
		string.split("&").each(function(param){
			var values = param.split("=");
			if(values.length == 2){
				if(encodedParams.length > 0) encodedParams += "&";
				encodedParams += values[0] + "=" + encodeURIComponent(values[1]);
			};
		});
		return encodedParams;
	},
	extractScripts: function(string){
		var data = [];
		var allScripts = new RegExp("<script[^>]*>([\\S\\s]*?)<\/script>", "img");
		var oneScript = new RegExp("<script[^>]*>([\\S\\s]*?)<\/script>", "im");
		$A(string.match(allScripts) || []).each(function(script){
			data.push((script.match(oneScript) || ["", ""])[1]);
		});
		return data;
	},
	evalScripts: function(string){
		string.extractScripts().each(function(script){
			eval(script);
		});
	},
	template: function(string, data){
		var pattern = new RegExp("#\{(.*?)\}", "g");
		var templated = this.toString();
		(templated.match(pattern) || []).each(function(matched){
			var val = matched.substring(2, matched.length-1);
			templated = templated.replace(matched, data[val]);
		});
		return templated;
	},
	decodeHtmlEntities: function(string){
		var tmp = new Element("div", {
			Styles: {
				"display": "none"
			}
		});
		Element.insert(tmp, document);
		tmp.innerHTML = string;
		var decode = tmp.childNodes[0].nodeValue;
		Element.remove(tmp);
		return decode;
	},
	addSlashes: function(string){
		return (string + '').replace(/[\\"']/g, '\\$&').replace(/\u0000/g, '\\0');
	},
	stripSlashes: function(string){
		return (string+'').replace(/\\(.?)/g, function(s, n1){
			switch (n1){
				case '\\':
					return '\\';
				case '0':
					return '\u0000';
				case '':
					return '';
				default:
					return n1;
			}
		});
	},
	br2nl: function(string){
		 return string.replace(/<br\s*\/?>/mg, "\n");
	}
}

Joof.makePrototype(String.prototype, Joof.string);

Joof["function"] = {
	bind: function(fnc, object){
		return function(){
			return fnc.apply(object, arguments);
		};
	},
	bindWithEvent: function(fnc, object){
		return function(event){
			return fnc.apply(object, [event || window.event]);
		};
	},
	delay: function(){
		var args = $A(arguments), fnc = args.shift(), delay = args.shift();
		return window.setTimeout(function(){
			return fnc.apply(this, args);
		}, delay);
	},
	loop: function(){
		var args = $A(arguments), fnc = args.shift(), loops = args.shift();
		$loop(loops, function(index){
			return fnc.call(this, index);
		});
	}
};

Joof.makePrototype(Function.prototype, Joof["function"]);Joof.number = {
	toInt: function(number, base){
		return parseInt(number, base || 10);
	},
	toFloat: function(number){
		return parseFloat(number);
	}
};

Joof.makePrototype(Number.prototype, Joof.number);

Joof.event = {
	getEvent: function(e){
		return e || window.event || null;
	},
	getElement: function(e){
		var event = Event.getEvent(e);
		return (event.target || event.srcElement);
	},
	add: function(element, type, fnc){
		element = $(element);
		if((element == document || element == window) && type == "domready"){
			document.onDOMReady(fnc);
			return;
		}
		if(Browser.isGecko && type == "mousewheel") type = "DOMMouseScroll";
		element.attachEvent ? element.attachEvent("on" + type, fnc) : element.addEventListener(type, fnc, false);
		if(!element.__events)
			element.__events = [];
		element.__events.push({type: type, fnc: fnc});
	},
	remove: function(element, type, fnc){
		type = type.toLowerCase();
		element = $(element);
		if(Browser.isGecko && type == "mousewheel") type = "DOMMouseScroll";
		element.removeEventListener ? element.removeEventListener(type, fnc, false) : element.detachEvent('on' + type, fnc);
		var destructId = -1;
		element.__events.each(function(event, index){
			if(event.type == type && event.fnc == fnc){
				destructId = index;
				throw $break;
			}
		});
		if(destructId > -1) element.__events.splice(destructId, 1);
	},
	stop: function(e){
		var event = Event.getEvent(e);
		if(!event) return;
		if(event.stopPropagation) event.stopPropagation();
		event.cancelBubble = true;
	},
	cancel: function(e){
		var event = Event.getEvent(e);
		if(!event) return;
		if(event.preventDefault) event.preventDefault();
		event.returnValue = false;
	},
	getCursorPositions: function(e){
		var event = Event.getEvent(e);
		if(!event) return;
		return {
			x: event.pageX || (event.clientX + (document.documentElement.scrollLeft || document.body.scrollLeft)),
			y: event.pageY || (event.clientY + (document.documentElement.scrollTop || document.body.scrollTop))
		};
	},
	getCursorX: function(e){
		return Event.getCursorPositions(e).x;
	},
	getCursorY: function(e){
		return Event.getCursorPositions(e).y;
	},
	getKey: function(e){
		var event = Event.getEvent(e);
		if(!event) return;
		return event.keyCode ? event.keyCode : event.which;
	},
	getWheelDelta: function(e){
		var event = Event.getEvent(e);
		if(!event) return;
		if(Browser.isIE || Browser.isWebKit) return (event.wheelDelta / 120) || 0;
		if(Browser.isOpera || Browser.isGecko) return -(event.detail / 3) || 0;
		return 0;
	},
	isWheelUp: function(e){
		return !!(Event.getWheelDelta(e) > 0);
	},
	isWheelDown: function(e){
		return !!(Event.getWheelDelta(e) < 0);
	}
};

window["Event"] = Joof.event;

Joof.element = {
	create: function(tag, attributes){
		tag = tag.toLowerCase();
		if(!attributes) return document.createElement(tag);
		var element = null;
		if(!Browser.isIE) element = document.createElement(tag);
		else{
			if(attributes.name){
				element = document.createElement("<" + tag + " name=\"" + attributes.name + "\"/>");
				delete attributes.name;
			}else
				element = document.createElement(tag);
		}
		if(attributes["Style"]){
			element = Element.setStyle(element, attributes["Style"]);
			delete attributes["Style"];
		}
		if(attributes["Text"]){
			element = Element.insert(new Text(attributes["Text"]), element);
			delete attributes["Text"];
		}
		if(attributes["Events"]){
			$parse(attributes["Events"], function(type, fnc){
				Event.add(element, type, fnc);
			});
			delete attributes["Events"];
		}
		return Element.writeAttributes(element, attributes);
	},
	
	writeAttributes: function(element, attributes){
		element = $(element);
		var boolAttributes = {
			readonly: "readOnly",
			disabled: "disabled",
			checked: "checked",
			selected: "selected",
			"for": "htmlFor"
		};
		for(var property in attributes){
			if(property == "class")
				element.setAttribute((Browser.isIE6 ? "className" : "class"), attributes[property]);
			else if(property == "style")
				element.setAttribute((Browser.isIE6 ? "cssText" : "style"), attributes[property]);
			else{				
				if(attributes[property] === false && boolAttributes[property]) 
					element.removeAttribute(property);
				if(attributes[property] === true && boolAttributes[property]) 
					element.setAttribute(property, boolAttributes[property]);
				if(!boolAttributes[property]) 
					element.setAttribute(property, attributes[property]);
			}
		};
		return element;
	},
	
	isBody: function(element){
		element = $(element);
		if(!element.tagName) return false;
		return (/^(?:body|html)$/i).test(element.tagName.toLowerCase());
	},
	
	getStyle: function(element, cssProperty){
		element = $(element);
		if(cssProperty == "opacity" && Browser.isIE && !element.style.opacity){
			if(value = (Element.getStyle(element, "filter") || "").match(/alpha\(opacity=(.*)\)/))
				if(value[1]) return parseFloat(value[1])/100;
			return 1;
		}
		var cssProperty = cssProperty == "float" ? "cssFloat" : cssProperty.camelize();
		var cssValue = element.style[cssProperty];
		if(cssValue == ""){
			if(window.getComputedStyle){
				var cssComputed = document.defaultView.getComputedStyle(element, null) || null;
				cssValue = cssComputed ? cssComputed[cssProperty] : null;
			}else cssValue = element.currentStyle[cssProperty];
		}
		if(cssValue == "undefined") return null;
		if(cssValue == "auto"){
			if((cssProperty == "width" || cssProperty == "height") && (Element.getStyle(element, "display") != "none"))
				return element["offset" + cssProperty.capitalize()]+"px";
			return 0;
		}
		if(cssValue == "rgba(0, 0, 0, 0)") return "transparent";
		return cssValue;
	},
	
	setStyle: function(element, styles){
		element = $(element);
		$parse(styles || {}, function(property, value){
			if(property == "opacity") Element.setOpacity(element, value);
			else if(property == "float") element.style[Browser.isIE ? "styleFloat" : "cssFloat"] = value;
			else element.style[property.camelize()] = value;
		});
		return element;
	},
	
	hasClass: function(element, sClassName){
		return new RegExp("(^| )" + sClassName + "( |$)").test($(element).className);
	},
	
	addClass: function(element, sClassName){
		element = $(element);
		if(!element.className)
			element.className = sClassName;
		else if(!Element.hasClass(element, sClassName))
			element.className += " " + sClassName;
	},
	
	removeClass: function(element, sClassName){
		element = $(element);
		element.className = element.className.replace(new RegExp("(^| )" + sClassName + "( |$)"),"$1").replace(/ $/, "");
		if(!element.className) element.removeAttribute("class");
	},
	
	replaceClass: function(element, className, newClassName){
		element = $(element);
		if(Element.hasClass(element, className))
			Element.removeClass(element, className);
		Element.addClass(element, newClassName);
	},
	
	switchClass: function(element, className1, className2){
		element = $(element);
		if(Element.hasClass(element, className1))
			Element.replaceClass(element, className1, className2);
		else
			Element.replaceClass(element, className2, className1);
	},
	
	toggleClass: function(element, className){
		element = $(element);
		if(Element.hasClass(element, className))
			Element.removeClass(element, className);
		else
			Element.addClass(element, className);
	},
	
	setOpacity: function(element, opac){
		element = $(element);
		if(opac > 1) opac = 1;
		if(opac < .001) opac = 0;
		if(Browser.isIE) element.style.filter = "alpha(opacity=" + opac * 100 + ")";
		element.style.opacity = opac;
	},
	
	show: function(element){
		var render = $(element).getAttribute("nativedisplay") ? $(element).getAttribute("nativedisplay") : "block";
		$(element).style.display = (render != "none" ? render : "block");
	},
	
	hide: function(element){
		element = $(element);
		if(!element.getAttribute("nativedisplay"))
			$(element).setAttribute("nativedisplay", Element.getStyle(element, "display"));
		$(element).style.display = "none";
	},
	
	toggle: function(element){
		$(element).style.display = Element.isVisible(element) ? "none" : "block";
	},
	
	isVisible: function(element){
		return !(Element.getStyle(element, "display") == "none");
	},
	
	remove: function(element){
		element = $(element);
		Element.removeEvents(element);
		element.parentNode.removeChild(element);
		element = null;
		return null;		
	},
	
	insert: function(newNode, element, position){
		element = $(element);
		if(element == document) element = document.body;
		var position = position || "bottom";
		switch(position){
			case "before": 
				var pN = element.parentNode;
				pN.insertBefore(newNode, element); 
			break;
			case "bottom":
				element.appendChild(newNode);
			break;
			case "after":
				var pN = element.parentNode;
				pN.insertBefore(newNode, element.nextSibling);
			break;
		};
		return element;
	},
	
	move: function(element, target, position){
		element = $(element);
		var events = element.__events || [];
		var newHandle = element.cloneNode(true);
		Element.insert(newHandle, target, position);
		if(element.__events && !Browser.isIE){
			events.each(function(event){
				Event.add(newHandle, event.type, event.fnc);
			});
		}
		Element.remove(element);
		events = null;
	},
	
	replaceWith: function(element, node){
		Element.insert(node, element, "after");
		Element.remove(element);
	},
	
	getChilds: function(element){
		element = $(element);
		var node = element == document ? document.body : element;
		var data = [];
		$A(node.childNodes).each(function(child){
			if(child.nodeType == 1) data.push($(child));
		});
		return data;
	},
	
	getFirstChild: function(element){
		return Element.getChilds(element).first();
	},
	
	getLastChild: function(element){
		return Element.getChilds(element).last();
	},
	
	getPreviousChild: function(element){
		element = $(element);
		var child = element["previousSibling"];
		while(child && child.nodeType != 1)
			child = child["previousSibling"];
		return child;
	},
	
	getNextChild: function(element){
		element = $(element);
		var child = element["nextSibling"];
		while(child && child.nodeType != 1)
			child = child["nextSibling"];
		return child;
	},
	
	scrollTo: function(elemen){
		var eltPos = Element.getPosition(element);
		window.scrollTo(eltPos.left, eltPos.top);
	},
	
	absolutize: function(element){
		if(Element.getStyle(element, "position") == "absolute")
			return null;
		var position = Element.getPosition(element);
		Element.setStyle(element, {
			"height": Element.getHeight(element) + "px",
			"width": Element.getWidth(element) + "px",
			"position": "absolute",
			"top": position.top + "px",
			"left": position.left + "px"
		});
	},
	
	getScroll: function(element){	
		element = $(element);
		if(element == window || element == document){
			return {
				left: window.pageXOffset || document.documentElement.scrollLeft || document.body.scrollLeft,
				top: window.pageYOffset || document.documentElement.scrollTop || document.body.scrollTop,
				height: Element.getHeight(document),
				width: Element.getWidth(document)
			};
		}
		var elt = element, position = {left: 0, top: 0};
		while(elt && !Element.isBody(elt)){
			position.left += elt.scrollLeft;
			position.top += elt.scrollTop;
			elt = $(elt.parentNode);
		}
		position.height = element.scrollHeight || 0;
		position.width = element.scrollWidth || 0;
		return position;
	},

	getPosition: function(element){			
		if(Element.isBody(element)) return {left: 0, top: 0};
		var offset = Element.getOffset(element), scroll = Element.getScroll(element);
		var position = {left: offset.x - scroll.left, top: offset.y - scroll.top};
		return {left: position.left, top: position.top};
	},
	
	getOffset: function(element){
		element = $(element);
		if(Browser.isIE){
			var bound = element.getBoundingClientRect(), dE = document.documentElement;
			return {
				x: bound.left + dE.scrollLeft - dE.clientLeft,
				y: bound.top + dE.scrollTop - dE.clientTop
			};
		}
		var elt = element, position = {x: 0, y: 0};
		if(Element.isBody(element)) return position;
		while(elt && !Element.isBody(elt)){
			position.x += elt.offsetLeft;
			position.y += elt.offsetTop;
			if(Browser.isGecko){
				position.x += Element.getStyle(elt, "borderLeftWidth").toInt();
				position.y += Element.getStyle(elt, "borderTopWidth").toInt();
				var parent = elt.parentNode;
				if(parent && Element.getStyle(parent, "overflow") != "visible"){
					position.x += Element.getStyle(parent, "borderLeftWidth").toInt();
					position.y += Element.getStyle(parent, "borderTopWidth").toInt();
				}
			}else if(elt != element && Browser.isWebKit){
				position.x += Element.getStyle(elt, "borderLeftWidth").toInt();
				position.y += Element.getStyle(elt, "borderTopWidth").toInt();
			}
			elt = elt.offsetParent;
		}
		if(Browser.isGecko){
			position.x -= Element.getStyle(element, "borderLeftWidth").toInt();
			position.y -= Element.getStyle(element, "borderTopWidth").toInt();
		}
		return position;
	},
	
	getDimension: function(element){
		element = $(element);
		if(element == window){
			var data = {};
			["width", "height"].each(function(offset){	
				data[offset] = document.compatMode == "CSS1Compat" && document.documentElement["client" + offset.capitalize()] || document.body["client" + offset.capitalize()];
			});
			return data;
		}
		if(element == document){
			var data = {};
			["width", "height"].each(function(offset){
				data[offset] = Math.max(
					document.documentElement["client" + offset.capitalize()], 
					document.body["scroll" + offset.capitalize()],
					document.documentElement["scroll" + offset.capitalize()]
				);
			});
			return data;
		}
		if(element.style.display != "none")
			return {width: element.offsetWidth, height: element.offsetHeight};
		var backup = {visibility: element.visibility, position: element.style.position, display: element.style.display};
		element.visibility = "hidden";
		if(Browser.isIE) element.style.position = "absolute";
		element.show();
		var eltWidth = element.clientWidth;
		var eltHeight = element.clientHeight;
		element.style.display = backup.display;
		if(Browser.isIE)
			element.style.position = backup.position;
		element.visibility = backup.visibility;
		return {width: eltWidth, height: eltHeight};
	},
	
	getHeight: function(element){
		return Element.getDimension(element).height;
	},
	
	getWidth: function(element){
		return Element.getDimension(element).width;
	},
	
	update: function(element, content){
		element = $(element);
		if(!content) content = "";
		if(["input"].inArray(element.tagName.toLowerCase()))
			element.value = content;
		else
			element.innerHTML = content;
	},
	
	addEvent: function(element, type, fnc){
		Event.add(element, type, fnc);
	},
	
	removeEvent: function(element, type, fnc){
		Event.remove(element, type, fnc);
	},
	
	removeEvents: function(element){
		element = $(element);
		if(!element.__events) return;
		element.__events.each(function(event){
			Element.removeEvent(element, event.type, event.fnc);
		});
	},
	
	fireEvent: function(element, eventType){
		element = $(element);
		if(document.createEventObject){
			var event = document.createEventObject();
			element.fireEvent("on" + eventType, event)
		}else{
			var mouseEvents = ["click", "dblclick", "mousedown", "mousemove", "mouseout", "mouseover", "mouseup"];
			var htmlEvents = ["change", "submit", "reset", "select", "blur", "focus"];
			var DOMEvent = mouseEvents.inArray(eventType) ? "MouseEvents" : "HTMLEvents";
			var event = document.createEvent(DOMEvent);
			event.initEvent(eventType, true, true );
			element.dispatchEvent(event);
		}
	}
};

(function(){
	window.Element = function(tag, attributes){
		return Joof.element.create(tag, attributes);
	};

	$parse(Joof.element, function(method, fnc){
		if(method != "create") window.Element[method] = fnc;
	});
})();

Joof.form = {
	serialize: function(form){
		var elements = Form.getElements(form), formObj = {}, formRadio = [];
		elements.each(function(component){
			var tagComponent = component.tagName.toLowerCase();
			if(tagComponent == "select" || tagComponent == "textarea" || (tagComponent == "input" && (component.type == "hidden" || component.type == "text" || component.type == "password")))
				formObj[component.name] = component.value;
			else if(tagComponent == "input" && component.type == "checkbox")
				formObj[component.name] = String(component.checked);
			else if(tagComponent == "input" && component.type == "radio" && !formRadio.inArray(component.name)){
				formRadio.push(component.name);
				$A(document.getElementsByName(component.name)).each(function(radio){
					if(radio.checked){
						formObj[component.name] = radio.id;
						throw $break;
					}
				});
				if(formObj[component.name] === undefined) formObj[component.name] = "";
			}
		});
		return $H(formObj).serialize();
	},
	getHash: function(form){
		return $H(Json.eval(Form.serialize(form)));
	},
	getElements: function(form){
		return $$("input, textarea, select", $(form));
	},
	disable: function(form){
		Form.getElements(form).each(function(element){
			Element.writeAttributes(element, {disabled: true});
		});
	},
	enable: function(form){
		Form.getElements(form).each(function(element){
			Element.writeAttributes(element, {disabled: false});
		});
	},
	request: function(form, options){
		form = $(form);
		if(!options) options = {};
		var defaultOptions = {
			method: options.method || form.getAttribute("method") || "post",
			parameters: Json.eval(Form.serialize(form))
		};
		var action = options.action ? options.action : form.getAttribute("action");
		if(options.action) delete options.action;
		new Ajax(action, $extend(options, defaultOptions));
	},
	toQueryString: function(form){
		return Form.getHash(form).toQueryString();
	}
};

window["Form"] = Joof.form;

Joof.__ajaxStack = [];
Joof.ajax = new Class({
	initialize: function(url, options){
		var defaultOptions = {
			method: "post",
			evalScript: false,
			noCache: false,
			encoding: "utf-8",
			contentType: "default",
			onUninitialized: function(){},
			onLoading: function(){},
			onLoaded: function(){},
			onInteractive: function(){},
			onComplete: function(){},
			onSuccess: function(){},
			onFailure: function(){}
		};
		var contentType = {
			"default": "application/x-www-form-urlencoded",
			"xml": "text/xml",
			"json": "application/json"
		};
		this.options = $extend(options || {}, defaultOptions);
		this.uid = $genUID();
		this.aborted = false;
		this.name = this.options.name ? this.options.name : "xhr" + this.uid;
		this.xhr = window.XMLHttpRequest ? new XMLHttpRequest() : new ActiveXObject("Microsoft.XMLHTTP");
		this.xhr.onreadystatechange = this.checkXHRState.bind(this);
		this.requestHeader = contentType[this.options.contentType] + (this.options.encoding ? "; charset=" + this.options.encoding : "");
		if(this.options.parameters){
			if(typeof this.parameters == "String") this.parameters = this.options.parameters.toQueryString();
			else this.parameters = $H(this.options.parameters).toQueryString();
		}else this.parameters = null;
		if(this.options.noCache) this.parameters += (this.parameters.length > 0 ? "&" : "") + "_requestId=" + this.uid;
		switch(this.options.method.toLowerCase()){
			case "get":
				try{	
					this.xhr.open("GET", url + (this.parameters ? "?" + this.parameters : ""), true);
					this.xhr.send(null);
				}catch(e){return e};
			break;
			case "post":
				try{
					this.xhr.open("POST", url, true);
					this.xhr.setRequestHeader("Content-Type", this.requestHeader);
					this.xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");
					this.xhr.setRequestHeader("Accept", "text/javascript, text/html, application/xml, text/xml, */*");
					this.xhr.send(this.options.body ? this.options.body : this.parameters);
				}catch(e){return e};
			break;
			default: return;
		}
		this.stacking();
		return this;
	},
	stacking: function(){
		Joof.__ajaxStack.each(function(entry){
			if(entry.name == this.name){
				entry.abort();
				Joof.__ajaxStack.remove(entry);
				throw $break;
			}
		}, this);
		Joof.__ajaxStack.push(this);
	},
	abort: function(){
		this.aborted = true;
		try{
			this.xhr.abort();
			this.xhr.onreadystatechange = function(){};
			this.xhr = null;
		}catch(e){ return e;}
	},
	checkXHRState: function(){
		switch(this.xhr.readyState){
			case 0 :
				this.options.onUninitialized.call(null, this.xhr);
			break;
			case 1 :
				this.options.onLoading.call(null, this.xhr);
			break;
			case 2 :
				this.options.onLoaded.call(null, this.xhr);
			break;
			case 3 :
				this.options.onInteractive.call(null, this.xhr);
			break;
			case 4 :
				this.defineComplete();
			break;
		};
	},
	defineComplete: function(){
		if(!this.aborted){
			this.options.onComplete.call(null, this.xhr);
			if(this.xhr.status == 200){
				if(this.options.evalScripts)
					this.xhr.responseText.evalScripts();
				this.options.onSuccess.call(null, this.xhr);
			}else
				this.options.onFailure.call(null, this.xhr);
		}
		Joof.__ajaxStack.remove(this);
		this.xhr.onreadystatechange = function(){};
		this.xhr = null;
	}
});

window["Ajax"] = Joof.ajax;

Joof.__animationListeners = [];
Joof.__animationGUID = 0;
Joof.animation = new Class({
	initialize: function(hObj, objCSSRules, parameters){
		var defaultParameters = {
			onComplete: function(){},
			duration: 500,
			fps: 60
		};
		var defaultCSSPropertyValues = {
			transition: "speedDown",
			unit: "px",
			roundValues: true
		};
		this.hObj = typeof hObj == "function" ? hObj : $(hObj);
		this.guid = Joof.__animationGUID++;
		this.parameters = $extend(parameters || {}, defaultParameters);
		this.cssRules = $H(objCSSRules);
		this.cssRules.each(function(cssProperty, values){
			var rule = this.cssRules[cssProperty];
			rule = $extend(values, defaultCSSPropertyValues);
			if(values.roundValues && cssProperty == "opacity")
				rule.roundValues = false;
			rule.isCSSColor = /(C|c)olor/.test(cssProperty);
			if(rule.isCSSColor){
				rule.rgbFrom =  this.hexToRGB(rule.from !== undefined ? rule.from : Element.getStyle(this.hObj, cssProperty));
				rule.rgbTo = this.hexToRGB(rule.to);			
				rule.from = 0;
				rule.to = 100;
			}else{
				rule.from = rule.from !== undefined ? rule.from.toFloat() : Element.getStyle(this.hObj, cssProperty).toFloat();
				rule.to = rule.to.toFloat();
			};
		}, this);
		Joof.__animationListeners.each(function(listener){
			if(listener && listener.hObj == this.hObj){
				window.clearInterval(listener.timer);
				Joof.__animationListeners.remove(listener);
			};
		}, this);
		this.frequency = (1000 / this.parameters.fps);
		this.nbMotions = Math.round(this.parameters.duration / this.frequency);
		this.mappings = this.getMappings();
		this.currentStep = 0;
		Joof.__animationListeners.push(this);
		this.animate();
	},
	hexToRGB: function(hex){
		var matchRGB = hex.match(/(\d+),\s*(\d+),\s*(\d+)/);
		if(matchRGB) hex = this.rgbToHex([matchRGB[1], matchRGB[2], matchRGB[3]]);
		if(hex.length == 4){
			var tmp = "#";
			$R(1, 3).each(function(index){
				tmp += hex.charAt(index) + hex.charAt(index);  
			});
			hex = tmp;
		}
		hex = hex.toLowerCase();
		var hexSplit = [hex.substring(1, 3), hex.substring(3, 5), hex.substring(5, 7)];
		return hexSplit.map(function(hexValue){
			if(hexValue.length == 1)
				hexValue += hexValue;
			return hexValue.toInt(16);
		});
	},
	rgbToHex: function(rgb){
		var hex = [];
		$loop(3, function(index){
			var bit = (rgb[index] - 0).toString(16);
			hex.push((bit.length == 1) ? "0" + bit : bit);
		}, this);
		return "#" + hex.join("");
	},
	getMappings: function(){
		var mappings = new Hash();
		this.cssRules.each(function(cssProperty, values, index){
			mappings[cssProperty] = [];
			var mapping = new Array(this.nbMotions);
			$R(0, (this.nbMotions - 2)).each(function(index){
				mapping[index] = this.getTweenValue(cssProperty, index,  values.from, values.to);
			}, this);
			mapping[this.nbMotions - 1] = values.isCSSColor ? this.rgbToHex(values.rgbTo) : values.to;
			mappings[cssProperty] = mapping;
		}, this);
		return mappings;
	},
	animate: function(){
		this.timer = window.setInterval((function(){
			if(this.currentStep < this.nbMotions){
				this.cssRules.each(function(cssProperty){
					this.applyTweenValue(cssProperty);
				}, this);
				this.currentStep++;
			}else{
				window.clearInterval(this.timer);
				this.parameters.onComplete.call(null, this.hObj);
				Joof.__animationListeners.each(function(listener){
					if(listener.guid == this.guid)
						Joof.__animationListeners.remove(listener);
				}, this);
				if(Joof.__animationListeners.length == 0)
					Joof.__animationGUID = 0;
				this.hObj = null;
			};
		}).bind(this), this.frequency);
	},
	applyTweenValue: function(cssProperty){
		var tweenValue = this.mappings[cssProperty][this.currentStep];
		if(typeof this.hObj == "function") this.hObj.call(null, cssProperty, tweenValue);
		else{	
			if(this.cssRules[cssProperty].isCSSColor)
				this.hObj.style[cssProperty] = tweenValue;
			else if(cssProperty == "opacity")
				Element.setOpacity(this.hObj, tweenValue);
			else if(cssProperty == "rotate")
				Element.setRotateDegree(this.hObj, tweenValue);
			else if(cssProperty == "scale")
				Element.setScale(this.hObj, tweenValue);
			else
				this.hObj.style[cssProperty] = tweenValue + this.cssRules[cssProperty].unit;
		}
	},
	getTweenValue: function(cssProperty, motion, from, to){
		var tweenValue = this.easing[this.cssRules[cssProperty].transition](motion * this.frequency, from, to - from, this.parameters.duration);
		var realValue = null;
		if(this.cssRules[cssProperty].isCSSColor)
			realValue = this.getColor(cssProperty, tweenValue);
		else if(this.cssRules[cssProperty].roundValues)
			realValue = parseInt(tweenValue);
		else
			realValue = parseFloat(tweenValue);
		return realValue;
	},
	getColor: function(cssProperty, percent){
		var cFrom = this.cssRules[cssProperty].rgbFrom;
		var cTo = this.cssRules[cssProperty].rgbTo;
		var pc = percent / 100;
		r= Math.floor(cFrom[0] + (pc * (cTo[0] - cFrom[0])));
		g= Math.floor(cFrom[1] + (pc * (cTo[1] - cFrom[1])));
		b= Math.floor(cFrom[2] + (pc * (cTo[2] - cFrom[2])));
		return this.rgbToHex([r, g, b]);
	},
	easing: {
		linear: function(t,b,c,d){
			return c*t/d + b;
		},
		elastic: function(t,b,c,d){
			var s=1.70158;var p=0;var a=c;
			if (t==0) return b;  if ((t/=d)==1) return b+c;  if (!p) p=d*.3;
			if (a < Math.abs(c)) { a=c; var s=p/4; }
			else var s = p/(2*Math.PI) * Math.asin (c/a);
			return a*Math.pow(2,-10*t) * Math.sin( (t*d-s)*(2*Math.PI)/p ) + c + b;
		},
		bounce: function(t,b,c,d){
			if((t/=d) < (1/2.75)) {
				return c*(7.5625*t*t) + b;
			}else if(t < (2/2.75)) {
				return c*(7.5625*(t-=(1.5/2.75))*t + .75) + b;
			}else if(t < (2.5/2.75)) {
				return c*(7.5625*(t-=(2.25/2.75))*t + .9375) + b;
			}else{
				return c*(7.5625*(t-=(2.625/2.75))*t + .984375) + b;
			}
		},
		skidStart: function (t,b,c,d){
			var s = 1.70158;
			return c*(t/=d)*t*((s+1)*t - s) + b;
		},
		skidEnd: function (t,b,c,d){
			var s = 1.70158;
			return c*((t=t/d-1)*t*((s+1)*t + s) + 1) + b;
		},
		skidStartEnd: function (t, b, c, d, s) {
			var s = 1.70158;
			if ((t/=d/2) < 1) return c/2*(t*t*(((s*=(1.525))+1)*t - s)) + b;
			return c/2*((t-=2)*t*(((s*=(1.525))+1)*t + s) + 2) + b;
		},
		speedUp: function(t, b, c, d){
			return (t==0) ? b : c * Math.pow(2, 10 * (t/d - 1)) + b;
		},
		speedDown: function(t, b, c, d){
			return (t==d) ? b+c : c * (-Math.pow(2, -10 * t/d) + 1) + b;
		},
		speedUpDown: function(t, b, c, d){
			if(t==0) return b;
			if(t==d) return b+c;
			if((t/=d/2) < 1) return c/2 * Math.pow(2, 10 * (t - 1)) + b;
			return c/2 * (-Math.pow(2, -10 * --t) + 2) + b;
		}
	}
});

window["Animation"] = Joof.animation;

Joof.cookie = {
	set: function(parameters){
		var cook = parameters.name + "=" + encodeURIComponent(parameters.value);
		if(parameters.duration){
			var date = new Date();
			date.setTime(date.getTime() + parameters.duration * 24 * 60 * 60 * 1000);
			cook += "; expires=" + date.toGMTString();
		}
		cook += "; path=/";
		document.cookie = cook;
	},
	get: function(name){
		var start = document.cookie.indexOf(name + "=")
		if(start >= 0){
			start += name.length + 1;
			var end = document.cookie.indexOf(";", start);
			if(end < 0) end = document.cookie.length;
			return decodeURIComponent(document.cookie.substring(start, end));
		}
		return null;
	},
	del: function(name){
		Cookie.set({
			name: name,
			value: "", 
			duration: -1
		});
	}
};

window["Cookie"] = Joof.cookie;

Joof.periodicalExecuter = new Class({
	initialize: function(fnc, parameters){
		var defaultParameters = {
			autoPlay: true,
			instantPlay: false,
			frequency: 1000
		};
		this.parameters = $extend(parameters || {}, defaultParameters);
		this.fnc = fnc;
		if(this.parameters.autoPlay) this.play();
	},
	stop: function(){
		window.clearInterval(this.timer);
		this.timer = null;
	},
	play: function(){
		if(this.timer) return null;
		if(this.parameters.instantPlay) this.fnc();
		this.timer = window.setInterval(this.fnc, this.parameters.frequency);
	}
});

window["PeriodicalExecuter"] = Joof.periodicalExecuter;

var ImagesPreloader = new Class({
	initialize: function(imagesList, options){
		var defaultOptions = {
			onImageLoad: function(){},
			onImageLoaded: function(){},
			onComplete: function(){}
		};
		this.imagesList = imagesList;
		this.options = $extend(options || {}, defaultOptions);
		this.currentImageIndex = 0;
		this.preloadImage = this._preloadImage.bind(this);
		this.preloadImage();
		return this;
	},
	_preloadImage: function(){
		if(this.currentImageIndex == this.imagesList.length){
			this.options.onComplete.call(null);
			return;
		}
		this.options.onImageLoad.call(null, this.currentImageIndex + 1);
		var imageCache = new Image;
		imageCache.onload = (function(){
			this.currentImageIndex++;
			this.options.onImageLoaded.call(null, this.currentImageIndex, imageCache.width, imageCache.height);
			this.preloadImage.delay(20, this.currentImageIndex);
		}).bind(this);
		imageCache.src = this.imagesList[this.currentImageIndex];
	}
});
