/* 
 * CORE 
 * 
 * A JS framework by phaistonians
 *
 * TODO:
 * 		Use native {} 	
 * 
 * Notes:
 * 	Max z-index value: The maximum value is 2147483647.
---------------------------------------------------------------------------------*/
var Core = {
	empty 		: function(){},
	version		: '1.00',
	author		: 'georgep@phaistosnetworks.gr',
	inspiration	: 'Mootools, Base2, Prototype',
	path		: ( location.protocol.indexOf('https') !== -1 ? 'https://s3.amazonaws.com/' : 'http://' )
	 			+ 'sobpool.phaistosnetworks.gr/pathfinder/core/',
	isHuman		: false,
	
	JSON		: {},
	hasProperJSONSupport	: ( 'JSON' in window)  && !(navigator.userAgent.match(/Chrome\/[1-6]\./gi)),
	supports	: {}
};  



window.xpath						= !!(document.evaluate);
window.opera						= window[document.isSameNode ? 'opera95' : 'opera9'] = window.opera;

if (window.ActiveXObject) 			{
	window.ie =  ( window[document.implementation ? (window.XMLHttpRequest || document.querySelector ?  (document.querySelector ? 'ie8' : 'ie7' )  : 'ie6') : 'ie5'] ) = true;

	// Temp solution - make it way better
	if(window.HTMLCanvasElement) {
		window.ie9 = true;
		window.ie8 = false;
	}
} else  if (document.childNodes && !document.all && !navigator.taintEnabled && !window.mozIndexedDB) {
	window.webkit 	= window.safari = window[window.xpath ? 'webkit420' : 'webkit419'] = true;
	window.safari3 	= window['webkit420'];
	window.safari2 	= window['webkit419'];
} else if (document.getBoxObjectFor !== null && !window.opera)	{ 
	window.gecko 	= window.moz = true; 
	window.gecko3	= window.gecko && !!document.getElementsByClassName && document.createElement('div').isContentEditable === undefined;
	window.gecko2	= window.gecko && !window.gecko3;
	window.gecko4	= window.gecko && !window.gecko2 && !window.gecko3;
}
window.chrome 		= !window.sidebar && !window.gecko && window.webkit && !window.ie && navigator.userAgent.indexOf('Chrome') != -1;

window.ios			= window.navigator.userAgent.indexOf('AppleWebKit') !== -1 && window.navigator.userAgent.indexOf('Mobile') !== -1;
window.android		= window.navigator.userAgent.indexOf('Android') !== -1;

if(window.chrome) {
	document.documentElement.className = "agent-chrome";
	window.safari = window.safari2 = window.safari3 = false;
} 
if(!window.HTMLElement) {
	window.HTMLElement				= {};
	if (window.safari) {
		document.createElement('iframe'); // fixes safari
	}
	
	window.HTMLElement.prototype  	= (window.safari) ? window['[[DOMElement.prototype]]'] : {};
} 


// HTML5 ie
if(window.ie && !window.ie9) {
	// http://www.hagenburger.net/BLOG/Simple-HTML5-Fix-for-IE.html
	'article aside footer header nav section time'.replace(/\w+/g,function(n){document.createElement(n)})

	// Core.html5ElementNames = ['section', 'article', 'nav', 'header', 'footer' ];
 
	// for (var i = 0, j = Core.html5ElementNames.length; i < j; i++) {
	//  document.createElement(Core.html5ElementNames[i]);
	// }
}

// Fix this later on
window.HTMLElement.prototype.ID = { type : 'element' }
window.ID 						= { type : 'window' }
document.ID 					= { type : 'document' }

// Register document.head for quick reference
document.head 		= document.getElementsByTagName('head')[0];
document.base 		= document.getElementsByTagName('base')[0] || null;
if(document.base) {
	document.base = document.base.href;
}

// Fix IE caching issues - take 1	
if (window.ie6 || window.ie5) {
	try {
		document.execCommand('BackgroundImageCache',false,true);
	} catch(ex) { };
}	

// Make sure console functions wont failt
if(!window.console) {
	window.console		 = window.console || {};
	var consoleMethods 	= [ 'time', 'timeEnd', 'clear', 'log', 'warn', 'error', 'info'];
	for(var i = 0, l = consoleMethods.length; i < l; i++ ){
		window.console[consoleMethods[i]] = window.console[consoleMethods[i]] || Core.empty;
	}
}

Core.supports['webSockets']		= 'WebSocket' in window || 'MozWebSocket' in window;
Core.supports['classList'] 		= !window.webkit && (document.documentElement && document.documentElement.classList !== undefined);
Core.supports['transitions']	= document.documentElement.style.webkitTransition !== undefined || document.documentElement.style.mozTransition !== undefined || document.documentElement.style.oTransition !== undefined || document.documentElement.style.transition !=undefined;

// ================================================================================
// Element Class
// ================================================================================
var Element  		= {}

// ================================================================================
// Garbage Module
// ================================================================================
var Garbage = {
	closures	: {},
	elements	: [],
	discarded	: 0,
	
	onBeforeUnload	: function(event) {
		this.removeEvent('DOMContentLoaded');
		// http://ajaxian.com/archives/are-you-sure-your-unload-handler-is-firing-in-ie
		if(window.ie && document.state === 'interactive') {
			function stop() {
			 	 // Prevent memory leak
			 	 document.detachEvent('onstop', stop);
				// Call unload handler
				Garbage.onLoad.call(this);
			};

			// Fire unload when the currently loading page is stopped
			document.attachEvent('onstop', stop);
			
			// Remove onstop listener after a while to prevent the unload function
			// to execute if the user presses cancel in an onbeforeunload
			// confirm dialog and then presses the stop button in the browser
			window.setTimeout(function() {
			  document.detachEvent('onstop', stop);
			}, 0);			
		} else {
			this.addEvent('unload', Garbage.onUnload.bind(this));
		}
	},
	
	onUnload	: function(event) {
		Garbage.collect(this.document);
		Garbage.collect(this);
		Garbage.discard(event); // all	
	},
	
	
	collect		: function(el){
		if(!el || (el.ID && el.ID.collected) || !Garbage.elements || !el.nodeType ) {
			return el;
		}
		
		el.ID = el.ID || {};
		el.ID.collected = true;
		
		Garbage.elements.push(el);		
		this.elCount ++;
		return el;
	},
	
	discard 	: function(elements) {
		var el, p;
		var discardAll 	= elements && elements.type && elements.type == 'beforeunload';
		elements 		= discardAll ? this.elements : elements;
		for( var i  = 0, j = elements.length; i < j;i++ ) {
			var permCheck 	= true;
			el 				= elements[i];	
			
			if(!el || !el.extended) {
				continue;
			}
			
			this.discarded ++;
			Garbage.elements[Garbage.elements.indexOf(el)] 							= null;
			// Garbage.elements[el.uniqueID]											= null;
			// Remove all native events handlers to prevent memory leaking in IE
			// This should remove if not all, most of the leaking in IE
			if(el.removeEvents) {
				el.removeEvents(); 
			}
			
			if( window.ie ) {
				for ( p in Element.Methods ) el[p] 										= null;
				for ( p in el.methods )		 el[p] 										= null;	 // Custom methods			
			}

			el.extended =  el.ID = el.events = el.methods =  el.cache = el 	= null;
		}
		
		if(discardAll) {
			Garbage.closures = Garbage.elements = null;
			if (window.CollectGarbage) {
				window.CollectGarbage();			
			}
		}
	}
};


// ================================================================================
// Global functions
// ================================================================================
$typeOf	= $type =  function(obj){
	var type, ret;
	
	if(obj === null || obj === undefined) {
		return false;
	}
	
	if(obj.ID && obj.ID.type) {
		return obj.ID.type;
	}
	
	// window check
	if(obj.window) {
		obj.ID = { type : 'window' }
		return 'window';
	}
	
	type = typeof(obj);
	if (type == 'object' && (obj.extented && obj.type) ||  (obj.target || obj.srcElement)) {
		// Double check
		// Changed: 19/10/08 by gp
		if(obj.type) {
			obj.ID = { type : 'event' }
			return 'event';
		}
	}

		
	if (type == 'object' && obj.nodeName){
		switch(obj.nodeType){
			case 1: ret =  'element'; break;
			case 3: ret = (/\S/).test(obj.nodeValue) ? 'textnode' : 'whitespace'; break;
		}
		obj.ID = { type : ret }
		return ret;
	}

	if (obj.length !== undefined && typeof obj.length == 'number') {
		if ('item' in obj)			ret 	= 'collection';
		if (obj.callee) 			ret 	= 'arguments';	
		
		if(!window.ie) {
			obj.ID = { type : ret }
		}

		return ret;
	}

	return type;
};

$extend = function() {
	var args 		     = arguments;
	if( !args[1] ) args = [this,args[0]]; // Missing first argument?
	if( !args[1] ) return args[0];
	
	
	for (var p in args[1]) {
		// We need to force a try / catch kinda thing here.
		// Otherwise we may end up with erors on IE
		try{
			args[0][p] = args[1][p];
		} catch(ex) {
			console.error('Failed to extend : ' + args[0] + ', to : ' + p);
		}
	}
	
	return args[0];
};	

$is  = function(obj)		{
	// CHANGED: beter way
	return !(obj === null || typeof(obj) === 'undefined' || typeof(obj) === 'unknown') &&  (obj || obj === 0);
};

$any = function() 			{ 
	var item = null, args = $A(arguments);
	for( var i = 0, l = args.length; i < l; i++ ) {
		if( $is(args[i]) ) {
			item = args[i];
			break;
		}
	}
	return item;
};


// Filter arguments and returns the one that actually are
$filter = function()  {
	return $A(arguments).filter($is);
}

// Lambda
function $arguments(i) {
	return function(){
		return arguments[i];
	};
};

$uniqueID	= function( obj ) 	{
	if( !this.$UID ) {
		this.$UID		= 0;
	}
	
	if(!obj.$UID) {
		obj.$UID 		= 'UID' + this.$UID++;
	}
	
	return obj.$UID
};

$A	= function( arg, start) {
	var newArray = [], type = $typeOf(arg);	
	if(type === 'string' ) {
		return arg.toArray();
	}

	if(type === 'object') { 
		each(arg, function(item) {
			newArray.push(item);					   
		});
		return newArray;
	}
	
	// Default
	start = start || 0;
	
	// Arguments? Do it the fast way
	if(start !==0 && type === 'arguments') {
		return Array.prototype.slice.call(arg);	
	}
	
	if (start < 0) {
		start = arg.length + start;
	}
	
	var length = length || (arg.length - start);
	
	for (var i = 0; i < length; i++) {
		newArray[i] = arg[start++];
	}
	
	return newArray;
};

$clear	=  function( timer ) {
	clearInterval 	(timer);
	clearTimeout  	(timer);
	return 			( (timer = null) );
};

$forEach  = each = function(  obj, fn, context ) {
	for (var property in obj)	{
		// Skip extend (Abstracts)
		if( !( property === 'extend' && $typeOf(obj[property]) === 'function' ) ) {
			fn.call(context, obj[property], property); 
		}
	}
};


$toJSON = function(object) {
	return JSON.stringify(object);	
	/*
	// FF 3.1, IE8 (for now)
	var type = typeof object;
	
	if (object === null) {
		return 'null';
	}
	
	// NOT for now
	// needs work :(
	if(0) {
		if(type === 'object' && typeof(JSON) !== 'undefined' && JSON.stringify) {
			return JSON.stringify(object);
		}
	}

	switch(type) {
		case 'undefined':
		case 'function':
		case 'unknown': return;
		case 'boolean': return object.toString();
	}
	
	// Custom
	if (object.toJSON) {
		return object.toJSON();
	}
	
	if (object.ownerDocument === document) {
		return;
	}
	
	var results = [];
	
	for (var property in object) {
		var value = $toJSON(object[property]);
	
		// NOTE: When returning nothing in a function the return value  == undefined
		if (value !== undefined) {
		//	console.warn('\"' + property.toJSON() + '" : ' + value);
			results.push( '\"' + property.toJSON() + '" : ' + value);
		}
	}

	//console.log('{' + results.join(', ') + '}');
	return '{' + results.join(', ') + '}';
	*/ 
};

$now	= function() 				{ return new Date().getTime(); };


//$rnd	= function( min, max) 		{ return min + Math.random() * ( max - min + 1);  };
$rnd	= function( min, max) 		{ return min + (Math.random() * ( max - min ));  };

$C 		= function( tagName, properties ) {return $(document.createElement(tagName) ).set(properties); };

$CT		= function( content ) { return document.createTextNode(content); };

$T		= function( tags, root ) {
	if(!tags.push) {
		tags = [tags];	
	}
	
	return ($(root) || document).getElements(tags.join(','));
};

$merge	= function(){
	var mix = {}, ap, mp;
	for (var i = 0; i < arguments.length; i++){
		for (var property in arguments[i]){
			ap 		= arguments[i][property];
			mp 		= mix[property];
			
			// CHANGED: fix this, mootools has an additional mp &
			// if (mp && $typeOf(ap) == 'object' && $typeOf(mp) == 'object') 
			if ($typeOf(ap) == 'object' && $typeOf(mp) == 'object') {
				mix[property] = $merge(mp, ap);
			}
			else mix[property] = ap;
		}
	}
	return mix;
};


// ================================================================================
// Abstract
// ================================================================================
Abstract = function( obj ) { 
	obj 		= obj || {};
	obj.extend 	= $extend;
	return obj;
}

// ================================================================================
// Some static addition to constructors
// ================================================================================
// https://developer.mozilla.org/en/Core_JavaScript_1.5_Reference/Global_Objects/Object/create
if(!Object.create && typeof(Object.create) !== 'function') {
	Object.create = function(src) {
		var F 		= new Function();
		F.prototype = src;
		return new F;
	}
}
// http://ejohn.org/blog/ecmascript-5-objects-and-properties/
if(!Object.keys) {
	Object.keys = function(obj) {
		var ret = [];
		for(var prop in obj) {
			if(obj.hasOwnProperty(prop)) {
				ret.push(prop);
			}
		}
		return ret;
	}
}

// ================================================================================
// Prototypes
// ================================================================================
var prototypes = [Array, String, Date, Number, Function, RegExp, Boolean];
for(var i = 0, l = prototypes.length; i < l; i++ ) {
 	prototypes[i].extend = function() { 
		$extend(this.prototype, arguments[0]);	
	}
	
	prototypes[i].prototype.ID 			= { type : ['array', 'string', 'date',  'number',  'function', 'regexp', 'boolean'][i] }	
};


// Array
Array.extend({
	forEach			: function(fn, context){
		for (var i = 0, j = this.length; i < j; i++)  {
			fn.call((context || this[i]), this[i], i, this);
		}
	},
	
	map				: function(fn, context)			{
		var results = [];
		var lala = function() {
			return this;
		};
		for (var i = 0, j = this.length; i < j; i++) {
			results[i] = fn.call(context, this[i], i, this);
		}

		return results;		
	},
	
	unique			: function(ignoreCase) {
		var hash = {}, ret = [];
		this.each(function(item) {
			var against = item;
			if(ignoreCase == true) {
				against = item.toLowerCase();
			}
			
			if(!hash[against]) {
				hash[against] = true;
				ret.push(item);
			}
		});
		
		return ret;
	},
	
	reduce			: function(fn) {
		var len = this.length || 0, i = 0, rv;
		if(arguments.length > 2) {
			rv = arguments[1];
		} else {
			do {
				if (i in this) {
					rv = this[i++];
					break;
				}
				// No values, no initial value to return
				if(++i >= len) {
					throw new TypeError();
				}
			} while(true);
		}
		
		for (; i < len; i++) {
			if(i in this) {
				rv = fn.call(undefined, rv, this[i], this);
			}
		}
		
		return rv;
		// https://developer.mozilla.org/en/JavaScript/Reference/Global_Objects/TypeError
	},
	
	indexOf 		: function( value ) 	{
		for (var i = 0 ; ( (thisItem = this[i]) || $is(thisItem)); i++ ) 	{
			// Special care for date items
			if($typeOf(thisItem) === 'date' && $typeOf(value) === 'date' ) {
				if( thisItem.getTime() === value.getTime() ) {
					return i;	
				}
			} else {
				if( thisItem == value )	{
					return i;
				}
			}
		}

		return -1;
	},
	
	empty			: function()			{ this.length = 0; return this;},
	
	/*
	// Extension
	toJSON_ext		: function() {
		var results = [];
		this.each(function(object) {
			var value = $toJSON(object);
			if (value !== undefined) {
				results.push(value);
			}
		});
		return '[' + results.join(', ') + ']';
	
	},
	*/
	
	shuffle			: function() 			{ 
		// TODO: study this loop
		for(var j, x, i = this.length; i; j = parseInt(Math.random() * i), x = this[--i], this[i] = this[j], this[j] = x);
		return this;
	},
	
	clone			: function()		{ return $A(this);},
	
	rnd				: function() 		{ return this[$rnd(0, this.length)] },
	
	every			: function(fn, context){
		for (var i = 0, j = this.length; i < j; i++)
			if (!fn.call(context, this[i], i, this)) {
				return false;
			}
		return true;
	},

	filter			: function(fn, context){
		var results = [];
		for (var i = 0, j = this.length; i < j; i++)
			if (fn.call(context, this[i], i, this)) {
				results.push(this[i]);
			}
		return results;
	},
	
	remove 			: function(item){
		var i = 0, len = this.length;
		while (i < len){
			if (this[i] === item){
				this.splice(i, 1);
				len--;
			} else 	i++;
		}
		return this
	},
	
	contains 	: function( item ) 	{ return !!(this.indexOf( item ) != -1 ); },
	
	extend		: function( array )  { 
		for (var i = 0, j = array.length; i < j; i++) {
			this.include(array[i]);
		}
		return this;	
	},
	
	getLast		: function()		{ return this[this.length - 1] },
	
	clone		: function()		{ return [].concat(this);		},
	
	include		: function(item)	{ if(!this.contains(item) ) this.push(item); return this}
});
Array.prototype.each 	= Array.prototype.forEach;
Array.prototype.copy	= Array.prototype.clone;

// String
String.extend({
	escapeRegExp	: function() 		{ 
		return this.replace(/([.*+?^${}()|[\]\\])/g, '\\$1'); 
		// TODO: I removed the \/. Did i do wrong?
		//return this.replace(/([.*+?^${}()|[\]\/\\])/g, '\\$1'); 
	},		

	// ADDED: 16.7.2010
	// Support for greek chars
	toUpper			: function()		{
		var rules	= {
			'Ά'	: 'Α',
			'Ό'	: 'Ο',
			'Ύ'	: 'Υ',
			'Ε'	: 'Ε',
			'Έ'	: 'Ε',
			'Ί'	: 'Ι',
			'Ώ'	: 'Ω',
			'Ή' : 'Η',
			'ς'	: 'Σ'
		},string = this.toUpperCase();
		each(rules, function(repl, key) {
			string = string.replace( new RegExp(key, 'g'), repl); 
		});

		return string;
	},

	reverse			: function() 		{ return $A(String(this)).reverse().join(''); },

	// http://blog.stevenlevithan.com/archives/faster-trim-javascript
	trim			: function()		{ return this.replace(/^\s\s*/, '').replace(/\s\s*$/, ''); },
	
	trimLeft		: function()		{ return this.replace( /^\s+/g, '');},	
	
	trimRight		: function()		{ return this.replace( /\s+$/g, '');},	
	
	stripScripts 	: function() 		{ return this.replace( new RegExp( '(?:<script.*?>)((\n|\r|.)*?)(?:<\/script>)', 'img'), ''); },
	
	toArray			: function() 		{ return this.split(''); },
	
	// From PHP:
	// Returns a string with backslashes stripped off. (\' becomes ' and so on.) 
	// Double backslashes (\\) are made into a single backslash (\). 
	stripSlashes	: function()		{ return this.replace(/\\('|")/g, '$1').replace(/\\{2}/g, '\\') },

	addSlashes		: function( doubleOnly ) 		{ 
		var self = this.replace( /"/g, "\\\"");
		if(!doubleOnly) {
			self = self.replace( /'/g, "'\\'");
		}
		return self;	
	},
	
	// ADDED: 5 Aug 2010
	// Super useful in case of submiting xmlrpc_structs.
	// DO check works/addressbook for more
	addSlashesSingle	: function() {
//		return this.replace(/\\/gi, '@').;	
	},
	
	camelCase		: function()		{ return this.replace(/-\D/g, function(match){return match.charAt(1).toUpperCase();	} ) },

	hyphenate: function()				{
		return this.replace(/\w[A-Z]/g, function(match){
			return (match.charAt(0) + '-' + match.charAt(1).toLowerCase());
		});
	},
	
	/*
	// Extension
	toJSON_ext				: function() {
		// CHANGED: 21.05.2009
		return '"' + this.addSlashes().replace(/'/g, '\\\'') + '"';		
		//	return '\'' + this.addSlashes().replace(/'/g, '\\\'') + '\'';		
	},
	*/
	
	// Json test regexp is by Douglas Crockford <http://crockford.org>. 
	evalJSON			: function() 		{ 
		// FF 3.1 , IE8 for now
		// If missing, we use the JSON object defined here (see JSON)
		var string = this.toString();
		
		if(!string || !string.length) {
			return null;
		}

		if(typeof(JSON) !== 'undefined'  && JSON.parse) {
			// Wee need an exception here for IE8
			try{ 
				return JSON.parse(string);	
			} catch(ex) {
				// Newest dev versions of chrome do this
				// Fix it here
				// 29.10.2011
				if(window.chrome && ex.type && ex.type !== 'unexpected_token' || string[0] === '{' || string[0] === '[') {
					return Core.JSON.parse(string) || null;
				}
				return null;	
			}
		}
		// OBSOLETE METHOD
		// return (  !(/^("(\\.|[^"\\\n\r])*?"|[,:{}\[\]0-9.\-+Eaeflnr-u \n\r\t])+?$/.
		// test(String(this))) ) ? null : eval('(' + String(this) + ')');
	},	
	
	contains 		: function(needle) 	{ return this.indexOf( needle ) != -1; },
	
	escapeHTML		 : function(singleQuotes) 		{
		 var self 	= arguments.callee; // A nice way to refenrece to this
		 var string = this.replace(/&/g,'&amp;').replace(/</g,'&lt;').replace(/>/g,'&gt;');
		 return singleQuotes ?	string.replace(/'/g, '&#039;')  : string.replace(/"/g, '&quot;');
	},
	
	unescapeHTML	: function() 		{ return this.replace(/&amp;/g,'&').replace(/&lt;/g,'<').replace(/&gt;/g,'>');	},
	
	capitalize		: function()		{ return this.charAt(0).toUpperCase() + this.substring(1).toLowerCase(); },
	
	pad				: function(length, padder) {
		length	= length || 2;
		padder	= padder || '0';
	
		for( var i = 0, str= ''; i < length - this.toString().length - padder.length + 1; i++ ) {
			str += padder;
		}
		
		return str + this;
	},
	
	toInt			: function() {
		var res = parseInt(this, 10);
		return !isNaN(res) ? res : 0;
	},
	
	toFloat			: function() { return parseFloat(this); },
	
	// 12340.2044 to 12,340.20
	toFormatted		: function( seperator, radix ) {
		// Figure out default radix
		var self = parseInt(Number(this)) || 0;
		
		// Figure out default radix
		radix 				=== undefined && (function() { 
			radix = self.toString().indexOf('.') !== -1 ? self.toString().replace(/^.+\./, '').length : 0;
		})();
		
		// Figure out seperators ( . OR ,)
		seperator			= seperator || '.';
		var seperatorDec 	= seperator == '.' ? ',' : '.';
		
		// Get sign, integer and decimal
		var match = String(self.toFixed(radix)).match( /^(\-)?(\d+?)(\.(\d+)?)?$/);

		return ((match[1] || '')  +
		match[2].reverse().replace( /(\d{3})(?!$)/g, '$1' + seperator ).reverse() +
		( match[4] ? 	(seperatorDec || ',' ) + match[4] : '' )).
		replace(new RegExp((seperator + seperatorDec).escapeRegExp()), seperatorDec);			// Lame:(
	},
	
	stripTags	: function(allowedTags) 	{ 
		var reg, content = this;
		
		if(allowedTags) {
			allowedTags = allowedTags.push ? allowedTags.join('|') : allowedTags;
			allowedTags = allowedTags.replace( /[,>]\s*/gi, '|').replace( /</gi, '').replace( /^\||\|$/gi, '');
			
			// FIXME: make it one :(					
			// Remove new lines from within tags
			reg 	= new RegExp( '(\r\n|\r|\n)', 'gi');
			content	= content.replace(reg, '');

			reg		 = new RegExp( '</(?!'+allowedTags+').+?>', 'gi');
			content = content.replace(reg, "");
			
			reg 	= new RegExp( '<(?!'+allowedTags+')[^/].+?>', 'gi');
			content = content.replace(reg, '');
			
			return content;			
		} else {
			reg = new RegExp( "<\/?[^>]+>", "gi"); // Default reg
			return content.replace( reg, "" ) 
		}
	},	
	
	/*
	Property: rgbToHex
		Converts an RGB value to hexidecimal. The string must be in the format of "rgb(255,255,255)" or "rgba(255,255,255,1)";

	Arguments:
		array - boolean value, defaults to false. Use true if you want the array ['FF','33','00'] as output instead of "#FF3300"

	Returns:
		hex string or array. returns "transparent" if the output is set as string and the fourth value of rgba in input string is 0.

	Example:
		>"rgb(17,34,51)".rgbToHex(); //"#112233"
		>"rgba(17,34,51,0)".rgbToHex(); //"transparent"
		>"rgb(17,34,51)".rgbToHex(true); //['11','22','33']
	*/
	rgbToHex: function(array){
		var rgb = this.match(/\d{1,3}/g);
		
		return (rgb) ? rgb.rgbToHex(array) : false;
	},

	/*
	Property: hexToRgb
		Converts a hexidecimal color value to RGB. Input string must be the hex color value (with or without the hash). Also accepts triplets ('333');

	Arguments:
		array - boolean value, defaults to false. Use true if you want the array [255,255,255] as output instead of "rgb(255,255,255)";

	Returns:
		rgb string or array.

	Example:
		>"#112233".hexToRgb(); //"rgb(17,34,51)"
		>"#112233".hexToRgb(true); //[17,34,51]
	*/

	hexToRgb: function(array){
		var hex = this.match(/^#?(\w{1,2})(\w{1,2})(\w{1,2})$/);
		return (hex) ? hex.slice(1).hexToRgb(array) : false;
	},
	
	isValidEmail	: function(){ 
		// http://twitter.com/html5/status/156559847029088256
		return new RegExp( '^(([^<>()[\\]\\\\.,;:\\s@\\"]+(\\.[^<>()[\\]\\\\.,;:\\s@\\"]+)*)|(\\".+\\"))@((\\[[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\])|(([a-zA-Z\\-0-9]+\\.)+[a-zA-Z]{2,}))$').test(this);
	},
	
	injectIn		: function(root) {
		var dummy = $C('div');
		$A(dummy.setHTML(this).childNodes).each(function(element) {
			$(element).injectIn(root);
		});
		dummy = null;
		
		return this;
	},
	
	injectAfter		: function(root) {
		var dummy = $C('div');
		$A(dummy.setHTML(this).childNodes).each(function(element) {
			$(element).injectAfter(root);
		});
		dummy = null;

		
		return this;
	},
	
	injectBefore		: function(root) {
		var dummy = $C('div');
		$A(dummy.setHTML(this).childNodes).each(function(element) {
			$(element).injectBefore(root);
		});
		dummy = null;

		
		return this;
	},
	
	
	// Unserialize a PHP serialized string 
	// Used mainly for C|MAN serialized strings.
	unserialize : function(returnBoth){ 
		
		var length;
		var input 	= this;
	
		var array	= [];
		var key		= null;
		var value	= null;
	
		var getLen	= function(string) {return Number( string.match(/:(.*?):/)[1]);}
		
		var num, bool;
		
		var obj		= {}
	
		switch(input.charAt(0)) {
			// Array
			case 'a':
				length 	= getLen(input);
				input	= input.substr(String(length).length + 4);
	
				for(var i = 0; i < length; ++i) {
					key 	= input.unserialize(true);
					input	= key[1];
					value	= input.unserialize(true);
					input	= value[1];
					obj[key[0]] = value[0];
				}
				
				input = input.substr(1);
				return returnBoth ? [obj, input] : obj;
			break;
			
			// String
			case 's':
				length = getLen(input);
				return returnBoth ? [String(input.substr(String(length).length + 4, length)), input.substr(String(length).length + 6 + length)] : String(input.substr(String(length).length + 4, length));
			break;
	
			// Number
			case 'i':
			case 'd':
				num = Number(input.substring(2, input.indexOf(';')));
				return returnBoth ? [num, input.substr(String(num).length + 3)] : num;
			break;		
			
			// Boolean
			case 'b':
				bool = (input.substr(2, 1) == 1);
				return returnBoth ? [bool, input.substr(4)] : bool;
			break;					
			
			// Default
			default:
			break;
		}
	}	

});

// Date related
String.extend({
	getWeekDay : function() {
		var days 	= Date.Constants.days;
		var string = this.trim();
		for(var i=0, j = days.length; i< j;i++ ) {
			var reg = days[i].replace(/\.$/, '').split(',').join('\.?|') + '\ ?';
			if(new RegExp( '^('+reg+')$', 'i').test(string)) {
				return i;
			}		
		}	
		return null;	
	},
	
	getMonth	: function() {
		string 		= this.trim();
		var months 	= Date.Constants.months;	
		for(var i=0, j = months.length; i< j;i++ ) {
			var reg = months[i].replace(/\.$/, '').split(',').join('\.?|') + '\ ?';
			if(new RegExp('^(' + reg + ')$', 'i').test(string)) {
				return i;
				
			}		
		}
	
		return null;
	},

	isDateDefinition : function() {
		for(var key in Date.Constants.tokens) {
			if(key.split(/,/).contains(this) ) {
				return Date.Constants.tokens[key][0];
				break;
			}
		}		
		return false;
	},

	// TODO
	// parse fulldate +next ..blah by testing new Date(string) on space seperated string
	toDate : function(date) {
		date			= date && $typeOf(date) === 'date' ? date : new Date();
		var testDate,parts, part, match, matches, day, month, year;
		var string 		= this.toString().replace(/(\d)[-\.]/g, '$1/').trim(); 	// Dash seperation makes it easy to cast to Date 
		string			= string.replace(/,/g, ' ');							// Also replace commas to spaces
		var monthReg 	= '(' + Date.Constants.months.join('|').replace(/\./, '').split(',').join('\.?|') + '\.?'+')';
		var res, isAdding = false;
		var flag		= false;
		
		// Empty string?
		if(string === '') {
			return date;	
		}	

		// Remove th, st, nd etc
		// ADED: 25 Aug 011
		string = string.replace(/([0-9]{1,2})(th|st|rd|nd)/gi, '$1');
		
		// Parse PM
		// Removed greek due to IE6< @#@!#
		string = string.replace(/([\d:]+)? ?(PM|pm|am)/, function(matches) {				   
			var hours;
			if(matches.toLowerCase().contains('pm')) {
				hours = matches.replace(/^(\d{1,2})/, function(hour) {
					return hour.toInt() + 12;
				});
			} else {
				hours = matches;
			}

			return hours.replace(/(PM|ΑΜ|AM|am)/gi, ':00');
		});

		
		// Try to define year order propely (Ymd)
		// TODO: We should prolly do it the other way around
		string = string.replace(/^(\d{1,2})\/(\d{1,2})\/(\d{2,4})/, '$3/$2/$1');
	
		// Fix multiple spaces
		string 			= string.replace(/ +/, ' ');

		// Single day of week
		if(string.getWeekDay() !== null) {
			return date.next(string);
		}
		
		// Single month
		if(string.getMonth()  !== null ) {
			return date.next(string);
		}
		
		// Single year
		if(/^\d{4}$/.test(string)) {
			return date.today().set(string+'/1/1');
		}
		
		// Single day
		if(/^\d{1,2}$/.test(string)) {
			return date.set({'day' : string });
		}	
	
		// Rephrase day Month year
		// E.g 15 Aug 2008 to Aug 15 2008
		if( matches = string.match(new RegExp('^(\\d{1,2})[ /](' + monthReg + ')(.*?)$', 'i') ) ) {
			string = [matches[2], matches[1], matches[4]].join(' ');
		}

		// 070708 || 20080707 scenario
		string = string.replace( /(\d{2,4})(\d{2})(\d{2,4})/, function(full, first, second, third) {
			var year = first.length > third.length ? first : third;
			var day  = year === first ? third : first;
			return [year.pad(4, '20'), second, day].join('/');
		});

		// Remove days from start (not needed)
		var reg = Date.Constants.days.join('|').replace(/\./, '').split(',').join('\.?|') + '\.?';
		string = string.replace(new RegExp('^('+reg+')', 'i'), '');

		// Parse ago
		string = string.replace( /(\d+ ?\w+) ago/i, '-$1');	

		// We need to add some kind of validation here
		// TODO: make it better		
		// 1. date
		if(matches = string.match(/(\d{2,4})\/(\d{2,4})\/(\d{2})/i)) {
			if(matches[3].toInt() > 31) {
				return null;	
			}
			if(matches[2].toInt()  > 12) {
				return null;	
			}
		}

		// 2. time
		if(matches = string.match(/(\d{2}):(\d{2})(:(\d{2}))?/i)) {
			if(matches[1].toInt() > 23 || matches[2] > 59 || (matches[3] && matches[3] > 59) ){
				return null;	
			}
		}

		// Are we lucky ( no more than 2 spaces, and valid date)
		if( ( ( matches = string.split(/ /g) ) && matches.length < 3 )  && ( testDate = new Date(string) ) && testDate.isValid()) {
			// Fix may 02 -> results in 2001 issue
			// Reported by Robert
			// Fixed
			if(!/[0-9]{4}/.test(string)) {
				testDate.setYear(new Date().getFullYear());
			}
			return testDate;
		}

		// If no match yet, figure out if we can make a match out of the first 2 space
		// seperated words

		//25:AUG:2011
		// Changed it to >= 3
		if(matches && matches.length >=3) { 
			// Match (2 runs)
			// alert(new Date(matches[0]));
			
			// 10.5.2010
			// new Date(-1) DOES not return invalid as it should have so
			// we take this into account.
			
			// FIXED: 10.5.2010 by GP
			// HOW: added matches[0].length > 3 && to the conditions
			if( matches[0].length > 3 &&  ( testDate = new Date(matches[0] + ' ' + matches[1]) )  && testDate.isValid() ) {
				string 		= string.replace(/^.*? .*? /gi, '');
				date		= testDate;	

				// Make sure the year is correct
				var _year 	= string.match(/[0-9]{4}/);
				if(!_year) {
					date.setYear(new Date().getFullYear());
				}

			} else if ( matches[0].length > 3 &&  ( testDate = new Date(matches[0]) ) && testDate.isValid()) {
				string		= string.replace(/^.*? /gi, '');
				date 		= testDate;
			}
		}
		console.log(string, date.toString());

		// Not really sure why I did this :(
		var __string = string.replace( / [^ ]+$/g, '');
		if(__string.length > 4 && matches && matches.length >= 2 && (testDate = new Date( __string ) ) && testDate.isValid()) {
			var _year = string.match(/[0-9]{4}/);
			
			if(!_year) {
				testDate.setYear(new Date().getFullYear());
			} else {
				string = string.replace( new RegExp('^.*?' + _year), '' );
				testDate.setYear(_year);
			}

			date 	= testDate;
		}

		// Another attempt
		// NOT REALLY SURE FOR THIS EITHER
		// THIS IS WRONG
		// __string = string.replace( / [^ ]+$/g, '');
		// if(__string.length > 4 && matches && matches.length >= 2 && (testDate = new Date( __string ) ) && testDate.isValid()) {
		// 	string 	= string.replace( / [^ ]+$/, '');
		// 	date 	= testDate;
		// }

		// Here is our approach
		// Break appart String using RegExp, traverse parts and handle them accodringly		
		parts 	= (string + ' ').match(/(\d{4}|^today|now|noon|midnight|yesterday|tomorrow|next \w+|last \w+|[\+\-]\d{1,} ?\w+|\d{1,} ?[a-z]+|\S+? )/gi) || [];

		if(!parts.length && date) {
			flag = true;
		}


		for(var i = 0, j = parts.length; i <j ;i++) {
			day 	= null;	year = null; year = null;
			part 	= parts[i].trim().toLowerCase();
			// next, last
			if( matches = part.match(/(next|last) (\w+)/)) {
				flag	= true;
				res 	= date[matches[1]](matches[2]);		
				continue;
			}		

			// Today, Tomorrow, Yesterday, now etc		
			if(date[part] && $typeOf(date[part]) === 'function') {
				// Only @ start
				if(part === 'today' && i > 0) {
					return null;	
				}
				res = date[part]();
				if(res) {
					flag = true;	
				}
				continue;
			}
					
			// +-
			if( /\+|-/.test(part) ) {
				res = date.add(part);
				if(!res) {
					return null;	
				} else {
					isAdding = part.contains('+') ? 1 : -1;
				}

				flag = true;
				continue;
			}
			
			// Keep on adding if more follow
			// e.g +1 week 2 days 4 hours 2 seconds
			// TODO: Nullify date here?		
			if(isAdding && /\d{1,} ?[a-z]+/.test(part) ) {
				res = date.add(part);
				
				flag = true;				
				if(!res) {
					isAdding = false;	
				} else {
					continue;	
				}
			}
			isAdding = false;
			
			// Time (:)
			if(  part.contains(':') && (matches = part.split(/:/) )  && $typeOf(matches) == 'array' ) {
				date.set({
					'hours' 	: matches[0],
					'mins'		: matches[1] || 0,
					'secs'		: matches[2] || 0,
					'millisecs' : matches[3] || 0
				});
				flag = true;				
				continue;
			}
			
			// Month and following date or year?
			if( (month = part.getMonth() ) !== null) {
				if(parts[i+1] && /^\d{1,2}$/.test(parts[i+1].trim())) {
					day = parts[i+1].trim();
					i++;
				}

				if(parts[i+1] && /^\d{4}$/.test(parts[i+1].trim())) {
					year = parts[i+1].trim();
					i++;
				}
				
				// No year define = next to the next one
				if(!year) {
					date.next(parts[i].trim());			
				}
				

				date.set({
					'month'  	: month,
					'day' 		: day || 1,
					'year' 		: year
				});
				

				flag = true;
				
				continue;

			}			
			
			// Year
			if(/^\d{4}$/i.test(part)) {
				date.set({ 'year': part});
				flag = true;
				continue;
			 }
			 
			 // Something is wrong - return null;
			 return null;
		}

		if(!flag) {
			date = null;	
		}
		// Validate date
		return date && date.isValid() ? date : null;
	}
});
					
String.prototype.htmlSpecialChars  		= String.prototype.escapeHTML;


// Function
// some browsers don't define this (Dean says so)
Function.prototype.prototype = {};
Function.extend({
	bind 			: function() { 
		var fn 						= this;
		var args					= $A(arguments);
		var context 				= args.shift();		
		var $method					= $uniqueID(fn);

		// Shit happens :)
		if(fn === null) {
			return null;
		}
		
		// FIXME: if its discarded or not yet ready
		if(!Garbage.closures) {
			Garbage.closures = {};	
		}
		
		
		Garbage.closures[$method]	= fn;
		fn 							= null;

		// TODO: use the global type here?
		// Mind the !e.extended - we want to avoid already extended event objects
		var isEvent 				= function(e) {	return e && e.type  && !e.extended && (e.target || e.srcElement !== undefined);}
	
		var bound = function() 	{ 
			// Prevent errors 
			if(typeof($A) === 'undefined') {
				return null;	
			}
			

			// TODO: Finalize this
			// In case we are providing additional arguments to the bound function
			// Extend them to the original args.
			// Special care to the event (if given);	
			var boundArgs 		= $A(arguments) || [];
			var evt	 			= boundArgs.getLast(), boundPopped;
			var boundArgsAll 	= $A(args);
			
			// In case we are dealing with an event 
			if( isEvent(evt)) {
				// Hack to force window.DOMContentLoaded
				// Needed for chrome, at least.
				// ADDED: 03.05.11
				if(evt.type === 'DOMContentLoaded' ) {
					window.DOMContentLoaded = true;
				}
				
				if(isEvent(boundArgsAll.getLast())) {
					boundArgsAll.pop();
				}
				if(( boundPopped = boundArgs.pop()) && boundPopped.length) {
					boundArgsAll.extend( boundArgs );				
				}
				boundArgsAll.push($E(evt));
			} else {
				boundArgsAll = boundArgsAll.concat(boundArgs);
				// CHANGED: extend does unique concat  prefere concat here
				//$A(boundArgsAll).extend(boundArgs);
			}
			if(context && context.nodeType && context.nodeType == 1) {
				context = $(context);
			}
			
			// We may have this removed already

			if(Garbage.closures && Garbage.closures[$method]) {
				return Garbage.closures[$method].apply(context, boundArgsAll); 
			} 
		}
	
		bound.bound			= true;
		return bound;
	},
	
	delay 			: function(msecs, context) { 
		var self 	= this;
		var args	= arguments;
		
		var fn 		= function() {
			return	self.apply(context, $A(args).slice(2) );
		}
		
		
		// Insant execution
		if(msecs === - 1) {
			fn();
			return -1;
		}
		
		return setTimeout (fn, msecs);
	},
	
	periodical 		: function(msecs, context) { 
		var self 	= this;
		var args	= arguments;
		var fn 		= function() {
			return	self.apply(context, $A(args).slice(2) );
		}
		return setInterval (fn, msecs);	
	}
	
});


// Number
Number.extend({
	// ADED: 18.11.2010
	clear		: function() {
		$clear(this);
		return this;
	},
	
	round		: function(precision) { 
		precision = Math.pow(10, precision || 0);
		return Math.round(this * precision) / precision;
	},
	
	repeat		: function( fn, context ) {
		for( var i = 0; i < this; i++ ) {
			fn.call(context,i + 1);
		}
	},
	
	/*
	toJSON_ext: function() {
		return isFinite(this) ? this.toString() : 'null';
	},
	*/
	
	limit: function(min, max){
		if(max === undefined) {
			return Math.max(this, min);	
		} else {
			return Math.min(max, Math.max(min, this));
		}
	},
	
	
	// The following are links to string prototypes
	toFormatted	: String.prototype.toFormatted,	
	pad			: String.prototype.pad,	
	toInt		: String.prototype.toInt,
	toFloat		: String.prototype.toFloat
});

// Date
Date.extend({
	toYMD		: function(){
		return this.format('Y-m-d');
	},
	
	isLeap	: function() {
		var year = this.getFullYear();
		return year % 400 === 0 || ( year % 4 === 0 && year % 100 !== 0);		
	},
	
	// TODO: Add more formats here
	format: function(format) {
		var date = this;
		return format.replace(/(Y|y|j|n|w|m|d|s|h|H|i|m|G|D|l|M|F)/g, function(match, index ) {
			var hrs24 	= date.getHours();
			var hrs12	= hrs24 > 12 ? hrs24-12 : hrs24;
			switch (match){
				case 'Y': return date.getFullYear();
				case 'y': return (date.getFullYear() - 2000).pad(2);
				case 'j': return date.getDate();
				case 'd': return date.getDate().pad(2);
				case 'n': return date.getMonth() + 1;
				case 'm': return (date.getMonth()+1).pad(2);
				case 'h': return hrs12.pad(2);
				case 'H': return hrs24.pad(2);
				case 'g': return hrs12;
				case 'G': return hrs24;				
				case 'i': return date.getMinutes().pad(2);
				case 's': return date.getSeconds().pad(2);
				case 'w': return date.getDay();
				
				case 'D': return Date.Constants.days[date.getDay()].split(/,/)[1];
				case 'l': return Date.Constants.days[date.getDay()].split(/,/)[0];
				case 'M': return Date.Constants.months[date.getMonth()].split(/,/)[1];
				case 'F': return Date.Constants.months[date.getMonth()].split(/,/)[0];
			}
		});
	},
	
	clone : function() {
		return new Date(this.getTime());
	},
	
	getDaysInMonth	: function() {
		this.daysInMonth = [31,28,31,30,31,30,31,31,30,31,30,31][this.getMonth()]
		// Feb on leap year
		if( this.isLeap() && this.getMonth() == 1)  {
			this.daysInMonth += 1;
		}
		return this.daysInMonth;
	}, 
	
	set : function(options) {
		var date 	= this;
		// Cast it 
		if($typeOf(options === 'number') && options < 100000) {
			options = String(options);		   
		}
		switch($typeOf(options)) {
			case 'string':
				return options.toDate(date);
				break;
				
			case 'number':
				return new Date(options);
				break;
				
			case 'date':
				return new Date(options.getTime());
				break;
		}

		each(options, function(optionValue, optionKey) {
			for(var key in Date.Constants.tokens) {
				var keys = key.split(/,/);
				if(keys.contains(optionKey) && $typeOf(optionValue)  ) {
					optionValue = optionValue.toInt();
					date.add((optionValue - date[Date.Constants.tokens[key][1]]() ) + optionKey);
					break;
				}
			}
		});
		
		return date;
	},
	
	clearTime	: function() {
		['Hours', 'Minutes', 'Seconds', 'Milliseconds'].each(function(item) {
			this['set'+item](0);
		}, this);
		return this;
	},
	
	addMilliseconds	: function(value){
		this.setMilliseconds(this.getMilliseconds()+value);
		return this;
	},
	
	addMonths		: function(value) {
		var n = this.getDate();
		this.setDate(1);
		this.setMonth(this.getMonth().toInt() + value.toInt());
		this.setDate(Math.min(n,  this.getDaysInMonth()));
		return this;	
	},

	addYears		: function(value) {
		return this.addMonths(12 * value);	
	},
	
	today		: function() {
		return this.set(new Date()).clearTime();
	},
	
	now			: function() {
		return this.set(new Date());
	},
	
	yesterday : function() {
		return this.add('-1d').clearTime();
	},
	
	tomorrow		: function() {
		return this.add('+1d').clearTime();
	},
	
	noon			: function() {
		return this.clearTime().add('+12h');
	},
	
	midnight		: function() {
		return this.tomorrow().clearTime();
	},
	
	toLastDayOfMonth	: function(month) {
		this.set({ 
			'month' : month || this.getMonth()
		});
		return this.set({ 'day' : this.getDaysInMonth() });
		
		// Alternative
		// return this.add('+1 month').set(0);
	},
	
	toLastDaysOfWeek	: function() {
		// TODO: implement this
	},
	
	// NOTE: date - date = timestamp
	getDayOfYear		: function() {
		return Math.floor((this - new Date(this.getFullYear(), 0, 1)) / 86400000);
	},
	
	next		: function(value, sign) {
		var date = this, argument,day, month;
		sign	=  sign || '+';
		
		if(!value) {
			return false;	
		} else {
			date.clearTime();	
		}
		
		if(argument = value.isDateDefinition()) {
			// eg. add month
			if($typeOf(argument) === 'string') {
				date[argument]( sign === '+' ? 1 : -1);	
				return date;
			}
			// Usually argument = msecs
			date.addMilliseconds( sign === '+' ? argument : -argument);
			
			return date;
		}

		if( ( day = value.getWeekDay() ) !== null) {
			while(date.getDay() !== day ) {
				date.add( sign + '1d');
			}
			return date;
		}
		
		if( ( month = value.getMonth() ) !== null) {
			while(date.getMonth() !== month  ) {
				date.add ( sign + '1month');	
				date.set( { day : 1 });
			}
			return date;
		}
	},
	
	add		: function(value) {
		var date	= this, msecs;

		// Define parts
		var parts	= value.match(/^([+-])? ?(\d{1,}) ?(.*?)$/);
		
		// Bail out
		if(parts === null || !parts[3]) {
			return false;
		}
		
		var sign	= parts[1] || '+';
		var amount	= parts[2];
		var metric	= parts[3];
		
		// Run through tokens
		for(var key in Date.Constants.tokens) {
			var keys = key.split(/,/);
			if(keys.contains(metric) ) {
				msecs = Date.Constants.tokens[key][0];
				if($typeOf(msecs) === 'string') {
					date[msecs]( sign == '+' ? amount : -amount);
					continue;
				}
				
				date.addMilliseconds( sign === '+' ? (msecs * amount)  : -(msecs * amount) );
				break;
			}
		}
		
		return date;
	},
	
	last		: function(value) {
		this.next(value, '-');
	},
	
	isValid		: function() {
		// Added a 1970 check just in case
		return this.toString() !== 'Invalid Date' && !isNaN(this) && this.getFullYear() !== 1970;
	},
	
	// Emulate Common's dt function
	dt			: function() {
		var today 	= new Date(), day, month, year, minutes;
		
		var tsToday	= today.clone().set('-1 day midnight').getTime(), tsNow = $now(), ts = this.getTime();
	    var days 	= ['Κυριακή', 'Δευτέρα', 'Τρίτη', 'Τετάρτη', 'Πέμπτη', 'Παρασκευή', 'Σάββατο'];
		var months  = ['Ιαν', 'Φεβ', 'Μαρ', 'Απρ', 'Μαι', 'Ιουν', 'Ιουλ', 'Αυγ', 'Σεπτ', 'Οκτ', 'Νοε', 'Δεκ']

		var diff	= (tsNow - ts) / 1000;	// In secs
		
		
		var	month 	= this.format('n').toInt(); // TODO: Array
		var	day		= this.format('j');
		var	year	= this.format('y');
			

		var monthSt = months[month-1] + '.';

		// Null time (midnight)
		if(this.format('Hi') === '00') {
			if( this.format('Y').toInt() !== today.getFullYear().toInt()) {
				return this.format('j/n/Y');	
			} else {
				return this.format('j/n');	
			}
		}
				
		// Later on
		if(ts > tsNow) {	
			if(year != today.getFullYear()) {
				return day + ' ' + monthSt + ' ' + year;	
			} else {
				return day + ' ' + monthSt;				
			}
		} 
		
		// Minutes
		diff = parseInt(diff)
		if(diff < 300) {
			if(diff < 60 && diff > 0) {
			 	return 'πριν ' + diff + '\'\'';	
			} else if (diff < 60) {
				return 'πριν 1 λεπτό';	
			} else { 
				return 'πριν ' + parseInt((diff/60+1)) + '\'';
			}
		} else if( diff < 3600 ) {
			return 'πριν ' + parseInt(( (diff / 60) / 10)  * 10) + '\'';
		} else if( diff < 7200 ) {
			minutes = ((( (diff - 3600) / 60 ) / 5) * 5).toInt();
			return 'πριν 1 ώρα' + ( minutes ? ' & ' + minutes + '\''  : '');
		} else if (diff < 10800 ) {
			minutes = (( ((diff - 7200) / 60) / 5) * 5).toInt();
			return 'πριν 2 ώρες' + ( minutes ? ' & ' + minutes + '\''  : '');			
		}
		
		if(ts < tsToday) {
			if( ts >= tsToday - 86400 * 1000 && this.format('Hi').toInt() > 400 ) {
				return this.format('χτές H:i');
			} else if ( ts >= tsToday - 86400 * 1000  * 5) {
				return days[this.format('w').toInt()] + ' ' + this.format('H:i');
			} else {
				if(this.getFullYear() !== today.getFullYear() ) {
					return day + ' ' + monthSt + ' ' + year;
				} else {
					return day + ' ' + monthSt;
				}
			}
		} else {
			return this.format('H:i');	
		}
	}	
});


Date.parseYMD = function(string) {	
	return new Date( string.replace(/^(\d+)?-(\d+)?-(\d+)$/, function(full, year, month, day) {
		return year+','+month.toInt()+','+day.toInt();																  
	})) ;
}

// Date from anything
Date.from 		= function(value) {
	if(value) {
		var date = new Date().set(value);
		return date && date.isValid() ? date : null;
	} else {
		return null;	
	}
}
	
// TODO: implement this
// default is greek
Date.setLocale 	= function(locale) { this.locale = Date.locales[locale]; }
Date.locales	=  {
};

// Hold some constants used in parsing (String.prototype.toDate)
Date.Constants = {
	days : [
		'Sunday,Sun',
		'Monday,Mon',
		'Tuesday,Tue',
		'Wednesday,Wen',
		'Thursday,Thu',
		'Friday,Fri',
		'Saturday,Sat'
	],
	/*
	days : [
		'Sunday,Sun,Κυριακ[ηήΗΉ],Κυρ',
		'Monday,Mon,Δευτ[εέΕΈ]ρα,Δευ',
		'Tuesday,Tue,Τρ[ιίΊΙ]τη,Τρι',
		'Wednesday,Wen,Τετ[αάΆΑ]ρτη,Τετ',
		'Thursday,Thu,Π[εέΕΈ]μπτη,Πεμ',
		'Friday,Fri,Παρασκευ[ηήΗΉ],Παρ',
		'Saturday,Sat,Σ[αάΆΑ]ββατο,Σαβ'
	],
	*/
	
	tokens	: {
		'ms,millisecs,milliseconds' : [1, 'getMilliseconds'],
		's,secs,second,seconds'		: [1000, 'getSeconds'],
		'm,mins,minutes,min'		: [60000, 'getMinutes'],
		'h,hour,hours'				: [3600000, 'getHours'],
		'd,day,days'		 		: [86400000,'getDate'],
		'w,weeks,week' 				: [86400000 * 7],
		'month,months' 				: ['addMonths', 'getMonth'],
		'y,year,years'				: ['addYears', 'getFullYear']
	},
	months	: [
		'January,Jan',
		'February,Feb',
		'March,Mar',
		'April,Apr',
		'May,May',
		'June,Jun',
		'July,Jul',
		'August,Aug',
		'September,Sept',
		'October,Oct',
		'November,Nov',
		'December,Dec'
	]	
	/*
	months	: [
		'January,Jan,Ιανου[αάΆΑ]ριος,Ιαν',
		'February,Feb,Φεβρου[αάΆΑ]ριος,Φεβ',
		'March,Tue,Μ[αάΆΑ]ρτιος,Μαρ',
		'April,Wen,Απρ[ιίΊΙ]λιος,Απρ',
		'May,Thu,Μα[αάΆΑϊ]ος,Μαϊ',
		'June,Jun,Ιο[ιίΊΙ]νιος,Ιο[υύΎΥ]ν',
		'July,Jul,Ιο[υύΎΥ]λιος,Ιο[υύΎΥ]λ',
		'August,Aug,Α[αάΆΑ]γουστος,Α[υύΎΥ]γ',
		'September,Sept,Σεπτ[εέΕΈ]μ?βριος,Σεπτ',
		'October,Oct,Οκτ[ωώΏΩ]βριος,Οκτ',
		'November,Nov,Νο[εέΕΈ]μβριος,Νοε',
		'December,Dec,Δεκ[εέΕΈ]μβριος,Δεκ'
	]
	*/
}

// ================================================================================
// Class | Inspired by Base2
// ================================================================================
var Class = function(properties){
	var klass 			= function(){
		this.constructor = klass;
		
		// TODO: We need to figure out a way to rule out the !== null thingy 
		// in order to properly pass null arguments[0]
		return (arguments[0] !== null && this.initialize && $typeOf(this.initialize) == 'function') ? 
		this.initialize.apply(this, arguments) : this;

		// Constructor is an alias to initialize 
		// ADDED: 25 May 09
		return (arguments[0] !== null && this.initialize && $typeOf(this.constructor) == 'function') ? 
		this.constructor.apply(this, arguments) : this;
		
	};
	$extend(klass, this);	// Inherit all from Class (even extend)
	klass.prototype 		= properties || {} // Use properties as prototype
	
	// ADDED: 03.06.2009
	// This is an easy way to 
	// if(klass.prototype.options) {
	//	klass.prototype.options = new Abstract(klass.prototype.options);	
	// }

	klass.constructor 		= Class;
	klass.ID 			 	= { type : 'class' }
	
	return klass;
};

Class.prototype =  {
	implement: function(){	
		var obj = {}, args = arguments;
		
		// TODO: fix this
		if( $typeOf(arguments[0]) == 'string'  && arguments[1] ) {
			obj[arguments[0]] 	= arguments[1];
			args				= [obj];
		}
		for (var i = 0, l = args.length; i < l; i++)  {
			$extend(this.prototype, args[i]);
			
			// ADDED: 03.06.09
			// An easy way to change default options for a class
			if(args[i] && args[i].constructor === Options) {
				this.setOptions = $extend.bind(this.prototype.options);
			}	
			/* 
			if(args[i] && args[i].constructor === Events) {
				['addEvent', 'addEvents', 'removeEvent', 'removeEvents'].each( function(event, index) {
					this.consturctor[event] = args[i][event].bind(
				}, this);
				this.addEvent = args[i].constructor.	
			}
			*/
		}
		
	
		return this;
	},	
	
	// var Animal 	= new Class();
	// var Cat 		= Animal.extend({});
	extend		: function( properties  ) {
		var proto 	= new this(null);
		var hash	= {};
		var _class;
		
		// Traverse through given properties
		for (var property in properties){
			var pp 			= proto[property];
			proto[property] = Class.Merge(pp, properties[property]);
		}
		
		proto.constructor = this; // GP
		
		// Attach .parent to functions not already merged (and thus having .parent registered)
		// Added by GP: 25/11/08
		for(var i in proto) {
			if($typeOf(proto[i]) === 'function' && !proto[i].parent) {
				proto[i].parent = proto[i];
			}
		}

		_class = new Class(proto);
	
		// ADDED: 01 July 09
		// Implement setOptions for Class extensions too
		if(this.setOptions) {			
			_class.setOptions = $extend.bind(_class.prototype.options);
		}	
		
		return _class;
	}
}
// Internal
// Dean's work
Class.Merge = function(previous, current){
	if (previous && previous != current){
		var type = $typeOf(current);
		
		if (type != $typeOf(previous)) {
			return current;
		}
		
		switch(type){
			case 'function':
				var merged = function(){
					this.parent = arguments.callee.parent;
					return current.apply(this, arguments);
				};
				
				merged.parent = previous;
				return merged;
				
			case 'object': 
				return $merge(previous, current);
		}
	}
	return current;
};


// ================================================================================
// $, $E
// ================================================================================
$ = function (element, properties) {
	if( $typeOf(element) == 'string') {	
		var elementString = element;
		// Support selectors if available
		// Fixed a bug with g
		if( ![' ', '<', '[', ']'].contains(element[0]) &&  /[#\. >]/.test(element)) {
			element =  $$(element)[0];
		} else {
			element = document.getElementById(element);
			// Fix an IE bug; match name and ID on form elements
			if(element && (element.nodeName === 'TEXTAREA' || element.nodeName === 'INPUT' || element.nodeName === 'SELECT') && (window.ie5 || window.ie6 || window.ie7)) {
				if(!element.id || (element.id != elementString) ) {
					// alert([ 'ID: ' + element.id, 'NAME: ' + element.name, elementString]);
					return null;
				}
			}
		}
	}
	
	// No element found
	if(!element) {
		return null;
	}

	if(element.extended) {
		if(!element.set) {
			console.info(element, element.nodeName)
		}
		return element.set(properties);
	}

	// Avoid scriptable elements
	// NOTE: Object elements can return object in lowerCase as their nodename, take that in mind
	if(element.nodeName && (element.nodeName.toLowerCase() === 'embed' || element.nodeName.toLowerCase() === 'object')) {
		return element;	
	}
	
	$extend(element, Element.Methods);	

	if(element.window && element.window.document) {
		// We have to do it this way
		// Rather than using the $extend in order to keep the scope
		each(window.Methods, function(fn, method) {
			element[method] = fn.bind(element);
		});
	}		

	element.extended 		= Core.empty
	element.methods			= {}
	element.cache			= {}	
	element.ID 				= element.ID || {};
	element.ID.type			= 'element';
		
	if( !element.uniqueID ) {
		element.uniqueID		=  $uniqueID(element);
	}

	return Garbage.collect(element).set(properties);
}

// Used internally
// TODO: is this extended check valid??
$E = function(event) { return  event.extended ? event : new Element.Event( event || window.event );}

// ================================================================================
// Element.Event class
// ================================================================================
Element.Event 		= new Class({
	initialize					: function(event) {				
		event		 				= event || window.event;	
		this.type					= event.type;
		this.extended				= Core.empty;
		
		this.target 				= $(event.target || event.srcElement || document);
		this.currentTarget			= event.currentTarget || this.target;
		this.eventPhase				= event.eventPhase;
		
		if(this.target && this.target.nodeType == 3) {
			this.target = $(this.target.parentNode);
		}
		
		this.layerX					= window.webkit ? event.offsetX : event.layerX || event.offsetX;
		this.layerY					= window.webkit ? event.offsetY : event.layerY || event.offsetY;		
		
		// > http://adomas.org/javascript-mouse-wheel/
		if (['DOMMouseScroll', 'mousewheel'].contains(this.type)) {
			this.wheel = (event.wheelDelta) ? event.wheelDelta / 120 : - (event.detail || 0) / 3;	
		}
		else  if( /click|mouse|menu/.test(this.type ) ) {		
			try {
				this.page = {
					'x': event.pageX || event.clientX + window.getBody().scrollLeft,
					'y': event.pageY || event.clientY + window.getBody().scrollTop
				};
				this.client = {
					'x': event.pageX ? event.pageX - window.pageXOffset : event.clientX,
					'y': event.pageY ? event.pageY - window.pageYOffset : event.clientY
				};
			} catch(ex) {
				// This is throw in case we mousover ton object
				// or embed element (occasionally)
			}
			
			if( ['mouseover', 'mouseout'].contains(event.type)) { 
				if(!event.relatedTarget) {
					if(window.ie) {
						try { 
							this.relatedTarget = $((event.type == 'mouseover' ? event.fromElement : event.toElement)); 
						} 	catch (ex) {}
					}
				} else {
					// avoid 'anonymous-div' (XUL) related errors
					try { this.relatedTarget = $(event.relatedTarget);}
					catch(ex) {this.relatedTarget = null; }
				}
				
				if( this.relatedTarget && this.relatedTarget.nodeType !== 1) {
					try {this.relatedTarget = this.relatedTarget.parentNode;}
					catch (ex) { this.relatedTarget = this.target; }
				}
				
				this.relatedTarget = this.relatedTarget || null;
			}

			
			this.rightClick			= (event.which == 3 ) || (event.button == 2) 	|| null;			
			this.middleClick		= (event.which == 2 ) || (event.button == 4) 	|| null;			
			this.leftClick			= (event.which < 2  || event.button < 2 ) 		|| null;
		} else if ( this.type.contains('key') ) {
			this.code 	= event.which || event.keyCode;
			
			// THIS CAUSE AN ISSUE WITH IE
			for (var key in Element.Event.keys ) {
				if(parseInt(Element.Event.keys[key]) == parseInt(this.code) ) {
					this.key = key.toLowerCase();
					break;
				}
			}
				
			if( this.type == 'keydown' ) {
				var fKey = this.code - 111;
				if (fKey > 0 && fKey < 13) {
					this.key = 'f' + fKey;
				}
			}
			
			this.key = this.key || String.fromCharCode(this.code).toLowerCase();
		}
		
		this.alt			= event.altKey;
		this.meta			= event.metaKey;
		this.shift			= event.shiftKey;
		
		if(!this.shift && event.modifiers) {
			this.shift = !!(event.modifiers & 4);
		}
		
		this.ctrl			= event.ctrlKey || this.key == 'ctrl';	
		this.timestamp   	= $now()
		this.event			= event;
		this.ID				= {  type : 'event' }

		
		// Changed 4/7/2010
		if(this.event.dataTransfer) {
			this.dataTransfer = this.event.dataTransfer;
		}
				
		return this;
	},
	
	stop				: function() {
		if(Core.showEvent) {
			console.log(this.type, this.event.target, this)
		}
		return this.stopPropagation().preventDefault();
	},
	
	stopPropagation		: function() {
		if (this.event.stopPropagation) {
			this.event.stopPropagation();
		} else {
			// TODO: Fix this / sometimes we get a "Member not found" error out of nowhere
			try{
				this.event.cancelBubble = true;
			} catch(ex){};
		}
		return this;
	},
	
	preventDefault	 : function() {
		if( this.event.preventDefault ) {
			this.event.preventDefault();
		}  else  {
			// TODO: Fix this / sometimes we get a "Member not found" error out of nowhere
			try {
				this.event.returnValue = false;
			} catch(ex) {}
		}
		return this;
	}	
	
});

Element.Event.checkRelatedTarget  = function(event) {
	var relatedTarget = event.relatedTarget;
	if(!relatedTarget) {
		return true;
	}
	///console.log($typeOf(this) != 'document', relatedTarget != this,  relatedTarget.prefix != 'xul',this.contains(relatedTarget));
	return ($typeOf(this) != 'document' && relatedTarget != this && relatedTarget.prefix != 'xul' && !this.contains(relatedTarget));			
}

Element.Event.keys = new Abstract({
	'backspace' 	: 8,
	'tab'			: 9,
	'return'		: 13,
	'shift'			: 16,
	'ctrl'			: 17,
	'alt'			: 18,
	'caps'			: 20,
	'esc'			: 27,
	'space'			: 32,  
	'pageup'		: 33,  
	'pagedown'		: 34,
	'end'			: 35,
	'home'			: 36,
	'left'			: 37,
	'up'			: 38,
	'right'			: 39,
	'down'			: 40,
	'insert'	 	: 45,
	'delete'	 	: 46
});


// ================================================================================
// Element
// ================================================================================
Element.Properties = new Abstract({
	'class': 'className', 'cellspacing': 'cellSpacing', 'cellpadding' : 'cellPadding', 'for': 'htmlFor', 'colspan': 'colSpan', 'rowspan': 'rowSpan',
	'accesskey': 'accessKey', 'tabindex': 'tabIndex', 'maxlength': 'maxLength',
	'readonly': 'readOnly', 'frameborder': 'frameBorder', 'value': 'value',
	'disabled': 'disabled', 'checked': 'checked', 'multiple': 'multiple', 'selected': 'selected'
});
Element.Styles = {'border': [], 'padding': [], 'margin': []};
['Top', 'Right', 'Bottom', 'Left'].each(function(direction){
	for (var style in Element.Styles) {
		Element.Styles[style].push(style + direction);
	}
});
Element.borderShort = ['borderWidth', 'borderStyle', 'borderColor'];

// ================================================================================
// window methods
// ================================================================================
window.Methods = {
	getBody			: function(root){ 
		// fix
		if(window.webkit) {
			return this.document.body;	
		}
		
		if(!window.ie || this.document.documentElement) {
			return this.document.documentElement;
		}
		// For IFrame windows, choose the document.body aproach
		return  this.document.compatMode == 'BackCompat' || this != top
		? this.document.body 
		: this.document.documentElement; 
	},
	
	getScrollTop	: function(){ 
		return this.pageYOffset || this.getBody().scrollTop;
	},

	getScrollLeft	: function(){ return this.pageXOffset || this.getBody().scrollLeft;},	
	
	getWidth		: function(){
		// NOTE: window.innerpWidth|Height] >> includes vertical bars
		// CHANGED: 08.09.09
		// WAS window.safari2 - changed to : window.safari
		
		// CHANGED: 08.09.09
		// Added Webkit thing for detecting scrollbars (slow but works)
		return  window.webkit ? 
		this.innerWidth - (this.document.body.offsetHeight > this.innerHeight ? 20 : 0) :
		this.getBody().clientWidth;
	},	
	
	getHeight		: function(){ 
		// CHANGED: 08.09.09
		// WAS window.safari2 - changed to : window.safari		
	
		// CHANGED: 08.09.09
		// Added Webkit thing for detecting scrollbars (slow but works)		
		return window.webkit ? 
		this.innerHeight - (this.document.body.offsetWidth > this.innerWidth ? 20 : 0) :
		this.getBody().clientHeight;
	},		
	
	getScrollWidth: function(){
		if (window.ie) return Math.max(this.document.documentElement.offsetWidth, this.document.documentElement.scrollWidth);
		if (window.safari) return this.document.body.scrollWidth;
		return this.document.documentElement.scrollWidth;
	},
	
	getScrollHeight: function(){
		if (window.ie) return Math.max(this.document.documentElement.offsetHeight, this.document.documentElement.scrollHeight);
		if (window.safari) return this.document.body.scrollHeight;
		return this.document.documentElement.scrollHeight;
	}
};

/* Taken from: http://paulirish.com/2011/requestanimationframe-for-smart-animating/ */
window.requestAnimFrame = (function() {
    return  window.requestAnimationFrame       || 
            window.webkitRequestAnimationFrame || 
            window.mozRequestAnimationFrame    || 
            window.oRequestAnimationFrame      || 
            window.msRequestAnimationFrame     || 
            function(callback, element) {
           		window.setTimeout(callback, 1000 / 60);
            };
})();


// ================================================================================
// Element methods
// ================================================================================
Element.Methods = {
	// Super function
	set			: function( properties ) {
		this.ID 		= this.ID || {};
		this.ID.type  	= 'element';
		
		if(!properties || $typeOf(properties) !== 'object') {
			return this;
		}
		
		for( var property in properties ) {	
			switch( property ) {
				case 'styles' :
					this.setStyles(  properties[property] );
					break;
				case 'events':
					this.addEvents( properties[property] );
					break;
				case 'properties':
					this.setProperties( properties[property] );
					break;
				case 'methods':
					this.addMethods( properties[property] );
					break;		
				default:
					switch( $typeOf( properties[property] ) ) {
						case 'function':
							this.addMethod( property, properties[property] );
							break;
						default:
						
						this.setProperty(property, properties[property] );
					}
					break;
			}	
		}
		return this;			
	},
	
	addMethod	: function( name, method ) {
		this[name] =  this.methods[name] = method;
		return this;
	},
	
	addMethods	: function( methods ) {
		each(methods, function( method, name) { this.addMethod( name, method); }, this);
		return;
	},

	setStyle	: function (style, value) {
		if( arguments.length == 1 && $typeOf(style) == 'string' ) 	return this.setProperty( 'style', style ); 	
		if( style == 'float' ) style = window.ie ? 'styleFloat' : 'cssFloat';
			if( style == 'opacity' ) this.setOpacity( parseFloat(value) );
		else {
			switch($typeOf(value)){
				case 'number': if (!['z-index', 'zoom'].contains(style)) value += 'px'; break;
				case 'array': value = 'rgb(' + value.join(',') + ')';
			}
			try {
				this.style[style.camelCase()] = value.toString();
			} catch(ex) {
				alert([style, value]);	
			}
		}
		
		return this;
	},

	setStyles	: function( css ) {
		each( css, function( value, style  ) {
			this.setStyle( style, value );
		}, this );
		return this;
	},
	
	getProperty		: function(property) {
		var flag = 0, attribute;
		var index = Element.Properties[property];
		if (index) return this[index];
		if(['href', 'src', 'type'].contains(property) || this.className === undefined ) flag = 2;	
		
		// IE8 Woos - ole
		if( ( !window.ie || flag) || window.ie8 || (window.ie && property == 'id')) {
			return this.getAttribute( property, flag );
		}
		
		return  ( attribute = this.attributes[property] ) && attribute.specified ? attribute.nodeValue : null;
	},
	
	getProperties  : function( ) {
		var obj = {};
		$A(arguments).each( function(prop) {
			obj[prop] = this.getProperty(prop);
		}, this );
		return obj;
	},	
	
	setProperty		: function( property, value ) {
		if( property == 'style' )  {
			this.style.cssText = value.toLowerCase(); 
			return this;
		}
		
		var index = Element.Properties[String(property)];
		if (index) {
			this[index] = value;
		} else {
			// FIX THIS MESS
			//if(!(window.ie && this.nodeName === 'FORM')) {
			if(1) {
							// Remove it if null
				if(value === null) {
					this.removeProperty(property)
				} else {
					// CHANGED: 6/4/2010
					if( this.attributes && property === 'action' && (window.ie7 || window.ie6)) {
						this.attributes[property].nodeValue = value;
					} else {
						this.setAttribute(property, value);
					}
				}
			} else {
				//alert(property);
				//alert(this.nodeName);
				this.attributes[property].nodeValue = value;
			}
		}
		return this;
	},
	
	setProperties	: function( properties ) {
		each( properties, function( value, property  ) { this.setProperty( property.trim(), value )}, this );
		return this;		
	},
	
	removeProperty	: function(property) {
		var index = Element.Properties[String(property)];
		if (index) {
			this[index] = null;
		} else {
			if(this.removeAttribute) {
				this.removeAttribute(property);	
			} else {
				this.attributes[property].nodeValue = null;
			}
		}
		
		return this;
	},
		
	addClass	: function( className ) { 
		if(!className) {
			return this;
		}		
		
		if(Core.supports['classList'])
		if(this.classList && !className.push) {
			try {
				this.classList.add(className.trim());
			} catch(ex) {
					
			}
			return this;
		}
		
		if( !this.hasClass( className )) {
			this.className += ((this.className  ? ' ' : '') + className);
		}
		return this;
	},
	
	removeClass	: function( className ) {
		if( Core.supports['classList'] )
		if(this.classList && !className.push) {
			this.classList.remove(className.trim());
			return this;
		}

		this.className = this.className.replace(new RegExp( '(^| )'+className.escapeRegExp()+'( |$)'), ' ' ).trim();
		return this;
	},
	
	makePositioned: function() {
		if(!this.isPositioned()) {
			this.setStyle( 'position', 'relative');
		}
		return this;
	},
	
	setClass	: function(className) { this.className = className; return this; },
	
	toggleClass : function(className) { 
		// Support classList (only for strings)
		if( Core.supports['classList'] )
		if(this.classList) {
			this.classList.toggle(className);
			return this;
		}	
		
		return this.hasClass( className) ? this.removeClass( className ) : this.addClass( className) 
	},
	
	// Non-element returns
	hasClass	: function( className ) {
		// Support classList (only for strings)
		// http://hacks.mozilla.org/2010/01/classlist-in-firefox-3-6/
		if( Core.supports['classList'] )
		if(this.classList && !className.push) {
			return this.classList.contains(className);
		}	
		
		if( this.className == className ) return true;
		
		if( $typeOf(className) == 'array' ) {
			className = '(' + className.join('|') + ')';
		}
		
		return new RegExp('(^| )'+className.escapeRegExp()+'($| )').test(this.className.toString()); 
	},
	
	// Using JS Ninja way
	isVisible	: function() 			 { 
		var isTR = this.nodeName.toLowerCase() === 'tr', 
		w = this.offsetWidth, 
		h = this.offsetHeight;
		
		return w !== 0 && h !== 0 && !isTR ? true :
		w === 0 && h === 0 && !isTR ? false :
		this.getStyle( 'display' ) !== "none";
//		return !!(this.getStyle('display') != 'none'); 
	},
	
	isHidden	: function()			 { return !this.isVisible() 		},
	
	clone		: function( contents, removeEvents )			 { 
		var clone 		= $(this.cloneNode( !!!(contents) ));
		if(!removeEvents) {
			return clone;
		}
		// IE needs to have the events removed
		// TODO: try this instead > return clone.cloneEvents( this ).removeEvents();
		clone.events = {};
		for (var type in this.events) clone.events[type] = {
			'keys'	: $A(this.events[type].keys),
			'values': $A(this.events[type].values)
		};
		return clone.removeEvents();	
	},
	
	remove		: function()			 { 
		Garbage.discard(this);		
		return this.parentNode.removeChild(this);
	},
	
	replace		: function(el)		 	{ this.parentNode.replaceChild(el, this);  return $(el) },
	
	adopt		: function(root)	 	{ $(root).injectIn(this); return this; },
	
	injectIn	: function(root)	 	{ 
		return ( root || document.body).appendChild( this);
	},
	
	injectBefore: function(root)	 	{ return ( $(root.parentNode.insertBefore(this, root) ) ) },
	
	injectAfter	: function(root)	 	{ 
		var next = $(root).getNext();
		
		if(next) {
			return next.parentNode.insertBefore(this, next)  
		} else {
			return this.injectIn( $(root).parentNode );
		}
	},
	
	setHTML 	: function( html )	 { 
		if( html === undefined || html === null) {
			html = '';	
		}
		html = html.toString();
		
		// Handle IE issues with table elements setting and innerHTML
		if( window.ie && ['THEAD','TBODY','TR','TD'].contains( this.tagName ) ) {
			var div = $C('div'), tagName = this.tagName;
			switch (tagName) {
				case 'THEAD':
				case 'TBODY':
					div.innerHTML = '<table><tbody>' +  html.stripScripts() + '</tbody></table>';
					depth = 2;
					break;
				case 'TR':
					  div.innerHTML = '<table><tbody><tr>' +  html.stripScripts() + '</tr></tbody></table>';
					  depth = 3;
					break;
				case 'TD':
					div.innerHTML = '<table><tbody><tr><td>' +  html.stripScripts() + '</td></tr></tbody></table>';
					depth = 4;
					break;
			}
			div = div.getChildDeep(depth);			
			
			$A(this.childNodes).each(function(node) {  this.removeChild(node); }, this);
			$A(div.childNodes).each(function(node) { this.appendChild(node); }, this);						
		}
		else {
			// Sometime IE throws an "uknown runtime error"
			// Tricky but only way to do it:(
			try {
				this.innerHTML = html;
			} catch(ex) { }
		}
		
		return this;
	},
	
	addEvents	: function( events, scope ) 	 {
		each( events, function( item, property) {
			this.addEvent(property, item, scope); 
		}, this );	
		return this;
	},
	
	addEvent: function(type, fn, scope){
		// Useful addition
		if (fn === null) {
			return this;
		}

		var skip = false, isCustom = !Element.nativeEvents.contains(type);
		var customEvent = null;
		var condition;
		var realType	= type;
		var realFn;
		
		var newDomReady	= true;

		// Attach scope
		if(scope) {
			fn = fn.bind(scope);	
		}
		
	
		// Patch mousewheel
		// TODO: add this and domcontent to custom
		if (type === 'mousewheel' && window.gecko )  {
			realType = type = 'DOMMouseScroll';
		}
		
		this.events				= this.events || {};
		this.events[type] 		= this.events[type] || { 'keys': [], 'values': [] };
		
		if (this.events[type].keys.contains(fn)) {
			return this;
		}

		this.events[type].keys.push(fn); 
		
		// DOM Content already loaded, fire away
		if( type == 'DOMContentLoaded' && ( document.readyState === 'complete' || window.DOMContentLoaded === true ) )  {
			fn();				
			return this;
		}
		
		// TODO: FIX THIS MESSSSSSS!
		// window.safari3 (chrome or safari > 3) does support DOMContentLoaded (ole)
		if( type == 'DOMContentLoaded' && (!document.addEventListener || window.safari2) ) {
			
			// make sure we are on toplevel
			var topLevel = false;
			
			try {
				topLevel = window.frameElement === null;
			} catch(ex) { }
			
			if(!topLevel) {
			//	return this;
			}
			
			if( !window._DOMCLEvents ) {
				var _DOMCLFn		= function() {
					if(arguments.callee.done || !window._DOMCLEvents) {
						return false;
					}
					arguments.callee.done 	= true;
					$clear( window._DOMCLTimer );
					window._DOMCLEvents.each( function(e) { e();} ) ;
					window._DOMCLEvents 	= null;

					// newDomReady comes into play here == will not proceed
					if(window._DOMScriptElement) {
						window._DOMScriptElement.onreadystatechange	= null;
						Element.remove( window._DOMScriptElement );
						window._DOMScriptElement 					= null;
					}
				}

				// JQuery uses document.readyStateChange - no idea why :-)
				var _ie				= function(force) {
					document.write('<scr'+'ipt id=__ie_onload defer src=' + ( window.location.protocol == 'https' ? 'https://0' : 'javascript:void(0)' ) + '><\/scr'+'ipt>');
					window._DOMScriptElement 					= document.all['__ie_onload'];
					window._DOMScriptElement.onreadystatechange = function() {
						  if (this.readyState === 'complete') {
						  		setTimeout(function() {
						  			_DOMCLFn();	
						  			window.onload = _DOMCLFn;
						  		}, 50);
						  }
					  };					
				};
				
				var _ieNew		= function(force) {
					var ready = false;
					var m;
					(function m() {
					    if (!window.DOMContentLoaded) {
					        try {
					            document.documentElement.doScroll('left');
					        } catch (a) {
					            setTimeout(m, 1);
					            return;
					        }
							window.DOMContentLoaded = true;
							_DOMCLFn();
							// window.onload = _DOMCLFn;
					    }
					})();
				}
				
				// IE
				if( window.ie ) {
					newDomReady ? _ieNew() : _ie();
				} else if( window.safari2) {
					 window._DOMCLTimer =  function() {
						if (/loaded|complete/.test(document.readyState)) {
							_DOMCLFn(); // call the onload handler
						}
					}.periodical( 50 )  
				} else { 
					$(window).addEvent ( 'onload',  _DOMCLFn );
				}
			}
			
			window._DOMCLEvents = 	window._DOMCLEvents || [];
			window._DOMCLEvents.push(fn);
			
			skip = true;
		};

		fn								= (isCustom  || fn.bound) ? fn : fn.bind(this);	
			
		realFn							= fn;
		condition						= null;
		
		
		// Are we dealing with a customEvent (mouseover, mouseleave) ?
		if( (customEvent = Element.customEvents[type]) ) {
			realType = customEvent.base || type;
			if(customEvent.condition) {
				 condition = function(event) {
					if(customEvent.condition.call(this, event)) {
						return fn.call(this, event);	
					}
					return false;
				}
				var self = this;
				realFn	= function(event) {
					event = $E(event);
					if (condition.call(self, event) === false) {
						event.stop();
					}
				}
			}
		}
		
		// Add it to events
		this.events[type].values.push(realFn);

		// TODO: fix this lame
		if( !skip && (!isCustom || customEvent)) {
			this.addEventListener ? this.addEventListener( realType, realFn, false ) : this.attachEvent ( 'on'  + realType, realFn );
		}
		return this;	
	},
	
	invokeEvent	: function() {
		var args = $A(arguments), type = args.shift(), element = this;
		if (this.events && this.events[type]){
			this.events[type].keys.each(function(fn){
				fn.apply( this, args );									 
			}, this);
		}
		return this;
	},
	
	removeEvent : function( type, fn ) {
		if (!this.events || !this.events[type]) { 
			return this;
		}
		
		var pos = this.events[type].keys.indexOf(fn);
		
		// ADDED "&& fn" to the following statement
		// to make it work properly.
		// 09.12.2011
		if (pos == -1 && fn) {
			return this;
		}
		var key 		= this.events[type].keys.splice(pos,1)[0];
		var value 		= this.events[type].values.splice(pos,1)[0];
		var isNative 	= Element.nativeEvents.contains(type);
					
		var custom		= Element.customEvents[type];
		
		var realType	= type;
		var realFn		= value;

		if(custom && custom.base) {
			isNative	= true;
			realType	= custom.base;
		}
	
		if( isNative ) {
			if (this.addEventListener)  {
				//realType === 'click' && console.log('edw', this, realType, realFn.toString());
				this.removeEventListener( realType, realFn , false )
			}
			else {
				if(type != 'DOMContentLoaded' && this.detachEvent){
					var res = this.detachEvent( 'on' + realType, realFn );
				}
			}
		}
		
		return this;		
	},
	
	removeEvents: function(type) {
		if (!this.events) return this;
		if (!type){
			for (var evType in this.events) {
				this.removeEvents(evType);
			}
			this.events = null;
		} else if( $typeOf(type) == 'object' )  {
			each(type, function( fn, evtType ) { 
				this.removeEvent( evtType, fn );
			}, this );
		} else if (this.events[type]) {
			this.events[type].keys.each(function(_fn){
				if (type === 'click') {
					console.log('edw', _fn)
				}
				//type === 'click' && fn && console.log('edw', fn);
				this.removeEvent(type, _fn);
			}, this);
			this.events[type] = null;
		}
		if (window.ie && !type) 	Element.nativeEvents.each( function( evt ) { this['on'+evt] = null; }, this );

		return this;
	},
	
	hasEvent 	: function(type) {
		return this.events && !!this.events[type];
	},
	
	cloneEvents	: function( source, type ) {
		if( !source.events ) return this;
		if (!type) for (var evType in source.events) this.cloneEvents(source, evType);
		else if ( source.events[type] ) {
			source.events[type].keys.each( function( fn ) {	this.addEvent( type, fn );}, this );
		}
		return this;
	},
	
	hide 		: function() { this.style.display = 'none'; return this; },
	
	show 		: function() {
		this.style.display = '';
		
		// FIXME: Should I just check for the offsetWidth here ?
		if(this.getStyle('display') == 'none' && !['TR', 'TBODY', 'TD', 'THEAD', 'TH'].contains(this.tagName)) {
			this.style.display = 'block';
		} else {
			if( !(window.ie || window.ie6 || window.ie7) && ['TR', 'TD', 'TH'].contains(this.tagName)) {
				this.style.display = { 
					'tr'	: 'table-row',
					'th'	: 'table-cell',
					'td'	: 'table-cell'
				}[this.tagName.toLowerCase()];
			}
		}
		
		return this;
	},
	
	toggle		: function() { 
		return this.isVisible() ? this.hide() : this.show() 
	},

	// Position related
	getPosition: function(asCSS, avoidClientRect){
		//overflown = (overflown  && overflown.length ) || [];
		var el = this, left = 0, top = 0, coords, box, doc = el.ownerDocument || document, fixed = false, parent = el.parentNode;
		var offsetChild 		= el, offsetParent = $(el.offsetParent);
		
		// When the element is inside an overflow container, skip using boundingclientrect
		// FIXME: we need tests here
		
		// var dontUseClientRect	= avoidClientRect === true || (offsetParent && ( ( el.offsetLeft > offsetParent.offsetWidth)  || (el.offsetTop > offsetParent.offsetHeight)));

		// Switched to &&
		var dontUseClientRect	= avoidClientRect === true || (offsetParent && ( ( el.offsetLeft > offsetParent.offsetWidth)  &&  (el.offsetTop > offsetParent.offsetHeight)));	
		// Use getBoundingClientRect if available
		if(el.getBoundingClientRect && !dontUseClientRect) {
			// http://dev.jquery.com/browser/trunk/jquery/src/offset.js
			if((window.ie6 || window.ie7) && !(window.ie && document.compatMode !== 'CSS1Compat')) {
				// ADDED: 10.09.09
				// The next statement will fix the flickering caused by IE when setting the border to 0
				if(document.documentElement.style.border) {
					document.documentElement.style.border = 0;		
				}
			}

			box 	= el.getBoundingClientRect();
		
			
			left 	= box.left + Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft);
			top 	= box.top  + Math.max(doc.documentElement.scrollTop,  doc.body.scrollTop);
			
			// IE adds the HTML element's border, by default it is medium which is 2px
			// IE 6 and 7 quirks mode the border width is overwritable by the following css html { border: 0; }
			// IE 7 standards mode, the border is always 2px
			// This border/offset is typically represented by the clientLeft and clientTop properties
			// However, in IE6 and 7 quirks mode the clientLeft and clientTop properties are not updated when overwriting it via CSS
			// Therefore this method will be off by 2px in IE while in quirksmode
			// http://developer.mozilla.org/en/docs/DOM:document.compatMode
			if((window.ie6 || window.ie7) && document.compatMode === 'BackCompat') {
				left += 2;
				top  += 2;
			}
			
			// I am not that sure this is okay
			// TODO: fix it or figure out if its okay
			// IT's NOT!
			if(window.ie8) {
				// left -= 2;
				 //top	-= 2;
			}
			
			// In Opera we need to remove the borders
			if(window.opera) {
				left 	-= this.getStyle('border-left-width').toInt();	
				top		-= this.getStyle('border-top-width').toInt();
			}
			
			left	-= doc.documentElement.clientLeft;
			top		-= doc.documentElement.clientTop;	


			if(asCSS !== true) {
				return { x: left, y : top };
			} else {
				return { 'left' : left, 'top' : top };
			}
		}
		
		fixed		= el.getStyle('position') === 'fixed';

		left 		+= el.offsetLeft 	|| 0;
		top 		+= el.offsetTop 	|| 0;
		while(offsetParent) {
			left 	+= offsetParent.offsetLeft 	|| 0;
			top 	+= offsetParent.offsetTop 	|| 0;
			// Mozilla and Safari > 2 does not include the border on offset parents
			// // However Mozilla adds the border for table or table cells
			
			// WAS
			//if(window.moz && !/^t(able|d|h)$/i.test(offsetParent.tagName) || window.safari && !window.webkit419) {
			
			// IS
			if(window.moz && !/^t(able|d|h)$/i.test(offsetParent.tagName) || (window.safari && !window.webkit420)) {
				left 	-= offsetParent.getStyle('border-left-width').toInt() || 0;
				top		-= offsetParent.getStyle('border-top-width').toInt() || 0;
			}
			
			// Add the document scroll offsets if position is fixed on any offsetParent
			if(!fixed && offsetParent.getStyle('position') === 'fixed') {
				fixed = true;	
			}
			
			// Set offsetChild to previous offsetParent unless it is the body element
			offsetChild  = /^body$/i.test(offsetParent.tagName) ? offsetChild : offsetParent;
			
			// Get next offsetParent
			offsetParent = $(offsetParent.offsetParent);
		}
		
		// Get parent scroll offsets
		while ( parent && parent.tagName && !/^body|html$/i.test(parent.tagName) ) {
			// Remove parent scroll UNLESS that parent is inline or a table to work around Opera inline/table scrollLeft/Top bug
			if ( window.opera && !/^inline|table.*$/i.test($(parent).getStyle('display')) ){
				// Subtract parent scroll offsets	
				//left 	-= parent.scrollLeft;
				//top 	-= parent.scrollTop;
			}
			
			// Mozilla does not add the border for a parent that has overflow != visible
			if(window.mozilla && parent.getStyle('overflow') != 'visible') {
				left 	-= parent.getStyle('border-left-width');
				top		-= parent.getStyle('border-top-wdith');				
			}
			
			parent = $(parent.parentNode);
		}
		
		// Safari <= 2 doubles body offsets with a fixed position element/offsetParent or absolutely positioned offsetChild 
		// Mozilla doubles body offsets with a non-absolutely positioned offsetChild
		if((window.webkit419 && ($fixed || offsetChild.getStyle('position') === 'absolute')) || 
		(window.mozilla && offsetChild.getStyle('position') !== 'absolute')) {
			left 	-= doc.body.offsetLeft;
			top		-= doc.body.offsetTop;
		}
		
		// Add the document scroll offsets if position is fixed
		if(fixed) {
			left 	+= Math.max(doc.documentElement.scrollLeft, doc.body.scrollLeft);
			top		+= Math.max(doc.documentElement.scrollTop,  doc.body.scrollTop);
		}
		
	
		/*
		OBSOLETE
		overflown.each(function(element){
			left 	-= element.scrollLeft || 0;
			top 	-= element.scrollTop  || 0;
		});
		*/
		
		// Safari needs this 
		// Dunno why
		// ADDED: 21/10/08 by GP
		if(window.webkit419) {
			left 	+= 2;
			top 	+= 2;
		}
		
		return asCSS === true ? { 'left' : left, 'top': top } : {'x': left, 'y': top};
	},
	
	isPositioned 	: function() { return ['relative', 'absolute', 'fixed'].contains( this.getStyle('position') );},
	
	getTop			: function(overflown){return this.getPosition(overflown).y; },

	getLeft			: function(overflown){ return this.getPosition(overflown).x;},	
	
	setBlockStyles 			: function(searchForParentToo) {
		// Look for the parent hidden element
		var parent 		= this;
		var dimCheck 	= (this.offsetHeight === 0 && this.offsetHeight === 0);
		
		// All okay
		if(!dimCheck) {
			return this;	
		}

		searchForParentToo = false;		

		// Already applies or already visible
		if(this.__hiddenElementsList) {
			return this;
		}
		
		// Initialize list
		this.__hiddenElementsList = [];
	
		// Hidden too
		if(this.getStyle('display') === 'none') {
			this.__hiddenElementsList.push(this);
		}

		// Run through the parents and get all the hidden ones
		// TODO: Webkit considers even children element's display as none > that sux
		while( parent 	= parent.getParent() ) {
			if(parent.getStyle('display') === 'none') {		
				this.__hiddenElementsList.push(parent);
			}
		}

	
		// All *Width and *Height properties give 0 on elements with display none,
		// so enable the element temporarily.
		// We need the INLINE styles, not the css ones.
		// OBSOLETE: this._originalStyles = this.getStyles( 'position', 'float', 'visibility', 'display');	
		
		this.__hiddenElementsList.each(function(element, index) {
			this._originalStyles = {};
			['position', 'float', 'visibility', 'display'].each(function(property) {
				var prop = property;																 
				if(prop === 'float') {
					prop = window.ie ? 'styleFloat' : 'cssFloat';
				}
				this._originalStyles[property]  = this.style[prop];
			}, this);
		
					

			this.setStyles({
				'position'		: searchForParentToo ? 'static' : 'absolute',
				'visibility'	: 'hidden',
				'float'			: 'left',
				'display' 		: 'block'
			});
		});
		
		return this;
	},
	
	restoreBlockStyles		: function() {	
		if(!this.__hiddenElementsList) {
			return this;	
		}
		
		this.__hiddenElementsList.each(function(element,index) {
			this.setStyles(this._originalStyles || {});														
			this._originalStyles = null;
		});
		
		this.__hiddenElementsList = null;
		
		return this;
	},
		
	
	/*
	 * OLDER KINDA OBSOLETE VERSIONS
	setBlockStyles 			: function(searchForParentToo) {
		var dimCheck 	= (this.offsetHeight === 0 && this.offsetHeight === 0);
		var parent		= this.getParent();
		
		// Remove previous
		if(this._originalStyles) {
			this.restoreBlockStyles();
		}	
		
		if(this._originalStyles || !dimCheck) {
			return this;
		}
		
		// All *Width and *Height properties give 0 on elements with display none,
		// so enable the element temporarily.
		// We need the INLINE styles, not the css ones.
		// OBSOLETE: this._originalStyles = this.getStyles( 'position', 'float', 'visibility', 'display');	
		this._originalStyles = {};
		['position', 'float', 'visibility', 'display'].each(function(property) {
			var prop = property;																 
			if(prop === 'float') {
				prop = window.ie ? 'styleFloat' : 'cssFloat';
			}
			this._originalStyles[property]  = this.style[prop];
		}, this);
	
				
		this.setStyles({
			'position'		: searchForParentToo ? 'static' : 'absolute',
			'visibility'	: 'hidden',
			'float'			: 'left',
			'display' 		: 'block'
		});


		// Search for parent too
		if(searchForParentToo === true) {
			while(parent && !(parent.offsetHeight === 0 && parent.offsetWidth === 0) ){
				parent = parent.getParent()	
			}
			if(parent) {
				this._parentToRestoreStyles = parent;
				parent.setBlockStyles();
			}			
		}	
		
		return this;
	},
	
	restoreBlockStyles		: function() {		
		if(!this._originalStyles) {
			return this;
		}
		this.setStyles(this._originalStyles || {});
		if(this._parentToRestoreStyles) {
			this._parentToRestoreStyles.restoreBlockStyles();
		}
		this._originalStyles 		= null; 
		this._parentToRestoreStyles = null;
		return this;
	},
	*/
	
	scrollTo: function(x, y){
		var scope = this, pos, scrollToThis  = false;
		
		// if x and y are undefined, scroll the window to the top/left of the item
		if( ( x == undefined || x == null ) && ( y == null || y == undefined) ) {
			pos 	= this.getCoordinates();
			x		= pos.right  - window.getWidth();
			y		= pos.bottom - window.getHeight();
			scrollToThis = true;			
		}
	
		if(this.window ) {
			scope = this.window.getBody();
		}
		if(scrollToThis) {
			scope = $(window).getBody();
		}
		
		if(x != undefined) {	
			scope.scrollLeft 	= x;
		}
		if(y != undefined) {
			scope.scrollTop 	= y;
		}
		
		return this;
	},
	
	getSize: function(){
		this.setBlockStyles();	
		var size = {
			'scroll'		: {'x': this.scrollLeft, 'y': this.scrollTop},
			'size'			: {'width': this.offsetWidth, 'height': this.offsetHeight},
			'scrollSize'	: {'x': this.scrollWidth, 'y': this.scrollHeight}
		};
		this.restoreBlockStyles();
		
		return size;
	},	

	getDimensions : function() {
		this.setBlockStyles();
		var dimensions = {
			'width' 	: this.offsetWidth,
			'height' 	: this.offsetHeight
		}
		this.restoreBlockStyles();
		
		return dimensions;
	},

	getCoordinates: function(overflown, useCache){
		if(useCache && this.cache.coordinates) {
			return this.cache.coordinates;
		}
		
		var position = this.getPosition(overflown);
		this.setBlockStyles();	
		var obj = {
			'width'	: this.offsetWidth,
			'height': this.offsetHeight,
			'left'	: position.x,
			'top'	: position.y
		};
		
		obj.right 	= obj.left + obj.width;
		obj.bottom 	= obj.top + obj.height;
		
		
		// Store it for later
		if(this.cache) {
			this.cache.coordinates = obj;
		}
		this.restoreBlockStyles();		
		return obj;
	},
	
	getStyle	: function( style ) {
		var css = null, value, defStyle = style;
		style 	= style.camelCase();
		
		// Applicable when dealing with documentElement (html)
		if(!this.style) {
			return this;	
		}
		value 	= this.style[style];
		
		if(!$is(value) || style.contains('Color')) {
			if( style == 'opacity' ) return this.getOpacity();
				
			if( window.ie && ['float', 'cssFloat'].contains( style) ) style = 'styleFloat';
			var result = [];
			
			for (var elStyle in Element.Styles){
				if (style == elStyle){
					Element.Styles[elStyle].each(function(s){
						var style = this.getStyle(s);
						result.push(parseInt(style) ? style : '0px');
					}, this);
					
					if (style == 'border'){
						var every = result.every(function(bit){
							return (bit == result[0]);
						});
						return (every) ? result[0] : false;
					}
					
					return result.join(' ');
				}
			}
			
			if (style.contains('border')){
				if (Element.Styles.border.contains(style)){
					return ['Width', 'Style', 'Color'].map(function(p){
						return this.getStyle(style + p);
					}, this).join(' ');
				} else if (Element.borderShort.contains(style)){
					return ['Top', 'Right', 'Bottom', 'Left'].map(function(p){
						return this.getStyle('border' + p + style.replace('border', ''));
					}, this).join(' ');
				}
			}
			
			if(document.defaultView)	 {
				// Safari < 3 sometimes gives an exception here
				// FIXME: Find out why
				var hyphen = defStyle.indexOf('-') === 0 ? defStyle : style.hyphenate();
				try {value = document.defaultView.getComputedStyle(this, null).getPropertyValue(hyphen);}
				catch(ex) { 
					if (console !== undefined  && console.log) {
						console.log('style :' + style, ex.message);
					}
				}
			} else if( this.currentStyle )  {
				value = this.currentStyle[style];
			}
	
			value =  Element.fixStyle(style, value, this);
			if (value && /color/i.test(style) && value.contains('rgb')){
				return value.split('rgb').splice(1,4).map(function(color){
					return color.rgbToHex();
				}).join(' ');
			}
		}
		return value;
		
	},

	getStyles : function( ) {
		var obj = {};
		$A(arguments).each( function( style) {
			obj[style] = this.getStyle(style);
		}, this );
		return obj;
	},

	remove		:  function(){ this.parentNode.removeChild(this); return this;},
	
	getOpacity	: function() {
		var opacity = 1;
		if(window.ie)
			opacity = this.filters && this.filters.alpha && $typeOf(this.filters.alpha.opacity) == 'number'	? Number(this.filters.alpha.opacity)/100 : 1;
		else
			opacity = this.style.opacity || this.style.MozOpacity || this.style.KhtmlOpacity || 1;
		return opacity >= 0.999999 ? 1 : Math.max( opacity, 0.000001 );
	},
	
	setOpacity	: function(opacity) {
		var _isIEIframe = (window.ie6 || window.ie5) && this.tagName === 'IFRAME';
		if (opacity == 0 ){
			if (this.style.visibility != 'hidden' && !_isIEIframe) {
				this.style.visibility = 'hidden';
			}
		} 
		// FIXME: need a way to skip this test
		else {
			if (this.style.visibility != 'visible' && ! _isIEIframe) this.style.visibility = 'visible';
		}
		
		if (!this.currentStyle || !this.currentStyle.hasLayout) {
			this.style.zoom = 1; // IE
		}
		if (window.ie) {
			this.style.filter = (opacity == 1) ? this.style.filter.replace(/alpha\([^\)]*\)/gi,'') : 'alpha(opacity=' + (opacity  * 100) + ')';	
			
		}

		this.style.opacity = this.style.MozOpacity = this.style.KhtmlOpacity = parseFloat(opacity);
		return this;		
	},
	
	// TODO: fix this mess a bit
	// relative : the element's relation e.g parentNode
	// index	: index from start to ... (e.g 1)
	// target 	: target index (usually null)
	// fn 		: Filter function for matching (binds to element)
	// start	: the element's start relation to begin from (e.g firstChild)
	traverse	: function( relative, index, target, fn, start){ 			   
		var elements = [], el = (start ? this[start] : this[relative]), indexCounter = 0;
		while( el ) {
			if ( el.nodeType == 1 ) {
				elements.push($(el));
				if( $is(index)  && ( indexCounter++ == index  )  ) 	break;
				if( $is(target) && ( target == el )  ) 				break;
				if( fn && fn.call(el,el) ) 	{	break;	}
			}
			el = el[relative] ;
		} 
		return elements;
	},
	
	getOwnerDocument: function() { return this.ownerDocument; },
	
	isElement	: function() { return $typeOf(this) === 'element'; },
	
	isDocument	: function() { return !!(this && this.documentElement); },
	
	getFirst	: function(index) { return this.getChild(index || 1);},

	getLast		: function(index) { return this.traverse( 'previousSibling', index || 1, null,  null, 'lastChild')[0]; },

	getNext		: function(index) { return $(this.traverse( 'nextSibling', 1)[index || 0]);},
	
	getPrevious	: function(index) { return $(this.traverse( 'previousSibling', 1)[index || 0]);},	
	
	getChild	: function(index) { return $(this.traverse( 'nextSibling', index, null, null, 'firstChild')[index-1]); },
	
	getChildDeep	: function(index) { return this.traverse( 'firstChild', index, null, null, 'firstChild')[index-1]; },
	
	getParent	: function(fn) {
		if(!fn) {
			return  $(this.parentNode);
		} else {
			return this.traverse('parentNode', null, null, fn).last();
		}
	},
	
	isWithin	: function(root, orSelf) { 
		// http://www.quirksmode.org/dom/tests/basics.html#contains()
		//if(!window.gecko) {
		//	return (orSelf ? this == root : false) || root.contains(this);
		//}
		return !!(this.traverse( 'parentNode', null, $(root)).getLast() == root) || (orSelf && this == root) ? true : false;
	},

	isOverlapping : function(elements, coords) {
		var isSingle = false, thisCoords;
		if(!elements.push) {
			isSingle = true;
			elements = [$(elements)];
		}
		
		// In case we have provided coords, use them instead
		// for example when dragging
		thisCoords = coords || this.getCoordinates();
		
		var overlapped = elements.filter(function(element) {
			var coords = element.getCoordinates(null, true);
			return (((coords.left >= thisCoords.left && coords.left <= thisCoords.left + thisCoords.width ) ||
					(coords.left  <= thisCoords.left && coords.left +  coords.width    >= thisCoords.left)) &&					
					((coords.top  >= thisCoords.top  && coords.top  <= thisCoords.top  + thisCoords.height) ||
					(coords.top   <= thisCoords.top  && coords.top  +  coords.height   >=  thisCoords.top)));
		}, this);
		
		return(isSingle ? !!overlapped[0] : overlapped);
	},
	
	contains	: function(target, orSelf) { 
		// TODO: fix this
		// http://www.quirksmode.org/dom/tests/basics.html#contains()
		// if(!window.gecko) {
		//	return (orSelf ? this == target : false) || this.contains(target);
		// }
		
		// ADDED: 02 Oct 08. && target.isWithin (just in case the target is not appropriate)
		// Useful for elements like object OR embed
		
		return $(this).isDocument() ?
		this == $(this).getOwnerDocument() : (orSelf && this == target) ? 1 : 0 ||
		this != target && $(target) && target.isWithin &&  !!( $(target).isWithin(this) );
	},
	
	getText		: function() {
		if( ['STYLE', 'SCRIPT'].contains(this.tagName) ) {
			if(window.ie) {
				return this.tagName == 'STYLE' ? this.styleSheet.cssText : this.getProperty('text');
			} else  {
				return this.innerHTML;
			}
		}
		return this.innerText || this.textContent;
	},
	
	empty	: function() {
		Garbage.discard(this.getElementsByTagName('*'));
		if(this.tagName === 'TABLE' && window.ie) {
			$$(this.children).each(Element.remove);	
			return $(this);
		} else { 
			return $(this.setHTML(''));
		}
	},
	
	makeUnselectable : function() {
		if(window.ie) {
			this['unselectable'] = 'on';
		} else {
			this.style['MozUserSelect'] = this.style['WebkitUserSelect'] = this.style['KhtmlUserSelect'] = 'none';			
		}
		return this;
	},
	
	effect 				: function(property, options ) { return new Fx.Style( this, property, options );},

	/*
	>var myEffects = $(myElement).effects({duration: 1000, transition: Fx.Transitions.Sine.easeInOut});
		>myEffects.start({'height': [10, 100], 'width': [900, 300]});
	*/
	effects				: function( options ) { return new Fx.Styles( this, options );},

	// Form related methods
	// TODO: Add some more
	serialize 			: function(returnObject) {
		var queryArray 	= [], queryObj = {}, qs;
		var counter		= {}; 
		$T(['select', 'input', 'textarea'], this).each( function( element ) {
			var value 	= element.value;
			var name	= element.name;
			
			// Special care goes for radio and checkboxes
			// ADDED: 11.06.2009
			if(element.nodeName === 'INPUT' && ( ['checkbox', 'radio'].contains(element.type) ) ) {
				value	= element.checked ? 'checked' : '';																		
			}
			
			if(name.contains('[]')) {
				name 			= name.replace(/\[\]/gi, '');
				counter[name] 	= (counter[name] !== undefined ? counter[name] : -1) + 1;
				
				name			= name+'['+counter[name]+']';
			}
			if( value === false || !name || element.disabled ) {
				return false;
			}
			
			
			if(!returnObject)  {
				qs = function( val ) { queryArray.push( encodeURIComponent(name) + '=' + encodeURIComponent(val)) };
			} else {
				qs = function( val ) { queryObj[name] = encodeURIComponent(val); };
			}
			
			if($typeOf(value) === 'array') {
				value.each(qs);
			} else {
				qs(value);
			}
		});
		
		return !returnObject ? queryArray.join('&') : queryObj;		
	},
	
	enable	: function() { this.disabled = false; return this;},
	
	disable	: function() { 
		with(this) {
			blur(); 
			disabled = true;
		};
		return this;
	},
	
	resetValue	: function(useDefault) {
		var def		= this.getProperty('default');
		var value 	= (useDefault ? def : '') || '';
		
		// Special handling for radio and checkboxes
		if(['radio', 'checkbox'].contains(this.type)) {
			this.checked = !!(def && def === 'default');
		}
		
		this.setValue(value);
		
		return this;
	},
	
	setValue	: function( value ) {
		var placeholder;
		if( this.tagName === 'SELECT' ) {
			for( var i = 0; i < this.options.length; i++ ) {
				if(this.options[i].value ==  value) {
					this.selectedIndex = i;
					break;
				}
			}
		}
			
		if(value === null) {
			value = '';	
		}
		
		this.value = value;
		
		// ADDED: 29.10.2010	
		if(value === '' && !Pathfinder.Fields.isHTML5Ready &&  ( placeholder = this.getProperty('placeholder') )  ) {
			this.value 			= placeholder;
			this.style.color	= '#A9A9A9';
			this._isEmpty 		= true;
		}
		
		return this;
	},
	
	getValue : function() {
		return this.value ? this.value : ''; 
	},
	
	
	// TODO: Use the ADMAN's (adman.js > Utils.shield) take on this one
	shield	: function() {
		// As of Windows Internet Explorer 7, the select property is windowless 
		// and can make use of the z-index attribute and the zIndex property.
		if(!(window.ie6 || window.ie5)) {
			return this;	
		}
		
		// Build the iframe if not present.
		// if present / position it.
		if(!this._shield) {
			this._shield 	= $C('iframe', {
				'marginheight'		: 0,
				'marginwidth'		: 0,
				'scrolling'			: 'no',
				'frameBorder'		: 0,
				'allowTransparency ': 'true',
				'styles' : {
					'opacity'		: 0,
					'z-index'		: -1, // That will do the trick
					'position'		: 'absolute',
					'top'			: -parseInt(this.getStyle('border-top-width').toInt()),
					'left'			: -parseInt(this.getStyle('border-left-width').toInt())
				}
			});
			
			this.getFirst() ? this._shield.injectBefore(this.getFirst()) : this._shield.injectIn(this);
			// .injectBefore(this.getFirst());			
			// Was injectIn, changed to injectBefore(this.getFirst())
		}	
		
		// (Re)Size it
		this._shield.show().setStyles(this.getDimensions()).show();
		return this;
	},
	
	unShield : function() {
		if(!(window.ie6 || window.ie5) || !this._shield) {
			return this;	
		}	
		this._shield.hide().remove();
		this._shield = null;
	}
}

Element.extend = function( args) { 
	$extend(this.Methods, args);
	$extend(window.HTMLElement,args );
	$extend($, args);
	
	// Make Element.MYMETHOD work
	$forEach( Element.Methods, function( fn, property ) {
		Element[property] 	=  function( el ) { return fn.apply($(el), $A(arguments).slice(1)); }
	});
}

// ================================================================================
// Element.Rules class
// ================================================================================
Element.Rules = new Class({
	initialize : function(rules){
		this.rules = rules;		
		return this;
	},
	
	apply : function() {
		each( this.rules, function( properties, rule  ) {
			$$(rule).each(function( el ) {
				el.set(properties);
			});
		});
		return this;
	}
});


// ================================================================================
// Element - Static properties
// ================================================================================
$extend( Element, {
	// Thanks to mootools
	fixStyle : function( property, value, element ) {
		// patch for background
		if(property === 'background') {
			var bg = element.getStyle('background-image');
			if(bg) {
				return value + ' ' + bg + ' ' + element.getStyle('background-repeat') + ' ' + element.getStyle('background-position');	
			}			
		}
		
		if( $is(value) && value !== 'auto') {
			return value;
		}
		
		if (['height', 'width'].contains(property)){
			var values = (property == 'width') ? ['left', 'right'] : ['top', 'bottom'];
			var size = 0;
			values.each(function(value){
				size += element.getStyle('border-' + value + '-width').toInt() + element.getStyle('padding-' + value).toInt();
			});
			return element['offset' + property.capitalize()] - size + 'px';
		} else if (/border(.+)Width|margin|padding/.test(property)){
			return '0px';
		}		
		if(property === 'background') {
			value = 'transparent';
		}
		return value;
	},
	
	nativeEvents : [
		'message',
		'click', 'dblclick', 'mouseup', 'mousedown', //mouse buttons
		'mousewheel', 'DOMMouseScroll', //mouse wheel
		'mouseover', 'mouseout', 'mousemove', //mouse movement
		'copy', 'paste', 'cut', 'beforecopy', 'beforepaste', 'beforecut',				// Clipboard related 
		'keydown', 'keypress', 'keyup', //keys
		'DOMContentLoaded','load', 'unload', 'beforeunload', 'resize', 'move', //window
		'focus', 'blur', 'change', 'submit', 'reset', 'select', 'input', //forms elements
		'error', 'abort', 'contextmenu', 'scroll', //misc
		'dragenter', 'dragexit' , 'dragend', 'dragover', 'drop', 'dragleave', // Drag related
		'haschanged' // new
	],
	
	customEvents	: new Abstract({
		'mouseenter'	: {
			base 		: 'mouseover',
			condition 	: Element.Event.checkRelatedTarget
		},
		
		'mouseleave'	: {
			base		: 'mouseout',
			condition	: Element.Event.checkRelatedTarget
		}
	})
	
})


// ================================================================================
// Cookie {}
// Based on the functions by Peter-Paul Koch (http://quirksmode.org).
// ================================================================================

// REMEMBER TO ENCODE URICOMPONENT BEFORE SETTING
// AND GETTING
var Cookie = {
	options: {
		path		: '/',
		domain		: false,
		duration	: false,
		secure		: false,
		encode		: true
	},
	
	// This will store the cookie changes
	// Since Chrome needs the page to be reloaded for the removal to take place
	cookies			: {
		
	},

	set: function(key, value, options){
		if($typeOf(options) === 'number') {
			options = {
				duration: options	
			}
		}
		var isRemoving = value === '';
		options 	= $merge(this.options, options);
		value 		= options.encode ? encodeURIComponent(value) : value;
		
		if (options.domain) value += '; domain=' + options.domain;
		if (options.path) 	value += '; path=' + options.path;
		if (options.duration) {
			var date = new Date();
			date.setTime(date.getTime() + options.duration * 24 * 60 * 60 * 1000);
			value += '; expires=' + date.toUTCString();
		}
		
		if (options.secure) value += '; secure';
		
		document.cookie = key + '=' + value;
		
		return $extend(options, {'key': key, 'value': value});
	},
	
	get : function(name){
		var value = null, cookies, cookie;
		if(document.cookie && document.cookie !== '') {
			cookies = document.cookie.split(';');
			for(var i = 0,  l = cookies.length; i < l; i++ ) {
				cookie = cookies[i].trim();
	           // Does this cookie string begin with the name we want?
				if (cookie.substring(0, name.length + 1) == (name + '=')) {
					value = decodeURIComponent(cookie.substring(name.length + 1));
					break;
				}
			}
		}
		return value;		// new

		// name = encodeURIComponent(name);
		// var value = document.cookie.match('(?:^|;)\\s*' + name.escapeRegExp() + '=([^;]*)');
		// return value ? decodeURIComponent( value[1]  ): false;

	},
	
	remove	 : function(cookie, options) { 
		var res;
		if ($type(cookie) == 'object') {
			res = this.set(cookie.key, '', $merge(cookie, {duration: -1}));
		}	else {
			res = this.set( cookie, '', $merge(options, {duration: -1}));
		}
		
		// Removed cookie
		//this.cookies[cookie+'-+'res.domain] = true;
		
		return true;
	}
}











// http://mootools.net/slickspeed/

/*
Script: Selectors.js
	Adds advanced CSS Querying capabilities for selecting elements.

License:
	MIT-style license.
*/
Core.useNativeSelectors = true;
Element.extend({
	//Native.implement([Element, Document], {
	getElements: function(selectors, nocash){
		var useNativeSelectors = Core.useNativeSelectors;
		
		// NOTE: For cases like [href=#] and webkit we need to use the 
		// custom selectors implementation
		if(window.webkit420 && selectors.contains('"#"') ) {
			useNativeSelectors = false;	
		}
		
		selectors = selectors.split(',');
		var elements = [], j = selectors.length;
		
		
		// ADDED: 08/09/2008
		if(useNativeSelectors && this.querySelectorAll) {
			selectors.each(function(selector) {
				var found 	= [];
				try { found = this.querySelectorAll(selector);	} catch(ex) {  }
				if(found) {
					elements = elements.concat($A(found));
				}
			}, this);		
			
			return $$.unique(elements);
		}
		
		
		var Local = {};
		var ddup = (j > 1);
		for (var i = 0; i < j; i++){
			var selector = selectors[i], items = [], separators = [];
			
			selector = selector.trim().replace(Selectors.sRegExp, function(match){
				if (match.charAt(2)) match = match.trim();
				separators.push(match.charAt(0));
				return ':)' + match.charAt(1);
			}).split(':)');

			for (var k = 0, l = selector.length; k < l; k++){
				var sel = Selectors.parse(selector[k]);	
				if (!sel) return [];
				// sel.tag gave for ie on data-id cades as the tag the data-id thing
				var temp = Selectors.Method.getParam(items, separators[k - 1] || false, this, sel, Local);
				// alert( [selector, temp.length]);
				if (!temp) break;
				items = temp;
			}
			var partial = Selectors.Method.getItems(items, this);
			elements = (ddup) ? elements.concat(partial) : partial;
		}
		return $$.unique(elements);
		//return new Elements(elements, {ddup: ddup, cash: !nocash});
	},
	
	getElement : function(selector, nocache) {
		var useNativeSelectors = Core.useNativeSelectors;
		
		// NOTE: For cases like [href=#] and webkit we need to use the 
		// custom selectors implementation
		if(window.webkit420 && selector.contains('#')) {
			useNativeSelectors = false;	
		}
		
		if(useNativeSelectors && this.querySelector) {
			return $(this.querySelector(selector)) || null;
		} else {
			return $(this.getElements(selector, true)[0] || null);
		}
	}

});

/*
Window.implement({

	$E: function(selector){
		return this.document.getElement(selector);
	}

});
*/





// SELECTORS

var Selectors = {
	// [([a-z\-]+) -> \w+
	'regExp': (/:([^-:(]+)[^:(]*(?:\((["']?)(.*?)\2\))?|\[([a-z\-]+)(?:([!*^$~|]?=)(["']?)(.*?)\6)?\]|\.[\w-]+|#[\w-]+|\w+|\*/g),

	'sRegExp': (/\s*([+>~\s])[a-zA-Z#.*\s]/g)

};

Selectors.parse = function(selector){
	var params 		= {'tag': '*', 'id': null, 'classes': [], 'attributes': [], 'pseudos': []};
	var oSelector 	= selector;
	selector = selector.replace(Selectors.regExp, function(bit){
		switch (bit.charAt(0)){
			case '.': params.classes.push(bit.slice(1)); break;
			case '#': params.id = bit.slice(1); break;
			case '[': params.attributes.push([arguments[4], arguments[5], arguments[7]]); break;
			case ':':
				// mootools line 702
				var xparser = Selectors.Pseudo[arguments[1]];
				if (!xparser){
					params.attributes.push([arguments[1], arguments[3] ? '=' : '', arguments[3]]);
					break;
				}
				var pseudo = {'name': arguments[1], 'parser': xparser, 'argument': (xparser.parser) ? xparser.parser(arguments[3]) : arguments[3]};
				params.pseudos.push(pseudo);
			break;
			default: params.tag = bit;
		}
		return '';
	});

	// if(oSelector.contains('required') ) 
	// 	alert(params.attributes);			
	return params;
};

Selectors.Pseudo = new Abstract;

Selectors.XPath = {

	getParam: function(items, separator, context, params){
		var temp = context.namespaceURI ? 'xhtml:' : '';
		switch (separator){
			case ' ': temp += '//'; break;
			case '>': temp += '/'; break;
			case '+': temp += '/following-sibling::*[1]/self::'; break;
			case '~': temp += '/following-sibling::'; break;
		}
		temp += params.tag;
		var i;
		for (i = params.pseudos.length; i--; i){
			var pseudo = params.pseudos[i];
			if (pseudo.parser && pseudo.parser.xpath) temp += pseudo.parser.xpath(pseudo.argument);
			else temp += ($chk(pseudo.argument)) ? '[@' + pseudo.name + '="' + pseudo.argument + '"]' : '[@' + pseudo.name + ']';
		}
	
		if (params.id) temp += '[@id="' + params.id + '"]';
		for (i = params.classes.length; i--; i) temp += '[contains(concat(" ", @class, " "), " ' + params.classes[i] + ' ")]';
		for (i = params.attributes.length; i--; i){
			var bits = params.attributes[i];
			switch (bits[1]){
				case '=': temp += '[@' + bits[0] + '="' + bits[2] + '"]'; break;
				case '*=': temp += '[contains(@' + bits[0] + ', "' + bits[2] + '")]'; break;
				case '^=': temp += '[starts-with(@' + bits[0] + ', "' + bits[2] + '")]'; break;
				case '$=': temp += '[substring(@' + bits[0] + ', string-length(@' + bits[0] + ') - ' + bits[2].length + ' + 1) = "' + bits[2] + '"]'; break;
				case '!=': temp += '[@' + bits[0] + '!="' + bits[2] + '"]'; break;
				case '~=': temp += '[contains(concat(" ", @' + bits[0] + ', " "), " ' + bits[2] + ' ")]'; break;
				case '|=': temp += '[contains(concat("-", @' + bits[0] + ', "-"), "-' + bits[2] + '-")]'; break;
				default: temp += '[@' + bits[0] + ']';
			}
		}
		items.push(temp);
		return items;
	},

	getItems: function(items, context){
		var elements = [], xpath;
		var doc = context.ownerDocument || context;
		
		// Sometime an exception is thrown
		// Try to catch it.
		try {
			xpath = doc.evaluate('.//' + items.join(''), context, Selectors.XPath.resolver, XPathResult.UNORDERED_NODE_SNAPSHOT_TYPE, null);
		} catch(ex) {
			// OOops	
		}
		
		if(xpath) {
			for (var i = 0, j = xpath.snapshotLength; i < j; i++) elements[i] = xpath.snapshotItem(i);
		}
		return elements;
	},

	resolver: function(prefix){
		return (prefix == 'xhtml') ? 'http://www.w3.org/1999/xhtml' : false;
	}

};

Selectors.Filter = {
	getParam: function(items, separator, context, params, Local){
		// data-id gave as tag the data-id= thing
		var found = [];
		var tag = params.tag;
		if (separator){
			var uniques = {}, child, children, item, k, l;
			var add = function(child){
				child.uid = $uniqueID(child);
				//|| [Native.UID++];
				if (!uniques[child.uid] && Selectors.Filter.match(child, params, Local)){
					uniques[child.uid] = true;
					found.push(child);
					return true;
				}
				return false;
			};
			for (var i = 0, j = items.length; i < j; i++){
				item = items[i];	
				switch(separator){
					case ' ':
						children = item.getElementsByTagName(tag);
						params.tag = false;
						for (k = 0, l = children.length; k < l; k++) add(children[k]);
					break;
					case '>':
						children = item.childNodes;
						for (k = 0, l = children.length; k < l; k++){
							if (children[k].nodeType == 1) add(children[k]);
						}
					break;
					case '+':
						while ((item = item.nextSibling)){
							if (item.nodeType == 1){
								add(item);
								break;
							}
						}
					break;
					case '~':
						while ((item = item.nextSibling)){
							if (item.nodeType == 1 && add(item)) break;
						}
					break;
				}
			}	
			return found;
		}
		
		if (params.id){
			var idGetter = context.getElementById ? context.getElementById : document.getElementById;
			el = idGetter && idGetter(params.id, true);
			params.id = false;
			return (el && Selectors.Filter.match(el, params, Local)) ? [el] : false;
		} else {
			items = context.getElementsByTagName(tag);
			params.tag = false;
			for (var m = 0, n = items.length; m < n; m++){
				if (Selectors.Filter.match(items[m], params, Local)) {
					found.push(items[m]);
				}
			}
		}
		return found;
	},

	getItems: $arguments(0)

};

Selectors.Filter.match = function(el, params, Local){
	Local = Local || {};

	if (params.id && params.id != el.id) return false;
	if (params.tag && params.tag != '*' && params.tag != el.tagName.toLowerCase()) return false;

	var i;

	for (i = params.classes.length; i--; i){
		// Line below is buggy. Fixed it though
		// if (!el.className || !el.className.contains(params.classes[i], ' ')) {
		
		// The line below is slower, so we directly use Element.hasClass()
		// if (!el.className || !$(el).hasClass(params.classes[i]) ) {	
		if (!el.className || !Element.hasClass(el, params.classes[i])  ) {	
			return false;
		} 
	}

	for (i = params.attributes.length; i--; i){
		var bits = params.attributes[i];
		// CHANGED: 30.09.2010
		// Add support for [input="required"] etc
 		if(bits[1]) {
			var result = Element.Methods.getProperty.call(el, bits[0]);
			if (!result) return false;
		} else {
			if(el[bits[0]] === undefined) {
				return false;
			}
		}

		
		if (!bits[1]) continue;
		if (!result['substr']) return false; // IE woes
		var condition;
		switch (bits[1]){
			case '=': condition = (result == bits[2]); break;
			case '*=': condition = (result.contains(bits[2])); break;
			case '^=': condition = (result.substr(0, bits[2].length) == bits[2]); break;
			case '$=': condition = (result.substr(result.length - bits[2].length) == bits[2]); break;
			case '!=': condition = (result != bits[2]); break;
			case '~=': condition = result.contains(bits[2], ' '); break;
			case '|=': condition = result.contains(bits[2], '-');
		}

		if (!condition) return false;
	}

	for (i = params.pseudos.length; i--; i){
		if (!params.pseudos[i].parser.filter.call(el, params.pseudos[i].argument, Local)) return false;
	}

	return true;
};

Selectors.Method = (window.xpath) ? Selectors.XPath : Selectors.Filter;

Element.extend({
	match: function(selector){
		return (!selector || Selectors.Filter.match(this, Selectors.parse(selector)));
	}

});


/*
Script: Selectors.Pseudo.js
	Adds CSS3 and other custom pseudo selectors support for selecting elements.

License:
	MIT-style license.

See Also:
	<http://www.w3.org/TR/2005/WD-css3-selectors-20051215/#pseudo-classes>
*/

Selectors.Pseudo.enabled = {

	xpath: function(){
		return '[not(@disabled)]';
	},

	filter: function(){
		return !(this.disabled);
	}
};


Selectors.Pseudo.empty = {

	xpath: function(){
		return '[not(node())]';
	},

	filter: function(){
		return !(this.innerText || this.textContent || '').length;
	}

};


Selectors.Pseudo.contains = {

	xpath: function(argument){
		return '[contains(text(), "' + argument + '")]';
	},

	filter: function(argument){
		for (var i = this.childNodes.length; i--; i){
			var child = this.childNodes[i];
			if (child.nodeName && child.nodeType == 3 && child.nodeValue.contains(argument)) return true;
		}
		return false;
	}

};

Selectors.Pseudo.nth = {

	parser: function(argument){
		argument = (argument) ? argument.match(/^([+-]?\d*)?([devon]+)?([+-]?\d*)?$/) : [null, 1, 'n', 0];
		if (!argument) return false;
		var inta = parseInt(argument[1]);
		var a = ($chk(inta)) ? inta : 1;
		var special = argument[2] || false;
		var b = parseInt(argument[3]) || 0;
		b = b - 1;
		while (b < 1) b += a;
		while (b >= a) b -= a;
		switch (special){
			case 'n': return {'a': a, 'b': b, 'special': 'n'};
			case 'odd': return {'a': 2, 'b': 0, 'special': 'n'};
			case 'even': return {'a': 2, 'b': 1, 'special': 'n'};
			case 'first': return {'a': 0, 'special': 'index'};
			case 'last': return {'special': 'last'};
			case 'only': return {'special': 'only'};
			default: return {'a': (a - 1), 'special': 'index'};
		}
	},

	xpath: function(argument){
		switch (argument.special){
			case 'n': return '[count(preceding-sibling::*) mod ' + argument.a + ' = ' + argument.b + ']';
			case 'last': return '[count(following-sibling::*) = 0]';
			case 'only': return '[not(preceding-sibling::* or following-sibling::*)]';
			default: return '[count(preceding-sibling::*) = ' + argument.a + ']';
		}
	},

	filter: function(argument, Local){
		var count = 0, el = this;
		switch (argument.special){
			case 'n':
				Local.Positions = Local.Positions || {};
				if (!Local.Positions[this.uid]){
					var children = this.parentNode.childNodes;
					for (var i = 0, l = children.length; i < l; i++){
						var child = children[i];
						if (child.nodeType != 1) continue;
						//child.uid = child.uid || [Native.UID++];
						child.uid = $uniqueID(child);
						Local.Positions[child.uid] = count++;
					}
				}
				return (Local.Positions[this.uid] % argument.a == argument.b);
			case 'last':
				while ((el = el.nextSibling)){
					if (el.nodeType == 1) return false;
				}
				return true;
			case 'only':
				var prev = el;
				while((prev = prev.previousSibling)){
					if (prev.nodeType == 1) return false;
				}
				var next = el;
				while ((next = next.nextSibling)){
					if (next.nodeType == 1) return false;
				}
				return true;
			case 'index':
				while ((el = el.previousSibling)){
					if (el.nodeType == 1 && ++count > argument.a) return false;
				}
				return true;
		}
		return false;
	}

};

Selectors.Pseudo.extend({

	'even': {
		parser: function(){
			return {'a': 2, 'b': 1, 'special': 'n'};
		},
		xpath: Selectors.Pseudo.nth.xpath,
		filter: Selectors.Pseudo.nth.filter
	},

	'odd': {
		parser: function(){
			return {'a': 2, 'b': 0, 'special': 'n'};
		},
		xpath: Selectors.Pseudo.nth.xpath,
		filter: Selectors.Pseudo.nth.filter
	},

	'first': {
		parser: function(){
			return {'a': 0, 'special': 'index'};
		},
		xpath: Selectors.Pseudo.nth.xpath,
		filter: Selectors.Pseudo.nth.filter
	},

	'last': {
		parser: function(){
			return {'special': 'last'};
		},
		xpath: Selectors.Pseudo.nth.xpath,
		filter: Selectors.Pseudo.nth.filter
	},

	'only': {
		parser: function(){
			return {'special': 'only'};
		},
		xpath: Selectors.Pseudo.nth.xpath,
		filter: Selectors.Pseudo.nth.filter
	}

});




function $ELS(selector, root){
	return ($(root) || document).getElementsBySelector(selector);
};

$$ = function() {
	var elements = [];
	for (var i = 0, j = arguments.length; i < j; i++){
		var selector = arguments[i];
		switch($typeOf(selector)){
			case 'element'	: elements.push(selector);
			case 'boolean'	: break;
			case false		: break;
			case 'string'	: selector = document.getElements(selector, true);
			default			: elements.extend(selector);
		}
		
	}
	
	return $$.unique(elements);
};

$$.unique = function(array){
	var elements 	= [];
	var hashTable 	= {};
	
	for (var i = 0, l = array.length; i < l; i++){
		var element = $(array[i]), uid;
		if(!element) { 
			return false;
		}
		uid = $uniqueID(element);
		if(hashTable[uid] || !element) {
			continue;	
		}
		elements.push(element);
	}
	hashTable = null;
	return elements.map($);
};
























// ================================================================================
// Events class
// ================================================================================
Events = new Class({
	addEvent	: function( type, fn) {
		if (fn != Class.empty && $typeOf(fn) == 'function' ){
			this.events 			= this.events 			|| {};
			this.events[type] 		= this.events[type] 	|| [];
			this.events[type].remove(fn); // Remove it first
			this.events[type].include(fn);
		}
		return this;
	},
	
	removeEvent	: function(type, fn) {
		if(this.events && this.events[type]) {
			if(fn) {
				this.events[type].remove(fn);
			} else {
				// Remove all events if no fn is given
				this.events[type] = null;
			}	
		}
		return this;
	},
	
	hasEvent	: function(type) {
		return( !!(this.events && this.events[type]) );
	},
	
	invokeEvent	: function() {
		var args = $A(arguments), type = args.shift();	
		
		if (this.events && this.events[type]){			
			this.events[type].each(function(fn) {	
				fn.apply(this, args);
			}, this);
		} else {
			//alert(this['on'+type.toCapitalize]);return;
			// For on related left out of setOptions
			// or added later on
			if(this['on'+type.capitalize()] && $typeOf(this['on'+type.capitalize()]) === 'function') {
				this['on'+type.capitalize()].apply(this, args); 
			}
		}	
		// Global broadcasting
		if( type != 'event' ) {
			this.invokeEvent( 'event', type, args );
		}
		return this;	
	}
	
});

/*
	(start code)
	var myFx = new Fx.Style('element', 'opacity');

	myFx.start(1,0).chain(function(){
		myFx.start(0,1);
	}).chain(function(){
		myFx.start(1,0);
	}).chain(function(){
		myFx.start(0,1);
	});
	//the element will appear and disappear three times
	(end
*/	 
// ================================================================================
// Chain class
// ================================================================================
var Chain = new Class({
	chain	: function(fn) {
		this.chains = this.chains || [];
		this.chains.push(fn);
		return this;
	},
	
	callChain : function(){
		// Added support for arguments
		// ADDED: 25.06.2008 
		var realFn, self = this, args = $A(arguments);
		if (this.chains && this.chains.length) {
			realFn 	= this.chains.shift();
			(function() {
				return 	realFn.apply(self, args.slice(0));
			}).delay(10, this);
			// The old way around
			// this.chains.shift().bind(this, $A(arguments)[0]).delay(10, this);	
		}
		return this;
	},
	
	clearChain	: function() {
		this.chains.empty();
		return this;
	}
});

// ================================================================================
// Options class
// ================================================================================
var Options 	= new Class({	
	setOptions			: function() {
		if( !arguments) return;
		// PRETTY useful for mergin
		this.options = $merge.apply( null, [this.options].extend( arguments) );

		// Handle events
		if( this.options['events'] ) 	each( this.options['events'], function( fn, event ) { this.addEvent( event, fn); }, this );
		
		// Legacy reasons
		// TODO: fix this mess
		each( this.options, function( value, key ) {
			if( key.indexOf( 'on') === 0 && $typeOf(value) == 'function'  ) {
				this.options[key] = null;
				var newKey 	=  key.replace( /on/, '');
				newKey		= newKey.charAt(0).toLowerCase() + newKey.substr(1);	
				this.addEvent(newKey, value ) 
			}
		}, this	);
		
		// Fix the functions scope
		each( this.options, function( fn, name) {
			var self = this;
			if( $typeOf(fn) == 'function' )	{ this.options[name] = function() { return fn.apply(self, arguments); };}
		}, this);
		
		return this;
	}
});

// ================================================================================
// Fx
// ================================================================================
// http://demos111.mootools.net/Fx.Transitions
Fx	= new Class({
	options		: {
		'transition' : function(p){
			return -(Math.cos(Math.PI * p) - 1) / 2;
		},
		'duration'		: 800,
		'unit'			: 'px',
		'wait'			: false,
		'useAttributes' : false,
		'fps'			: 60		 // was 50
	},
				
	initialize	: function( options ) {
		this.element = this.element || null;
		this.setOptions(options);
		
		if (this.options.initialize) {
			this.options.initialize.call(this);
		}
	},
	
	step		: function() {
		var time = $now();
		// Return false if we have reloaded the page
		// while the fx is running (we have removed the methods)
		if( !this.element || !this.element.extended ) {
			return false;
		}
		
		if (time < this.time + this.options.duration){
			this.delta = this.options.transition((time - this.time) / this.options.duration);
			this.setNow();
			this.invokeEvent( 'progress', 1-this.now );	
			this.next(); // not increase
		} else {
			return this.complete();
		}	
	},
	
	set			: function( to ) {
		this.now = to;
		this.next();
		return this;

	},
	
	complete : function() {
		this.stop(true);
		this.set(this.to);
		// Used to internally handle b4complete events
		this.invokeEvent( 'complete', this.element);
		this.invokeEvent( 'postComplete', this.element);
		
		this.invokeEvent( 'progress', 1-this.now );	
		
		return this.callChain();		
	},
	
	stop	: function( end ) {
		if (!this.timer) {
			return this;
		}
		this.timer = $clear(this.timer);
		
		if (!end) {
			this.invokeEvent('cancel');
		}
		return this;		
	},
	
	setNow: function(){
		this.now = this.compute(this.from, this.to);
	},

	compute: function(from, to){
		return (to - from) * this.delta + from;
	},
	
	start		: function(from, to) {
		if (!this.options.wait) this.stop();
		else if (this.timer) return this;
		this.from 	= from;
		this.to 	= to;
		this.change = this.to - this.from;
		this.time 	= $now();

		this.timer 	= this.step.bind(this).periodical( Math.round(1000 / this.options.fps) );
		this.invokeEvent ( 'start' );
		return this;		
	},
	
	toggle		: function() {
		return this.start(this.now, this.from);	
	}
				
});
Fx.implement ( new Chain, new Events, new Options );

/*
Script: Fx.CSS.js
	Css parsing class for effects. Required by <Fx.Style>, <Fx.Styles>, <Fx.Elements>. No documentation needed, as its used internally.

License:
	MIT-style license.
*/

Fx.CSS = {

	select: function(property, to){
		if (/color/i.test( property ) ) return this.Color;
		var type = $typeOf(to);
		if ((type == 'array') || (type == 'string' && to.contains(' '))) return this.Multi;
		return this.Single;
	},

	parse: function(el, property, fromTo, useAttributes){
		if (!fromTo.push) fromTo = [fromTo];
		var from = fromTo[0], to = fromTo[1];
		
		if (!$is(to)){
			to 		= from;
			from 	= useAttributes ? el.getProperty(property) : el.getStyle(property);
		}
		
		var css = this.select(property, to);
		return {'from': css.parse(from), 'to': css.parse(to), 'css': css};
	}

};

Fx.CSS.Single = {

	parse: function(value){
		return parseFloat(value);
	},

	getNow: function(from, to, fx){
		return fx.compute(from, to);
	},

	getValue: function(value, unit, property){
		if (unit == 'px' && property != 'opacity') value = Math.round(value);
		return value + unit;
	}

};

Fx.CSS.Multi = {

	parse: function(value){
		return value.push ? value : value.split(' ').map(function(v){
			return parseFloat(v);
		});
	},

	getNow: function(from, to, fx){
		var now = [];
		for (var i = 0; i < from.length; i++) now[i] = fx.compute(from[i], to[i]);
		return now;
	},

	getValue: function(value, unit, property){
		if (unit == 'px' && property != 'opacity') value = value.map(Math.round);
		return value.join(unit + ' ') + unit;
	}

};

Fx.CSS.Color = {
	parse: function(value){
		// FIXME: Make this inherit the right color
		if(window.opera && value === 'transparent') {
			value = '#ffffff';	
		}
		return value.push ? value : value.hexToRgb(true);
	},

	getNow: function(from, to, fx){
		var now = [];
		for (var i = 0; i < from.length; i++) now[i] = Math.round(fx.compute(from[i], to[i]));
		return now;
	},

	getValue: function(value){
		return 'rgb(' + value.join(',') + ')';
	}

};

/*
Script: Fx.Style.js
	Contains <Fx.Style>

License:
	MIT-style license.
*/

/*
Class: Fx.Style
	The Style effect, used to transition any css property from one value to another. Includes colors.
	Colors must be in hex format.
	Inherits methods, properties, options and events from <Fx>.

Arguments:
	el - the $(element) to apply the style transition to
	property - the property to transition
	options - the Fx.Base options (see: <Fx.Base>)

Example:
	>var marginChange = new Fx.Style('myElement', 'margin-top', {duration:500});
	>marginChange.start(10, 100);
*/

Fx.Style = Fx.extend({

	initialize: function(el, property, options){
		this.element = $(el);
		this.property = property;
		this.parent(options);

	},

	/*
	Property: hide
		Same as <Fx.Base.set> (0); hides the element immediately without transition.
	*/

	hide: function(){
		return this.set(0);
	},

	setNow: function(){
		this.now = this.css.getNow(this.from, this.to, this);
	},

	/*
	Property: set
		Sets the element's css property (specified at instantiation) to the specified value immediately.

	Example:
		(start code)
		var marginChange = new Fx.Style('myElement', 'margin-top', {duration:500});
		marginChange.set(10); //margin-top is set to 10px immediately
		(end)
	*/

	set: function(to){
		this.css = Fx.CSS.select(this.property, to);
		return this.parent(this.css.parse(to));
	},

	/*
	Property: start
		Displays the transition to the value/values passed in

	Arguments:
		from - (integer; optional) the starting position for the transition
		to - (integer) the ending position for the transition

	Note:
		If you provide only one argument, the transition will use the current css value for its starting value.

	Example:
		(start code)
		var marginChange = new Fx.Style('myElement', 'margin-top', {duration:500});
		marginChange.start(10); //tries to read current margin top value and goes from current to 10
		(end)
	*/

	start: function(from, to){
		if (this.timer && this.options.wait) return this;
		var parsed = Fx.CSS.parse(this.element, this.property, [from, to]);
		this.css = parsed.css;
		return this.parent(parsed.from, parsed.to);
	},

	next: function(){
		if(this.options.useAttributes) {
			this.element.setProperty(this.property, this.css.getValue(this.now, this.options.unit, this.property));
		} else {
			this.element.setStyle(this.property, this.css.getValue(this.now, this.options.unit, this.property));
		}
	}

});


/*
	var myEffects = new Fx.Styles('myElement', {duration: 1000, transition: Fx.Transitions.linear});

	//height from 10 to 100 and width from 900 to 300
	myEffects.start({
		'height': [10, 100],
		'width': [900, 300]
	});

	//or height from current height to 100 and width from current width to 300
	myEffects.start({
		'height': 100,
		'width': 300
	});
	(end)
	*/

Fx.Styles = Fx.extend({
	initialize: function(el, options){
		this.element = $(el);
		this.parent(options);
	},

	setNow: function(){
		for (var p in this.from) this.now[p] = this.css[p].getNow(this.from[p], this.to[p], this);
	},

	set: function(to){
		var parsed = {};
		this.css = {};
		for (var p in to){
			this.css[p] = Fx.CSS.select(p, to[p]);
			parsed[p] = this.css[p].parse(to[p]);
		}
		return this.parent(parsed);
	},

	/*
	Property: start
		Executes a transition for any number of css properties in tandem.

	Arguments:
		obj - an object containing keys that specify css properties to alter and values that specify either the from/to values (as an array) or just the end value (an integer).

	Example:
		see <Fx.Styles>
	*/

	start: function(obj){
		if (this.timer && this.options.wait) return this;
		this.now = {};
		this.css = {};
		var from = {}, to = {};
		for (var p in obj){
			var parsed = Fx.CSS.parse(this.element, p, obj[p]);
			from[p] = parsed.from;
			to[p] = parsed.to;
			this.css[p] = parsed.css;
		}
		return this.parent(from, to);
	},

	next: function(){
		for (var p in this.now) {
			if(!this.options.useAttributes) {
				this.element.setStyle(p, this.css[p].getValue(this.now[p], this.options.unit, p));
			} else {
				this.element.setProperty(p, this.css[p].getValue(this.now[p], this.options.unit, p));	
			}
		}
	},
	
	toggle : function() {
		var obj = {}
		for (var p in this.now)  obj[p]		 	= [ this.now[p], this.from[p] ]
		this.start( obj );
		return this;
	}

});



Fx.Slide = Fx.extend({

	options: {
		mode: 'vertical',
		wait: true,
		
		// Used in case we need to reverse the direction
		// This is kinda obsolete
		reverse : false,
		
		postComplete : function(element) {
			// In case we are going to full height / restore it
			if(this.to[1] === this.offset) {
				return this.restore();
			}
		}	
	},

	initialize: function(el, options){
		this.element = $(el);
		// TODO: fix this
		var styles = {
			'margin-left' 	: this.element.getStyle('margin-left'),
			'margin-right' : this.element.getStyle('margin-right'),
			//'margin-top' 	: this.element.getStyle('margin-right'),
			// TODO: needed?
			'position'		: (this.element.getStyle('position') != 'static' && window.ie) ? 'relative' : 'static',
			'overflow'		: 'hidden'
		};

		// Keep a track on current styles
		this.element.setBlockStyles();
		this.element._initStyles = $extend(this.element.getStyles('margin-top', 'margin-bottom', 'margin-top'), { 'height' : 'auto' });
		this.element.restoreBlockStyles();
		
		
		this.wrapper = $C('div', {'styles': styles }).injectAfter(this.element).adopt(this.element);
		this.element.setStyle('margin-top', 0);
		this.element.setStyle('margin-bottom', 0);
		this.setOptions(options);
		this.now = [];
		this.parent(this.options);
		this.open = true;
		this.addEvent('complete', function(){
			this.open = (this.now[0] >= 0);
		});
		if (window.webkit419) this.addEvent('complete', function(){
			if (this.open) this.element.remove().injectIn(this.wrapper);
		});
		
		// Hide it if display === none
		if(this.element.isHidden()) {
			this.hide();	
		}
		
	},

	// Restore element and wraper to start
	restore : function() {
		if(this.wrapper) { 
			this.wrapper.style.height = 'auto';
		}

		// Restore element styles
		this.element.setStyles(this.element._initStyles);
	},


	setNow: function(){
		for (var i = 0; i < 2; i++) this.now[i] = this.compute(this.from[i], this.to[i]);
	},

	vertical: function(){
		this.margin = this.options.reverse ? 'margin-top' : 'margin-top';
		this.layout = 'height';		
		this.offset = this.element.offsetHeight || this.element.scrollHeight;
	},

	horizontal: function(){
		this.margin = this.options.reverse ? 'margin-right' : 'margin-left';
		this.layout = 'width';
		this.offset = this.element.offsetWidth || this.element.scrollWidth;
	},

	/*
	Property: slideIn
		Slides the elements in view horizontally or vertically.

	Arguments:
		mode - (optional, string) 'horizontal' or 'vertical'; defaults to options.mode.
	*/

	slideIn: function(mode){
		this.element.show();
		this[mode || this.options.mode]();
		return this.start([parseInt(this.element.getStyle(this.margin)), parseInt(this.wrapper.getStyle(this.layout))], [0,this.offset]);
	},

	/*
	Property: slideOut
		Sides the elements out of view horizontally or vertically.

	Arguments:
		mode - (optional, string) 'horizontal' or 'vertical'; defaults to options.mode.
	*/

	slideOut: function(mode){
		this.element.show();
 		this[mode || this.options.mode]();
		return this.start([parseInt(this.element.getStyle(this.margin)), parseInt(this.wrapper.getStyle(this.layout))], [-this.offset, 0]);
	},

	/*
	Property: hide
		Hides the element without a transition.

	Arguments:
		mode - (optional, string) 'horizontal' or 'vertical'; defaults to options.mode.
	*/

	hide: function(mode){
		// CHANGED: detect if its hidden
		var isHidden = false;

		if(this.element.isHidden()) {
			isHidden = true;
			this.element.setBlockStyles();
		}
	
		this[mode || this.options.mode]();
		this.open = false;
		this.set([-this.offset, 0]);
		
		if(isHidden) {
			this.element.restoreBlockStyles();
			this.element.show();	
		}
		return this;
	},

	/*
	Property: show
		Shows the element without a transition.

	Arguments:
		mode - (optional, string) 'horizontal' or 'vertical'; defaults to options.mode.
	*/

	show: function(mode){
		this[mode || this.options.mode]();
		this.open = true;
		return this.set([0, this.offset]);
	},

	/*
	Property: toggle
		Slides in or Out the element, depending on its state

	Arguments:
		mode - (optional, string) 'horizontal' or 'vertical'; defaults to options.mode.

	*/

	toggle: function(mode){
		if (this.wrapper.offsetHeight == 0 || this.wrapper.offsetWidth == 0) return this.slideIn(mode);
		return this.slideOut(mode);
	},

	next: function(){
		this.element.setStyle(this.margin, this.now[0] + this.options.unit);
		this.wrapper.setStyle(this.layout, this.now[1] + this.options.unit);
	}

});

/*
Script: Fx.Elements.js
	Contains <Fx.Elements>

License:
	MIT-style license.
*/

/*
Class: Fx.Elements
	Fx.Elements allows you to apply any number of styles transitions to a selection of elements. Includes colors (must be in hex format).
	Inherits methods, properties, options and events from <Fx.Base>.

Arguments:
	elements - a collection of elements the effects will be applied to.
	options - same as <Fx.Base> options.
*/

Fx.Elements = Fx.extend({

	initialize: function(elements, options){
		this.elements = $$(elements);
		this.parent(options);
	},

	setNow: function(){
		for (var i in this.from){
			var iFrom = this.from[i], iTo = this.to[i], iCss = this.css[i], iNow = this.now[i] = {};
			for (var p in iFrom) iNow[p] = iCss[p].getNow(iFrom[p], iTo[p], this);
		}
	},

	set: function(to){
		var parsed = {};
		this.css = {};
		for (var i in to){
			var iTo = to[i], iCss = this.css[i] = {}, iParsed = parsed[i] = {};
			for (var p in iTo){
				iCss[p] = Fx.CSS.select(p, iTo[p]);
				iParsed[p] = iCss[p].parse(iTo[p]);
			}
		}
		return this.parent(parsed);
	},

	/*
	Property: start
		Applies the passed in style transitions to each object named (see example). Each item in the collection is refered to as a numerical string ("1" for instance). The first item is "0", the second "1", etc.

	Example:
		(start code)
		var myElementsEffects = new Fx.Elements($$('a'));
		myElementsEffects.start({
			'0': { //let's change the first element's opacity and width
				'opacity': [0,1],
				'width': [100,200]
			},
			'4': { //and the fifth one's opacity
				'opacity': [0.2, 0.5]
			}
		});
		(end)
	*/

	start: function(obj){
		if (this.timer && this.options.wait) return this;
		this.now = {};
		this.css = {};
		var from = {}, to = {};
		for (var i in obj){
			var iProps = obj[i], iFrom = from[i] = {}, iTo = to[i] = {}, iCss = this.css[i] = {};
			for (var p in iProps){
				var parsed = Fx.CSS.parse(this.elements[i], p, iProps[p]);
				iFrom[p] = parsed.from;
				iTo[p] = parsed.to;
				iCss[p] = parsed.css;
			}
		}
		return this.parent(from, to);
	},

	increase: function(){
		for (var i in this.now){
			var iNow = this.now[i], iCss = this.css[i];
			for (var p in iNow) this.elements[i].setStyle(p, iCss[p].getValue(iNow[p], this.options.unit, p));
		}
	}

});



/*
Script: Fx.Scroll.js
	Contains <Fx.Scroll>

License:
	MIT-style license.
*/

/*
Class: Fx.Scroll
	Scroll any element with an overflow, including the window element.
	Inherits methods, properties, options and events from <Fx.Base>.

Note:
	Fx.Scroll requires an XHTML doctype.

Arguments:
	element - the element to scroll
	options - optional, see Options below.

Options:
	all the Fx.Base options and events, plus:
	offset - the distance for the scrollTo point/element. an Object with x/y properties.
	overflown - an array of nested scrolling containers, see <Element.getPosition>
*/

Fx.Scroll = Fx.extend({

	options: {
		overflown: [],
		offset: {'x': 0, 'y': 0},
		wheelStops: false
	},

	initialize: function(element, options){
		this.now = [];
		this.isDocument = false;
		
		// Use document
		if(!element || $(element).isDocument()) {
			this.isDocument 	= true;
			element				= window.getBody();
			
			// ADDED: 8.11.2010
			this.options.wheelStops = true;
		}
		
		this.element 	= $(element);
		this.bound 		= {'stop': this.stop.bind(this, false)};
		
		this.parent(options);
		
		if (this.options.wheelStops){
			this.addEvent('start', function(){
				document.addEvent('mousewheel', this.bound.stop);
			}.bind(this));
			this.addEvent('complete', function(){
				document.removeEvent('mousewheel', this.bound.stop);
			}.bind(this));
		}
	},

	setNow: function(){
		for (var i = 0; i < 2; i++) this.now[i] = this.compute(this.from[i], this.to[i]);
	},

	/*
	Property: scrollTo
		Scrolls the chosen element to the x/y coordinates.

	Arguments:
		x - the x coordinate to scroll the element to
		y - the y coordinate to scroll the element to
	*/

	scrollTo: function(x, y){
		if (this.timer && this.options.wait) {
			return this;
		}
		var el 			= this.element.getSize(), max;

		el.size['x'] 	= el.size['width'];
		el.size['y'] 	= el.size['height'];
		
		var values		= { 'x' : x, 'y' : y};

		for (var z in values){
			if(!this.isDocument) {
				max			= el.scrollSize[z] - el.size[z];			
			} else {
				max			= el.scrollSize[z];	
			}
			if ($is(values[z])) {
				values[z] = ($typeOf(values[z]) == 'number') ? values[z].limit(0, max) : max;
			} else {
				values[z] = el.scrollSize[z];
			}
			
			values[z] += this.options.offset[z];
		}
		
		return this.start([el.scroll.x, el.scroll.y], [values.x, values.y]);
	},

	/*
	Property: toTop
		Scrolls the chosen element to its maximum top.
	*/

	toTop: function(){
		return this.scrollTo(false, 0);
	},

	/*
	Property: toBottom
		Scrolls the chosen element to its maximum bottom.
	*/

	toBottom: function(){
		return this.scrollTo(0, 'full');
	},

	/*
	Property: toLeft
		Scrolls the chosen element to its maximum left.
	*/

	toLeft: function(){
		return this.scrollTo(0, false);
	},

	/*
	Property: toRight
		Scrolls the chosen element to its maximum right.
	*/

	toRight: function(){
		return this.scrollTo('full', false);
	},

	/*
	Property: toElement
		Scrolls the specified element to the position the passed in element is found.

	Arguments:
		el - the $(element) to scroll the window to
	*/
 
	toElement: function(el){
		// var parent = this.element.getPosition(this.options.overflown);
		// var target = $(el).getPosition(this.options.overflown);
		// Avoid clientRect
		var parent = this.element.getPosition(false, true);
		var target = $(el).getPosition(false, true);
		
		return this.scrollTo(target.x - parent.x, target.y - parent.y);
	},

	next: function(){
		this.element.scrollTo(this.now[0], this.now[1]);
	}

});


JSONP	= new Class({
	options		: {
		'callback'	 	: 'callback', 	// Callback key ($_GET['callback']) - set to null if we want don't want to use our own system
		'timeout'		: 20000,		// Timeout to bail remove the thing
		'cache'			: false 		// Set to true in order to cache a jsonp query
	},
	
	initialize	: function(src, options) {
		if(src && !src.toLowerCase) {
			options 	= src;
			src 		= null;
		}
		
		this.setOptions(options);
		
 		JSONP.id			= (JSONP.id || 0) + 1;
		JSONP.callbacks 	= JSONP.callbacks || {}; 
		
		this.id				= JSONP.id;
	
		src && this.getSrc(src);
		
		return this;
	},
	
	setCallback	: function() {
		var self = this;
		JSONP.callbacks[this.id] 	= function(response) {
			this.endTs	= $now();
			this.time	= this.endTs - this.startTs;
			// Nothing in here
			if(!response) {
				this.error('NO_CONTENT');
			} else {
				this.invokeEvent('success', response);
				this.callChain(response);
			}
			this.remove.delay(10, this);
		}.bind(this);

		// This is way better
		// Since e.g Twitter is not working okay with callbacks[tis.id]
		JSONP['callbacks_'+this.id] = JSONP.callbacks[this.id];
		return this;
	},
	
	getSrc		: function(src) {
		if(src !== undefined ) {
			
			// When options.callback we just skip our own callbacks 
			// and let the js script do magic on its own.
			if(this.options.callback) {
				this.src	= src.replace(/&callback=.*?$/, '') + ( src.contains('?') ? '&' : '?') + this.options.callback + '=JSONP.callbacks_'+this.id + (this.options.cache ? '&rnd=1' : '&rnd='+$now() );
			} else{
				//.replace(/&callback=.*?$/, '')
				this.src = src + ( src.contains('?') ? '&' : '?') + (this.options.cache ? '&rnd=1' : '&rnd='+$now() );
			}
			
		} else {
			this.src 	= this.src.replace(/rnd=.*?$/, 'rnd='+$now());
		}
		
		return this.src.replace(/&&/, '&');
	},
	
	send		: function(src) {
		!this.src && this.getSrc(src);
		
		// Stop current
		this.abort();
		
		// Only if we use our own callback system
		this.options.callback && this.setCallback();

		this.startTs = $now();
		
		this.element = $C('script').
		setProperties({
			'async' : true,
			'src'	: this.getSrc(src)
		}).addEvents({
			'load'	: this.invokeEvent.bind(this, 'load'),
			'error'	: this.error.bind(this, 'SCRIPT_ERROR'),
			'abort'	: this.abort.bind(this)
		}).
		injectIn(document.head || document.body);
		
		this.timer = function() {
			this.invokeEvent('timeout');
			this.abort();
			
			// we need to assign a function here
			// to be called if needed
			// tricky but works
			
			// note: only if we use our own callback system
			if(this.options.callback) {
				JSONP.callbacks[this.id]  = function() {
					//console.log(1)
					this.remove();
				}.bind(this);
				
				delete(JSONP['callbacks_'+this.id]);
			}
		}.delay(this.options.timeout, this);

		return this.invokeEvent('send');
	},
	
	error		: function(type, event) {
		return this.invokeEvent('error', type).abort();
	},
	
	abort		: function() {
		if(this.timer) {
			this.timer.clear();
			this.timer = null;
		}
		
		if(!this.element) {
			return this;
		}

		return this.invokeEvent('abort').remove();
	},
	
	
	remove		: function() {
		this.options.callback && (JSONP.callbacks[this.id] = Core.empty);
		
		delete(JSON['callbacks_' + this.id]);
		
		if(this.element) {
			this.element.remove();
			this.element = null;
		}

		return this;		
	}
}).implement(new Options, new Events, new Chain);

// ================================================================================
// Ajax
//
// Examples on setting params
// 1. new Ajax('myAjax.php').send('lala=1');
// 2. new Ajax('post', {url : 'myAjax.php'});
// 3. new Ajax().send('lala=1').chain(...) 
// 4. new Ajax({ parameters : { lala : 1 } ).send();
// ================================================================================
Ajax = new Class({
	options			: {			 
		'method'		: 'POST',
		'asynchronous'	: true,
		'encoding'		: 'iso-8859-7',
		'timeout'		: 20000,
		'mimeType'		: 'text/plain', // If parameters are set, automatically set it to text/html
		'multipart'		: false,
		'queryString'	: '',
		'forceAbort'	: true,
		'username'		: null,
		'password'		: null,
		'encodeURI'		: true,
		
		'url'			: '' 		// TODO: remove this later
	},

	setXHR				: function() {
		// These versions of XHR are known to work with MXHR (providing proper interactive state)
		// Prefer those as oppsed to standard XMLHttpRequest
		// this.XHR =  new ActiveXObject('MSXML2.XMLHTTP.6.0');	  
		if(window.ie && !window.XMLHttpRequest) {
			try { 
				this.XHR = new ActiveXObject('MSXML2.XMLHTTP.6.0') 
			} catch(ex) {
				this.XHR = new ActiveXObject('MSXML3.XMLHTTP');
			}
		} 
		this.XHR = this.XHR || ( (window.XMLHttpRequest) ? new XMLHttpRequest() : (window.ie ? new ActiveXObject('Microsoft.XMLHTTP') : false));		  
		
		// ADDED: 20.11.2010
		try {
			this.XHR.withCredentials = true
		} catch(ex) {
			/// 
		}
		return this;
	},

	// For private use only
	_timeOut			: function() { 	return this.abort().invokeEvent( 'timeout' ); },
	
	_setQueryString		: function() {
		var parameters = [];	
		each(this.parameters, function( value, key ) 	{	
			if(value !== null) {
				parameters.push( key+ '=' +  ( this.options.encodeURI ? encodeURIComponent(value) : value) ); 
			}
		}, this);	
		this.queryString 	= parameters.join('&');	
	},
	
	abort			: function() {
		if( !this.isRunning) {
			return this;
		}

		this.isRunning				= Ajax.isRunning = this.isSuccess = false;
		this.timer = $clear(this.timer); // nullify timer
		
		this.XHR.abort();

		this.XHR.onreadystatechange = Core.empty;		

		this.isAborted				= true;
		this.__startedLoading 		= false;

		this.setXHR();
		return this.invokeEvent('abort');
	},
	
	getHeader		: function ( header) {
		// note: getallResponseHeaders is supported from IE7 and on
		try {
			return header ? 
			this.XHR.getResponseHeader(header) :
			this.XHR.getAllResponseHeaders();
		} catch(ex) { return null; }
	},
	
	set 			: function( options ) {
		var parameters = [];			
		// Assuming its a queryString
		if( $typeOf(options) == 'string' ) {
			this.queryString 	= options;
		} else {
			this.setOptions(options);
			this.options.method 		= this.options.method.toUpperCase();
			this._setQueryString();
		}
		
			
		if( this.options.method == 'GET' ) {
			this.URI = this.options.url +  ( this.queryString ? ( this.options.url.contains( '?') ? '&'  : '?' ) + this.queryString  : '');	
		} else {
			this.URI	= this.options.url;
		}
	
		
		return this;
	},
	
	send			: function(parameters) {
		// ADDED: 20.8.2010
		// if(this.chain && this.options.asynchronous === false && !this._postponed) {
		// 	this._postponed = true;
		// 	(this.send.parent ? this.send.parent : this.send).delay(0, this, parameters);
		// 	return this;
		// }
		var self = this;
		if (this.options.forceAbort && this.isRunning) {
			// ADDED: 19.02.2011
			// We need a delay here
			this.abort();
		}
		else  {
			if( this.isRunning ) {
				return this;
			}
		}

		
		// This will let us know wether state[1] is already called
		// so we can skip re-calling it. 
		this.__startedLoading	= false;

		this.isRunning = Ajax.isRunning = true;
		// We dont want to mess with the options.parameters
		// and query string, we create copies
		this.parameters 	= $merge(this.options.parameters);
		this.queryString	= this.options.queryString;
		
		//TODO: fix this mess
		if( $typeOf(parameters) != 'string') {
			$extend(this.parameters, parameters);
		}
		
		// CHANGED: 17.02.09
		var mimeType = this.options.mimeType || '';
		if(mimeType.toLowerCase() === 'text/plain' && this.parameters) {
			// NOT FOR now since we need to have some JSON response on firebug
			//	mimeType = 'text/html';	
			//	mimeType = 'application/atom+xml';
		}
		if( this.XHR.overrideMimeType && this.options.mimeType ) {
			this.XHR.overrideMimeType( mimeType );
		}
		
		// http://www.xulplanet.com/tutorials/mozsdk/serverpush.php
		if( this.options.multipart )				this.XHR.multipart = this.options.multipart;
	
		this.set(parameters);

		// ADDED: 10 April 09 
		// Decache string 
		var decacheString = function(string) {
			string = string.replace( /._crnd=\d+/, '');
			return string + ( string.contains( '?' ) ? '&' : '?' ) + '_crnd=' + $now();
		}

		// NOTE: this will cause issues when used for multipart XHR (on IE)
		this.URI = decacheString(this.URI);

		try  { 
			this.XHR.open (this.options.method, this.URI, this.options.multipart ? false : this.options.asynchronous, this.options.username, this.options.password); 

			if (this.options.asynchronous)  {
				this.onreadystatechange.delay( 10, this, 1);
			}

			this.XHR.onreadystatechange 	= this.onreadystatechange.bind(this);
			
			// Attach events to XHR
			// ADDED: gp : 4/7/210

			// // Not for IE yet
			if(this.XHR.addEventListener) {
			 	var events = {
			 		'progress' : function(event) {
			 			if(event.lengthComputable) {
			 				self.invokeEvent('progress', event.loaded / event.total);
			 			}
			 		},
			 		'load'		: function(event) {	
						self.invokeEvent('load', event);
			 		},
			
			 		'abort'		: function(event) {
			 			self.invokeEvent('abort');
			 		},
			
			 		'error'		: function(event) {
						
			 		}
			 	}
			
			 	if(!window.gecko || (window.gecko && this.XHR.upload)) {
					// ADDED: 30 Noe 2010 
					// This will cause everlasting loading.
					!this.options.multipart && this.XHR.addEventListener('progress', 	events.progress.bind(this), false);
			 		this.XHR.addEventListener('load',		events.load.bind(this), false);
			 		this.XHR.addEventListener('error',		events.error.bind(this), false);
			 		this.XHR.addEventListener('abort', 		events.abort.bind(this), false);
				}
			}

			each( this.options.headers, function( header, key) {
				this.XHR.setRequestHeader( encodeURIComponent(key), encodeURIComponent(header) );
			}, this );

			// Mandatory
			if(!this.options.skipCoreHeader) {
				this.XHR.setRequestHeader( 'X-Core-version', Core.version);
			}
									  
			// Add window.XAjaxAuthToken headers
			// Only if we can do so.
			if(window.XAjaxAuthToken && !this.options.skipCoreHeader) {
				// Not yet evaluated
				if( $typeOf(window.XAjaxAuthToken) === 'string') {
					window.XAjaxAuthToken = window.XAjaxAuthToken.evalJSON() || {};
				}
				
				each(window.XAjaxAuthToken, function(header,key) {
					this.XHR.setRequestHeader( encodeURIComponent(key), encodeURIComponent(header) );
				}, this );
			} 
			
			this.times++;			
			this.timeStart 	= $now();
			this.timeEnd 	= this.isAborted = this.isSuccess = null;
			this.invokeEvent( 'send' );
			
			switch( this.options.method ) {

				case 'POST' : 
					if(!this.options.skipCoreHeader) {
						this.XHR.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded' + ( this.options.encoding ? '; encoding: ' + this.options.encoding : '') );
					} else {
						//this.XHR.setRequestHeader('Content-Type', 'text/json' + ( this.options.encoding ? '; encoding: ' + this.options.encoding : '') );
					}
						
					// Force 'Connection: close' for older Mozilla browsers to work
					// around a bug where XMLHttpRequest sends an incorrect
					// Content-length header. See Mozilla Bugzilla #246651.
					//if (this.XHR.overrideMimeType && (navigator.userAgent.match(/Gecko\/(\d{4})/) || [0,2005])[1] < 2005)	
					//	this.options.headers.extend( { 'connection' : 'close' } );

					// Mozilla has a bug with the async calls from a different window - the reply object is all trashed
					// up in this case. The solution to this is to just use a closure here with a 0 ms delay.
					// TODO: do it so
					this.XHR.send( this.queryString);
					break;
					
				case 'GET' 	: 
					this.XHR.send(null);
					break;
			}
			
			// Force Firefox to handle ready state 4 for synchronous requests
			if (!this.options.asynchronous && this.XHR.overrideMimeType) {
				this.onreadystatechange();			
			}
		}
		catch (ex)	{ 	
			if(console && console.log) {
				console.log(ex, this.URI);	
			}
			return this.invokeEvent( 'error', ex); 
		}		
		
		if( this.options.timeout > 1000 ) {
			this.timer = this._timeOut.bind(this).delay(this.options.timeout);
		}
		
		return this;
	},
	
	onreadystatechange	: function( state ) {
		// Bail out on multipart scenario
		if(this.options.multipart) {
			return this;
		}
		// Mozilla gets an exception throw here - you abort the thread, it calls
		// the readystatechange as part of the abort, and you're not
		// allowed to look at the status field. So, if we get one
		// just assume the request is aborted.
		var aborted  = false
		var req		 = this.XHR;

		try{ aborted = ( this.XHR.readyState == 4 && this.XHR.status == 0); }
		catch( ex) { aborted = true; } 
		
		switch( parseInt(state) || this.XHR.readyState ) {
			case 1: 
				if(!this.__startedLoading) {
					this.invokeEvent('startLoading').invokeEvent('loading');
					this.__startedLoading = true;
				} 
				return this;	
				break;
			case 2:	return this.invokeEvent('loaded');
			case 3: return this.invokeEvent('interactive', this.XHR);
			case 4:				
				// Custom events to be used with loaders
				this.invokeEvent('finishLoading');		

				// Avoid memory leaks
				// IE needs to wait a bit before detaching
				(function() { 
					 req.onreadystatechange = Core.empty;
				}).delay(10);
				
				if( aborted) {
					//console.log('ABORTED')
					//return this;
				}
				
				this.invokeEvent('complete');
				$clear( this.timer );
				this.responseText		= this.XHR.responseText || '';
				this.responseXML		= this.XHR.responseXML;
				
				
				var jsonString			= this.XHR.responseText;
				if(this.XHR.responseText.indexOf('while') === 0) {
					jsonString			= jsonString.slice(9);
					
					// this.responseText 	= jsonString;
					
					// This will not work on firefox - older
					// this.XHR.responseText = jsonString;
				}
				this.responseJSON 		= jsonString.evalJSON();
			
				
				if( this.XHR.status ) {
					this.status			= $any( this.XHR.status, -1 );
					this.statusText		= $any( this.XHR.statusText, '' );
				}			
								
				// parseerrror bug workaround
				// http://www.ilinsky.com/articles/XMLHttpRequest/
				// TODO: Fix it
				// if(this.responseXML && window.moz && this.responseXML.documentElement.tagName === 'parseerrror') {
				//	console.log('ekei');
				//	this.responseXML = null;	
				// }
				// Hooray, yipie		
				if( this.status >= 200 || this.status < 300) {
					// Changed @ 17/11/08
					if(!this.options.asynchronous) {
						this.invokeEvent('beforeSuccess', this); // Added 21.02.2011
						this.invokeEvent('success', this);
					} else {
						this.invokeEvent.bind(this, 'beforeSuccess', this).delay(1, this); 	 // Added 21.02.2011
						this.invokeEvent.bind(this, 'success', this).delay(1, this); 
					}
					this.callChain(this);
					this.timeEnd			= $now();
					this.time				= this.timeEnd - this.timeStart;
					this.isSuccess			= true; 
					this.isRunning 			= false;
					this.__startedLoading 	= false;
				}
				return this;
		}
	},
	
	initialize	: function() { 
		var args = $A(arguments), options;
		
		// First argument is defined and is a string
		// It can be either the method or the url
		// E.g 
		// 1. new Ajax('myAjax.php').send('lala=1');
		// 2. new Ajax('post', {url : 'myAjax.php'});
		// 3. new Ajax().send('lala=1').chain(...) 
		if(args[0].toLowerCase) {
			options 				= args[1] || {};
			
			if ( ['get', 'post'].contains(args[0].toLowerCase()) ) {
				options['method']	= args[0];
			} else {
				options['url']		= args[0];
			}
			
		} else {
			 options 				= args[0];				
		}

		this.options['parameters'] 	= {};
		this.options['headers']		= {};
		this.times					= 0;
		
		this.setXHR();
		if( !this.XHR ) return invokeEvent( 'error', "Can't set up XHR" );				
		return this.set(options);
	}
});
Ajax.implement( new Chain, new Events, new Options );
XHR = Ajax; // Alias :)

// Trap escapes on Gecko browsers
window.gecko && window.addEventListener( 'keypress', function(event) {					  
	if(event.keyCode == 27 && Ajax && Ajax.isRunning) {
		event.preventDefault();	
	}
}, false);


Ajax.RPC		= Ajax.extend({
	initialize	: function() {
		var options 				= this.getRPCOptions.apply(this, arguments);
		options.url					= Ajax.RPC.proxy;

		return this.parent( 'post', options);
	},
	
	// Notes on passing strings and structs
	// struct = 'key' => value
	getRPCOptions	: function(){
		var args = arguments, rpcQueryString = '', rpcMethod = '', options;
		
		if( $typeOf(args[0]) == 'array' ) {
			args = args[0];
		} else if($typeOf( args[0] ) == 'object' ){
			return args[0];
		} else {
			args = $A(arguments);
		}
			
		options = args.pop();
		if( $typeOf( options ) != 'object' ) {
			options  = {};
			args	 = $A(arguments);
		}
		options.parameters 	= options.parameters || new Abstract();
		rpcMethod 			= args.shift();
		
		args.each( function( value, index ) {
			// ADDED: 15.07.2008
			if(value === null) {
				return;	
			}
			
			
			
			if( $typeOf( value ) == 'string' ) 	{
				value 			= value.trim();

				// FIXED: 12.2.2010
				// OUF!
				value 			= value.replace(/\n/gi, '\\n')
			
				// Step 0: We need to escape \" with \\" first
				value = value.replace(/\\"/gi, '\\\\"');
				// Step1: Escape " (/") in order for eval to work properly
				// TODO: Figure out if it' really needed outside xmlrpc stuff that is  
				//if(!indexOf('xmlrpc_struct'))
				if(value.indexOf('xmlrpc_struct') === -1) {
					value = value.replace(/\"/gi, '\\\"');	
				}
				
				// Step2: Escape last trailing slash / (//) in order for eval to work properly
				value			= value.replace(/\\$/g, '\\\\\\\\');	
				
				// Step3: The char(9) fix
				value = value.replace(/\\'$/, '\\'+String.fromCharCode(9)+'\'');
				
				//alert(value)
				if(value.match(/^\(float\)/)) {
					rpcQueryString += value.replace(/\(float\)/, '');
				} else {
					rpcQueryString	+= '"' + value + '"';
				}
				
				
			} else if($typeOf(value) == 'array') {
				rpcQueryString += 'array(' + value.join(',').replace(/\"/gi, '\\\"') + ')';
			} else rpcQueryString += value;

			if( index < args.length -1 ) rpcQueryString += ', ';
			
		});
		

		
		// Hold it up (I hate typos)
		this.rpcMethod = this.rpcMethod = rpcMethod;
		$extend(options.parameters, {
			rpcQuery 	: '"' + rpcMethod + '", ' + rpcQueryString,
			port		: this.options.port 	|| null,
			host		: this.options.host 	|| null,
			api			: this.options.api 		|| null,
			ttl			: this.options.ttl		|| null,
			charset		: (document.characterSet).toLowerCase()
		});
		
		return options;
	},
	
	send		: function() {
    	return this.parent(arguments.length ? this.getRPCOptions.apply(this, arguments).parameters : this.parameters);
	}
});
Ajax.RPC.proxy 		= '/gateway.php';


// The JSONP version
Ajax.RPC.JSONP		= Ajax.RPC.extend({
	send	: function() {
		var self 	= this;
		var obj		= arguments.length ? this.getRPCOptions.apply(this, arguments).parameters : this.parameters;	
		this.jsonP 	= this.jsonP || ( new JSONP( '', {
			'cache'		: this.options.cache,
			'callback'	: 'coreCallback',
			'onSuccess'	: function(json) {
				self.callChain(json);
				self.invokeEvent('success', json);
			}
		}));

		this.jsonP.send( 'http://c.pathfinder.gr/gateway.php?rpcQuery=' + encodeURIComponent( obj.rpcQuery ) );	

		return this;
	}
});

// A static method for Ajax.RPC used to PROPERLY parse 
// xmlrpc_rpc(array()) related rpc values
// CHANGED: 08/08/2011 to support boolean and numbers

/*
	requestX.push( Ajax.RPC.getStruct({
				'fid'	: parseInt(id),
				'value'	: value


			}));
*/
Ajax.RPC.getStruct 	= function(obj) {
	var string = 'xmlrpc_struct(array(', ar = [];
	each(obj, function( value, key ) {
		// For nested structing
		var valueIsStruct = value && value['indexOf'] && value.indexOf('xmlrpc_struct') === 0 ? true : false;
	
		var val =  ('\'' + key + '\' => '+(typeof(value) !== 'number' && typeof(value) !== 'boolean' && !valueIsStruct ?'\'' : '')) + 
		(typeof(value) !== 'number' && typeof(value) !== 'boolean' && !valueIsStruct ? value.replace(/\\/gi, '\\\\').
		replace( /'/g, "\\'") + '\'' : value);

		// Trailing slash fix by appending a char(9). (  /' => /9' )
		// char(9) is a horiontal tab char - practically not used
		// TODO: Make it better.
		// NOTE: This is due to an issue xmlrpc_parser has.
		val = val.replace(/\\'$/, '\\'+String.fromCharCode(9)+'\'');
		ar.push(val)
	});
	
	string += ar.join( ', ' ) + '))';
	
	return string;
}

/*
Script: Color.js
	Contains the Color class.

License:
	MIT-style license.
*/

/*
Class: Color
	Creates a new Color Object, which is an array with some color specific methods.
Arguments:
	color - the hex, the RGB array or the HSB array of the color to create. For HSB colors, you need to specify the second argument.
	type - a string representing the type of the color to create. needs to be specified if you intend to create the color with HSB values, or an array of HEX values. Can be 'rgb', 'hsb' or 'hex'.

Example:
	(start code)
	var black = new Color('#000');
	var purple = new Color([255,0,255]);
	// mix black with white and purple, each time at 10% of the new color
	var darkpurple = black.mix('#fff', purple, 10);
	$('myDiv').setStyle('background-color', darkpurple);
	(end)
*/

var Color = new Class({

	initialize: function(color, type){
		var rgb, hsb;
		type = type || (color.push ? 'rgb' : 'hex');
		switch(type){
			case 'rgb':
				rgb = color;
				hsb = rgb.rgbToHsb();
				break;
			case 'hsb':
				rgb = color.hsbToRgb();
				hsb = color;
				break;
			default:
				rgb = color.hexToRgb(true);
				hsb = rgb.rgbToHsb();
		}
		rgb.hsb = hsb;
		rgb.hex = rgb.rgbToHex();
		return $extend(rgb, Color.prototype);
	},

	/*
	Property: mix
		Mixes two or more colors with the Color.
		
	Arguments:
		color - a color to mix. you can use as arguments how many colors as you want to mix with the original one.
		alpha - if you use a number as the last argument, it will be threated as the amount of the color to mix.
	*/

	mix: function(){
		var colors = $A(arguments);
		var alpha = ($typeOf(colors[colors.length - 1]) == 'number') ? colors.pop() : 50;
		var rgb = this.copy();
		colors.each(function(color){
			color = new Color(color);
			for (var i = 0; i < 3; i++) rgb[i] = Math.round((rgb[i] / 100 * (100 - alpha)) + (color[i] / 100 * alpha));
		});
		return new Color(rgb, 'rgb');
	},

	/*
	Property: invert
		Inverts the Color.
	*/

	invert: function(){
		return new Color(this.map(function(value){
			return 255 - value;
		}));
	},

	/*
	Property: setHue
		Modifies the hue of the Color, and returns a new one.
	
	Arguments:
		value - the hue to set
	*/

	setHue: function(value){
		return new Color([value, this.hsb[1], this.hsb[2]], 'hsb');
	},
	
	getHue: function() {
		return this.hsb[0];
	},

	/*
	Property: setSaturation
		Changes the saturation of the Color, and returns a new one.
	
	Arguments:
		percent - the percentage of the saturation to set
	*/

	setSaturation: function(percent){
		return new Color([this.hsb[0], percent, this.hsb[2]], 'hsb');
	},

	getSaturation: function() {
		return this.hsb[1];	
	},

	/*
	Property: setBrightness
		Changes the brightness of the Color, and returns a new one.
	
	Arguments:
		percent - the percentage of the brightness to set
	*/

	setBrightness: function(percent){
		return new Color([this.hsb[0], this.hsb[1], percent], 'hsb');
	},
	
	getBrightness: function() {
		return this.hsb[2];	
	},
	
	getGreen: function(){
		return this.rbg[0];	
	},
	
	getBlue: function(){
		return this.rgb[1];	
	},
	
	getRed: function() {
		return this.rgb[2];	
	}
	

});

/* Section: Utility Functions */

/*
Function: $RGB
	Shortcut to create a new color, based on red, green, blue values.

Arguments:
	r - (integer) red value (0-255)
	g - (integer) green value (0-255)
	b - (integer) blue value (0-255)

*/

function $RGB(r, g, b){
	return new Color([r, g, b], 'rgb');
};

/*
Function: $HSB
	Shortcut to create a new color, based on hue, saturation, brightness values.

Arguments:
	h - (integer) hue value (0-100)
	s - (integer) saturation value (0-100)
	b - (integer) brightness value (0-100)
*/

function $HSB(h, s, b){
	return new Color([h, s, b], 'hsb');
};

/*
Class: Array
	A collection of The Array Object prototype methods.
*/

Array.extend({
	
	/*
	Property: rgbToHsb
		Converts a RGB array to an HSB array.

	Returns:
		the HSB array.
	*/

	rgbToHsb: function(){
		var red = this[0], green = this[1], blue = this[2];
		var hue, saturation, brightness;
		var max = Math.max(red, green, blue), min = Math.min(red, green, blue);
		var delta = max - min;
		brightness = max / 255;
		saturation = (max != 0) ? delta / max : 0;
		if (saturation == 0){
			hue = 0;
		} else {
			var rr = (max - red) / delta;
			var gr = (max - green) / delta;
			var br = (max - blue) / delta;
			if (red == max) hue = br - gr;
			else if (green == max) hue = 2 + rr - br;
			else hue = 4 + gr - rr;
			hue /= 6;
			if (hue < 0) hue++;
		}
		return [Math.round(hue * 360), Math.round(saturation * 100), Math.round(brightness * 100)];
	},

	/*
	Property: hsbToRgb
		Converts an HSB array to an RGB array.

	Returns:
		the RGB array.
	*/

	hsbToRgb: function(){
		var br = Math.round(this[2] / 100 * 255);
		if (this[1] == 0){
			return [br, br, br];
		} else {
			var hue = this[0] % 360;
			var f = hue % 60;
			var p = Math.round((this[2] * (100 - this[1])) / 10000 * 255);
			var q = Math.round((this[2] * (6000 - this[1] * f)) / 600000 * 255);
			var t = Math.round((this[2] * (6000 - this[1] * (60 - f))) / 600000 * 255);
			switch(Math.floor(hue / 60)){
				case 0: return [br, t, p];
				case 1: return [q, br, p];
				case 2: return [p, br, t];
				case 3: return [p, q, br];
				case 4: return [t, p, br];
				case 5: return [br, p, q];
			}
		}
		return false;
	},
	
	/*
	Property: rgbToHex
		see <String.rgbToHex>, but as an array method.
	*/

	rgbToHex: function(array){
		if (this.length < 3) return false;
		if (this.length == 4 && this[3] == 0 && !array) return 'transparent';
		var hex = [];
		for (var i = 0; i < 3; i++){
			var bit = (this[i] - 0).toString(16);
			hex.push((bit.length == 1) ? '0' + bit : bit);
		}
		return array ? hex : '#' + hex.join('');
	},

	/*
	Property: hexToRgb
		same as <String.hexToRgb>, but as an array method.
	*/

	hexToRgb: function(array){
		if (this.length != 3) return false;
		var rgb = [];
		for (var i = 0; i < 3; i++){
			rgb.push(parseInt((this[i].length == 1) ? this[i] + this[i] : this[i], 16));
		}
		return array ? rgb : 'rgb(' + rgb.join(',') + ')';
	}	

});


 

/*
Script: Fx.Transitions.js
	Effects transitions, to be used with all the effects.

License:
	MIT-style license.

Credits:
	Easing Equations by Robert Penner, <http://www.robertpenner.com/easing/>, modified & optimized to be used with mootools.
*/

/*
Class: Fx.Transitions
	A collection of tweening transitions for use with the <Fx.Base> classes.

Example:
	>//Elastic.easeOut with default values:
	>new Fx.Style('margin', {transition: Fx.Transitions.Elastic.easeOut});
	>//Elastic.easeOut with user-defined value for elasticity.
	> var myTransition = new Fx.Transition(Fx.Transitions.Elastic, 3);
	>new Fx.Style('margin', {transition: myTransition.easeOut});

See also:
	http://www.robertpenner.com/easing/
*/

Fx.Transition = function(transition, params){
	params = params || [];
	if ($typeOf(params) != 'array') params = [params];
	return $extend(transition, {
		easeIn: function(pos){
			return transition(pos, params);
		},
		easeOut: function(pos){
			return 1 - transition(1 - pos, params);
		},
		easeInOut: function(pos){
			return (pos <= 0.5) ? transition(2 * pos, params) / 2 : (2 - transition(2 * (1 - pos), params)) / 2;
		}
	});
};

Fx.Transitions = new Abstract({

	/*
	Property: linear
		displays a linear transition.

	Graph:
		(see Linear.png)
	*/

	Linear: function(p){
		return p;
	}

});

Fx.Transitions.extend = function(transitions){
	for (var transition in transitions){
		Fx.Transitions[transition] = new Fx.Transition(transitions[transition]);
		/*compatibility*/
		Fx.Transitions.compat(transition);
		/*end compatibility*/
	}
};

/*compatibility*/

Fx.Transitions.compat = function(transition){
	['In', 'Out', 'InOut'].each(function(easeType){
		Fx.Transitions[transition.toLowerCase() + easeType] = Fx.Transitions[transition]['ease' + easeType];
	});
};

/*end compatibility*/

Fx.Transitions.extend({

	/*
	Property: Quad
		displays a quadratic transition. Must be used as Quad.easeIn or Quad.easeOut or Quad.easeInOut

	Graph:
		(see Quad.png)
	*/

	//auto generated

	/*
	Property: Cubic
		displays a cubicular transition. Must be used as Cubic.easeIn or Cubic.easeOut or Cubic.easeInOut

	Graph:
		(see Cubic.png)
	*/

	//auto generated

	/*
	Property: Quart
		displays a quartetic transition. Must be used as Quart.easeIn or Quart.easeOut or Quart.easeInOut

	Graph:
		(see Quart.png)
	*/

	//auto generated

	/*
	Property: Quint
		displays a quintic transition. Must be used as Quint.easeIn or Quint.easeOut or Quint.easeInOut

	Graph:
		(see Quint.png)
	*/

	//auto generated

	/*
	Property: Pow
		Used to generate Quad, Cubic, Quart and Quint.
		By default is p^6.

	Graph:
		(see Pow.png)
	*/

	Pow: function(p, x){
		return Math.pow(p, x[0] || 6);
	},

	/*
	Property: Expo
		displays a exponential transition. Must be used as Expo.easeIn or Expo.easeOut or Expo.easeInOut

	Graph:
		(see Expo.png)
	*/

	Expo: function(p){
		return Math.pow(2, 8 * (p - 1));
	},

	/*
	Property: Circ
		displays a circular transition. Must be used as Circ.easeIn or Circ.easeOut or Circ.easeInOut

	Graph:
		(see Circ.png)
	*/

	Circ: function(p){
		return 1 - Math.sin(Math.acos(p));
	},


	/*
	Property: Sine
		displays a sineousidal transition. Must be used as Sine.easeIn or Sine.easeOut or Sine.easeInOut

	Graph:
		(see Sine.png)
	*/

	Sine: function(p){
		return 1 - Math.sin((1 - p) * Math.PI / 2);
	},

	/*
	Property: Back
		makes the transition go back, then all forth. Must be used as Back.easeIn or Back.easeOut or Back.easeInOut

	Graph:
		(see Back.png)
	*/

	Back: function(p, x){
		x = x[0] || 1.618;
		return Math.pow(p, 2) * ((x + 1) * p - x);
	},

	/*
	Property: Bounce
		makes the transition bouncy. Must be used as Bounce.easeIn or Bounce.easeOut or Bounce.easeInOut

	Graph:
		(see Bounce.png)
	*/

	Bounce: function(p){
		var value;
		for (var a = 0, b = 1; 1; a += b, b /= 2){
			if (p >= (7 - 4 * a) / 11){
				value = - Math.pow((11 - 6 * a - 11 * p) / 4, 2) + b * b;
				break;
			}
		}
		return value;
	},

	/*
	Property: Elastic
		Elastic curve. Must be used as Elastic.easeIn or Elastic.easeOut or Elastic.easeInOut

	Graph:
		(see Elastic.png)
	*/

	Elastic: function(p, x){
		return Math.pow(2, 10 * --p) * Math.cos(20 * p * Math.PI * (x[0] || 1) / 3);
	}

});

['Quad', 'Cubic', 'Quart', 'Quint'].each(function(transition, i){
	Fx.Transitions[transition] = new Fx.Transition(function(p){
		return Math.pow(p, [i + 2]);
	});
	
	/*compatibility*/
	Fx.Transitions.compat(transition);
	/*end compatibility*/
});

/*
Script: Drag.Base.js
	Contains <Drag.Base>, <Element.makeResizable>

License:
	MIT-style license.
*/


/*
Class: Drag.Base
	Modify two css properties of an element based on the position of the mouse.
	
Note:
	Drag.Base requires an XHTML doctype.

Arguments:
	el - the $(element) to apply the transformations to.
	options - optional. The options object.

Options:
	handle - the $(element) to act as the handle for the draggable element. defaults to the $(element) itself.
	modifiers - an object. see Modifiers Below.
	limit - an object, see Limit below.
	grid - optional, distance in px for snap-to-grid dragging
	snap - optional, the distance you have to drag before the element starts to respond to the drag. defaults to false

	modifiers:
		x - string, the style you want to modify when the mouse moves in an horizontal direction. defaults to 'left'
		y - string, the style you want to modify when the mouse moves in a vertical direction. defaults to 'top'

	limit:
		x - array with start and end limit relative to modifiers.x
		y - array with start and end limit relative to modifiers.y
		
Events:
	onStart - optional, function to execute when the user starts to drag (on mousedown);
	onComplete - optional, function to execute when the user completes the drag.
	onDrag - optional, function to execute at every step of the drag
*/

Drag = new Class({

	options: {
		handle: false,
		unit: 'px',
		onStart: Class.empty,
		onBeforeStart: Class.empty,
		onComplete: Class.empty,
		onSnap: Class.empty,
		onDrag: Class.empty,
		limit: false,
		modifiers: {x: 'left', y: 'top'},
		grid: false,
		snap: 6
	},

	initialize: function(el, options){
		this.setOptions(options);
		this.element = $(el);
		this.handle = $(this.options.handle) || this.element;
		this.mouse = {'now': {}, 'pos': {}};
		this.value = {'start': {}, 'now': {}};
		this.bound = {
			'start': this.start.bind(this),
			'check': this.check.bind(this),
			'drag': this.drag.bind(this),
			'stop': this.stop.bind(this)
		};
		this.attach();
		if (this.options.initialize) this.options.initialize.call(this);
	},

	attach: function(){
		this.handle.addEvent('mousedown', this.bound.start);
		return this;
	},

	detach: function(){
		this.handle.removeEvent('mousedown', this.bound.start);
		return this;
	},

	start: function(event){
		this.invokeEvent('beforestart', this.element);
		this.mouse.start = event.page;
		var limit = this.options.limit;
		this.limit = {'x': [], 'y': []};
		for (var z in this.options.modifiers){
			if (!this.options.modifiers[z]) continue;
			this.value.now[z] = this.element.getStyle(this.options.modifiers[z]).toInt();	
			this.mouse.pos[z] = event.page[z] - this.value.now[z];
			if (limit && limit[z]){
				for (var i = 0; i < 2; i++){
					if ($is(limit[z][i])) {
						this.limit[z][i] = ($typeOf(limit[z][i]) == 'function') ? limit[z][i]() : limit[z][i];
					}
				}
			}
		}
		if ($typeOf(this.options.grid) == 'number') this.options.grid = {'x': this.options.grid, 'y': this.options.grid};
		$(document).addEvent('mousemove', this.bound.check);
		$(document).addEvent('mouseup', this.bound.stop);
		this.invokeEvent('start', this.element);
		event.stop();
	},

	// Re do limits and stuff
	reCalc	: function() {
		var limit = this.options.limit;
	
		this.limit = {'x': [], 'y': []};
		for (var z in this.options.modifiers){
			if (!this.options.modifiers[z]) continue;
			this.value.now[z] = this.element.getStyle(this.options.modifiers[z]).toInt();	
			//this.mouse.pos[z] = event.page[z] - this.value.now[z];
			if (limit && limit[z]){
				for (var i = 0; i < 2; i++){
					if ($is(limit[z][i])) {
						this.limit[z][i] = ($typeOf(limit[z][i]) == 'function') ? limit[z][i]() : limit[z][i];
					}
				}
			}
		}
		
		
		
		if ($typeOf(this.options.grid) == 'number') this.options.grid = {'x': this.options.grid, 'y': this.options.grid};
		
		return this;		
	},

	check: function(event){
		var distance = Math.round(Math.sqrt(Math.pow(event.page.x - this.mouse.start.x, 2) + Math.pow(event.page.y - this.mouse.start.y, 2)));
		if (distance > this.options.snap){
			$(document).removeEvent('mousemove', this.bound.check);
			$(document).addEvent('mousemove', this.bound.drag);
			this.drag(event);
			this.invokeEvent('snap', this.element);
		}
		event.stop();
	},

	drag: function(event){
		this.out		 = false;
		this.isDragging = true;
		this.mouse.now = event.page;
		for (var z in this.options.modifiers){
			if (!this.options.modifiers[z]) continue;
			this.value.now[z] = this.mouse.now[z] - this.mouse.pos[z];
			if (this.limit[z]){
				if ($is(this.limit[z][1]) && (this.value.now[z] > this.limit[z][1])){
					this.value.now[z] = this.limit[z][1];
					this.out = true;
				} else if ($is(this.limit[z][0]) && (this.value.now[z] < this.limit[z][0])){
					this.value.now[z] = this.limit[z][0];
					this.out = true;
				}
			}
			if (this.options.grid[z]) this.value.now[z] -= (this.value.now[z] % this.options.grid[z]);
			this.element.setStyle(this.options.modifiers[z], this.value.now[z] + this.options.unit);
		}
		
		this.invokeEvent('drag', event, this);
		event.stop();
	},

	stop: function(){
		this.isDragging = false;
		document.removeEvent('mousemove', this.bound.check);
		document.removeEvent('mousemove', this.bound.drag);
		document.removeEvent('mouseup', this.bound.stop);
		this.invokeEvent('complete', this.element);
		return this;
	}

});

Drag.implement(new Events, new Options);

/*
Class: Element
	Custom class to allow all of its methods to be used with any DOM element via the dollar function <$>.
*/

Element.extend({

	/*
	Property: makeResizable
		Makes an element resizable (by dragging) with the supplied options.

	Arguments:
		options - see <Drag.Base> for acceptable options.
	*/

	makeResizable: function(options){
		return new Drag(this, $merge({modifiers: {x: 'width', y: 'height'}}, options));
	}

});

/*
Script: Drag.Move.js
	Contains <Drag.Move>, <Element.makeDraggable>

License:
	MIT-style license.
*/

/*
Class: Drag.Move
	Extends <Drag.Base>, has additional functionality for dragging an element, support snapping and droppables.
	Drag.move supports either position absolute or relative. If no position is found, absolute will be set.
	Inherits methods, properties, options and events from <Drag.Base>.

Note:
	Drag.Move requires an XHTML doctype.

Arguments:
	el - the $(element) to apply the drag to.
	options - optional. see Options below.

Options:
	all the drag.Base options, plus:
	container - an element, will fill automatically limiting options based on the $(element) size and position. defaults to false (no limiting)
	droppables - an array of elements you can drop your draggable to.
	overflown - an array of nested scrolling containers, see Element::getPosition
*/

Drag.Move = Drag.extend({

	options: {
		'droppables'	: [],
		'container'	: false,
		'overflown'	: []
	},

	initialize: function(el, options){
		this.setOptions(options);
		this.element = $(el);
		this.droppables = $$(this.options.droppables);
		this.container = $(this.options.container);
		this.elementSize = {
			width : this.element.offsetWidth,
			height : this.element.offsetHeight
		};
		this.position = {'element': this.element.getStyle('position'), 'container': false};
		if (this.container) this.position.container = this.container.getStyle('position');
		if (!['relative', 'absolute', 'fixed'].contains(this.position.element)) this.position.element = 'absolute';

		var pos		= this.element.getPosition();
		
		// Check if fixed or absolute positioned already 
		// container that is
		// ADDED: 22.June.011
		var parent = this.element.getParent(), offseParent;
		if(!this.isFixedOrAbs) {
			while(parent && !this.isFixedOrAbs) {
					if(['absolute', 'fixed'].contains( parent.getStyle('position')) ) {
					this.isFixedOrAbs 	= true;
					offseParent 		= parent;
					break;
				}
				parent = parent.getParent();
			}
		}
		if(this.isFixedOrAbs) {
			pos	= {
				x	: this.element.offsetLeft - this.element.parentNode.scrollLeft,
				y	: this.element.offsetTop - this.element.parentNode.scrollTop
			}
		}
		
		var left	= pos.x.toInt();
		var top		= pos.y.toInt();

		if (this.element.position == 'absolute' && !['relative', 'absolute', 'fixed'].contains(this.position.container)){
			top		= $is(top) ? top 	: this.element.getTop(this.options.overflown);
			left 	= $is(left) ? left 	: this.element.getLeft(this.options.overflown);
		} else {
			top = $is(top) 		? top 	: 0;
			left = $is(left) 	? left 	: 0;
		}
		this.element.setStyles({'top': top, 'left': left, 'position': this.position.element});
		this.parent(this.element);
	},

	start: function(event){
		this.overed = null;
		if (this.container){
			var cont = this.container.getCoordinates();
			var el = this.element.getCoordinates();
			if (this.position.element == 'absolute' && !['relative', 'absolute', 'fixed'].contains(this.position.container)){
				this.options.limit = {
					'x': [cont.left, cont.right - el.width],
					'y': [cont.top, cont.bottom - el.height]
				};
			} else {
				this.options.limit = {
					'y': [0, cont.height - el.height],
					'x': [0, cont.width - el.width]
				};
			}
		}
		this.parent(event);
	},

	drag: function(event){
		this.parent(event);
		//var overed = this.out ? false : this.droppables.filter(this.checkAgainst, this).getLast();
		var overed = this.out ? false : this.droppables.filter(this.checkAgainst, this)[0];
		if (this.overed != overed){
			if (this.overed) this.overed.invokeEvent('out', this.element, this.overed, event, this);
			this.overed = overed ? overed.invokeEvent('over', this.element, overed, event, this) : null;
		}
		return this;
	},

	checkAgainst: function(el){
		var container = el, now = this.mouse.now;
		el = el.getCoordinates(this.options.overflown);
		
		// TODO: fix this better
		var elementWidth = 0, elementHeight = 0;
		
		if(this.options.droppablesCenter) {
			elementWidth 	= this.elementSize.width/2;
			elementHeight 	= this.elementSize.height/2;
			now				= this.value.now;
		}

		var left 	= now.x +  elementWidth * 2 >= el.left  + elementWidth;
		var right 	= now.x < el.right  - elementWidth;
		var top		= now.y + elementHeight * 2 > el.top 	+ elementHeight;
		var bottom  = now.y < el.bottom - elementHeight;
		
		if (this.options.droppablesAxis) {
			return this.options.droppablesAxis == 'x' ? (left && right) : (bottom && top);
		} else {
			return left && right && top && bottom;
		}
	},

	stop: function(event){
		this.parent();		
		if (this.overed && !this.out) this.overed.invokeEvent('dropped', this.element, event);
		else this.element.invokeEvent('emptydrop', this);
		return this;
	}

});

/*
Class: Element
	Custom class to allow all of its methods to be used with any DOM element via the dollar function <$>.
*/

Element.extend({

	/*
	Property: makeDraggable
		Makes an element draggable with the supplied options.

	Arguments:
		options - see <Drag.Move> and <Drag.Base> for acceptable options.
	*/

	makeDraggable: function(options){
		return new Drag.Move(this, options);
	}

});




/*
Class: Slider
	Creates a slider with two elements: a knob and a container. Returns the values.
	
Note:
	The Slider requires an XHTML doctype.

Arguments:
	element - the knob container
	knob - the handle
	options - see Options below

Options:
	steps - the number of steps for your slider.
	mode - either 'horizontal' or 'vertical'. defaults to horizontal.
	offset - relative offset for knob position. default to 0.
	
Events:
	change - a function to fire when the value changes.
	onComplete - a function to fire when you're done dragging.
	onTick - optionally, you can alter the onTick behavior, for example displaying an effect of the knob moving to the desired position.
		Passes as parameter the new position.
*/

var Slider = new Class({

	options: {
		'tick'	: function(pos) {
			this.knob.setStyle(this.p, pos);
			return this;
		},
		//onComplete: Class.empty,
		mode: 'horizontal',
		steps: 100,
		offset: 0
	},

	initialize: function(el, knob, options){
		this.element = $(el);
		this.knob = $(knob);
		this.setOptions(options);
		this.previousChange = -1;
		this.previousEnd = -1;
		this.step = -1;
		this.element.addEvent('mousedown', this.clickedElement.bind(this));
		var mod, offset;
		switch(this.options.mode){
			case 'horizontal':
				this.z = 'x';
				this.p = 'left';
				mod = {'x': 'left', 'y': false};
				offset = 'offsetWidth';
				
				this.knobOffset	= this.knob.offsetWidth / 2;
				
				break;
			case 'vertical':
				this.z = 'y';
				this.p = 'top';
				mod = {'x': false, 'y': 'top'};
				offset = 'offsetHeight';
				
				this.knobOffset	= this.knob.offsetHeight / 2;
		}
		//this.max = this.element[offset] -  this.knob[offset]  + (this.options.offset * 2);
		
		this.max = this.element[offset] -  this.knobOffset  + (this.options.offset * 2);
		
		
		//this.knobOffset = this.knob[offset];
		this.getPos 	= function() {
			return this.element['get' + this.p.capitalize()]() + this.element.offsetParent.getStyle('border-'+this.p+'-width').toInt();
		}.bind(this);
		this.knob.setStyle('position', 'relative').setStyle(this.p, - this.options.offset);
		var lim = {};
		lim[this.z] = [-  this.knob[offset]/2 + (this.options.offset), this.max - this.options.offset];
		this.drag = new Drag(this.knob, {
			limit: lim,
			modifiers: mod,
			snap: 0,
			onStart: function(){
				this.invokeEvent('start');
				this.draggedKnob();
			}.bind(this),
			onDrag: function(){
				this.draggedKnob();
			}.bind(this),
			onComplete: function(){
				this.draggedKnob();
				this.end();
			}.bind(this)
		});
		if (this.options.initialize) this.options.initialize.call(this);
	},
	
	reCalc	: function() {
		var lim 		= {};
		var offset		= 'offset'  + ( this.options.mode === 'vertical' ? 'Height' : 'Width' );
		this.max = this.element[offset] -  this.knobOffset  + (this.options.offset * 2);

		lim[this.options.mode === 'vertical' ? 'y' : 'x'] 	= [- this.knob[offset]/2 + (this.options.offset), this.max - this.options.offset];


		this.drag.setOptions( { 'limit' : lim } );
		
		this.drag.reCalc();
		
		return this;
	},
	

	/*
	Property: set
		The slider will get the step you pass.

	Arguments:
		step - one integer
	*/

	set: function(step, force){
		this.step = step.limit(0, this.options.steps);
		if(!force) {
			this.checkStep();
		}
		
		this.end();
		//f(this.step);
	//	this.options.tick(this.toPosition(this.step));
	//	this.toPosition(this.step);
		//alert(this.toPosition(this.step));
		//alert(this.toPosition(this.step));
		return this.options.tick( this.toPosition(this.step));
		
		if(!force) {
			return this.invokeEvent( 'change', this.step);
		} else {
			return this;
		}
		//this.invokeEvent('tick', this.toPosition(this.step));
		//return this;
	},

	clickedElement: function(event){
		var position = event.page[this.z] - this.getPos() - this.knobOffset / 2; 
		
		// GP verion - work on move
		position = position.limit(-this.options.offset, this.max -this.options.offset);
		
		this.step = this.toStep(position);		
		this.options.tick( position).invokeEvent( 'change', this.step );
		this.drag.start(  event);
		this.checkStep();
		this.end();
		return this;
		
		
		/* Older version
		var position = event.page[this.z] - this.getPos() - this.half;
		position = position.limit(-this.options.offset, this.max -this.options.offset);
		this.step = this.toStep(position);		
		this.checkStep();
		this.end();
		
		this.options.tick( position).invokeEvent( 'tick', position );
		*/
	},

	draggedKnob: function(){
		this.step = this.toStep(this.drag.value.now[this.z]);
		this.checkStep();
	},

	checkStep: function(){
		if (this.previousChange != this.step){
			this.previousChange = this.step;
			this.invokeEvent('change', this.step);
		}
	},

	end: function(){
		if (this.previousEnd !== this.step){
			this.previousEnd = this.step;
			this.invokeEvent('complete', this.step + '');
		}
	},

	toStep: function(position){
		return Math.round((position + this.options.offset + this.knobOffset/2) / (this.max + this.knobOffset/2) * this.options.steps);
	},

	toPosition: function(step){
		return -this.knobOffset/2 + (this.max  -  this.options.offset + this.knobOffset/2) * step / this.options.steps;
	}

});
Slider.implement(new Events, new Options);



// ================================================================================
// Preloader Class
// ================================================================================
var Preloader = new Class({	
	options		: {
		asynchronous		: true,
		returnElement 		: false
	},
	
	initialize : function(elements, options){
		this.elements = elements.push ? elements : [elements];
		this.setOptions(options);
		this.total	 	= this.elements.length;
		this.current 	= this.index = this.percent = 0;

		if (!this.options.asynchronous) {
			this.next();
		} else {
			this.elements.each(this.next, this);
		}
		return this;
	},
	
	next		: function() {
		if(this.index === this.total) {
			return this;	
		}
		this.index++;
		
		var item		= this.elements[this.index-1], element;
		var extension	= item.match(/\.(js|css|gif|png|jpeg|jpg|tiff|bmp)(\??|$)/i);
		extension		= extension ? extension[0].replace(/\?/, '') : '';

		switch(extension) {
			default:
			case '.js'	: 
				element = this.script(item, this.index);
				break;
			case '.css'	: 
				element = this.css(item, this.index);
				break;				
			case '.png':
			case '.jpg':
			case '.gif':
			case '.jpeg': 
				element = this.image(item, this.index);
				break;
			
		}
		
		return this.invokeEvent('start', element, this.index);
	},
	
	progress	: function(item, index, event) {
		var ret 	= this.options.returnElement ? item : {};
		// If we use options.returnElement DO remove it later on

		this.current++;
		this.percent = (100*this.current)/this.total;
		
		if(item.nodeName === 'IMG') {
			if(window.webkit419) {
				var size = {
					width : item.width || item.scrollWidth,
					height: item.height || item.scrollHeight
				};
				item.remove();
			}
			
			if(!this.options.returnElement) {
				ret = {
					width 	: item.width,
					height 	: item.height
				}
			}
			
		}

		// TODO: Is this (below) necessary?
		item.removeEvents();
		
		if(!this.options.returnElement) {
			Garbage.discard(item); 
			item = null;
		}
		
		(function() {
			this.invokeEvent('progress', ret, index, this.elements[index-1], event);
		}).delay(0, this);
		
		
		if (this.current == this.total)  {
			(function(){ this.invokeEvent('complete', ret, index,this.elements[index-1], event).callChain(ret, index, this.elements[index-1],event);}).delay( 0, this);
			return this;
		} else {
			return !this.options.asynchronous ? this.next() : this;
		}
	},
	
	error 	: function(item, index, event) {
		this.invokeEvent('error', item, index, event);
		return this.progress(item, index,event);
	},
	
	image	: function(item, index) {
		var img = $C('img',{
			 src	: item,
			 alt 	: 'core preloaded image'
		});
		
		// Safari < 3 has issues with image.width and image.height
		// TODO: Find a better way to do it
		if(window.webkit419) {
			img.setStyles({
				'position' 		: 'absolute',
				'visibility' 	: 'hidden',
				'top'			: -50000,
				'left'			: -50000
			}).injectIn(document.body);
		}
		
		img.addEvents({
			'load' 	: this.progress.bind(this, img, index),
			'abort' : this.error.bind(this, img, index), 
			'error' : this.error.bind(this, img, index)
		});
		
		// If already loaded
		// FIXME: Safari < 3 wont work with this
		if(img.width && img.height && (window.ie ? img.readyState == 'complete' : 1)) {
			// FIXME: add a timeout here??
			//img.invokeEvent.bind(img, 'load', img, index).delay(0);
			img.invokeEvent.delay(0, img, 'load', img, index);
		}
		return img;
	},
	
	script	: function(item, index) {
		var script = $C('script', {'src': item}).injectIn(document.head);

		script.addEvents({
			'load'				: this.progress.bind(this, script, index)
		});

		// FIXME: This seems buggy
		script['onreadystatechange'] = function(){ 
			// Must be a bug here | never invokes complete :(
			if ( /loaded|complete/.test(this.readyState)) {
				this.invokeEvent('load');
			}
		}.bind(script);
		
		return script;
	},

	// Not properly working [TODO] Fix it
	css		: function(item, index) {
		var css = $C('style', { 'type' : 'text/css', 'media' : 'all', 'src': item}).injectIn(document.head);

		css.addEvents({
			'load'				: this.progress.bind(this, css, index)
		});
		
		// FIXME: this could be buggy too
		css['onreadystatechange'] = function(){ 
			if (/loaded|complete/.test(this.readyState)) {
				this.invokeEvent('load');
			}
		}.bind(css);				
		
		return css;
	}

});
Preloader.implement ( new Options, new Events, new Chain);



new Abstract($(document)); new Abstract($(window));


// ================================================================================
// Garbage cleanup ( IE mainly )
// ================================================================================
$(window).addEvent('beforeunload', Garbage.onBeforeUnload);




// ================================================================================
// Standards extend
// ================================================================================
$extend ( window.HTMLElement.prototype, Element.Methods );
$extend ( $, Element.Methods);

// Extend Element.Methods to arrays
// TODO: Add this to Element.extend();
each(Element.Methods, function(method, name) {
	if(/set|hide|toggle|make|show|Event|fix|Class/.test(name)) {
		Array.prototype[name]  = function() {
			var args = arguments;
			this.each(function(element, index) {
				method.apply($(element), args);	
			});
			return this;
		};
	}
});

// $(element).hide to Element.hide ( element );  hack for legacy 
$forEach( Element.Methods, function( fn, name ) { Element[name] 	=  function( el ) { return fn.apply($(el), $A(arguments).slice(1)); }});


// ================================================================================
// PNGFix
// ================================================================================
// Additional info: http://www.satzansatz.de/cssd/tmp/alphatransparency.html
PNGFix = {
	blankImg		: 'http://c.pathfinder.gr/img/void.gif',
	alphaFilter		: 'DXImageTransform.Microsoft.AlphaImageLoader',
	
	filter			: function( s, m ) {
		if( this.filters[this.alphaFilter] ) {			
			this.filters[this.alphaFilter].enabled = s ? true : false;
			if( s ) {
				
				with( this.filters[this.alphaFilter]) {
					src			 = s;
					sizingMethod = m;
				}
			}
		}
		else if(s)  {
			this.style.filter = 'progid:'+PNGFix.alphaFilter+'(src="'+s+'",sizingMethod="'+m+'")';
		}
	},
	
	fixImg		: function() {
		// Show it here
		if( /\.png($|\?|\/)/i.test(this.src)) {
			if( this.currentStyle.width == 'auto' && this.currentStyle.height == 'auto' ) {
				this.style.width	 = this.offsetWidth + 'px';
				this.style.height	 = this.offsetHeight + 'px';
			}

			var oldSrc 	= this.src;
			this.src	= PNGFix.blankImg;
			
			setTimeout( function(){
				this.style.visibility = 'visible';
				PNGFix.filter.apply( this, [oldSrc, 'scale']);
				this.src = PNGFix.blankImg;
			 }.bind(this), 150);
		}
		else if( this.src.indexOf( PNGFix.blankImg) < 0 ) { 
			this.style.visibility = 'visible';
			PNGFix.filter.apply(this);		
		}
	},
	
	removeFix			: function(element) {
		// Lame but works :-)
		element.style.backgroundImage = '';
	},
	
	fix				: function( elements ) {
		if( ! (window.ie6 || window.ie5 ) ) {
			return;		
		}
		if( !elements )	elements = $T('img'); 
		else elements = $$(elements);
		
		// Run through nodes
		elements.each( function( element ) { 
				
				var thisBgImg = element.currentStyle.backgroundImage || element.style.backgroundImage;
				if( element.tagName == 'IMG' && /\.png($|\?|\/)/i.test(element.src) ){
						element.style.visibility = 'hidden';
						if( element.readyState != 'complete' )
							element.addEvent ( 'load', PNGFix.fixImg.bind(element) );
						else 
							PNGFix.fixImg.apply(element)
				} else if( thisBgImg && thisBgImg != 'none' ) {
					if (thisBgImg.match(/^url[("']+(.*\.png)[)"']+$/i)) {
						var s = RegExp.$1;			
						if( element.currentStyle.width == 'auto' && element.currentStyle.height == 'auto' ) {
							element.style.width		 = this.offsetWidth 	+ 'px';
							element.style.height	 = this.offsetHeight 	+ 'px';
						}
					
						element.style.backgroundImage = 'none';
						PNGFix.filter.apply(element, [s, 'crop']);	
						
						// IF Link fix
					   for (var n = 0; n < element.childNodes.length; n++)
							if (element.childNodes[n].style) element.childNodes[n].style.position = 'relative';
					
						
						
						element.__pngFixed = true;
					}
				}
				else 	PNGFix.filter.apply(element);
			}	
		)
	
	}
}

// ================================================================================
// DOMContentLoader
// ================================================================================
window.addEvent('DOMContentLoaded', function(){
	// NOTE: this is kinda obsolete and not really needed
	window.DOMContentLoaded = true;	

	// Fix IE caching issues - take 2	
	if (window.ie6 || window.ie5) {
		try {
			document.execCommand('BackgroundImageCache',false,true);
		} catch(ex) { };
	}	
		
	// Fix Function.prototype.bind 
	// In some case Adman banners tend to screw it
	// We restore it ;)
	if(Function.__bindSource) {
		Function._bindSource = Function.__bindSource.trim().replace(/^\(|\)$/, '');
		eval  ( "Function.prototype.bind = " + Function._bindSource);
	}
});


// ================================================================================
// JSON: By douglas
// ================================================================================
/*
      http://www.JSON.org/json2.js
    2009-04-16

    Public Domain.

    NO WARRANTY EXPRESSED OR IMPLIED. USE AT YOUR OWN RISK.

    See http://www.JSON.org/js.html

    This file creates a global JSON object containing two methods: stringify
    and parse.

        JSON.stringify(value, replacer, space)
            value       any JavaScript value, usually an object or array.

            replacer    an optional parameter that determines how object
                        values are stringified for objects. It can be a
                        function or an array of strings.

            space       an optional parameter that specifies the indentation
                        of nested structures. If it is omitted, the text will
                        be packed without extra whitespace. If it is a number,
                        it will specify the number of spaces to indent at each
                        level. If it is a string (such as '\t' or '&nbsp;'),
                        it contains the characters used to indent at each level.

            This method produces a JSON text from a JavaScript value.

            When an object value is found, if the object contains a toJSON
            method, its toJSON method will be called and the result will be
            stringified. A toJSON method does not serialize: it returns the
            value represented by the name/value pair that should be serialized,
            or undefined if nothing should be serialized. The toJSON method
            will be passed the key associated with the value, and this will be
            bound to the object holding the key.

            For example, this would serialize Dates as ISO strings.

                Date.prototype.toJSON = function (key) {
                    function f(n) {
                        // Format integers to have at least two digits.
                        return n < 10 ? '0' + n : n;
                    }

                    return this.getUTCFullYear()   + '-' +
                         f(this.getUTCMonth() + 1) + '-' +
                         f(this.getUTCDate())      + 'T' +
                         f(this.getUTCHours())     + ':' +
                         f(this.getUTCMinutes())   + ':' +
                         f(this.getUTCSeconds())   + 'Z';
                };

            You can provide an optional replacer method. It will be passed the
            key and value of each member, with this bound to the containing
            object. The value that is returned from your method will be
            serialized. If your method returns undefined, then the member will
            be excluded from the serialization.

            If the replacer parameter is an array of strings, then it will be
            used to select the members to be serialized. It filters the results
            such that only members with keys listed in the replacer array are
            stringified.

            Values that do not have JSON representations, such as undefined or
            functions, will not be serialized. Such values in objects will be
            dropped; in arrays they will be replaced with null. You can use
            a replacer function to replace those with JSON values.
            JSON.stringify(undefined) returns undefined.

            The optional space parameter produces a stringification of the
            value that is filled with line breaks and indentation to make it
            easier to read.

            If the space parameter is a non-empty string, then that string will
            be used for indentation. If the space parameter is a number, then
            the indentation will be that many spaces.

            Example:

            text = JSON.stringify(['e', {pluribus: 'unum'}]);
            // text is '["e",{"pluribus":"unum"}]'


            text = JSON.stringify(['e', {pluribus: 'unum'}], null, '\t');
            // text is '[\n\t"e",\n\t{\n\t\t"pluribus": "unum"\n\t}\n]'

            text = JSON.stringify([new Date()], function (key, value) {
                return this[key] instanceof Date ?
                    'Date(' + this[key] + ')' : value;
            });
            // text is '["Date(---current time---)"]'


        JSON.parse(text, reviver)
            This method parses a JSON text to produce an object or array.
            It can throw a SyntaxError exception.

            The optional reviver parameter is a function that can filter and
            transform the results. It receives each of the keys and values,
            and its return value is used instead of the original value.
            If it returns what it received, then the structure is not modified.
            If it returns undefined then the member is deleted.

            Example:

            // Parse the text. Values that look like ISO date strings will
            // be converted to Date objects.

            myData = JSON.parse(text, function (key, value) {
                var a;
                if (typeof value === 'string') {
                    a =
/^(\d{4})-(\d{2})-(\d{2})T(\d{2}):(\d{2}):(\d{2}(?:\.\d*)?)Z$/.exec(value);
                    if (a) {
                        return new Date(Date.UTC(+a[1], +a[2] - 1, +a[3], +a[4],
                            +a[5], +a[6]));
                    }
                }
                return value;
            });

            myData = JSON.parse('["Date(09/09/2001)"]', function (key, value) {
                var d;
                if (typeof value === 'string' &&
                        value.slice(0, 5) === 'Date(' &&
                        value.slice(-1) === ')') {
                    d = new Date(value.slice(5, -1));
                    if (d) {
                        return d;
                    }
                }
                return value;
            });


    This is a reference implementation. You are free to copy, modify, or
    redistribute.

    This code should be minified before deployment.
    See http://javascript.crockford.com/jsmin.html

    USE YOUR OWN COPY. IT IS EXTREMELY UNWISE TO LOAD CODE FROM SERVERS YOU DO
    NOT CONTROL.
*/

/*jslint evil: true */

/*global JSON */

/*members "", "\b", "\t", "\n", "\f", "\r", "\"", JSON, "\\", apply,
    call, charCodeAt, getUTCDate, getUTCFullYear, getUTCHours,
    getUTCMinutes, getUTCMonth, getUTCSeconds, hasOwnProperty, join,
    lastIndex, length, parse, prototype, push, replace, slice, stringify,
    test, toJSON, toString, valueOf
*/

// Create a JSON object only if one does not already exist. We create the
// methods in a closure to avoid creating global variables.

(function (OBJ) {
    function f(n) {
        // Format integers to have at least two digits.
        return n < 10 ? '0' + n : n;
    }

    if (typeof Date.prototype.toJSON !== 'function') {

        Date.prototype.toJSON = function (key) {

            return this.getUTCFullYear()   + '-' +
                 f(this.getUTCMonth() + 1) + '-' +
                 f(this.getUTCDate())      + 'T' +
                 f(this.getUTCHours())     + ':' +
                 f(this.getUTCMinutes())   + ':' +
                 f(this.getUTCSeconds())   + 'Z';
        };

        String.prototype.toJSON =
        Number.prototype.toJSON =
        Boolean.prototype.toJSON = function (key) {
            return this.valueOf();
        };
    }

    var cx = /[\u0000\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        escapable = /[\\\"\x00-\x1f\x7f-\x9f\u00ad\u0600-\u0604\u070f\u17b4\u17b5\u200c-\u200f\u2028-\u202f\u2060-\u206f\ufeff\ufff0-\uffff]/g,
        gap,
        indent,
        meta = {    // table of character substitutions
            '\b': '\\b',
            '\t': '\\t',
            '\n': '\\n',
            '\f': '\\f',
            '\r': '\\r',
            '"' : '\\"',
            '\\': '\\\\'
        },
        rep;


    function quote(string) {

// If the string contains no control characters, no quote characters, and no
// backslash characters, then we can safely slap some quotes around it.
// Otherwise we must also replace the offending characters with safe escape
// sequences.

        escapable.lastIndex = 0;
        return escapable.test(string) ?
            '"' + string.replace(escapable, function (a) {
                var c = meta[a];
                return typeof c === 'string' ? c :
                    '\\u' + ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
            }) + '"' :
            '"' + string + '"';
    }


    function str(key, holder) {

// Produce a string from holder[key].

        var i,          // The loop counter.
            k,          // The member key.
            v,          // The member value.
            length,
            mind = gap,
            partial,
            value = holder[key];

// If the value has a toJSON method, call it to obtain a replacement value.

        if (value && typeof value === 'object' &&
                typeof value.toJSON === 'function') {
            value = value.toJSON(key);
        }

// If we were called with a replacer function, then call the replacer to
// obtain a replacement value.

        if (typeof rep === 'function') {
            value = rep.call(holder, key, value);
        }

// What happens next depends on the value's type.

        switch (typeof value) {
        case 'string':
            return quote(value);

        case 'number':

// JSON numbers must be finite. Encode non-finite numbers as null.

            return isFinite(value) ? String(value) : 'null';

        case 'boolean':
        case 'null':

// If the value is a boolean or null, convert it to a string. Note:
// typeof null does not produce 'null'. The case is included here in
// the remote chance that this gets fixed someday.

            return String(value);

// If the type is 'object', we might be dealing with an object or an array or
// null.

        case 'object':

// Due to a specification blunder in ECMAScript, typeof null is 'object',
// so watch out for that case.

            if (!value) {
                return 'null';
            }

// Make an array to hold the partial results of stringifying this object value.

            gap += indent;
            partial = [];
 
// Is the value an array?

            if (Object.prototype.toString.apply(value) === '[object Array]') {

// The value is an array. Stringify every element. Use null as a placeholder
// for non-JSON values.

                length = value.length;
                for (i = 0; i < length; i += 1) {
                    partial[i] = str(i, value) || 'null';
                }

// Join all of the elements together, separated with commas, and wrap them in
// brackets.

                v = partial.length === 0 ? '[]' :
                    gap ? '[\n' + gap +
                            partial.join(',\n' + gap) + '\n' +
                                mind + ']' :
                          '[' + partial.join(',') + ']';
                gap = mind;
                return v;
            }

// If the replacer is an array, use it to select the members to be stringified.

            if (rep && typeof rep === 'object') {
                length = rep.length;
                for (i = 0; i < length; i += 1) {
                    k = rep[i];
                    if (typeof k === 'string') {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            } else {

// Otherwise, iterate through all of the keys in the object.

                for (k in value) {
                    if (Object.hasOwnProperty.call(value, k)) {
                        v = str(k, value);
                        if (v) {
                            partial.push(quote(k) + (gap ? ': ' : ':') + v);
                        }
                    }
                }
            }

// Join all of the member texts together, separated with commas,
// and wrap them in braces.

            v = partial.length === 0 ? '{}' :
                gap ? '{\n' + gap + partial.join(',\n' + gap) + '\n' +
                        mind + '}' : '{' + partial.join(',') + '}';
            gap = mind;
            return v;
        }
    }

// If the JSON object does not yet have a stringify method, give it one.

    if (typeof OBJ.stringify !== 'function') {
        OBJ.stringify = function (value, replacer, space) {

// The stringify method takes a value and an optional replacer, and an optional
// space parameter, and returns a JSON text. The replacer can be a function
// that can replace values, or an array of strings that will select the keys.
// A default replacer method can be provided. Use of the space parameter can
// produce text that is more easily readable.

            var i;
            gap = '';
            indent = '';

// If the space parameter is a number, make an indent string containing that
// many spaces.

            if (typeof space === 'number') {
                for (i = 0; i < space; i += 1) {
                    indent += ' ';
                }
 
// If the space parameter is a string, it will be used as the indent string.

            } else if (typeof space === 'string') {
                indent = space;
            }

// If there is a replacer, it must be a function or an array.
// Otherwise, throw an error.

            rep = replacer;
            if (replacer && typeof replacer !== 'function' &&
                    (typeof replacer !== 'object' ||
                     typeof replacer.length !== 'number')) {
                throw new Error('Core.JSON.stringify');
            }

// Make a fake root object containing our value under the key of ''.
// Return the result of stringifying the value.

            return str('', {'': value});
        };
    }


// If the JSON object does not yet have a parse method, give it one.

    if (typeof OBJ.parse !== 'function') {
        OBJ.parse = function (text, reviver) {

// The parse method takes a text and an optional reviver function, and returns
// a JavaScript value if the text is a valid JSON text.

            var j;

            function walk(holder, key) {

// The walk method is used to recursively walk the resulting structure so
// that modifications can be made.

                var k, v, value = holder[key];
                if (value && typeof value === 'object') {
                    for (k in value) {
                        if (Object.hasOwnProperty.call(value, k)) {
                            v = walk(value, k);
                            if (v !== undefined) {
                                value[k] = v;
                            } else {
                                delete value[k];
                            }
                        }
                    }
                }
                return reviver.call(holder, key, value);
            }


// Parsing happens in four stages. In the first stage, we replace certain
// Unicode characters with escape sequences. JavaScript handles many characters
// incorrectly, either silently deleting them, or treating them as line endings.

            cx.lastIndex = 0;
            if (cx.test(text)) {
                text = text.replace(cx, function (a) {
                    return '\\u' +
                        ('0000' + a.charCodeAt(0).toString(16)).slice(-4);
                });
            }

// In the second stage, we run the text against regular expressions that look
// for non-JSON patterns. We are especially concerned with '()' and 'new'
// because they can cause invocation, and '=' because it can cause mutation.
// But just to be safe, we want to reject all unexpected forms.

// We split the second stage into 4 regexp operations in order to work around
// crippling inefficiencies in IE's and Safari's regexp engines. First we
// replace the JSON backslash pairs with '@' (a non-JSON character). Second, we
// replace all simple value tokens with ']' characters. Third, we delete all
// open brackets that follow a colon or comma or that begin the text. Finally,
// we look to see that the remaining characters are only whitespace or ']' or
// ',' or ':' or '{' or '}'. If that is so, then the text is safe for eval.

            if (/^[\],:{}\s]*$/.
test(text.replace(/\\(?:["\\\/bfnrt]|u[0-9a-fA-F]{4})/g, '@').
replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

// In the third stage we use the eval function to compile the text into a
// JavaScript structure. The '{' operator is subject to a syntactic ambiguity
// in JavaScript: it can begin a block or an object literal. We wrap the text
// in parens to eliminate the ambiguity.

                j = eval('(' + text + ')');

// In the optional fourth stage, we recursively walk the new structure, passing
// each name/value pair to a reviver function for possible transformation.

                return typeof reviver === 'function' ?
                    walk({'': j}, '') : j;
            }

// If the text is not JSON parseable, then a SyntaxError is thrown.

            throw new SyntaxError('Core.JSON.parse');
        };
    }
}(Core.JSON));

// Provide JSON support
if(!this.JSON) {
	this.JSON = Core.JSON;
}
		


			   
// ================================================================================
// Prototypes / Add to JSON iff missing
// ================================================================================

// NOT FOR NOW
if(0)
if(!JSON.stringify) {
	for(var i = 0, l = prototypes.length; i < l; i++ ) {	
		prototypes[i].prototype.toJSON		= prototypes[i].prototype.toJSON || prototypes[i].prototype.toJSON_ext;
	};
}



// ================================================================================
// Legacy: Provide support for previous Core version
// ================================================================================
Core.newClass		= function() { return new Class(); };
Core.debug 			= Core.empty; // null for now
Object.extend		= $extend;

if(window.chrome) {
	document.Node				= Element;
	document.Node.disableSelect = document.Node.makeUnselectable;
} else {
	document.Node		= Element;
	document.Node.disableSelect = document.Node.makeUnselectable;

	Node				= Element;
	Node.disableSelect	= Node.makeUnselectable;
}

Event				= {};
Event.add			= Element.addEvent;
Event.remove		= Element.removeEvent;
Event.target		= function( e )   { return e.target || e.srcElement; };
Event.stop			= function( e) {
	if( e.stopPropagation) {e.stopPropagation(); e.preventDefault();}
	else { e.returnValue = false; e.cancelBubble = true;}
}


Ajax.rpc			= function() 		{  return new Ajax.RPC( $A(arguments)).send();}
Ajax.get			= function(options) { return new Ajax ( 'get', options ).send() }
Ajax.post			= function(options) { return new Ajax ( 'post', options ).send() }

System				= {
	'browser'	: {
		'ie'		: window.ie,
		'ie70'		: window.ie7,
		'ie60'		: window.ie6,	
		'ie50'		: window.ie5,		
		'moz'		: window.gecko,
		'mozilla'	: window.gecko,
		'safari'	: window.webkit
	}
};

$CN					= function (className, root) {
	var selector = '';
	if(!className.push) {
		selector = '[class='+className+']';	
	} else {
		for(var i = 0, l = className.length; i < l; i ++ ) {
			selector += ', [class='+className[i]+']';
		} selector = selector.replace(/^, /, '');
	}

	return $(root || document).getElements(selector);
}

var Pos 			= {};
Pos.get				= Element.getPosition;
Pos.getOffset 		= function( element ) {
	return Element.getPosition(element, null, true );	
}
Pos.getScrollTop	= window.getScrollTop;
Pos.getScrollLeft	= window.getScrollLeft;
Array.prototype.inArray = Array.prototype.contains;
Array.prototype.last = Array.prototype.getLast;
Array.prototype.forEachBind = Array.prototype.each;

isFunction			= function( obj ) { return $typeOf(obj) == 'function'; } ;
isUndefined			= function( obj ) { return !$typeOf(obj); 	};
isObject			= function( obj ) { return $typeOf(obj) == 'object'; }
isString			= function( obj ) { return $typeOf(obj) == 'string' ; 	};

Fx.fade				= function( element, options) {
	return new Fx.Style ( $(element), 'opacity', options ).start(1);
};
Event.keyModifier = function(e) {
	var m = {}
	m.alt		 = e.altKey;
	m.altLeft	 = e.altLeft;
	m.altRight	 = e.altRight;		
	m.ctrl		 = e.ctrlKey;
	m.ctrlLeft	 = e.ctrlLeft;
	m.meta		 = e.metaKey || false;
	m.shift		 = e.shiftKey;
	return m
}


// Store it for later
Function.__bindSource = Function.prototype.bind.toString();


// Figure out if we are dealing with a human
(function() {
	var __fn 		= function() {
		window.invokeEvent('isHuman', true);
		document.removeEvents(document.__humanEvents);
		Core.isHuman = true;
	}	
	document.__humanEvents = {
		'keydown' 	: __fn,
		'mousemove' : __fn,
		'mousedown' : __fn
	};
	document.addEvents(document.__humanEvents);		  
})();






















/* PATHFINDER RELATED STUFF - WILL BE MOVED TO SEPERATE JS WHEN THIS FILE GETS HUGE */
Pathfinder = {
	Catcher : {
		initialize : function() {
			if( !( location.host.match(/(pathfinder|phaistosnetworks)\.gr/gi) && !location.host.match(/(gp|phaistonian|dev)/gi) ) ) {
				return false;
			}
			
		///	window.onerror = this.handler;
		},
		
		handler		: function(errorMsg, url, lineNumber) {
			if(lineNumber == undefined || location.href.indexOf('clubs') !== -1 || errorMsg.match(/(Script error\.|Adman is not defined|location.toString|error loading script|mediawrap)/gi)) {
				return false;
			}
			new XHR({
				'url'			: 'gateway.php',
				'parameters' 	: {
					'action' 	: 'catch',
					'url'		: location.href,
					'host'		: location.host,
					'errorMsg' 	: errorMsg,
					'lineNumber': lineNumber,
					'userAgent'	: navigator.userAgent
				}
			}).send();
		}
	},
	
	Login : {
		debug			: location.host === 'gp.pathfinder.gr',													// If true, then point to login.ddev
		inLogin			: true,																					// if true then we are in signup screen
		ad				: {
			active		: false,	
			height		: 270,																					// If set, then use this as ad height
			duration	: 6000,																					// Time to off the ad when succesfully logged in
			preload		: ['http://ads3.adman.gr/banners/2011/clients/carlsb/4PathFinderLogin.jpg'],
			url			: 'http://ads3.adman.gr/banners/2011/clients/carlsb/Carlsberg.html',					// iframe url
			'1x1'		: 'http://talos.adman.gr/banner?webspace=1127&auto=1'									//
		},
		
		fx				: null,
		
		css				: {
			modal		: 'width: 668px;position: fixed; top: 50%; left: 50%; margin-left: -334px; margin-top: -131px;background: #EDEFF4; text-align: left; z-index: 9999999;-webkit-perspective: 1000;-webkit-transition: -webkit-transform .3s, opacity .3s;-moz-transition: -moz-transform .3s, opacity .3s; -ms-transition: -ms-transform .3s, opacity .3s; transition: transform .3s, opacity .3s; opacity:0;-webkit-transform: scale(0); -moz-transform: scale(0); -ms-transform: scale(0); transform: scale(0); -webkit-backface-visibility: hidden; -moz-backface-visibility: hidden; -ms-backface-visibility: hidden; backface-visibility: hidden;',
			
			modalCard	: 'width: 668px;-webkit-transform-style: preserve-3d;-webkit-transition: -webkit-transform 2.1s;',
			face		: 'padding: 0;background: #EDEFF4; text-align: left; width: 668px; height: 262px; border: 4px solid #044C7E; -webkit-box-shadow: rgba(0, 0, 0, 0.49) 0px 0px 8px; -moz-box-shadow: rgba(0, 0, 0, 0.49) 0px 0px 8px; box-shadow: rgba(0, 0, 0, 0.49) 0px 0px 8px; position: absolute; top: 0; left: 0px;z-index:99999;-webkit-backface-visibility: hidden;z-index: 99999;_width: 660px; _height: 254px;'
		},
		
		isOpen 		 	: false,
		prevDomain	 	: null,
		
		transition		: function() {
			// No ad
			if(!this.ad || !this.ad.active) {
				return this;
			}
			
			if(this.ad.height && this.ad.url) {
				$('pf-login-ad').effect('height', {
					duration: 100
				}).start(this.ad.height - (window.ie6 ? 8 : 0));				
			}
			
			// Track 1x1
			if(this.ad['1x1']) {
				var img = new Image();
				img.src = this.ad['1x1'];
				document.body.appendChild(img);
			}
			
			// Must do for multi-filter reasons
			if(window.ie) {
				$('pf-login-close').remove();
			}
			
			// Sadly only webkit does it for now
			if(window.webkit) {
				$('pf-login-ad').setOpacity(1);
				var s = document.createElement('style');
				s.innerHTML = '.pf-login-trans { -webkit-transform: rotateY(180deg) }';
				document.body.appendChild(s);
				$('pf-login-modal-card').addClass('pf-login-trans');
			} else {
				$('pf-login-container').effect('opacity', { 
					duration: 3100
				}).start(1,0);
				
				$('pf-login-ad').effect('opacity', {
					duration: 3100
				}).start(0, 1);
			}
			
			return this;
		},
		
		enable  : function(element) {
			// For those using core when they are not supposed to.
			// E.g http://justtesting.pblogs.gr/2011/07/testing-i8-again-3.htm
			if(location.host.indexOf('.pathfinder.gr') === -1) {
				return false;
			}
			var elements               = [];  
			if(!element || !element.nodeName) {
				elements               = $$('a[href^="http://login.pathfinder.gr"]');
			} else {
				elements.push(element);
			}

			// Bail out
			if(!elements || elements.length === 0) {
				return;
			}
			
			
			elements.addEvent('click', function(event) {
				if($(this).getProperty('href').contains('doLogout')) {
					return;
				}
				Pathfinder.Login.prevDomain = Pathfinder.Login.prevDomain || document.domain;
				
				event.preventDefault();
				document.domain = 'pathfinder.gr';
				Pathfinder.Login.open();
			});

			if('postMessage' in window) {
				if (typeof window.addEventListener != 'undefined') {
					window.addEventListener('message', function(event) { Pathfinder.Login.bindMessage(event); }, false);
				}  else if (typeof window.attachEvent != 'undefined') {
					window.attachEvent('onmessage', function(event) { Pathfinder.Login.bindMessage(event); });  
				}
			} else {
				// IE7, IE6
				setInterval(function() {
					if(!Pathfinder.Login.isOpen) {
						return false;
					}

					var hash                              = window.location.hash;

					// No reson
					if(hash == Pathfinder.Login.hash) {
						return;
					}
					
					Pathfinder.Login.hash = hash;

					if(hash.charAt(0) == "#") {
						hash             = hash.substr(1, hash.length);
					}

					if(hash != '' && JSON.parse(hash)) {
						Pathfinder.Login.bindMessage( { 'data' : hash } );
					}

					if(window.ie7 || window.ie8) {
						window.location.hash      = " ";
					} else {
						window.location.hash      = "";
					}
				}, 100);  
			}            
		},

		bindMessage : function(event) {
			if(event && event.orgin && event.origin.indexOf('matrix.pathfinder.gr') !== -1 ) {
				return false;
			}
		
			try{ 
				var message                   = JSON.parse(event.data);
			} catch(ex) {

			}
			
			if(message.method == 'resizeIframe') {
				this.inLogin		= !this.inLogin;
				message.height 		= parseInt(message.height);
				if(window.ie6) {
					message.height = message.height - 10;
				}
				
				if(window.ie6) {
					$('#pf-login-container iframe').style.height = message.height + 'px';
					if(this.ad && this.ad.active && this.ad.url) {
						$('#pf-login-ad iframe').style.height = message.height + 'px';
					}
				}

				(this.fx = (this.fx || $('pf-login-container').effects({
					'duration'		: 300
				}))).
				start({
					'height'	: message.height
				});
				
				if(this.ad && this.ad.active && this.ad.url) {
					(this.fxAd= (this.fxAd || $('pf-login-ad').effects({
						'duration'		: 300
					}))).
					start({
						'height'	: message.height
					});
				}				
				
			} else if(message.method == 'reloadParent') {
 				if(this.ad && this.ad.active && this.ad.duration && this.inLogin) {
					this.transition();
					(function(){
						window.location.reload();
					}).delay(this.ad.duration);
					return;
				} else {
					window.location.reload();
 				}
			}
		},

		open : function() {
			this.close();
			if(this.isOpen) {
				return this;
			}

			// Make sure we are on the same domain
			document.domain		= 'pathfinder.gr';
			this.isOpen			= true;

			var html			= [], loginURL = 'http://login.' + (this.debug ? 'dev.' : '') + 'pathfinder.gr/?overlay=1' + (window.ie6 || window.ie7 ? '&iframe=' + encodeURIComponent(window.location) : '');
			loginURL			+= (this.debug ?   ( loginURL.contains('?') ? '&' : '?' ) + 'rnd='+(+new Date() ) : '');
			
			// The backdrop
			html.push('<div style="width: 100%; height: 100%; bottom:0; right: 0; overflow: hidden; z-index: 9999999; visibility: visible; zoom: 1; opacity: 0.25; top: 0px; left: 0px;position: fixed;webkit-perspective: 1000;" id="pf-login-backdrop"></div>');

			// START modal and modal-card
			html.push('<div id="pf-login-modal" style="'+this.css.modal+'">');
			html.push('<div id="pf-login-modal-card" style="'+this.css.modalCard+'">')

			// FACE-front
			html.push('<div id="pf-login-container" style="'+this.css.face+'">');
			html.push('<span id="pf-login-close" onclick="Pathfinder.Login.close();" style="z-index: 999999;cursor: pointer; position: absolute; top: -22px; right: -22px; width: 36px; height: 36px; display: block; background: transparent url(\'http://c.pstatic.gr/img/act/nob/close.png\') no-repeat center center;line-height: 5em;">&nbsp;</span>');
			html.push('<iframe src="' + loginURL + '" frameborder="0" framewidth="0" height="100%" width="100%"  scrolling="no"></iframe>');
			html.push('</div>');
					
			// face-back
			if(this.ad && this.ad.active && this.ad.url ) {
				html.push('<div id="pf-login-ad" style="'+this.css.face+'-webkit-transform: rotateY(180deg);opacity:0;-webkit-box-sizing: border-box;z-index:999;-ms-filter:\'progid:DXImageTransform.Microsoft.Alpha(opacity=0)\';filter:progid:DXImageTransform.Microsoft.Alpha(opacity=0);">');
				html.push('<iframe frameborder="0" framewidth="0" border="0" style="width: 100%; height: 100%; border:0;" src="' + this.ad.url + '" ></iframe>');
				html.push('</div>')
			}						
			
			// END modal and modal-card
			html.push('</div>');
			html.push('</div>');
			


			(html.join('')).injectIn();
			

			
			if(window.ie6 || window.ie7 || window.ie8) {
				$('pf-login-backdrop').setOpacity(.25);
			}
			
			if(window.ie6) {
				$('pf-login-backdrop').setStyle('position', 'absolute');
				$('pf-login-modal').setStyle('position', 'absolute');

				$('pf-login-close') && $('pf-login-close').addEvent('click', Pathfinder.Login.close );
				PNGFix.fix($('pf-login-close'));
			} else {
				$('pf-login-backdrop').setStyle('background-color', '#333');
			}
			
			// Preload stuff if set
			// This way iframe will not - for example - sport broken images etc
			if(this.ad && this.ad.active && this.ad.preload) {
				new Preloader( this.ad.preload );
			}
			
			// Make it fancy :-)
			(function(){
				$('pf-login-modal').style.opacity = 1;
				$('pf-login-modal').style.webkitTransform =  $('pf-login-modal').style.MozTransform  =  $('pf-login-modal').style.msTransform =   $('pf-login-modal').style.transform  = 'scale(1)'; 
				
				// This is must do
				(function(){
					$('pf-login-modal').style.webkitTransform =  $('pf-login-modal').style.MozTransform  =  $('pf-login-modal').style.msTransform =   $('pf-login-modal').style.transform  = null;
				}).delay(1000);
			}).delay(120);
		},

		close : function() {
			if(!Pathfinder.Login.isOpen) {
				return false;
			}
			Pathfinder.Login.isOpen    = false;

			$('pf-login-backdrop')  &&  $('pf-login-backdrop').remove();
			$('pf-login-modal')   &&  $('pf-login-modal').remove();

			return this;
		}
	},

	
	CSS			: {
		stylesheet	: null,
		initialize 	: function() {
			var root;
			if(document.styleSheets && document.styleSheets.length === 0 ) {
				// NOTE: document.head or else we shall have loads of errors on IE6 when base is set
				if(document.head && !(root = $(document.head).getFirst())) {
					document.head.appendChild($C('style'));
				} else {
					$C('style').injectBefore($(document.head || document.body).getFirst());	
				}
			}
						
			this.styleSheet = document.styleSheets[document.styleSheets.length - 1];
			
			// Fix an issue with CSS img 
			// http://css-tricks.com/ie-fix-bicubic-scaling-for-images/
			if(window.ie7) {
				this.styleSheet.addRule('img', '-ms-interpolation-mode: bicubic;');
			}	
			
			// App fixes on selects
		///	if(navigator.appVersion.indexOf("Mac") !=-1  && window.webkit) {
				//Pathfinder.CSS.rules['body select.field'] = 'background: auto!important; border: auto !important; -webkit-appearance: menulist!important;'			
			//}
			
			// Add Pathfinder related CSS
			each(Pathfinder.CSS.rules, function(rule, key) {
				if(this.styleSheet.addRule) {
					this.styleSheet.addRule( key , rule);
				} else {
					this.styleSheet.insertRule( key +' {' + rule + '}', this.styleSheet.cssRules.length );
				}
			}, this);
			
		},
		
		rules		: {
			//'a#pathfinder-logout' : 'background:  url("http://c.pstatic.gr/img/act/x.gif") no-repeat center center; height: 13px;width: 13px; display: inline-block; overflow: hidden; text-indent: -10000px;overflow:  hidden; font-size:0px; outline: none; vertical-align: middle; line-height: 13px;_margin-top: 2px;'
		}
	},
	
	User	 : {
		login	: function() {
			
		},
		
		logout	: function() {
			
		},
		
		signup	: function() {
			
		}
	},
	// COMMENTS
	Comments : {
		flag : function(id, element, callFn, userID) {
			var reason;
			userID = ( userID || (  Cookie.get('lid') || (window.User ? window.User.id : 0) ) ).toInt();
			if( !window.confirm('Είστε σίγουρος για την αναφορά του σχολίου ως προσβλητικό-ανάρμοστο;') ) {
				return false;	
			}
			
			while(!reason) {
				reason = window.prompt('Για ποίο λόγο θεωρείτε το σχόλιο προσβλητικό ή ανάρμοστο;').trim();    
			}
			
			new Ajax.RPC().
			send('comments.Flag', id.toInt(), userID, reason.trim()).
			chain(function() {
				if(callFn) {
					callFn(element, id, userID);
				} else if ($(element)) {
					element.hide();			   
				}
			}); 
		}
	},
	
	// SHARE
	Share		: {
		isOpen	: false,
		clickFn	: function(event) {
			if(!Pathfinder.Share.isOpen) {
				return false;
			}			
			if(!event.target.isWithin(Pathfinder.Share.container, true)) {
				Pathfinder.Share.close();
			};			
		},
		
		open	: function(element) {
			element.blur && element.blur();
			this.element 	= element;
			var self 		= this;
			
			(function(){
				document.addEvent('click', this.clickFn);			
			}).delay(20, this)
						
			this.isOpen = true;

			if(this.container) {
				this.container.show();	
				return this;
			}
			
			return this.render(element);
		},
		
		render	: function(element) {
			if(arguments.callee.rendered) {
				return this;
			}
		
			if(!window.DOMContentLoaded) {
				  window.addEvent('DOMContentLoaded', this.render.bind(this, element));
				  return this;
			}
			arguments.callee.rendered = true;
			var title 		= escape(document.title.replace(/ /g, '+'));
			title			= encodeURIComponent(document.title); // Better way to handle this
			var location	= document.location.href;
			var styleA		= 'style=\"text-align: left;float: none; padding: 0; font-family: arial, serif;  font-weight: normal; font-size: 12px; border: none; background: transparent;\"'
			var html = [];
			var _float;
			// Not for now
			// html.push('<li style="padding: 5px 5px 5px 20px; background:url(http://c.pstatic.gr/img/share/pathfinder.gif) no-repeat center left; margin: 0;"><a target="share" title="Pathfinder" href="http://clk.pathfinder.gr/share/pathfinder/http://www.pathfinder.gr/share?subject='+document.title+'&amp;body='+location+'">Pathfinder</a></li>');
			// Force encoding to utf if needed
			html.push('<li style="padding: 3px 5px 3px 20px; background:white url(http://c.pstatic.gr/img/share/twitter.gif) no-repeat center left; margin: 0;"><a ' + styleA + ' target="share" title="Twitter" href="http://clk.pathfinder.gr/share/twitter/http://share.pathfinder.gr/twitter?url=' + location + '&amp;enc='+ (document.characterSet && document.characterSet.toLowerCase().contains('iso') ? 'iso' : 'utf') + '&amp;title='+title+'">Twitter</a></li>');
			html.push('<li style="padding: 3px 5px 3px 20px; background:white url(http://c.pstatic.gr/img/share/facebook.gif) no-repeat center left; margin: 0;"><a ' + styleA + ' target="share" title="Facebook" href="http://clk.pathfinder.gr/share/fb/http://www.facebook.com/sharer.php?u='+location+'&amp;t='+title+'">Facebook</a></li>');
			html.push('<li style="padding: 3px 5px 3px 20px; background:white url(http://c.pstatic.gr/img/share/mail.gif) no-repeat center left; margin: 0;"><a ' + styleA + ' href="mailto:?subject='+document.title+'&amp;body='+location+'">E-mail</a></li>');
						
			// Construct a parent relative container
			var parent = $C('span').makePositioned().setStyles({'display' : window.ie6 ? 'inline-block' : 'inline-block', 'overflow' : 'visible', 'height' : 'auto', 'padding' : 0, 'margin' : 0, 'text-align': 'left'}).injectBefore(element).adopt(element);
			if((_float = $(element).getStyle('float')) && _float !== 'none') {
				parent.setStyle('float', _float);
			}
		
			this.container = $C('ul').
			injectIn(parent).
			setStyles({
				'margin'			: 0,
				'list-style-type' 	: 'none',
				'position'  		: 'absolute',
				'top'				: '100%',
				'left'				: 0,
				'width'				: 150,
				'padding'			: '5px 10px',
				'border'			: '1px solid #aaa',
				'-moz-border-radius': 2,
				'-webkit-border-radius' :2,
				'border-radius'		: 2,
				'margin-top' 		: window.ie6 ? 2 :  (window.webkit ? -1 :1) , // Border thingy box model
				'margin-left'		: window.ie6 ? 0 :  (window.webkit ?  2 :0) , // Border thingy box model
				'z-index'			: 9999999,
				'background-color' 	: 'white',
				'-moz-box-shadow'			: '2px 2px 0px rgba(100,100,100,.2)',
				'-webkit-box-shadow'		: '2px 2px 0px rgba(100,100,100,.2)'
			}).setHTML(html.join(''));
				
			return this;
		},

		close	: function() {
			this.isOpen = false;
			document.removeEvent('click', this.clickFn);			
			this.container.hide();
		},
		
		toggle	: function(element) {
			//alert(document.characterSet.toLowerCase().contains('iso') );
			return this[this.isOpen ? 'close' : 'open'](element);
		}
	},
	
	// LIKES
	/*
	- We beed #pathfinder-likes-report, #pathfinder-likes-report-counter, #pathfinder-likes-report-me
	*/
	Likes		: {
		initialize : function(element) {
			if(element.__likes) {
				return element;
			}

			var _float;
			// Construct the fetch container
			var parent = $C('span').makePositioned().setStyle('display', window.ie6 ? 'inline' : 'inline-block').injectBefore(element).adopt(element);

			// Construct a parent relative container
			var parent = $C('span').makePositioned().setStyles({'display' : window.ie6 ? 'inline-block' : 'inline-block', 'overflow' : 'visible', 'height' : 'auto', 'padding' : 0, 'margin' : 0, 'text-align': 'left'}).injectBefore(element).adopt(element);
			if((_float = $(element).getStyle('float')) && _float !== 'none') {
				parent.setStyle('float', _float);
			}
					
				
			element.__likes = {
				// The element
				element			: $C('div').injectIn(parent).setStyles({
					'margin'				: 0,
					'position'  			: 'absolute',
					'top'					: '100%',
					'left'					: 0,				
					'padding'				: '10px 10px',
					'border'				: '1px solid #aaa',
					'margin-top' 		: window.ie6 ? 2 :  (window.webkit ? -1 :1) , // Border thingy box model
					'margin-left'		: window.ie6 ? 0 :  (window.webkit ?  2 :0) , // Border thingy box model
					'z-index'				: 9999999,
					'-moz-border-radius': 2,
					'-webkit-border-radius' :2,
					'border-radius'		: 2,
					
					'visibility'			: 'hidden',
					'overflow'				: 'auto',
					'white-space'			: 'nowrap',
					'background-color' 		: 'white',
					'-moz-box-shadow'		: '2px 2px 0px rgba(100,100,100,.2)',
					'-webkit-box-shadow'	: '2px 2px 0px rgba(100,100,100,.2)'					
				}),
				
				// The RPCs	
				RPCToggle	 	: new Ajax.RPC(),
				RPCFetch		: new Ajax.RPC({
					onSuccess	: function(req) {
						Pathfinder.Likes.open(element);
						var json = req.responseJSON;
						var html = [];					
						json.each(function(user, index){
							html.push('<div style="font-size: 11px; float: left; text-align: center; width: 60px; margin: 5px 0;">');
							html.push('<a target="ext" href="http://profiles.pathfinder.gr/view/'+user.username + '"><img width="24" height="24" alt="avatar" border="0" src="'+ user.avatars[1]+'" /></a>');
							html.push('<br /><a target="ext" href="http://profiles.pathfinder.gr/view/'+user.username + '">' + user.username + '</a>');
							html.push('</div>')
						});
						element.__likes.element.setHTML(html.join(''));
						Pathfinder.Likes.update(json.length);						
											
						element.__likes.element.setStyles({
							'height'		: json.length > 12 ? 135 : 'auto',
							'width'			: Math.min(4, json.length) * 60 + (json.length > 12 ? 20 : 0),
							'visibility' 	: 'visible'
						});
					}
				})
			}
	
			return element;
		},
		
		toggle	: function(section, id, userID, element, fn) {
			this.initialize(element);
	
			element.__likes.RPCToggle.send('social.ToggleLike', section, id, userID).chain(function(req){
				Pathfinder.Likes.update(!!req.responseText.toInt());
			});
		},
		
		open	: function(element) {
			this.fetchElement = element;
			element.__likes.element.empty();
			element.__likes.element.show();
			return this;
		},
		
		close	: function(element) {
			this.fetchElement = null;
			element.__likes.element.hide();
			return this;
		},
	
		fetch	: function(section,id, element) {
			this.initialize(element);
			
			document.addEvent('click', function(event) {
				if(this.fetchElement && !event.target.isWithin(element, true) ) {
					Pathfinder.Likes.close(this.fetchElement);
				}
			}, this);
			
			if(this.fetchElement  === element) {
				this.close(element);
				return this;
			}
			
			element.__likes.RPCFetch.send('social.FetchLikes', section, id, false);
			
		},
		
		// This is dirty
		update	: function(total, you) {
			var r = $('pathfinder-likes-report');
			if(!r) {
				return false;
			} 
			you = (you === undefined ? $('pathfinder-likes-report-me').isVisible() : you);
			
			if( $typeOf(total) === 'boolean' ){
				you 	= total;
				total 	= r.innerHTML.match(/\d+ /)[0].trim().toInt() + ( you ? 1 : -1);
			}	
			
			r[total ? 'show' : 'hide']();			
			$('pathfinder-likes-report-counter').innerHTML = $('pathfinder-likes-report-counter').innerHTML.replace(/\d+/,  total);
			$('pathfinder-likes-report-me')[you ? 'show' : 'hide']();			
		}		
		
	},
	
	// In order to setup a new element - once it's created after DOMContentLoaded use Pathfinder.Fields.setup(field);
	Fields			: {
		initialize : function() {
			var isHTML5Ready  =  ('placeholder' in document.createElement('input') );
			Pathfinder.Fields.isHTML5Ready = isHTML5Ready;
			// Initiate HTML5 procedure
			if(!isHTML5Ready) {
				$$('input[type="text"], textarea').each(function(element) {
					// TODO: Tweak this
					if(element.getProperty('placeholder')) {
						Pathfinder.Fields.setupHTML5(element);
					}
					
					if(element.getProperty('autofocus')) {
						element.focus();
					}
				});
			}
			
			if(!(window.ie5 || window.ie6)) {
				return false;
			}
			
			Pathfinder.Fields.skipCheckForContainer = true;
			$$('.field, .button').each(Pathfinder.Fields.setup);
			Pathfinder.Fields.skipCheckForContainer = false;
			
			// Apply the :focus to the activeElement, is defined
			// ADDED: 2.12.09 (try, catch)
			try {
				if( document.activeElement && ['INPUT', 'TEXTAREA'].contains(document.activeElement.nodeName)  && $(document.activeElement).hasClass('field')) {
					$(document.activeElement).invokeEvent('focus');
				}
			} catch(ex) {

				
			}
		},
		
		setupHTML5	: function(element) {
			var placeholder = $(element).getProperty('placeholder');
			var form;
			if(element.__setup || !placeholder) {
				return false;
			}

			// Handle form
			if(element.form && !element.form.__setup) {
				var form = element.form;
				$(element.form).addEvent('submit', function(event)  {
				//$(window).addEvent('unload', function() {
					form && $(form).getElements('input[type="text"], textarea').each(function(element, index) {
						var placeholder = element.getProperty('placeholder');
						if(placeholder && element.value === placeholder) {
							element.value = '';
						}
					});
				});		   
				
				element.form.__setup = true;
			}
			
			if(placeholder && (!document.activeElement || (document.activeElement && document.activeElement !== element)) && ( element.value === '' || element.value === placeholder ) )  {
				element._isEmpty	= true;
				element.style.color = '#A9A9A9';
				element.value 		= placeholder;
			}
			
			element.addEvents({
				'focus'	: function(event) {
					if(element._isEmpty) {
						element.value 			= '';
						element.style.color		= '';
						element._isEmpty 		= false;
					}
				},
				
				'blur'	: function() {
					var placeholder = element.getProperty('placeholder');
					if(element.value.length === 0 && placeholder)  {
						element.value 			= placeholder;
						element.style.color		= '#A9A9A9';
						element._isEmpty 		= true;
					}					
				},
				
				'change'	: function() {

				}
			});
			
			element.__setup = true;
		},
		
			
		fromElement : function(src) {
			
		},
		
		setup	: function(element) {		
			if(!$(element) || element.__setup) {
				return false;
			}			
			
			// Apply HTML5 stuff
			if(!Pathfinder.Fields.isHTML5Ready) {
				Pathfinder.Fields.setupHTML5(element);
			}
				
			if( !!(window.ie5 || window.ie6)) {
				return false;
			}
			element.__setup = true;							
			
			// In case we are going to setup all elements of a container
			if(!Pathfinder.Fields.skipCheckForContainer) {
				if(!['INPUT', 'BUTTON', 'TEXTAREA', 'SELECT', 'A'].contains(element.nodeName)) {
					element.getElements( 'input[type="text"], textarea, select, input[type="password"], input[type="submit"], button').each(function(el) {
						Pathfinder.Fields.setup(el);	
					});
							
					return false;
				};
			}
			Pathfinder.Fields.skipCheckForContainer = false;

			var isField 	= $(element).hasClass('field');
			var isButton 	= $(element).hasClass('button');
		
			if(!isField && !isButton) {
				isButton = element.nodeName === 'BUTTON' || (element.nodeName === 'INPUT' && element.type === 'submit');
				if(!isButton) {
					isField = true;	
				}
				element.addClass(isField ? 'field' : 'button');
			}
		
			var type = (isField ? 'field' :  'button');
			

			
			if(element.disabled) {
				element.addClass( type + '-disabled');
			}
			
			$(element).addEvents({
				'mouseenter' : function(event) {
					this.addClass( type + '-hover');
				},
				
				'mouseleave' : function(event) {
					this.removeClass(type + '-hover');
				},
				
				'focus'		: function(event) {
					this.addClass(type + '-focus');
				},
				
				'blur'		: function(event) {
					this.removeClass(type+'-focus');				
				}
			});
		}
	}
}

// Initialize Pathfinder related modules
Pathfinder.CSS.initialize();
Pathfinder.Catcher.initialize();
window.addEvent('DOMContentLoaded', Pathfinder.Fields.initialize);
window.addEvent('DOMContentLoaded', Pathfinder.Login.enable);

// Figure out document.characterSet 
// For the browsers that have no clue what is
if(!document.characterSet) {
	var metas = document.getElementsByTagName('meta');
	if(metas) {
		for(var i = 0; i < metas.length; i++ ) {
			// Classique
			if(metas[i] && metas[i].content && metas[i].content.toLowerCase().indexOf('charset') !== -1) {
				document.characterSet =  metas[i].content.replace(/^.*charset.*?=(.*?)$/, '$1').replace(/;/, '').trim();
				break;
			}
			// New
			if(metas[i] && metas[i].charset) {
				document.characterSet = metas[i].charset.trim();
				break;
			}
		}
	}
} 
// ADDED: 28.03.2011
Ajax.setOptions({ 'encoding' : document.characterSet }); 

