if ( !window.console ) {
    window.console = {
        log : function ( ) {
            //debugger;
        }
    };
}

// Prototype (et al) adjustments
( function ( ) {

    // setTimeout/Interval
    ( function ( ) {
        function adjust ( base ) {
            return function ( scope, fn, timeout ) {
                var args = Array.prototype.slice.call ( arguments );
                var scope = args.shift();
                if ( typeof scope == "function" ) {
                    args.unshift ( scope );
                    scope = null;
                }
                fn = args.shift();
                timeout = args.shift();
                return base ( function ( ) {
                    fn.apply ( scope, args );
                }, timeout );
            }
        }
        window.setTimeout = adjust ( window.setTimeout );
        window.setInterval = adjust ( window.setInterval );
    } )();

    // Array
	if ( Array.prototype.shuffle === undefined ) {
		Array.prototype.shuffle = function ( ) {
			return this.sort ( function ( ) {
				return 0.5 - Math.random();
			} );
		}
	}
	if ( Array.prototype.random === undefined ) {
		Array.prototype.random = function ( count ) {
			if ( !this.length ) return null;
			var c = Math.max ( Math.min ( this.length, parseInt ( count ) || 1 ), 0 );
			var r = this.shuffle().slice(0,c);
			if ( count == null ) return r[0];
			return r;
		}
	}
	if ( Array.prototype.peek === undefined ) {
		Array.prototype.peek = function ( ) {
			return this.length ? this[this.length-1] : undefined;
		}
	}
    if ( Array.prototype.copy === undefined ) {
        Array.prototype.copy = function ( ) {
            return this.slice ( 0 );
        }
    }

	// Math
	if ( Math.randomInt === undefined ) {
		Math.randomInt = function ( min, max ) {
			if ( min > max ) {
				var tmp = min;
				min = max;
				max = tmp;
			}
			return Math.floor ( Math.random() * ( max - min + 1 ) + min );
		}
	}
	if ( Math.ln === undefined ) {
		Math.ln = Math.log;
		Math.log = function ( x, b ) {
			if ( b == null ) b = Math.E;
			return Math.ln ( x ) / Math.ln ( b );
		}
	}

	// String
	if ( String.prototype.empty === undefined ) {
		String.prototype.empty = function ( ) { return this.length == 0; }
	}
	if ( String.prototype.toHref === undefined ) {
		String.prototype.toHref = function ( ) {
			var string = this;
			var f = function ( x ) {
				if ( x ) string = string.substring ( x.length );
				return x;
			}
			var l = document.location;
			var o = {};
			o.protocol = ( s = f ( ( s = string.match(/^([a-z]+:\/\/).+/i) ) ? s[1] : null ) ) ? s.replace(/\/\/$/,'') : null;
			o.host = f ( ( s = (o.protocol+"//"+string).match(/^[a-z\.]+:\/\/([\w\.:]+)(\/|$)/i) ) ? s[1] : null );
			o.hostname = ( s = ( o.host || "" ).match(/^([^:]+)/) ) ? s[1] : null;
			o.port = ( s = ( s = ( o.host || "" ).match(/^[^:]+(:\d+)?$/) ) ? s[1] : ( o.hostname ? 80 : null ) ) ? s.replace(/^:/,'') : null;
			o.pathname = f ( ( s = string.match(/^([^#]*)/) ) ? s[1] : null );
			o.hash = f ( ( s = string.match(/(#[^\?]*)(\?|$)/) ) ? s[1] : "" );
			o.search = f ( ( s = string.match(/(\?.*)$/) ) ? s[1] : "" );
			if ( o.pathname.charAt(0) != "/" ) {
				if ( o.host == l.host ) {
					o.pathname = l.pathname.replace(/\/[^\/]*$/,'/') + o.pathname;
				} else {
					o.pathname = "/" + o.pathname;
				}
			}
			for ( p in o ) { if ( o[p] === null ) o[p] = l[p]; }
			o.href = o.protocol + "//" + o.host + o.pathname + o.hash + o.search;
			o.internal = !!( ( o.protocol + "//" + o.host + o.pathname == l.protocol + "//" + l.host + l.pathname ) && o.hash );
			return o;
		}
	}
	if ( String.prototype.stripTags === undefined ) {
		String.prototype.stripTags = function ( ) {
			return this.replace ( /<[^>]*>/g, '' );
		}
	}
	if ( String.prototype.lpad === undefined ) {
		String.prototype.lpad = function ( length, chr ) {
			var s = this;
			chr = ( chr || "" ).toString().charAt(0) || " ";
			while ( s.length < length ) {
				s = chr + s;
			}
			return s;
		}
	}
	if ( String.prototype.rpad === undefined ) {
		String.prototype.rpad = function ( length, chr ) {
			var s = this;
			chr = ( chr || "" ).toString().charAt(0) || " ";
			while ( s.length < length ) {
				s += chr;
			}
			return s;
		}
	}
    if ( String.prototype.ucWords === undefined ) {
        String.prototype.ucWords = function ( ) {
            return this.replace ( /(.?\b)(\w)/g, function ( all, p, m ) {
                if ( p.match ( /\S/ ) ) return all;
                return p + m.toUpperCase();
            } );
        }
    }
	String.prototype.charAt = ( function ( ) {
		var charAt = String.prototype.charAt;
		return function ( pos ) {
			if ( parseInt ( pos ) >= 0 ) return charAt.call ( this, pos );
			pos = parseInt ( pos );
			if ( isNaN ( pos ) ) return "";
			return this.substr ( this.length + pos, 1 );
		}
	} )();

	// Date
	if ( Date.days === undefined ) {
		Date.days = [
			{
				abbr : "Mon",
				standard : "Monday"
			},
			{
				abbr : "Tue",
				standard : "Tuesday"
			},
			{
				abbr : "Wed",
				standard : "Wednesday"
			},
			{
				abbr : "Thu",
				standard : "Thursday"
			},
			{
				abbr : "Fri",
				standard : "Friday"
			},
			{
				abbr : "Sat",
				standard : "Saturday"
			},
			{
				abbr : "Sun",
				standard : "Sunday"
			}
		];
	}
	if ( Date.months === undefined ) {
		Date.months = [
			{
				abbr : "Jan",
				standard : "January"
			},
			{
				abbr : "Feb",
				standard : "February"
			},
			{
				abbr : "Mar",
				standard : "March"
			},
			{
				abbr : "Apr",
				standard : "April"
			},
			{
				abbr : "May",
				standard : "May"
			},
			{
				abbr : "Jun",
				standard : "June"
			},
			{
				abbr : "Jul",
				standard : "July"
			},
			{
				abbr : "Aug",
				standard : "August"
			},
			{
				abbr : "Sep",
				standard : "September"
			},
			{
				abbr : "Oct",
				standard : "October"
			},
			{
				abbr : "Nov",
				standard : "November"
			},
			{
				abbr : "Dec",
				standard : "December"
			}
		]
	}
	if ( Date.suffixes === undefined ) {
		Date.suffixes = [ "st", "nd", "rd", "th" ];
	}
	if ( Date.now === undefined ) {
		Date.now = function ( ) {
			return new Date().getTime();
		}
	}
	if ( Date.fromUTC === undefined ) {
		Date.fromUTC = ( function ( ) {
			var p = function ( n ) {
				return parseInt ( (""+n).replace(/^0*(.*)$/,'$1') || 0 );
			}
			return function ( ) {
				if ( arguments.length == 1 && arguments[0] instanceof Date ) {
					var d = arguments[0];
					return arguments.callee (
						d.getFullYear(), d.getMonth()-1, d.getDate(),
						d.getHours(), d.getMinutes(), d.getSeconds()
					);
				} else if ( arguments.length == 1 && typeof arguments[0] == "string" ) {
					var tsData = arguments[0].split(/[- :T]/);
					if ( tsData.length == 6 ) {
						tsData[1] = p(tsData[1]) - 1;
						return arguments.callee.apply ( null, tsData );
					} else {
						return null;
					}
				} else {
					var d = new Date();
					var n;
					var params = [ "FullYear", "Month", "Date", "Hours", "Minutes", "Seconds" ];
					for ( var i = 0; i < params.length; i ++ ) {
						if ( !isNaN(n=p(arguments[i]) ) )
							d["setUTC"+params[i]](n);
					}
					return d;
				}
			}
		} )();
	}
	if ( Date.create === undefined ) {
		Date.create = ( function ( ) {
			var p = function ( n ) {
				return parseInt ( (""+n).replace(/^0*(.*)$/,'$1') || 0 );
			}
			return function ( ) {
				var a = arguments;
				return new Date ( p(a[0]), p(a[1]), p(a[2]), p(a[3]), p(a[4]), p(a[5]) );
			}
		} )();
	}
	if ( Date.prototype.copy === undefined ) {
		Date.prototype.copy = function ( ) {
			return new Date ( this.getTime() );
		}
	}
	if ( Date.prototype.getISOYear === undefined ) {
		Date.prototype.getISOYear = function ( ) {
			var weekNum = this.getWeekOfYear();
			return ( weekNum > 4 && this.getMonth() == 0 ) ? this.getFullYear() - 1 : this.getFullYear();
		}
	}
	if ( Date.prototype.getTimezoneOffsetHours === undefined ) {
		Date.prototype.getTimezoneOffsetHours = function ( ) {
			return parseInt ( this.getTimezoneOffset() / 60 );
		}
	}
	if ( Date.prototype.getTimezoneOffsetMinutes === undefined ) {
		Date.prototype.getTimezoneOffsetMinutes = function ( ) {
			var o = this.getTimezoneOffset();
			return o - parseInt ( o / 60 ) * 60;
		}
	}
	if ( Date.prototype.getAmPm === undefined ) {
		Date.prototype.getAmPm = function ( ) {
			return ( this.getHours() < 12 ) ? "AM" : "PM";
		}
	}
	if ( Date.prototype.getDayNameAbbr === undefined ) {
		Date.prototype.getDayNameAbbr = function ( ) {
			var d = ( ( this.getDay() + 6 ) % 7 );
			return Date.days[d].abbr || Date.days[d].standard.substring(0,3);;
		}
	}
	if ( Date.prototype.getDayName === undefined ) {
		Date.prototype.getDayName = function ( ) {
			var d = ( ( this.getDay() + 6 ) % 7 );
			return Date.days[d].standard;
		}
	}
	if ( Date.prototype.getMonthNameAbbr === undefined ) {
		Date.prototype.getMonthNameAbbr = function ( ) {
			return Date.months[this.getMonth()].abbr || Date.months[this.getMonth()].standard.substring(0,3);;
		}
	}
	if ( Date.prototype.getMonthName === undefined ) {
		Date.prototype.getMonthName = function ( ) {
			return Date.months[this.getMonth()].standard;
		}
	}
	if ( Date.prototype.getSuffix === undefined ) {
		Date.prototype.getSuffix = function ( ) {
			var l1 = parseInt((""+this.getDate()).charAt(-1));
			var l2 = parseInt((""+this.getDate()).replace(/^.*(\d\d)$/,'$1'));
			return Date.suffixes[(l2-10==l1)?3:Math.min(l1,4)-1];
		}
	}
	if ( Date.prototype.getDifference === undefined ) {
		Date.prototype.getDifference = function ( d ) {
			d = d || new Date();
			var difference = d.getTime() - this.getTime();
			switch ( ( arguments[1] || "" ).toString().toLowerCase() ) {
				case "days":
				case "d":
					return Math.ceil ( difference / ( 1000 * 60 * 60 * 24 ) );
					break;
				case "hours":
				case "h":
					return Math.ceil ( difference / ( 1000 * 60 * 60 ) );
					break;
				case "minutes":
				case "mins":
				case "m":
					return Math.ceil ( difference / ( 1000 * 60 ) );
					break;
				case "seconds":
				case "secs":
				case "s":
					return Math.ceil ( difference / ( 1000 ) );
					break;
				default:
					return difference;
			}
		}
	}
	if ( Date.prototype.getDaysUntil === undefined ) {
		Date.prototype.getDaysUntil = function ( d ) {
			d = ( d || new Date() ).copy();
			d.setHours(this.getHours(), this.getMinutes(), this.getSeconds(), this.getMilliseconds());
			var diff = d.getTime() - this.getTime();
			return Math.round ( diff / ( 1000 * 60 * 60 * 24 ) );
		};
	}
	if ( Date.prototype.getDayOfYear === undefined ) {
		Date.prototype.getDayOfYear = function ( ) {
			return this.getDaysUntil ( new Date ( this.getFullYear(), 0, 1 ) ) * -1;
		};
	}
	if ( Date.prototype.getWeekOfYear === undefined ) {
		Date.prototype.getWeekOfYear = function ( ) {
			var d = new Date ( this.getFullYear(), 0, 1 );
			d.setDate ( d.getDate() + ( 8 - d.getDay() ) );
			var days = this.getDaysUntil ( d ) * -1;
			var week = Math.floor ( days / 7 );
			return week < 0 ? 53 + week : week;
		}
	}
	if ( Date.prototype.getDaysInMonth === undefined ) {
		Date.prototype.getDaysInMonth = function ( ) {
			return new Date ( this.getFullYear(), this.getMonth() + 1, 0 ).getDate();
		};
	}
	if ( Date.prototype.isLeapYear === undefined ) {
		Date.prototype.isLeapYear = function ( ) {
			return ( new Date ( this.getFullYear(), 1, 1 ).getDaysInMonth() ) == 29;
		}
	}
	Date.prototype.toString = ( function ( ) {
		var toString = Date.prototype.toString;
		return function ( ) {
			if ( !arguments.length ) return toString.call ( this );
			if ( arguments.length > 1 ) {
				var a = [];
				for ( var i = 0; i < arguments.length; i ++ ) {
					a.push ( this.toString ( arguments[i] ) );
				}
				return a;
			}
			var input = arguments[0].toString();
			var date = this;
			var output = input.replace ( /%(.)/g, function ( ) {
				switch ( arguments[1] ) {
					// Day
					case "d":
						return (""+date.getDate()).lpad(2,"0"); break;
					case "D":
						return date.getDayNameAbbr();
					case "j":
						return date.getDate(); break;
					case "l":
						return date.getDayName(); break;
					case "N":
						return ( ( ( date.getDay() + 6 ) % 7 ) + 1 ); break;
					case "S":
						return date.getSuffix(); break;
					case "w":
						return date.getDay(); break;
					case "z":
						return date.getDayOfYear(); break;

					// Week
					case "W":
						return date.getWeekOfYear(); break;

					// Month
					case "F":
						return date.getMonthName(); break;
					case "m":
						return ("0"+(date.getMonth()+1)).replace(/.*(\d\d)$/,'$1'); break;
					case "M":
						return date.getMonthNameAbbr(); break;
					case "n":
						return date.getMonth()+1; break;
					case "t":
						return date.getDaysInMonth(); break;

					// Year
					case "L":
						return date.isLeapYear() ? 1 : 0; break;
					case "o":
						return date.getISOYear(); break;
					case "Y":
						return date.getFullYear(); break;
					case "y":
						return (""+date.getFullYear()).replace(/^.*(\d\d)$/,'$1'); break;

					// Time
					case "a":
						return date.getAmPm().toLowerCase(); break;
					case "A":
						return date.getAmPm();
					case "B":
						return "B"; break; // not implemented
					case "g":
						return ( ( ( ( date.getHours() % 12 ) + 11 ) % 12 ) + 1 ); break;
					case "G":
						return date.getHours(); break;
					case "h":
						return (""+(date.getHours()%12)).lpad(2,"0"); break;
					case "H":
						return (""+date.getHours()).lpad(2,"0"); break;
					case "i":
						return (""+date.getMinutes()).lpad(2,"0"); break;
					case "s":
						return (""+date.getSeconds()).lpad(2,"0"); break;
					case "u":
						return date.getMilliseconds(); break;

					// Timezone
					case "e":
						return "e"; break; // not implemented
					case "I":
						return "I"; break; // not implemented
					case "P":
						var seperator = ":"; // Yes, 'P' should follow 'O', but
											 // it's more efficient this way!
					case "O":
						var seperator = seperator || "";
						var to = date.getTimezoneOffset() * -1;
						var h = parseInt ( Math.abs ( to / 60 ) );
						var m = parseInt ( Math.abs ( to ) - h * 60 );
						return ( to < 0 ? "-" : "+" ) + (""+h).lpad(2,"0") + seperator + (""+m).lpad(2,"0"); break;
					case "T":
						return "T"; break; // not implemented
					case "Z":
						return date.getTimezoneOffset() * 60; break;

					// Full Date/Time
					case "c":
						return date.toString("%Y-%m-%dT%H:%i:%s%P"); break;
					case "r":
						return date.toString("%D, %j %M %Y %H:%i:%s %O"); break;
					case "U":
						return parseInt ( date.getTime() / 1000 ); break;

					default:
						return arguments[1];
				}
			} );
			return output;
		}
	} )();

} )();

if ( !document.whenReady ) {
	document.whenReady = ( function ( ) {
		var fnStack = [];
		function init() {
			// quit if this function has already been called
			if (arguments.callee.done) return;
			// flag this function so we don't do the same thing twice
			arguments.callee.done = true;
			// kill the timer
			if (_timer) clearInterval(_timer);
			// do stuff
			//alert ( fnStack.length );
			for ( var i = 0; i < fnStack.length; i ++ ) {
				var fn = fnStack[i];
				if ( typeof fn == "function" ) {
					try {
						//alert ( fn );
						fn();
					} catch ( ex ) {
						var a = [];
						for ( e in ex ) {
							a.push ( e + ": " + ex[e] );
						}
						alert ( a.join ( "\n" ) );
						//console.log ( ex );
						debugger;
					}
				}
			}
			/*
			while ( typeof ( fn = fnStack.shift() ) == "function" ) {
				try {
					alert ( fn );
					fn();
				} catch ( ex ) {
					alert ( ex );
				}
			}
			*/
		};

		/* for Mozilla/Opera9 */
		if (document.addEventListener) {
			document.addEventListener("DOMContentLoaded", init, false);
		}

		/* for Internet Explorer */
		/*@cc_on @*/
		/*@if (@_win32)
		document.write("<script id=__ie_onload defer src=javascript:void(0)><\/script>");
		var script = document.getElementById("__ie_onload");
		script.onreadystatechange = function() {
			if (this.readyState == "complete") {
				init(); // call the onload handler
			}
		};
		/*@end @*/

		/* for Safari */
		if (/WebKit/i.test(navigator.userAgent)) { // sniff
			var _timer = setInterval(function() {
				if (/loaded|complete/.test(document.readyState)) {
					init(); // call the onload handler
				}
			}, 10);
		}

		/* for other browsers */
		window.onload = init;

		var f = function ( fn ) {
			for ( var i = 0, fn; fn = arguments[i]; i ++ ) {
				if ( typeof fn == "function" ) {
					if ( init.done ) {
						fn();
					} else {
						fnStack.push ( fn );
					}
				} else if ( fn instanceof Array ) {
					f.apply ( this, fn );
				}
			}
		}

		return f;
	} )();
}

window.scrollTo = ( function ( ) {
	var scrollTo = window.scrollTo;
	var last;
	function getScroll() {
		var scrOfX = 0, scrOfY = 0;
		if( typeof( window.pageYOffset ) == 'number' ) {
			//Netscape compliant
			scrOfY = window.pageYOffset;
			scrOfX = window.pageXOffset;
		} else if( document.body && ( document.body.scrollLeft || document.body.scrollTop ) ) {
			//DOM compliant
			scrOfY = document.body.scrollTop;
			scrOfX = document.body.scrollLeft;
		} else if( document.documentElement && ( document.documentElement.scrollLeft || document.documentElement.scrollTop ) ) {
			//IE6 standards compliant mode
			scrOfY = document.documentElement.scrollTop;
			scrOfX = document.documentElement.scrollLeft;
		}
		return { x : scrOfX, y : scrOfY };
	}
	function scroll ( ) {
		var cur = getScroll();
		//console.log ( [ cur, last ] );
		if ( ( cur.x == last.x ) && ( cur.y == last.y ) ) {
			return clearInterval ( interval );
		}
		last = cur;

		var xDiff = to.x - cur.x;
		var yDiff = to.y - cur.y;

		var xDiff_a = Math.abs ( xDiff );
		var yDiff_a = Math.abs ( yDiff );
		
		var xDelta = Math.min ( Math.abs ( to.xStep ), xDiff_a );
		var yDelta = Math.min ( Math.abs ( to.yStep ), yDiff_a );
		
		//console.log ( [ xDelta, yDelta ] );

		window.scrollBy ( xDelta  * ( xDiff / xDiff_a ), yDelta * ( yDiff / yDiff_a ) );
		
		cur = getScroll();
		if ( cur.x == to.x && cur.y == to.y ) {
			clearInterval ( interval );
		}
	}
	var interval;
	var to = { x : 0, y : 0, xStep : 0, yStep : 0 };
	return function ( x, y, smooth ) {
		clearInterval ( interval );
		last = { x : -100, y : 0 };

		if ( !smooth )
			return scrollTo ( x, y );

		var cur = getScroll();
		var xDiff = parseInt ( x ) - cur.x;
		var yDiff = parseInt ( y ) - cur.y;
		var zDiff = Math.max ( Math.abs ( xDiff ), Math.abs ( yDiff ) );
		
		var step = zDiff / 160 * 25;
		var steps = Math.round ( zDiff / step );
		
		to = {
			x : parseInt ( x ),
			y : parseInt ( y ),
			xStep : isNaN ( xDiff / steps ) ? 0 : xDiff / steps,
			yStep : isNaN ( yDiff / steps ) ? 0 : yDiff / steps
		};
		
		//console.log ( [ cur, xDiff, yDiff, step, steps, to ] );

		if ( isNaN ( to.x ) || isNaN ( to.y ) || ( ( to.xDiff == 0 ) && ( to.yDiff == 0 ) ) ) return false;
		
		interval = setInterval ( scroll, 1 );
	}
} )();

var __ADD_MODULE__ = ( function ( ) {

	var cache = {};
	var resolveNamespace = function ( ns, root ) {
		if ( root ) root = resolveNamespace ( root );
		if ( !ns ) return root;
		if ( typeof ns == "string" ) {
			if ( cache[ns] ) return cache[ns];
			var _ns = ns.split(f.options.separator||".");
			var r = root || window;
			for ( var i = 0; i < _ns.length; i ++ ) {
				if ( r[_ns[i]] === undefined ) r[_ns[i]] = {};
				if ( typeof r[_ns[i]] != "object" ) return false;
				r = r[_ns[i]];
			}
			cache[ns] = r;
			ns = r;
		}
		return ns;
	}
	var f = function ( name, module, ns ) {
		if ( !name || !module ) return false;
		ns = resolveNamespace ( ns, f.options.root || window );
		if ( typeof ns != "object" ) return false;
		ns[name] = module;
		return module;
	}
	f.options = {
		root : null,
		separator : "."
	}
	return f;

} )();

__ADD_MODULE__ ( "CACHE", ( function ( ) {

	// this is ultimately where we're putting *everything*!
	var cache = { };

	function getCache ( k, c ) {
		if ( !c ) return undefined;
		var key = k.replace(/(^\/|\/$)/,'').split('/');
		k = key.shift();
		if ( key.length ) {
			return getCache ( key.join('/'), c[k] );
		} else {
			return c[k];
		}
	}

	function setCache ( k, v, c ) {
		var key = k.replace(/(^\/|\/$)/,'').split('/');
		k = key.shift();
		if ( key.length ) {
			if ( !c[k] ) c[k] = {};
			return setCache ( key.join('/'), v, c[k] );
		} else {
			var o = c[k];
			c[k] = v;
			return o;
		}
	}

	function dropCache ( k, c ) {
		var key = ( k ? k.replace(/(^\/|\/$)/g,'').split('/') : [] );
		k = key.shift();
		if ( key.length ) {
			if ( !c[k] ) return false;
			return dropCache ( key.join('/'), c[k] );
		} else {
			if ( !k ) {
				delete c;
				c = {};
			} else {
				delete c[k];
			}
			return true;
		}
	}

	// return a simplified reference for the outside world
	return {
		get : function ( key ) {
			return getCache ( key, cache );
		},
		set : function ( key, value ) {
			return setCache ( key, value, cache );
		},
		drop : function ( key ) {
			return dropCache ( key, cache );
		}
	};
} )() );

__ADD_MODULE__ ( "SERVER", ( function ( ) {

	var createXHRObj = ( function ( ) {
		if ( typeof XMLHttpRequest != "undefined" ) {
			return function ( ) {
				return new XMLHttpRequest();
			}
		} else if ( typeof ActiveXObject != "undefined" ) {
			return function ( ) {
				try {
					return new ActiveXObject("Msxml2.XMLHTTP");
				} catch ( e ) {
					try {
						return new ActiveXObject("Microsoft.XMLHTTP");
					} catch ( e ) {
						return null;
					}
				}
			}
		} else {
			return function ( ) {
				return null;
			}
		}
	} )();

	var buildData = function ( data ) {
		if ( data === undefined ) return null;
		var template = arguments[1] || "%k=%v";
		switch ( typeof data ) {
			case "object":
				var a = [];
				if ( data instanceof Array ) {
					for ( var i = 0; i < data.length; i ++ ) {
						a.push ( buildData ( data[i], template.replace(/%k/g,"["+i+"]%k")) );
					}
				} else {
					for ( i in data ) {
						a.push ( buildData ( data[i], template.replace(/%k/g,"["+i+"]%k") ) );
					}
				}
				data = a.join("&").replace(/(^|&)\[([^\]]+)\]/g,'$1$2');
				break;
			case "boolean":
				data = data ? 1 : 0;
			case "string":
			default:
				data = template.replace(/%v/g,data);
		}
		return data.replace(/%k|%v|\[\]/g,'').replace(/^=/g,'');
	}

	var isCache = function ( c ) {
		return ( c && ( typeof c.get == "function" ) && ( typeof c.set == "function" ) );
	}

	var handleResponse = function ( r, o ) {
		var f = [ ( r.error ? "onError" : "onSuccess" ), "onComplete" ];
		
		for ( var i = 0; i < f.length; i ++ ) {
			if ( typeof o[f[i]] == "function" ) o[f[i]](r);
			try { $_[f[i]](r); } catch ( ex ) { }
		}
	}

	var getHeaders = function ( xhr ) {
		var headers = xhr.getAllResponseHeaders();
		headers = headers.split(/\n/g);
		var h = {};
		for ( var i = 0; i < headers.length; i ++ ) {
			var header = headers[i].match(/^([^:]+)\s*:\s*(.*)$/);
			if ( header && header[1] ) h[header[1]] = header[2]
		}
		return h;
	}

	var doRequest = function ( method, url, options ) {
		options = options || {};

		method = method.toUpperCase();
		if ( method == "POST" ) options.useCache = options.useCache || false;
		if ( ( url.charAt(0) != "/" ) && ( url.indexOf('http://') != 0 ) ) url = $_.options.root + url;
		var data = buildData ( options.data );

		var cache = null;
		if ( isCache ( $_.options.cache ) ) cache = $_.options.cache;
		if ( isCache ( options.cache ) ) cache = options.cache;
		
		var cacheKey = "server/" + method.toUpperCase() + "::" + url.replace(/\//g,"_*_");
		if ( data ) cacheKey += "/" + escape ( data );

		var response = null;
		
		if ( cache && ( options.useCache !== false ) ) response = cache.get ( cacheKey );
		
		if ( response ) {
			setTimeout ( function ( ) {
				response.fromCache = true;
				handleResponse ( response, options );
			}, 1 );
			return;
		}

		response = {
			error : 0,
			response : null,
			responseXML : null,
			url : url,
			method : method,
			fromCache : false,
			headers : {}
		}

		var xhr = createXHRObj();
		xhr.onreadystatechange = function ( ) {
			try { options.onChange() } catch ( ex ) { };
			try { $_.onChange() } catch ( ex ) { };
			if ( xhr.readyState == 4 ) {
				response.headers = getHeaders ( xhr );
				if ( xhr.status == 200 ) {
					response.response = xhr.responseText;
					response.responseXML = xhr.responseXML;
					handleResponse ( response, options );
				} else {
					response.response = xhr.statusText;
					response.error = xhr.status;
					handleResponse ( response, options );
				}
				if ( cache ) cache.set ( cacheKey, response );
			}
		}
		switch  ( method ) {
			case "POST":
				var charset = options.charset || "UTF-8";
				options.headers = options.headers || {};
				options.headers['Content-Type'] = "application/x-www-form-urlencoded; charset=" + charset;
				break;
			case "GET":
			default:
				if ( data != null ) url += "?" + escape ( data );
		}
		xhr.open ( method, url.replace(/#.*$/,""), true );
		if ( options.headers ) {
			for ( h in options.headers ) {
				//alert ( h + "\n\n" + options.headers[h] );
				xhr.setRequestHeader(h,options.headers[h]);
			}
		}
		xhr.send ( data );
	}

	var $_ = {
		get : function ( url, options ) {
			doRequest ( "GET", url, options );
		},
		post : function ( url, options ) {
			options = options || {};
			doRequest ( "POST", url, options );
		},
		submit : function ( form, options ) {
			if ( options == null ) options = {};
			if ( typeof form == "string" ) form = document.getElementById('form');
			if ( !form.nodeName || ( form.nodeName.toUpperCase() != "FORM" ) ) return;

			var data = {};
			for ( var i = 0, element; element = form.elements[i]; i ++ ) {
				var name = element.nodeName.toLowerCase();
				var type = element.type ? element.type.replace(new RegExp(name+"-?"),"") : "";
				var key = name + ( type ? ":" + type : "" );
				var value = null;
				switch ( key ) {
					case "input:text":
					case "input:hidden":
					case "textarea":
					case "select:one":
						value = element.value;
						break;
					case "select:multiple":
						break;
					case "input:radio":
					case "input:checkbox":
						if ( element.checked ) {
							value = ( element.value || "checked" );
						};
						break;
					default:
						//alert ( key );
				}
				if ( element.name && value != null ) {
					data[element.name] = escape ( value );
				}
			}
			
			options.data = data;
			doRequest ( form.method.toUpperCase(), form.action || document.location.href, options );
		},
		onChange : function ( ) {},
		onComplete : function ( ) {},
		onSuccess : function ( ) {},
		onError : function ( ) {},
		options : {
			root : document.location.pathname.replace(/\/[^\/]*$/,'/'),
			cache : window.CACHE
		}
	}

	$_.submit.prepare = function ( form ) {
		if ( form.prepared ) return;
		form.prepared = true;
		var elements = form.getElementsByTagName('input');
		for ( var i = 0, element; element = elements[i]; i ++ ) {
			switch ( element.type.toLowerCase() ) {
				case "image":
					window.EVENTS.add ( "click", function ( e ) {
						// don't rely on this - layerX/Y is not standard!
						var x = this.input_x;
						if ( x && x.parentNode ) x.parentNode.removeChild ( x );
						var y = this.input_y;
						if ( y && y.parentNode ) y.parentNode.removeChild ( y );
						
						this.input_x = window.DOM.create ( "input", {
							"type" : "hidden",
							"parent" : form,
							"name" : "x",
							"value" : e.layerX
						} );
						this.input_y = window.DOM.create ( "input", {
							"type" : "hidden",
							"parent" : form,
							"name" : "y",
							"value" : e.layerY
						} );
					}, element );
				case "submit":
					window.EVENTS.add ( "click", function ( e ) {
						var input = this.input;
						if ( input && input.parentNode ) input.parentNode.removeChild ( input );
						
						if ( this.name ) {
							this.input = window.DOM.create ( "input", {
								"type" : "hidden",
								"parent" : form,
								"name" : this.name,
								"value" : this.value
							} );
						}
					}, element );
					break;
			}
		}
	}
	$_.submit.prepareAll = function ( ) {
		for ( var i = 0; i < document.forms.length; i ++ ) {
			$_.submit.prepare ( document.forms[i] );
		}
	}

	return $_;

} )() );

__ADD_MODULE__ ( "JSON", ( function ( ) {

	// Format integers to have at least two digits.
	function f(n) {
		return n < 10 ? '0' + n : n;
	}

	// Eventually, this method will be based on the date.toISOString
	// method.
	Date.prototype.toJSON = function () {
		return this.getUTCFullYear()   + '-' +
			f(this.getUTCMonth() + 1) + '-' +
			f(this.getUTCDate())      + 'T' +
			f(this.getUTCHours())     + ':' +
			f(this.getUTCMinutes())   + ':' +
			f(this.getUTCSeconds())   + 'Z';
	};

	// table of character substitutions
	var m = {
		'\b': '\\b',
		'\t': '\\t',
		'\n': '\\n',
		'\f': '\\f',
		'\r': '\\r',
		'"' : '\\"',
		'\\': '\\\\'
	};

	function encode(value, whitelist) {
		var a,          // The array holding the partial texts.
			i,          // The loop counter.
			k,          // The member key.
			l,          // Length.
			r = /["\\\x00-\x1f\x7f-\x9f]/g,
			v;          // The member value.

		switch (typeof value) {
			case '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 sequences.
				if ( r.test(value) ) {
					return '"'
						+ value.replace(r, function (a) {
							var c = m[a];
							if (c) {
								return c;
							}
							c = a.charCodeAt();
							return '\\u00'
								+ Math.floor(c / 16).toString(16)
								+ (c % 16).toString(16);
						})
						+ '"';
				} else {
					return ( '"' + value + '"' );
				}
		case 'number':
		// JSON numbers must be finite. Encode non-finite numbers as
		// null.
			return isFinite(value) ? String(value) : 'null';
		case 'boolean':
		case 'null':
			return String(value);
		case 'object':
			// Due to a specification blunder in ECMAScript, typeof null
			// is 'object', so watch out for that case.
			if (!value) return 'null';

			// If the object has a toJSON method, call it, and encode
			// the result.
			if (typeof value.toJSON === 'function') {
				return encode(value.toJSON());
			}

			a = [];

			// If the object is an array. Stringify every element. Use
			// null as a placeholder for non-JSON values.
			if (typeof value.length === 'number' &&
					!(value.propertyIsEnumerable('length'))) {

				l = value.length;
				for (i = 0; i < l; i += 1) {
					a.push(encode(value[i], whitelist) || 'null');
				}
				// Join all of the elements together and wrap them in
				// brackets.
				return '[' + a.join(',') + ']';
			}

			// If a whitelist (array of keys) is provided, use it to
			// select the components of the object.
			if (whitelist) {
				l = whitelist.length;
				for (i = 0; i < l; i += 1) {
					k = whitelist[i];
					if (typeof k === 'string') {
						v = encode(value[k], whitelist);
						if (v) {
							a.push(encode(k) + ':' + v);
						}
					}
				}
			// Otherwise, iterate through all of the keys in the object.
			} else {
				for (k in value) {
					if (typeof k === 'string') {
						v = encode(value[k], whitelist);
						if (v) {
							a.push(encode(k) + ':' + v);
						}
					}
				}
			}
			// Join all of the member texts together and wrap them in
			// braces.
			return '{' + a.join(',') + '}';
		}
	}

	return {
		encode: encode,
		decode: function (text, filter) {
			var j;

			function walk(k, v) {
				var i, n;
				if (v && typeof v === 'object') {
					for (i in v) {
						if (Object.prototype.hasOwnProperty.apply(v, [i])) {
							n = walk(i, v[i]);
							if (n !== undefined) {
								v[i] = n;
							}
						}
					}
				}
				return filter(k, v);
			}

	// Parsing happens in three stages. In the first 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 first stage into 4 regexp operations in order to work around
	// crippling inefficiencies in IE's and Safari's regexp engines. First we
	// replace all 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(/\\./g, '@').
					replace(/"[^"\\\n\r]*"|true|false|null|-?\d+(?:\.\d*)?(?:[eE][+\-]?\d+)?/g, ']').
					replace(/(?:^|:|,)(?:\s*\[)+/g, ''))) {

	// In the second 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 third stage, we recursively walk the new structure,
	// passing each name/value pair to a filter function for possible
	// transformation.

				return typeof filter === 'function' ? walk('', j) : j;
			}

			// If the text is not JSON parseable, then a SyntaxError is thrown.

			throw new SyntaxError('parseJSON');
		}
	};
} )() );

__ADD_MODULE__ ( "cookies", ( function ( ) {

	var all = function ( ) {
		var cookies = document.cookie.split ( /\s*;\s*/ );
		var all = {};
		for ( var i = 0, cookie; cookie = cookies[i]; i ++ ) {
			var parts = cookie.split("=");
			var value = parts[1];
			try { value = eval ( "(" + parts[1] + ")" ); } catch ( e ) { }
			all[parts[0]] = value;
		}
		return all;
	}
	var get = function ( key ) {
		return all()[key];
	}
	var set = function ( key, value, days ) {
		var expires = "";
		if ( days ) {
			var date = new Date();
			date.setTime ( date.getTime() + ( days * 24 * 60 * 60 * 1000 ) );
			var expires = "; expires=" + date.toGMTString();
		}
		document.cookie = key+"="+value+expires+"; path=/";
		return value;
	}

	return {
		all : function ( ) {
			return all();
		},
		get : function ( key ) {
			return get(key);
		},
		set : function ( key, value, days ) {
			return set(key,value,days);
		},
		remove : function ( key ) {
			if ( arguments.length == 1 ) {
				return set ( key, "", -1 );
			} else {
				var ok = true;
				for ( var i = 0; i < arguments.length; i ++ ) {
					ok = ok && set ( arguments[i], "", -1 );
				}
				return ok;
			}
		},
		toString : function ( ) {
			return document.cookie;
		}
	}

} )(), "UTILS" );

__ADD_MODULE__ ( "DOM", ( function ( ) {

	var $_ = {
		create : function ( el, options ) {
			options = options || {};
			if ( el == "text" ) {
				el = document.createTextNode(options.value||"");
			} else {
				el = document.createElement(el);
			}
			if ( !el ) return null;
            if ( options.autoremove && ( el.nodeName.toLowerCase() == "script" ) ) {
                el.onload = function ( ) { this.parentNode.removeChild ( this ); }
                el.onerror = function ( ) { this.parentNode.removeChild ( this ); }
                delete options.autoremove;
            }

			for ( e in options ) {
				try {
					if ( ( e.charAt(0) != "_" ) || ( typeof options[e] != "function" ) ) {
						switch ( e ) {
							case "class":
								el.className = options[e];
								break;
							case "style":
								for ( s in ( options[e] || {} ) ) {
									el.style[s] = options[e][s];
								}
								break;
                            case "insertAfter":
                                var sibling = options[e];
                                if ( typeof sibling == "string" )
                                    sibling = document.getElementById(sibling);
                                if ( sibling && sibling.nodeName && sibling.parentNode )
                                    sibling.parentNode.insertBefore ( el, sibling.nextSibling );
                                break;
                            case "insertBefore":
                                var sibling = options[e];
                                if ( typeof sibling == "string" )
                                    sibling = document.getElementById(sibling);
                                if ( sibling && sibling.nodeName && sibling.parentNode )
                                    sibling.parentNode.insertBefore ( el, sibling );
                                break;
							case "parent":
								var parent = options[e];
								if ( typeof parent == "string" )
									parent = document.getElementById(parent);
								if ( parent && parent.nodeName )
									parent.appendChild ( el );
								break;
                            case "replace":
                                var replace = options[e];
                                if ( typeof replace == "string" )
                                    replace = document.getElementById(replace);
                                if ( replace && replace.nodeName && replace.parentNode )
                                    replace.parentNode.replaceChild ( el, replace );
                                break;
							case "child":
							case "children":
								var children = options[e];
								if ( !(children instanceof Array) ) children = [ children ];
								for ( var i = 0; i < children.length; i ++ ) {
									var child = children[i];
									if ( typeof child == "string" )
										child = document.getElementById(child);
									if ( child && child.nodeName )
										el.appendChild ( child );
									//else
									//	console.log ( [ el, child ] );
								}
								break;
							case "innerHTML":
								if ( typeof options[e] == "string" )
									el[e] = options[e];
								break;
							default:
                                el[e] = options[e];
						}
					} else if ( typeof options[e] == "function" ) {
						options[e.substr(1)] = options[e]();
					}
				} catch ( e ) { }
			}
			return el;
		},
        parse : function ( html ) {
            if ( !html || !html.length ) return [];

            var match = html.match ( /^([^<]*)(<\w+.*)?$/ );
            var text = match[1];
            html = match[2];

            var nodes = [];

            if ( text.length )
                nodes.push ( window.DOM.create ( "text", { value : text } ) );

            if ( html && html.length ) {
                var node = html.match ( /^<(\w+)([^>]*)(\/?)>(.*)$/ );

                var tag = node[1];
                var attributes = ( function ( a ) {
                    var r = {};
                    for ( var i = 0; i < a.length; i ++ ) {
                        var m = (a[i]+'"').match ( /^\s*(\w+)="(.*)"\s*$/ );
                        r[m[1]] = m[2].replace(/"\s*$/,'');
                    }
                    return r;
                } )(node[2] ? node[2].split(/"\s+/) : []);
                
                var children = [];
                if ( !node[3] ) {
                    var match = node[4].match ( new RegExp ( "^(.*)</"+tag+">(.*)$" ) ) || [];
                    children = arguments.callee ( match[1] );
                    html = match[2];
                }
                
                attributes.children = children;
                nodes.push ( window.DOM.create ( tag, attributes ) );
                
                if ( html && html.length )
                    nodes = nodes.concat ( arguments.callee ( html ) );
            }

            return nodes;
        },
		getPosition : function ( el ) {
			var offset = 0;
			var x = 0, y = 0;
			var w = el.offsetWidth;
			var h = el.offsetHeight;
			while (el != null) {
				y += el.offsetTop;
				x += el.offsetLeft;
				el = el.offsetParent;
			}
			return { top: y, left: x, width: w, height: h };
		},
		getStyle : function ( el, property, asInt ) {
			if ( typeof el == "string" ) el = document.getElementById(el);
			var x = null;
			if (el.currentStyle)
				x = el.currentStyle[property];
			if (window.getComputedStyle)
				x = document.defaultView.getComputedStyle(el,null).getPropertyValue(property);
			return asInt ? ( isNaN ( parseInt ( x ) ) ? 0 : parseInt ( x ) ) : x;
		},
		getWindowHeight : function ( ) {
			if (window.self && self.innerHeight) {
				return self.innerHeight
			}
			if (document.documentElement && document.documentElement.clientHeight) {
				return document.documentElement.clientHeight;
			}
			return 0;
		},
		setOpacity : function ( el, opacity ) {
			opacity = parseInt ( opacity );
			if ( opacity >= 100 ) opacity = 99.999;
			if ( opacity < 0 ) opacity = 0;
	
			el.opacity = opacity;
			el.style.opacity = opacity / 100;
			el.style.zoom = 1;
			el.style.filter = 'alpha(opacity=' + opacity + ')';
		},
		setStyle : function ( el, property, value ) {
			if ( !property ) return false;
			var map = property;
			if ( typeof property != "object" ) {
				map = {};
				map[property] = value;
			}
			for ( property in map ) {
				if ( ( value = map[property] ) != null ) {
					switch ( property ) {
						case "opacity":
							$_.setOpacity ( el, value ); break;
						default:
							el.style[property] = value;
					}
				}
			}
		},
		draggable : ( function ( ) {
			/**************************************************
			* dom-drag.js
			* 09.25.2001
			* www.youngpup.net
			**************************************************
			* 10.28.2001 - fixed minor bug where events
			* sometimes fired off the handle, not the root.
			**************************************************/
			var Drag = {
				obj : null,
			
				init : function(o, oRoot, minX, maxX, minY, maxY, bSwapHorzRef, bSwapVertRef, fXMapper, fYMapper) {
					o.onmousedown = Drag.start;
			
					o.hmode = bSwapHorzRef ? false : true ;
					o.vmode = bSwapVertRef ? false : true ;
			
					o.root = oRoot && oRoot != null ? oRoot : o ;
			
					if (o.hmode && isNaN(parseInt(o.root.style.left ))) o.root.style.left = "0px";
					if (o.vmode && isNaN(parseInt(o.root.style.top ))) o.root.style.top = "0px";
					if (!o.hmode && isNaN(parseInt(o.root.style.right ))) o.root.style.right = "0px";
					if (!o.vmode && isNaN(parseInt(o.root.style.bottom))) o.root.style.bottom = "0px";
			
					o.minX = ( typeof minX == "function" ) ? minX
								: ( ( typeof minX != 'undefined' ) ? function ( ) { return minX; }
								: function ( ) { return null; } );
					o.minY = ( typeof minY == "function" ) ? minY
								: ( ( typeof minY != 'undefined' ) ? function ( ) { return minY; }
								: function ( ) { return null; } );
					o.maxX = ( typeof maxX == "function" ) ? maxX
								: ( ( typeof maxX != 'undefined' ) ? function ( ) { return maxX; }
								: function ( ) { return null; } );
					o.maxY = ( typeof maxY == "function" ) ? maxY
								: ( ( typeof maxY != 'undefined' ) ? function ( ) { return maxY; }
								: function ( ) { return null; } );
			
					o.xMapper = fXMapper ? fXMapper : null;
					o.yMapper = fYMapper ? fYMapper : null;
			
					o.root.onDragStart = new Function();
					o.root.onDragEnd = new Function();
					o.root.onDrag = new Function();
				},
			
				start : function(e) {
					var o = Drag.obj = this;
					e = Drag.fixE(e);
					var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom);
					var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right );
					o.root.onDragStart(x, y);
			
					o.lastMouseX = e.clientX;
					o.lastMouseY = e.clientY;
			
					if (o.hmode) {
						if ((min=o.minX(x)) != null) o.minMouseX = e.clientX - x + parseInt(min);
						if ((max=o.maxX(x)) != null) o.maxMouseX = o.minMouseX + parseInt(max) - parseInt(min);
					} else {
						if ((min=o.minX(x)) != null) o.maxMouseX = -(parseInt(min)) + e.clientX + x;
						if ((max=o.maxX(x)) != null) o.minMouseX = -(parseInt(max)) + e.clientX + x;
					}
			
					if (o.vmode) {
						if ((min=o.minY(y)) != null) o.minMouseY = e.clientY - y + parseInt(min);
						if ((max=o.maxY(y)) != null) o.maxMouseY = o.minMouseY + parseInt(max) - parseInt(min);
					} else {
						if ((min=o.minY(y)) != null) o.maxMouseY = -(parseInt(min)) + e.clientY + y;
						if ((max=o.maxY(y)) != null) o.minMouseY = -(parseInt(max)) + e.clientY + y;
					}
			
					document.onmousemove = Drag.drag;
					document.onmouseup = Drag.end;
			
					return false;
				},
			
				drag : function(e) {
					e = Drag.fixE(e);
					var o = Drag.obj;
			
					var ey = e.clientY;
					var ex = e.clientX;
					var y = parseInt(o.vmode ? o.root.style.top : o.root.style.bottom);
					var x = parseInt(o.hmode ? o.root.style.left : o.root.style.right );
					var nx, ny;
			
					if (o.minX(x) != null) ex = o.hmode ? Math.max(ex, o.minMouseX) : Math.min(ex, o.maxMouseX);
					if (o.maxX(x) != null) ex = o.hmode ? Math.min(ex, o.maxMouseX) : Math.max(ex, o.minMouseX);
					if (o.minY(y) != null) ey = o.vmode ? Math.max(ey, o.minMouseY) : Math.min(ey, o.maxMouseY);
					if (o.maxY(y) != null) ey = o.vmode ? Math.min(ey, o.maxMouseY) : Math.max(ey, o.minMouseY);
			
					nx = x + ((ex - o.lastMouseX) * (o.hmode ? 1 : -1));
					ny = y + ((ey - o.lastMouseY) * (o.vmode ? 1 : -1));
			
					if (o.xMapper) nx = o.xMapper(y)
					else if (o.yMapper) ny = o.yMapper(x)
			
					Drag.obj.root.style[o.hmode ? "left" : "right"] = nx + "px";
					Drag.obj.root.style[o.vmode ? "top" : "bottom"] = ny + "px";
					Drag.obj.lastMouseX = ex;
					Drag.obj.lastMouseY = ey;
			
					Drag.obj.root.onDrag(nx, ny);
					return false;
				},
			
				end : function() {
					document.onmousemove = null;
					document.onmouseup = null;
					Drag.obj.root.onDragEnd( parseInt(Drag.obj.root.style[Drag.obj.hmode ? "left" : "right"]),
					parseInt(Drag.obj.root.style[Drag.obj.vmode ? "top" : "bottom"]));
					Drag.obj = null;
				},
			
				fixE : function(e) {
					if (typeof e == 'undefined') e = window.event;
					if (typeof e.layerX == 'undefined') e.layerX = e.offsetX;
					if (typeof e.layerY == 'undefined') e.layerY = e.offsetY;
					return e;
				}
			};
			var f = function ( el, options ) {
				if ( typeof el == "string" ) el = document.getElementById(el);
				if ( !el.nodeName ) return;
				
				var args = [ el ];
				var argNames = [ "root", "minX", "maxX", "minY", "maxY", "swapHorizontal", "swapVertical", "xMapper", "yMapper" ];
				for ( var i = 0, arg; arg = argNames[i]; i ++ ) {
					args.push ( options[arg] );
				}
				Drag.init.apply ( null, args );
				var argNames = [ "onDragStart", "onDrag", "onDragEng" ];
				for ( var i = 0, arg; arg = argNames[i]; i ++ ) {
					el[arg] = options[arg] || options[arg.toLowerCase()] || el[arg];
				}
			}
			return function ( el, options ) {
				document.whenReady ( function ( ) {
					f ( el, options || {} );
				} );
			}
		} )(),
		addClass : function ( el, className ) {
			if ( el && className && el.className !== undefined ) {
				var re = new RegExp ( "(^| )" + className.replace(/^\s*|\s*$/g,'') + "( |$)", "g" );
				if ( !el.className.match ( re ) ) el.className += " " + className;
			}
		},
		removeClass : function ( el, className ) {
			if ( el && className && el.className !== undefined ) {
				var re = new RegExp ( "(^| )" + className + "( |$)", "g" )
				el.className = el.className.replace ( re, '$2' );
			}
		},
		replaceClass : function ( el, classNameFrom, classNameTo ) {
			$_.removeClass ( el, classNameFrom );
			$_.addClass ( el, classNameTo );
		},
		hasClass : function ( el, className ) {
			var re = new RegExp ( "(^| )" + className.replace(/^\s*|\s*$/g,'') + "( |$)", "g" );
			return ( !!el.className.match ( re ) );
		}
	}
	return $_;

} )() );

__ADD_MODULE__ ( "EVENTS", ( function ( ) {
	return {
		add : function ( evt, fn, source ) {
			if ( !source ) source = window;
			if ( typeof source == "string" ) source = document.getElementById(source);
			var f = null;
			var m = "";
			if ( source.attachEvent ) {
				f = "attachEvent";
				m = "on";
			} else if ( source.addEventListener ) {
				f = "addEventListener";
			}
			if ( f ) {
				if ( !( evt instanceof Array ) ) {
					evt = [ evt ];
				}
				for ( var i = 0; i < evt.length; i ++ ) {
					source[f] ( m + evt[i].toString(), function ( e ) {
						fn.call ( source, e || window.event );
					}, false );
					
					/*
					if ( f.call ) {
						f.call ( source, m + evt[i].toString(), fn, false );
						if ( evt[i] == "mousewheel" ) {
							f.call ( source, "DOMMouseScroll", fn, false );
						}
					} else {
						f ( m + evt[i].toString(), fn, false );
						if ( evt[i] == "mousewheel" ) {
							f ( "DOMMouseScroll", fn, false );
						}
					}
					*/
				}
			}
		},
		remove : function ( evt, fn, source ) {
			var f = function ( ) { };
			if ( !source ) source = window;
			if ( typeof source == "string" ) source = document.getElementById(source);
			var m = "";
			if ( source.detachEvent ) {
				f = source.detachEvent;
				m = "on";
			} else if ( source.removeEventListener ) {
				f = source.removeEventListener;
			}
			if ( !( evt instanceof Array ) ) {
				evt = [ evt ];
			}
			for ( var i = 0; i < evt.length; i ++ ) {
				f ( m + evt[i].toString(), fn, false );
				if ( evt[i] == "mousewheel" ) {
					f ( "DOMMouseScroll", fn, false );
				}
			}
		},
		fire : function ( evt ) {
			// to do!
		}
	}
} )() );
