/*!

Jelly JavaScript, Copyright (c) 2008-2010 Pete Boere.

MIT style license: http://www.opensource.org/licenses/mit-license.php
project page: http://code.google.com/p/jelly-javascript/
this build compiled: 2010-08-05 

*/
;(function () {

/**

Initialization of JELLY namespace. 
Base set of shortcuts and utility functions.

*/
var J = window.JELLY = function () {
		if ( typeof __JELLY !== 'undefined' ) { 
			return null; 
		}
		J.__JELLY = 1.14; // Version
		var stack = [ 'var J=JELLY' ], mem, i = 1;
		for ( mem in J ) { 
			stack[ i++ ] = mem + '=J.' + mem;
		}
		return stack.join( ',' ) + ';';
	},
	
	// Shortcuts
	win = window,
	doc = win.document,
	nav = win.navigator,
	ua = nav.userAgent,
	docRoot = doc.documentElement,
	docHead = doc.getElementsByTagName( 'head' )[0],
	standardEventModel = 'addEventListener' in doc,
	querySelectorAll = 'querySelectorAll' in doc,
	functionLit = function () {},
	
	/**
	Browser detection
	*/	
	browser = function () {
		var activex = 'ActiveXObject' in win,
			xhr = 'XMLHttpRequest' in win,
			securityPolicy = 'securityPolicy' in nav,
			taintEnabled = 'taintEnabled' in nav,
			opera = /opera/i.test(ua),
			firefox = /firefox/i.test(ua),				
			webkit = /webkit/i.test(ua),
			ie = activex ? ( querySelectorAll ? 8 : ( xhr ? 7 : 6 ) ) : 0;
		return {
			ie: ie,
			ie6: ie === 6,
			ie7: ie === 7,
			ie8: ie === 8,
			opera: opera,
			firefox: firefox || ( securityPolicy && !activex && !opera ),
			webkit: webkit || ( !taintEnabled && !activex && !opera ),
			safariMobile: /safari/i.test( ua ) && /mobile/i.test( ua ),
			chrome: webkit && /chrome/i.test( ua )
		};
	}(),
	msie = browser.ie,
	
	/**
	Platform detection
	*/	
	platform = function () {
		var obj = {};
		obj[ ( /mac|win|linux/i.exec( nav.platform ) || [ 'unknown' ] )[0].toLowerCase() ] = true;
		return obj;
	}(),
	
	objToString = {}.toString,
	
	/**
	Check if object is defined
	*/	
	isDefined = function ( obj ) { 
		return typeof obj !== 'undefined'; 
	},
	
	/**
	Check if object is <undefined>
	*/	
	isUndefined = function ( obj ) { 
		return typeof obj === 'undefined'; 
	},
	
	/**
	Check if object is <null>
	*/	
	isNull = function ( obj ) { 
		return obj === null; 
	},
	
	/**
	Check if object is a boolean
	*/
	isBoolean = function ( obj ) { 
		return typeof obj === 'boolean'; 
	},
	
	/**
	Check if object is a string
	*/
	isString = function ( obj ) { 
		return typeof obj === 'string'; 
	},
	
	/**
	Check if object is a number
	*/
	isNumber = function ( obj ) { 
		return typeof obj === 'number'; 
	},
	
	/**
	Check if object is an integer
	*/
	isInteger = function ( obj ) { 
		return isNumber( obj ) ? !( obj % 1 ) : false; 
	}, 
	
	/**
	Check if object is a floating number
	*/
	isFloat = function ( obj ) { 
		return isNumber( obj ) ? !!( obj % 1 ) : false; 
	}, 
	
	/**
	Check if object is numeric; strings or numbers accepted
	*/
	isNumeric = function ( obj ) { 
		return isString( obj ) || isNumber( obj ) ? /^\s*\d+\.?\d*?\s*$/.test( ( obj+'' ) ) : false; 
	},
	
	/**
	Check if object is an object literal (an instance of <Object>)
	*/
	isObject = function ( obj ) { 
		return objToString.call( obj ) === '[object Object]';
	},
	
	/**
	Check if object is an object (excluding <null>) and is not an instance of <Object>
	*/
	isObjectLike = function ( obj ) { 
		return !!obj && !isObject( obj ) && ( typeof obj === 'object' || isFunction( obj ) );
	},
	
	/**
	Check if object is an instance of <Function>
	*/
	isFunction = function ( obj ) { 
		// Opera can't handle a wrapped 'return typeof === "function"'
		return objToString.call( obj ) === '[object Function]'; 
	},
	
	/**
	Check if object is an HTML Element
	*/
	isElement = function () {
		if ( !msie ) {
			return function ( obj ) {
				return /^\[object HTML[A-Za-z]*Element\]$/.test( objToString.call( obj ) );
			};
		} 
		return function ( obj ) {
			return isObjectLike( obj ) && !!obj.nodeName && obj.nodeType === 1; 
		};

	}(),
	
	/**
	Check if object is an HTML Node List
	*/
	isNodeList = function () { 
		if ( !msie ) {
			return function ( obj ) {
				return /^\[object (HTMLCollection|NodeList)\]$/.test( objToString.call( obj ) );
			};
		} 
		return function ( obj ) {
			return isObjectLike( obj ) && !isObject( obj ) && 
				!isArray( obj ) && !isFunction( obj ) && isInteger( obj.length ) && !!obj.item; 
		};
	}(),
	
	/**
	Check if object is an instance of <Array>
	*/
	isArray = function ( obj ) { 
		return Array.isArray( obj ); 
	},	
	
	/**
	Check for the existance of an object in an array
	*/
	inArray = function ( obj, arr ) { 
		return arr.indexOf( obj ) !== -1; 
	},

	/**
	Convert enumerable object to an array
	*/
	toArray = function ( obj ) {
		var result = [], n = obj.length, i = 0;
		for ( i; i < n; i++ ) { 
			result[i] = obj[i]; 
		}
		return result;
	},
	
	/**
	Check to see if object is empty; works for instances of <Object>, <Array> and <String>
	*/
	empty = function ( arg ) {
		if ( isString( arg ) ) {
			return /^\s*$/.test( arg );
		}
		else if ( isArray( arg ) ) {
			return !arg.length;
		}
		else if ( isObject( arg ) ) {
			return !Object.keys( arg ).length;
		}
		return !arg;
	},
	
	/**
	Returns a function wrapper with negated return values, useful for <Array> filter 
	
	@example 
	' foo, 12.1 bar 101 '.split( ' ' ).map( parseFloat ).filter( negate( isNaN ) )
	>>> [ 12.1, 101 ]
	*/
	negate = function ( fn ) {
		return function () {
			return !fn.apply( this, arguments );
		}
	},	
	
	/**
	Returns function wrapper with optional preset arguments, useful for <Array> map 
	
	@example 
	' foo, bar '.split( ',' ).map( String.trim ).map( preset( String.split, '' ) )
	>>> [ ['f','o','o'], ['b','a','r'] ]
	*/
	preset = function () {
		var args = toArray( arguments ),
			fn = args.shift();
		return function ( item ) {
			return fn.apply( this, [ item ].concat( args ) );
		};
	},
		
	/**
	Extend objects with the option to not overwrite defined members
	*/
	extend = function ( a, b, overwrite ) {
		for ( var mem in b ) {
			if ( isUndefined( a[ mem ] ) || isDefined( a[ mem ] ) && overwrite !== false ) {
				a[ mem ] = b[ mem ];
			}
		}
		return a;
	},
	
	/**
	Generic iterator function; works for objects, arrays and nodelists
	*/
	each = function ( obj, callback ) {
		if ( isObject( obj ) ) {
			for ( var key in obj ) { 
				callback.call( obj, key, obj[ key ] ); 
			}
		}
		else if ( obj.length ) {
			for ( var i = 0; i < obj.length; i++ ) { 
				callback.call( obj, obj[ i ], i ); 
			}
		}		
	},
	
	/**
	Defer function calls; equivilant to setTimeout( myfunc, 0 )
	*/
	defer = function () {
		var args = toArray( arguments ),
			func = args.shift(),
			scope = args.shift() || {};
		return setTimeout( function () { func.apply( scope, args ); }, 0 );
	},
	
	createLogger = function ( method ) {
		var console = win.console;
		if ( console && console[ method ] && console[ method ].apply ) {  	
			return function () {
				console[ method ].apply( console, toArray( arguments ) ); 
			};
		}
		return functionLit;
	},
	/**
	console.log wrapper
	*/
	log = createLogger( 'log' ),
	/**
	console.warn wrapper
	*/
	logWarn = createLogger( 'warn' ),
	/**
	console.error wrapper
	*/
	logError = createLogger( 'error' );
		
extend( J, {
	win: win,
	doc: doc,
	docRoot: docRoot, 
	docHead: docHead,
	functionLit: functionLit,
	browser: browser,
	platform: platform,
	isDefined: isDefined,
	isUndefined: isUndefined,
	isNull: isNull,
	isBoolean: isBoolean,
	isString: isString,
	isNumber: isNumber,
	isInteger: isInteger,
	isFloat: isFloat,
	isNumeric: isNumeric,
	isObject: isObject,	
	isFunction: isFunction,
	isElement: isElement,
	isNodeList: isNodeList,
	isArray: isArray,
   	inArray: inArray,
	toArray: toArray,
	empty: empty,
	negate: negate,	
	preset: preset,
	extend: extend,	
	each: each,
	defer: defer,
	log: log,
	logWarn: logWarn,
	logError: logError	
});

/**

Patching native support for standard object methods
Implementing ecmascript 5 features where possible	

*/
var makeGenerics = function ( constructor, methodNames ) {
	methodNames.each( function ( name ) {
		if ( !constructor[ name ] ) {
			constructor[ name ] = function () {
				var args = toArray( arguments ),
					subject = args.shift();
				return constructor.prototype[ name ].apply( subject, args );
			};
		} 
	});
};

/* ECMA script 5 */
extend( Object, {
	keys: function ( obj ) {
		var res = [], key;
		for ( key in obj ) {
			if ( obj.hasOwnProperty( key ) ) {
				res.push( key );
			}
		}
		return res;
	}
}, false);

extend( Array, {
	isArray: function ( obj ) {
		return objToString.call( obj ) === '[object Array]';
	}
}, false);


/* Array methods */
var arrayMethods = {
	forEach: function ( fn, obj ) {
		for ( var i = 0, n = this.length; i < n; i++ ) { 
			fn.call( obj, this[i], i, this ); 
		}
	},
	indexOf: function ( obj, from ) {
		from = isDefined(from) ? 
			( from < 0 ? Math.max( 0, this.length + from ) : from ) : 0;
		for ( var i = from, n = this.length; i < n; i++ ) { 
			if ( this[i] === obj ) { 
				return i; 
			} 
		}
		return -1;
	},
	filter: function ( fn, obj ) {
		for ( var i = 0, n = this.length, arr = []; i < n; i++ ) { 
			if ( fn.call( obj, this[i], i, this ) ) { 
				arr.push( this[i] ); 
			} 
		}
		return arr;
	},
	map: function ( fn, obj ) {
		for ( var i = 0, n = this.length, arr = []; i < n; i++ ) { 
			arr.push( fn.call( obj, this[i], i, this ) ); 
		}
		return arr;
	},
	some: function ( fn, obj ) {
		for ( var i = 0, n = this.length; i < n; i++ ) { 
			if ( fn.call( obj, this[i], i, this ) ) { 
				return true; 
			} 
		}
		return false;
	},
	every: function ( fn, obj ) {
		for ( var i = 0, n = this.length; i < n; i++ ) { 
			if ( !fn.call( obj, this[i], i, this ) ) { 
				return false; 
			} 
		}
		return true;
	}
};
extend( Array.prototype, arrayMethods, false );

// Common alias for convenience
Array.prototype.each = Array.prototype.forEach;

// Add Array methods as generics
makeGenerics( Array, Object.keys( arrayMethods ).concat( 'each,concat,join,pop,push,reverse,shift,splice,sort,splice,toString,unshift,valueOf'.split( ',' ) ) );


/* String methods */
var stringMethods = {
	trim: function () {
		return this.replace( /^\s\s*/, '' ).replace( /\s\s*$/, '' );
	}
}
extend( String.prototype, stringMethods, false );

// Add String methods as generics
makeGenerics( String, Object.keys( stringMethods ).concat( 'charAt,charCodeAt,concat,fromCharCode,indexOf,lastIndexOf,match,replace,search,slice,split,substr,substring,toLowerCase,toUpperCase,valueOf'.split( ',' ) ) );


/* Function methods */
extend( Function.prototype, {
	bind: function () {
		if ( arguments.length < 2 && !isDefined( arguments[0] ) ) { 
			return this; 
		}
		var args = toArray( arguments ),
			scope = args.shift(),
			fn = this; 
		return function () {
			for ( var i = 0, arr = toArray( args ); arguments.length > i; i++ ) { 
				arr.push( arguments[i] ); 
			}
			return fn.apply( scope, arr );
		};
	}
}, false);


/* HTMLElement methods */
if ( win.HTMLElement && HTMLElement.prototype ) {
	extend( HTMLElement.prototype, {
		contains: function ( el ) {
			return !!( this.compareDocumentPosition( el ) & 16 );
		}
	}, false );
}

/**

Monitor

@description 
	A means to finding bugs when no decent console API is available

*/

(function () {

var self = J.Monitor = {

	lines: 0,
	logLimit: 150,
	
	container: function () {
		var elem = doc.createElement( 'div' );
		elem.style.cssText = [
				'font: 11px consolas, "courier new", monospace',
				'position: ' + ( !browser.ie ? 'fixed' : 'absolute' ),
				'overflow: auto',
				'width: 300px',
				'height: 150px',
				'background: #000',
				'border: 1px solid #fff',
				'border-width: 0 0 1px 1px',
				'top: 0',
				'right: 0',
				'color: #fff' 
			].join(';');
		return elem;
	}(),
	
	enable: function () {
		if ( !self.loadHandler ) {
			self.loadHandler = addDomReady ( function () {
				insertElement( self.container );
			});
		} 
		return self;
	},
	
	log: function () {
		if ( self.cancel ) { return; }
		var printLine = function ( msg ) {
				self.container.innerHTML += 
					'<span style="' + [
					'display: block',
					'border-bottom: 1px solid #333',
					'padding: 5px 8px 2px',
					].join(';') + 
					'">' + 
					msg + 
					'</span>';
			},
			styler = function ( obj ) {
				var format = { tag: 'b', color: '#fff', value: obj+'' },
					tmpl = '<%{tag} style="color:%{color};">%{value}</%{tag}>';
				if ( isNumber( obj ) ) {
					format.color = 'orange';
				}
				else if ( isObject( obj ) ) {
					format.color = 'lime';
					format.value ='Object';
				}
				else if ( isElement( obj ) ) {
					format.color = 'hotpink';
					format.value = obj.nodeName + ' ' + obj;
				} 
				else if ( isNodeList( obj ) ) {
					format.color = 'tomato';
					format.value = 'NodeList ' + obj;
				}
				else if ( isFunction( obj ) ) {
					format.color = 'yellow';
				}
				return bindData( tmpl, format );	
			},
			args = toArray( arguments ).map( function ( arg ) {
				if ( isArray( arg ) ) {
					return '[' + arg.map( styler ).join(', ') + ']';
				}
				return styler( arg );
			});
				
		if ( self.lines++ < self.logLimit ) {
			printLine( args.length < 2 ? args[0] : args.join(' ') ); 
		}
		else {
			self.cancel = true;
			printLine( 'Log limit reached' );
		}
	}
};

})();

/**

Profiler

@description 
	A simple profiling utility

*/

(function () {

var self = J.Profiler = {
	
	container: function () {
		var elem = doc.createElement( 'div' );
		elem.style.cssText = [
				'position: ' + ( !browser.ie ? 'fixed' : 'absolute' ),
				'font: bold 17px arial, sans-serif',
				'max-height: 200px',
				'text-align: center',
				'overflow: auto',
				'background: #000',
				'border: 1px solid #fff',
				'border-width: 0 0 1px 1px',
				'top: 0',
				'left: 0',
				'color: #fff' 
			].join(';');
		return elem;
	}(),
	
	startTime: 0,
	
	start: function ( label ) {
		self.label = label ? ('<span style="font-size:12px;font-weight:normal;color:#ccc;display:block;padding:0 0 3px;">' + label + '</span>') : '';
		self.active = true;
		self.startTime = (+new Date);
	},
	
	stop: function () {
		var result = (+new Date) - self.startTime; 
		if ( !self.active ) {
			result = 'n/a';
		}
		self.active = false;
		return self.reveal( result );
	},
	
	reveal: function ( result ) {
		if ( !self.container.parentNode ) {
			try {
				doc.body.appendChild( self.container );
			} 
			catch ( ex ) {
				if ( !self.loadHandler ) {
					self.loadHandler = addDomReady( function () {
						insertElement( self.container );
					});
				}
			}
		}
		self.container.innerHTML += 
			'<span style="padding:10px 20px;display:block;border-bottom:1px solid #333;">' + 
			self.label + 
			result + 
			'</span>';
		return self;
	},
	
	clear: function () {
		if ( self.container.parentNode ) {
			self.container.innerHTML = '';
		}
		return self;
	}
	
};

})();

/** 

Create new classes and bind them to the JELLY namespace.
Special members are prefixed with a double underscore.

@example 
var Class = defineClass( 'Foo', {

	// List class(es) to inherit; pass more than one as an Array
	__extends: Bar,

	// Constructor (optional)
	__init: function ( x, y ) { 
		this.left = x;
		this.top = y;
	},  
     
	// Static members
	__static: {
		counter: 0,
		increment: function () {
			Class.counter++;
		}
	},

	// Setter methods
	__set: {
		prop1: function () { ... } // compiles to 'setProp1' public method
	},

	// Standard prototype methods
	moveTo: function () { ... },
	fireGun: function () { ... }
});
*/
var	defineClass = function ( name, opts ) {

		// Setup constructor, create shortcuts
		var Class = opts.__init || function () {},
			Static = opts.__static || {},
			Extends = opts.__extends,
			Prototype = Class.prototype; 
		
		Class.toString = function () {
			return 'JELLY.' + name;
		};
		
		// Copy over mixins
		extend( Prototype, defineClassMixins );
		
		// Loop through parent class(es) prototypes and copy members
		( isArray( Extends ) ? Extends : 
			( Extends ? [ Extends ] : [] ) ).each( function ( obj ) {
			extend( Prototype, obj.prototype ); 
			Class.__parent = obj;
		});
		
		// Add in static members to the constructor
		extend( Class, Static );
		
		// Add in setter methods
		if ( opts.__set ) {
			each( opts.__set, function ( mem, value ) {
				Prototype[ 'set' + capitalize( mem ) ] = value;
			})
		}
		
		// Delete special members from <opts>
		each( opts, function ( mem, value ) {
			if ( startsWith( mem, '__' ) ) {
				delete opts[ mem ];
			} 
		});
			
		// Explicitly reference the class in the prototype 
		opts.constructor = Class;
		
		extend( Prototype, opts );
		
		// Attach the class name to the constructor
		Class.__name = name;
		
		// Parse the class name as a path and map it to the constructor
		var path = J, 
			parts = name.split( '.' ),
			i = 0,
			part; 
		for ( ; i < parts.length; i++ ) {
			var part = parts[i];
			if ( !( part in path ) ) {
				path[ part ] = Class;
				break;
			}
			else {
				path = path[ part ];
			}
		}
		// Return constructor
		return Class;
	},
	
	// Mixin methods implemented by every class created with <defineClass> 
	//
	defineClassMixins = {
		/**
		Simple <toString> method to improve on the default
		*/
		toString: function () {
			return 'JELLY.' + this.constructor.__name + ' (inst)';
		},
		
		/** 
		Generic handler for custom events
		
		@example 
		var foo = new Bar;
		  
		// Internally
		self.fire( 'complete' )
		// In the callback instance is available as 'this' or as a named argument
		foo.onComplete = function ( foo ) { ... }
		
		@example 
		var foo = new Bar;
		  
		// Internally
		self.fire( 'complete', foobar )
		// The callback
		foo.onComplete = function ( foobar ) { ... }
		
		*/
		fire: function () {
			var args = toArray( arguments ),
				event = 'on' + capitalize( args.shift() ),
				func = this[ event ];
			if ( empty( args ) ) {
				args.push( this );
			}
			return func ? func.apply( this, args ) : false;
		},

		/** 
		Set instance members dynamically by passing in an object literal.
		If a named method is available for setting a member it is applied 
		
		@example 
		var foo = new Bar;
		foo.set( 'name', 'yoda' );
		foo.set({
		    'ears': 2,
		    'nose': 1,
		    'salutation': 'master'
		});
		*/
		set: function () {
			var self = this, 
				args = arguments,
				feed = {};
			if ( isObject( args[0] ) ) {
				feed = args[0]; 
			} 
			else if ( args.length > 1 ) {
				feed[ args[0] ] = args[1];
			}
			each( feed, function ( key, value ) {
				var methodName = 'set' + capitalize( key );
				if ( methodName in self ) {
					self[ methodName ]( value );
				}
				else {
					self[ key ] = value;
				}
			});
		}

	};

J.defineClass = defineClass;

/**

Utility functions for working with strings

*/
var	contains = function ( haystack, needle, caseInsensitive ) {
		if ( caseInsensitive ) {
			haystack = haystack.toLowerCase();
			needle = needle.toLowerCase();			
		}
		return haystack.indexOf( needle ) !== -1;
	},
	
	startsWith = function ( str, find ) {
		return str.indexOf( find ) === 0;
	},
	
	endsWith = function ( str, find ) {
		return str.indexOf( find ) === str.length - find.length;
	},
		
	normalize = function ( str ) {
		return str.replace( /\s{2,}/g, ' ' ).trim();
	},
	
	capitalize = function ( str, firstWord ) {
		return str.replace( firstWord ? /^\s*[a-z]/ : /(^|\s+)[a-z]/g, function ( m ) {
			return m.toUpperCase();
		});
	},
	
	camelize = function ( str ) {
		return str.replace( /-([a-z])/g, function ( m, m1 ) {
			return m1.toUpperCase();
		});
	}, 

	rgbToHex = function ( str ) {
		var rgb = str.match( /[\d]{1,3}/g ), 
			hex = [], 
			i = 0;
		for ( i; i < 3; i++ ) {
			var bit = ( rgb[i]-0 ).toString( 16 );
			hex.push( bit.length === 1 ? '0' + bit : bit );
		}
		return '#' + hex.join('');
	},
	
	hexToRgb = function ( str, array ) {
		var hex = str.match( /^#([\w]{1,2})([\w]{1,2})([\w]{1,2})$/ ), 
			rgb = [], 
			i = 1;
		for ( i; i < hex.length; i++ ) {
			if ( hex[i].length === 1 ) { 
				hex[i] += hex[i]; 
			}
			rgb.push( parseInt( hex[i], 16 ) );
		}
		return array ? rgb : 'rgb(' + rgb.join(',') + ')';
	},
	
	// http://dean.edwards.name/weblog/2009/10/convert-any-colour-value-to-hex-in-msie/
	msieToHex = function ( color ) {
		var body  = msieToHex.popup = msieToHex.popup || win.createPopup().document.body
			range = body.createTextRange();
		body.style.color = color;
		var value = range.queryCommandValue( 'ForeColor' );
		value = ( ( value & 0x0000ff ) << 16 ) | ( value & 0x00ff00 ) | ( ( value & 0xff0000 ) >>> 16 );
		value = value.toString( 16);
		return "#000000".slice( 0, 7 - value.length ) + value;
	},
	
	parseColor = function ( str, mode ) {
		mode = mode || 'rgb';
		var hex = startsWith( str, '#' ); 
		if ( !hex ) {
			if ( msie ) {
				str = msieToHex( str );
				hex = true;
			}
			else if ( !startsWith( str, 'rgb' ) ) {
				var test = createElement( 't style:"display:none;color:' + str );
				insertElement( test );
				str = getStyle( test, 'color' );
				removeElement( test );
				hex = startsWith( str, '#' );
			}
		} 
		switch ( mode ) {
			case 'hex':	
				return hex ? str : rgbToHex( str );
			case 'rgb': 
				return hex ? hexToRgb( str ) : str;
			case 'rgb-array': 
				if ( hex ) { 
					return hexToRgb( str, true ); 
				} 
				else {
					return str.replace( /rgb| |\(|\)/g, '' ).split( ',' ).map( parseFloat );
				}
		}
	},
	
	stripTags = function ( str, allow ) {
		if ( !allow ) { 
			return str.replace( /<[^>]*>/g, '' ); 
		} 
		allow = allow.replace( /\s+/g, '' ).split( ',' ).map( function ( s ) {
			return s +' |'+ s +'>|/'+ s +'>';   
		}).join( '|' );
		return str.replace( new RegExp( '<(?!'+ allow +')[^>]+>', 'g' ), '' );
	},
	
	bindData = function ( str, data ) {
		var m;
		while ( m = /%\{\s*([^\}\s]+)\s*\}/.exec( str ) ) {
			str = str.replace( m[0], data[ m[1] ] || '??' );
		}
		return str;
	},
	
	/**
	Useful for parsing string sub-languages
	
	@example
	var extract = extractLiterals( 'Hello World, "quoted string" foobar "another quoted string"' )
	// {
	//	literals: { 
	//		_LIT1_: 'quoted string',
	//		_LIT2_: 'another quoted string'
	//	}
	//  string: 'Hello World, _LIT1_, foobar _LIT2_',
	//  prefix: 'LIT'
	// }
	*/
	extractLiterals = function ( str, prefix ) {
		var literals = {}, 
			prefix = prefix || 'LIT',
			counter = 0,
			label,
			m; 
		while ( m = /('|")(?:\\1|[^\1])*?\1/.exec( str ) ) {	
			label = '_' + prefix + ( ++counter ) + '_';
			literals[ label ] = m[0].substring( 1, m[0].length-1 );
			str = str.substring( 0, m.index ) + label + str.substring( m.index + m[0].length );
		}
		return {
			string: str,
			literals: literals,
			prefix: prefix,
			match: function ( test ) {
				return ( test in literals ) ? literals[ test ] : test;
			}
		};
	},
	
	evalScripts = function ( str ) {
		var wrapper = createElement( 'div', { html: str } ), 
			res = [];
		toArray( getElements( 'script', wrapper ) ).each( function ( el ) {
			res.push( win[ 'eval' ]( el.innerHTML ) );
		});
		return res;
	};
	
extend( J, { 
	contains: contains,
	startsWith: startsWith,
	endsWith: endsWith,
	normalize: normalize,
	capitalize: capitalize,
	camelize: camelize,
	parseColor: parseColor,
	stripTags: stripTags,
	bindData: bindData,
	extractLiterals: extractLiterals,
	evalScripts: evalScripts
});

/**

Utility functions for working with elements and manipulating the DOM

*/
var addClass = function ( el, cn ) {
		el = getElement( el );
		if ( !el || hasClass( el, cn ) ) { return; }
		el.className += el.className ? ' ' + cn : cn;
	}, 
	
	removeClass = function ( el, cn ) {
		el = getElement( el );
		if ( !el || el.className === '' ) { return; } 
		var patt = new RegExp( '(^|\\s)' + cn + '(\\s|$)' );
		el.className = normalize( el.className.replace( patt, ' ' ) );
	},
	
	hasClass = function ( el, cn ) {
		el = getElement( el );
		if ( !el ) { return; }
		var elCn = el.className;
		return elCn !== '' && 
			( elCn === cn || new RegExp( '(^|\\s)' + cn + '(\\s|$)' ).test( elCn ) );
	},
	
	toggleClass = function ( el, cn ) {
		el = getElement( el );
		if ( hasClass( el, cn ) ) { 
			removeClass( el, cn ); 
		} 
		else { 
			addClass( el, cn ); 
		}
	},
	
	createElement = function ( arg, attrs ) {
		var el;
		if ( !/[#:\.]/.test( arg ) ) {
			el = doc.createElement( arg ), key;
			for ( key in attrs ) {
				switch (key) {
					case 'html': 
						el.innerHTML = attrs[ key ]; 
						break;
					case 'text': 
						el.appendChild( doc.createTextNode( attrs[ key ] ) ); 
						break;
					case 'class': 
						el.className = attrs[ key ]; 
						break;
					case 'style': 
						el.style.cssText = attrs[ key ]; 
						break;
					default: 
						el.setAttribute( key, attrs[ key ] );
				}
			}
		} 
		else {
			var extract = extractLiterals( arg ),
				parts = extract.string.trim().replace( /\s*(:|,)\s*/g, '$1' ).split( ' ' ),
				first = parts.shift(),
				leadId = contains( first, '#' ),
				leadClass = contains( first, '.' ),
				type = 'div',
				attributes = {},
				branchMapData = null,
				tmp;
			if ( leadId || leadClass ) {
				tmp = leadId ? first.split( '#' ) : first.split( '.' ); 
				type = tmp.shift() || type;
				attributes[ leadId ? 'id':'class' ] = tmp.join( ' ' );
			} 
			else {
				type = first;
			}
			if ( parts[0] ) {
				parts[0].split( ',' ).each( function ( tkn ) {
					tkn = tkn.split( ':' );
					var value = extract.match( tkn[1] );
					if ( tkn[0] === '@' ) {
						branchMapData = value;
					} 
					else {
						attributes[ tkn[0] ] = value;
					}
				});
			} 
			el = createElement( type.toLowerCase(), attributes );
		}
		return attrs === true ? { elem: el, ref: branchMapData } : el;
	},

	createBranch = function () {
		var args = toArray( arguments ),
			res = {},
			context,
			parseToken = function ( arg ) {
				if ( arg && isObject( arg ) ) {
					if ( isElement( arg.root ) ) {
						for ( var key in arg ) {
							if ( isArray( arg[ key ] ) ) {
								var nodeName = arg[ key ][0].nodeName.toLowerCase();
								res[ nodeName ] = res[ nodeName ] || [];
								arg[ key ].each( function ( el ) { 
									res[ nodeName ].push( el ); 
								});
							} 
							else if ( key !== 'root' ) { 
								res[ key ] = arg[ key ]; 
							}
						} 
						return arg.root;
					} 
				}
				else if ( isElement( arg ) ) { 
					return arg; 
				} 
				else if ( !isString( arg ) ) { 
					return; 
				} 
				var obj = createElement( arg, true ),
					elem = obj.elem,
					type = elem.nodeName.toLowerCase();
				res[ type ] = res[ type ] || [];
				res[ type ].push( elem );
				if ( obj.ref ) { res[ obj.ref ] = elem; }
				return elem;
			};
		res.root = context = parseToken( args.shift() );
		args.each( function ( feed ) {
			if ( !isArray( feed ) ) { 
				context = context.appendChild( parseToken( feed ) ); 
			} 
			else { 
				feed.each( function ( o ) { 
					context.appendChild( parseToken( o ) ) 
				}); 
			}
		});
		return res;
	},
	
	getElement = function ( obj ) { 
		if ( !msie || msie > 7 ) {
			return function ( obj ) {
				return isString( obj ) ? doc.getElementById( obj ) : obj; 
			};
		}
		else {
			return function ( obj ) {
				if ( isString( obj ) ) { 
					var el = doc.getElementById( obj ); 
					if ( el && el.id !== obj ) {
						var named = doc.getElementsByName( obj ), n = named.length, i = 0; 
						for ( i; i < n; i++ ) {
							if ( named[ i ].id === obj ) {
								return named[ i ];
							} 
						}
						return null;
					}
					return el;
				}
				return obj;
			};
		}
	}(),
		
	getElements = function ( a, b ) { 
		var context = b ? getElement( b ) : doc;
		return context && context.getElementsByTagName( a ); 
	},
	
	wrapElement = function ( el, wrapper ) {
		el = getElement( el );
		var pnt = el.parentNode, next = el.nextSibling;
		wrapper.appendChild( el );
		return next ? pnt.insertBefore( wrapper, next ) : pnt.appendChild( wrapper );	
	},
	
	withElement = function ( el, callback, scope ) {
		el = getElement( el );
		if ( el ) { return callback.call( scope || el, el ); }
		return el;
	},
	
	replaceElement = function ( el, replacement ) {
		el = getElement( el );
		return el.parentNode.replaceChild( replacement, el );
	},
	
	removeElement = function ( el ) {
		el = getElement( el );
		return el.parentNode.removeChild( el );
	},
	
	removeChildren = function ( parent ) {
		var children = getChildren( getElement( parent ) );
		children.each( removeElement );
		return children;
	}, 
	
	insertElement = function ( el, datum ) {
		el = getElement(el);
		return ( getElement(datum) || doc.body ).appendChild( el );
	},
	
	insertTop = function ( el, datum ) {
		if ( !( el = getElement( el ) ) || !( datum = getElement( datum ) ) ) { return false; }
		if ( datum.firstChild ) { 
			return datum.insertBefore( el, datum.firstChild ); 
		}
		else { 
			return datum.appendChild( el ); 
		}
	},
	
	insertBefore = function ( el, datum ) {
		datum = getElement( datum );
		return datum.parentNode.insertBefore( getElement( el ), datum );
	},
	
	insertAfter = function ( el, datum ) {
		if ( !( el = getElement( el ) ) || !( datum = getElement( datum ) ) ) { return false; }
		var next = J.getNext( datum );
		if ( next ) { 
			return datum.parentNode.insertBefore( el, next ); 
		} 
		else { 
			return datum.parentNode.appendChild( el ); 
		}
	},
	
	getFirst = function ( el ) {
		el = el.firstChild;
		while ( el && el.nodeType !== 1 ) {
			el = el.nextSibling;
		}
		return el;
	},
	
	getLast = function ( el ) {
		el = el.lastChild;
		while ( el && el.nodeType !== 1 ) {
			el = el.previousSibling;
		}
		return el;
	},
	
	getNext = function ( el ) {
		el = el.nextSibling;
		while ( el && el.nodeType !== 1 ) {
			el = el.nextSibling;
		}
		return el;
	},
	
	getPrevious = function ( el ) {
		el = el.previousSibling;
		while ( el && el.nodeType !== 1 ) {
			el = el.previousSibling;
		}
		return el;
	},
	
	getChildren = function ( el ) {
		var elements = [], el = el.firstChild;
		while (el) {
			if ( el.nodeType == 1 ) {
				elements[ elements.length ] = el;
			}
			el = el.nextSibling;
		}
		return elements;
	},
	
	getXY = function ( el ) {
		el = getElement( el );
		var xy = [ 0, 0 ];
		if ( !el ) {
			return xy;
		} 
		if ( 'getBoundingClientRect' in el ) {
			var bounds = el.getBoundingClientRect(),
				winScroll = getWindowScroll(),
				left = bounds.left,
				top = bounds.top;
			xy = [ left + winScroll[0], top + winScroll[1] ];
		} 
		else {
			xy = [ el.offsetLeft, el.offsetTop ];
			while ( el = el.offsetParent ) {
				xy[0] += el.offsetLeft;
				xy[0] += parseInt( getStyle( el, 'border-left-width' ) ) || 0;
				xy[1] += el.offsetTop;
				xy[1] += parseInt( getStyle( el, 'border-top-width' ) ) || 0;
			}
		}
		return xy;
	},

	setXY = function ( el, X, Y, unit ) {
		el = getElement( el );
		unit = unit || 'px';
		el.style.left = X + unit;
		el.style.top = Y + unit;
	},
	
	getX = function ( el ) {
		return getXY( el )[0];
	},
	
	setX = function ( el, X, unit ) {
		( getElement( el ) ).style.left = X + ( unit || 'px' );
	},
	
	getY = function (el) {
		return getXY( el )[1];
	},
	
	setY = function ( el, Y, unit ) {
		( getElement( el ) ).style.top = Y + ( unit || 'px' );
	},
	
	getAttribute = function () {
		if ( !isDefined( docRoot.hasAttribute ) && msie ) {
			return function ( node, attr ) {
				switch ( attr ) {
					case 'class': 
						return node.className || null;
					case 'href': 
					case 'src': 
						return node.getAttribute( attr, 2 ) || null;						
					case 'style': 
						return node.getAttribute( attr ).cssText.toLowerCase() || null;
					case 'for': 
						return node.attributes[attr].nodeValue || null;
				}
				return node.getAttribute( attr ) || null;
			};
		}
		return function ( node, attr ) { 
			return node.getAttribute( attr ); 
		};
	}(),
	
	getStyle = function () {
		if ( 'getComputedStyle' in win ) {
			return function ( el, prop ) {
				return win.getComputedStyle( el, null )[ camelize( prop ) ]; 
			};
		}
		return function ( el, prop ) {
			prop = camelize( prop );
			var elStyle = el.style;
			if ( prop === 'opacity' && elStyle.opacity === '' ) { 
				elStyle.opacity = 1; 
				return 1;
			}
			return el.currentStyle[ prop ]; 
		};
	}(),

	/**
	Get the computed font-size for an element in pixels
	*/
	getComputedFontSize = function ( el ) {
		el = getElement( el );
		if ( el ) {
			if ( 'getComputedStyle' in win ) {
				return parseInt( win.getComputedStyle( el, null ).fontSize );	
			}
			else {
				var testElement = getComputedFontSize.el = 
						getComputedFontSize.el || createElement( 'foo text:x,style:"line-height:1;font-size:100%;position:absolute"' );
				insertElement( testElement, el );
				var result = testElement.offsetHeight;
				removeElement( testElement );
				return result;
			}
		}
	},

	setStyle = function ( el, a, b ) {
		var set = function ( prop, value ) {
				if ( prop === 'float' ) {
					prop = 'cssFloat';
				}
				if ( prop === 'opacity' ) {
					setOpacity( el, value );	
				}
				else {
					el.style[ camelize( prop ) ] = value;
				}
			},
			prop;
		if ( isObject( a ) ) {	
			for ( prop in a ) {
				set( prop, a[prop] );
			}
		}
		else {
			set( a, isDefined( b ) ? b : '' );			
		}
	},
	
	setOpacity = function () {
		if ( 'opacity' in docRoot.style ) {
			return function ( el, val ) {
				var elStyle = el.style;
				if ( elStyle.opacity === '' ) {
					elStyle.opacity = 1;
				}
				elStyle.opacity = val;
			};
		}
		return function ( el, val ) {
			var elStyle = el.style;
			if ( isUndefined( elStyle.opacity ) ) {
				elStyle.opacity = 1;
				elStyle.zoom = 1;
			}
			elStyle.filter = val === 1 ? '' : 'alpha(opacity=' + ( val * 100 ) + ')';
			elStyle.opacity = val;
		};		
	}(),
	
	storeData = function ( el, name, value ) {
		var cache = elementData, elementKey = cache.ns;
		if ( !( el = getElement( el ) ) ) { return; }
		if ( !( elementKey in el ) ) { 
			el[ elementKey ] = elementUid(); 
			cache[ el[ elementKey ] ] = {};
		}
		cache[ el[ elementKey ] ][ name ] = value;
	},
	
	retrieveData = function ( el, name ) {
		var cache = elementData, elementKey = cache.ns;
		if ( !( el = getElement( el ) ) ) { return; }
		if ( elementKey in el && el[ elementKey ] in cache ) {
			return cache[ el[ elementKey ] ][ name ];
		}
		return null;
	},
	
	removeData = function ( el, name ) {
		var cache = elementData, elementKey = cache.ns;
		if ( !( el = getElement( el ) ) ) { return; }
		if ( elementKey in el && el[ elementKey ] in cache ) {  
			delete cache[ el[ elementKey ] ][ name ];
		}
	},
	
	elementData = { ns:'jelly_' + ( +new Date ) },
	
	elementUid = function () { 
		var uid = 0;
		return function () { return ++uid; }
	}();

extend( J, {
	addClass: addClass,
	removeClass: removeClass,
	hasClass: hasClass,
	toggleClass: toggleClass,
	getElements: getElements,	
	getElement: getElement,
	createElement: createElement,
	createBranch: createBranch,
	wrapElement: wrapElement,
	withElement: withElement,
	replaceElement: replaceElement,
	removeElement: removeElement,
	insertElement: insertElement,
	insertTop: insertTop,
	insertBefore: insertBefore,
	insertAfter: insertAfter,
	getFirst: getFirst,
	getLast: getLast,
	getNext: getNext,
	getPrevious: getPrevious,
	getChildren: getChildren,
	getXY: getXY,
	setXY: setXY,
	getX: getX,
	setX: setX,
	getY: getY,
	setY: setY,
	getAttribute: getAttribute,
	getStyle: getStyle,
	getComputedFontSize: getComputedFontSize,
	setStyle: setStyle,
	setOpacity: setOpacity,
	storeData: storeData,
	retrieveData: retrieveData,
	removeData: removeData
});

/**

Utilities for working with events

*/
var addEvent = function ( obj, type, fn ) {
		obj = getElement(obj);
		var mouseEnter = type === 'mouseenter',
			mouseLeave = type === 'mouseleave',
			wrapper, 
			handle;
			
		if ( obj === doc && type === 'domready' ) {
			return addDomReady( fn );
		} 
		if ( !standardEventModel ) {
			wrapper = function ( e ) {
				fn.call( obj, fixEvent( e ) );
			};
		}
		if ( mouseEnter || mouseLeave ) {
			wrapper = function ( e ) {
				e = fixEvent( e );
				if ( !mouseEnterLeave.call( obj, e ) ) { return; }
				fn.call( obj, e );
			};
			type = mouseEnter ? 'mouseover' : 'mouseout';
		}
		handle = [ obj, type, wrapper || fn ];
		eventLog.push( handle );
		if ( standardEventModel ) { 
			obj.addEventListener( type, wrapper || fn, false ); 
		} 
		else { 
			obj.attachEvent( 'on' + type, wrapper ); 
		}
		return handle;
	},
	
	removeEvent = function ( handle ) {
		if ( handle ) { 
			if ( !isArray( handle ) ) {
				return removeDomReady( handle );
			} 
			if ( standardEventModel ) {
				handle[0].removeEventListener( handle[1], handle[2], false ); 
			} 
			else {
				handle[0].detachEvent( 'on' + handle[1], handle[2] ); 
			}
		}
	},
	
	eventLog = [], 
	
	purgeEventLog = function () {
		for ( var i = 0, handle; eventLog[i]; i++ ) {
			handle = eventLog[i];					
			if ( handle[0] !== win && handle[1] !== 'unload' ) {
				removeEvent( handle );
			}
		}
	}, 
	
	fixEvent = function () {
		if ( standardEventModel ) {
			return function (e) { return e; };
		}
		return function (e) {
			e = win.event;
			e.target = e.srcElement;
			e.relatedTarget = function () {
					switch ( e.type ) {
						case 'mouseover': return e.fromElement;
						case 'mouseout': return e.toElement;
					}
				}();
			e.stopPropagation = function () { e.cancelBubble = true; };
			e.preventDefault = function () { e.returnValue = false; };
			e.pageX = e.clientX + docRoot.scrollLeft;
			e.pageY = e.clientY + docRoot.scrollTop;
			return e;
		};		
	}(),
	
	mouseEnterLeave = function (e) { 
		var related, i;
		if ( e.relatedTarget ) {
			try {
				related = e.relatedTarget;
				if ( related.nodeType !== 1 || related === this ) { 
					return false; 
				}
				var children = this.getElementsByTagName('*'), n = children.length, i = 0;
				for ( i; n > i; i++ ) {
					if ( related === children[i] ) { 
						return false; 
					}
				}
			}
			catch ( ex ) {}
		}
		return true;
	},
	
	stopEvent = function (e) {
		e = fixEvent(e);
		e.stopPropagation();
		e.preventDefault();
		return e;
	};
	
extend( J, { 
	addEvent: addEvent,
	removeEvent: removeEvent,
	stopEvent: stopEvent,
	fixEvent: fixEvent
});

// IE garbage collection to prevent memory leaks
if ( browser.ie && browser.ie < 8 ) { 
	addEvent( win, 'unload', purgeEventLog );
}

/**

Cross browser 'DOM ready' event 

*/
(function () {
  
	var self = J.DomReady = {
	
			ready: false,
			
			handlers: {},
			
			add: function ( callback, ref ) {
				var ref = ref || ++uid;
				self.handlers[ref] = callback;
				return ref;
			},
			
			remove: function ( ref ) {
				delete self.handlers[ ref ];
			},
			
			fire: function () {
				if ( self.ready ) { return; }
				self.ready = true;
				clearTimeout( pollTimer );
				for ( var handler in self.handlers ) {
					try { 
						self.handlers[ handler ](); 
					}
					catch (ex) { 
						logError(ex); 
					}
				} 
			}
		},
		
		uid = 0,
		pollTimer,
	
		checkReadyState = function () {
			if ( doc.readyState === 'complete' ) {
				doc.detachEvent( 'onreadystatechange', checkReadyState );
				self.fire();
			}
		}, 
		
		scrollPoller = function () {
			try { 
				docRoot.doScroll( 'left' ); 
			}
			catch (e) {
				pollTimer = setTimeout( scrollPoller, 10 );
				return;
			}
			self.fire();
		};
	
	//	The easy way and the convoluted way 
	if ( standardEventModel ) {
		addEvent( doc, 'DOMContentLoaded', self.fire );
	} 
	else {
		doc.attachEvent( 'onreadystatechange', checkReadyState );
		if ( win === top ) {
			pollTimer = setTimeout( scrollPoller, 0 );
		}
	}

	// Fallback catch all
	addEvent( win, 'load', self.fire );
	
})();

var DomReady = J.DomReady,
	addDomReady = function ( callback, ref ) {
		return DomReady.add( callback, ref || null );
	},
	removeDomReady = function ( ref ) {
		DomReady.remove( ref );
	};

extend( J, {
	addDomReady: addDomReady,
	removeDomReady: removeDomReady
});

/**

Utility functions for working with cookies

*/
extend( J, { 
	
	getCookie: function ( name ) {
		var result = new RegExp( name + '=([^; ]+)' ).exec( doc.cookie );
		return result ? unescape( result[1] ) : null;
	},
	
	setCookie: function ( name, value, expires, path, domain, secure ) {
		if ( expires ) {
			var expireTime = ( +new Date ) + ( ( 1000*60*60*24 ) * expires );
			expires = new Date( expireTime ).toUTCString();
		}
		doc.cookie = name + '=' + escape( value ) +
			( expires ? ';expires=' + expires : '' ) + 
			( path ? ';path=' + path : '' ) +
			( domain ? ';domain=' + domain : '' ) +	
			( secure ? ';secure' : '' );
	},
	
	removeCookie: function ( name, path, domain ) {
		if ( J.getCookie( name ) ) {
			doc.cookie = name + '=' +
				( path ? ';path=' + path : '' ) +
				( domain ? ';domain=' + domain : '' ) +	
				( ';expires=' + new Date(0) );
		}
	}
	
});

/**

Utility functions for working with flash objects

*/
extend( J, {
	
	getFlashVersion: function () {
		var version = { major: 0, build: 0 },
			plugins = navigator.plugins,
			desc,
			versionString,
			testString = 'Shockwave Flash';
		if ( plugins && isObject( plugins[ testString ] ) ) {
			desc = plugins[ testString ].description;
			if ( desc !== null ) {
				versionString = desc.replace( /^[^\d]+/, '' );
				version.major = parseInt( versionString.replace( /^(.*)\..*$/, '$1' ), 10 );
				version.build = parseInt( versionString.replace( /^.*r(.*)$/, '$1' ), 10 );
			}
		} 
		else if ( msie ) {
			try {
				var axflash = new ActiveXObject( 'ShockwaveFlash.ShockwaveFlash' );
				desc = axflash.GetVariable( '$version' );
				if ( desc !== null ) {
					versionString = desc.replace( /^\S+\s+(.*)$/, '$1' ).split(',');
					version.major = parseInt( versionString[0], 10 );
					version.build = parseInt( versionString[2], 10 );
				}
			} catch (ex) {}
		}
		return version;
	},
	
	createFlashObject: function ( obj ) {
		var path = obj.path || '',
			width = obj.width || 1,
			height = obj.height || 1,
			params = obj.params || {},
			vars = obj.flashvars || {},
			attrs = obj.attributes || {},
			fallback = obj.fallback || 
				'You need <a href="http://www.adobe.com/go/getflashplayer">Adobe Flash Player</a> installed to view this content</a>',
			data = [],
			key,
			out = '<object';
		if ( msie ) {
			attrs.classid = 'clsid:D27CDB6E-AE6D-11cf-96B8-444553540000';
			params.movie = path;
		} 
		else {
			attrs.data = path;
			attrs.type = 'application/x-shockwave-flash';
		}
		attrs.width = width;
		attrs.height = height;
		for ( key in attrs ) { 
			out += ' ' + key + '="' + attrs[ key ] + '"'; 
		}
		out += '>\n';
		for ( key in vars ) { 
			data.push( key + '=' + encodeURIComponent( vars[ key ] ) ); 
		}
		if ( data.length > 0 ) { 
			params.flashvars = data.join('&'); 
		} 
		for ( key in params ) { 
			out += '\t<param name="' + key + '" value="' + params[ key ] + '" />\n'; 
		}
		return out + fallback + '\n</object>';
	},
	
	embedFlashObject: function ( el, obj ) {
		el = getElement( el );
		el.innerHTML = J.createFlashObject( obj );
		return el;
	}
	
});

/**

Various utilities

*/
var getViewport = function () {
		if ( isDefined( docRoot.clientWidth ) && docRoot.clientWidth !== 0 ) { 
			return function () {
				return [ docRoot.clientWidth, docRoot.clientHeight ];
			};
		}
		return function () {
			return [ doc.body.clientWidth || 0, doc.body.clientHeight || 0 ];
		};
	}(),

	getWindowScroll = function () {
		if ( isDefined( win.pageYOffset ) ) {
			return function () {
				return [ win.pageXOffset, win.pageYOffset ];
			};
		} 
		return function () {
			if ( isDefined( docRoot.scrollTop ) && 
				( docRoot.scrollTop > 0 || docRoot.scrollLeft > 0 ) ) {
				return [ docRoot.scrollLeft, docRoot.scrollTop ];
			}
			return [ doc.body.scrollLeft, doc.body.scrollTop ];
		};
	}(),
	
	parseQuery = function ( el ) {
		el = el || win.location;
		var data = {};
		if ( /\?/.test( el.href ) ) {
			var queries = el.href.split('?')[1].split('&'),
				i = queries.length-1,
				parts;
			do {
				parts = queries[i].split('=');
				data[parts[0]] = decodeURIComponent( parts[1].replace(/\+/g, '%20') );
			} while (i--);
		}
		return data;
	},
	
	/**
	Handle multiple input types from the argument list and return a queryString
	*/
	buildQuery = function () {
		var append = function ( name, value ) {
				if ( !name ) { return; } 
				data.push( name + '=' + encodeURIComponent(value).replace(/%20/g, '+') );
			}, 
			parseElement = function ( el ) {
				if ( !isElement( el ) || !/^(input|textarea|select)$/i.test(el.nodeName) ) {
					return; 
				}
				var type = el.type.toLowerCase(),
					name = el.name, 
					value = el.value;
				switch ( type ) {
					case 'checkbox': 
						if ( el.checked ) { append( name, value || 'on' ); }
						break;
					case 'radio': 
						if ( el.checked ) { append( name, value ); }
						break;
					default: 
						append( name, value );
				}
			},
			args = toArray( arguments ),
			data = [];
			
		args.each( function ( arg ) {
			// Object literals
			if ( isObject( arg ) ) {
				for ( var key in arg ) { 
					append( key, arg[key] ); 
				}
			}
			// Arrays and NodeLists
			else if ( arg.length ) {
				( isArray( arg ) ? arg : toArray( arg ) ).each( parseElement );
			}
			// Element ID's, elements, or raw query data
			else if ( isString( arg ) || isElement( arg ) ) {
				var el = getElement( arg );
				if ( el ) {
					// Parse element and all its children
					parseElement( el );
					J.Q( el, 'textarea,input,select' ).each( parseElement );
				}
				else {
					data.push( arg );
				}
			}
		});
		return data.join( '&' );
	},
	
	/**
	Convert a pixel value into typographical ems; requires a base font-size (in pixels) for calculation, 
	which can be passed as a literal value or as a reference element to be parsed for computed font-size
	*/
	pxToEm = function ( pixel, base ) {
		var defaultBase = 16, 
			parsedBase;
		base = base || defaultBase;
		parsedBase = parseInt( base, 10 );
		if ( isNaN( parsedBase ) ) {
			base = getComputedFontSize( el ) || defaultBase;
		} 
		return pixel / base;
	};
	
	/**
	Utility for importing all symbols under the JELLY namespace into the current scope

	@example
	(function () {
		window[ 'eval' ]( JELLY.unpack() )
		...
	})();
	*/
	/*unpack = function () {
		if ( typeof __JELLY !== 'undefined' ) { 
			return null; 
		}
		var stack = [ 'var J=JELLY' ], mem, i = 1;
		for ( mem in J ) { 
			stack[ i++ ] = mem + '=J.' + mem;
		}
		return stack.join(',') + ';';
	};*/

extend( J, {
	getViewport: getViewport,
	getWindowScroll: getWindowScroll,
	pxToEm: pxToEm,
	parseQuery: parseQuery,
	buildQuery: buildQuery
	//unpack: unpack
});

/**

Page load tasks

*/
// fix background image flicker on ie6
if ( browser.ie6 ) { 
	try { 
		doc.execCommand( 'BackgroundImageCache', false, true );
	} catch (ex) {}; 
}

// adding informational css classes to the root element for convenience
var classname = ['unknown'], key;
for ( key in browser ) {
	if ( browser[key] ) {
		if ( classname[0] === 'unknown' ) { 
			classname = [key]; 
		} 
		else {
			classname.push( key );
		}
	}
}
addClass( docRoot, 'js ' + classname.join(' ') );

/**

A fast cross-browser interface for querying the DOM with CSS selectors

*/
(function () {
	
	var	pushToStack = function ( value ) {
			stack[stack.length] = value;
		},
		
		unMark = function ( group ) {
			for ( var i = 0, n = group.length; i < n; i++ ) {
				group[i][uniqueKey] = null;
			}
		},
		
		filterUnique = function ( group ) {
			var el, 
				n = group.length, 
				uniques = [];
			while (n) {
				el = group[--n];
				if ( !el[uniqueKey] ) {
					el[uniqueKey] = true;
					uniques[uniques.length] = el;
				}
			}
			n = uniques.length;
			while (n) {
				uniques[--n][uniqueKey] = null;
			}
			return uniques.reverse();
		},
		
		/*====================================
			selector parsing
		=====================================*/
		
		parseTokenComponent = function ( part, fetchOrFilter ) {
			var obj = { 
					mode: fetchOrFilter ? _fetch_ : _filter_, 
					not: false,
					type: _id_
				};
			if ( /^(\w+)?#\w/.test( part ) ) {
				obj.val = part.split('#');
			} 
			else if ( /^\w+|\*$/.test( part ) ) {
				obj.type = _tag_; 
				obj.val = part;
			} 
			else if ( /^\.\w/.test( part ) ) { 
				obj.type = _class_;	
				obj.val = part.replace( /^\./, '' );
			} 
			else if ( /^\[/.test( part ) ) { 
				obj.type = _attr_;	
				obj.val = part.replace( /\[|\]/g, '' );	
			} 
			else if ( /^\+|>|~/.test( part ) ) { 
				obj.type = _combi_; 
				obj.val = part;			
			} 
			else if ( /:not\(/.test( part ) ) {
				obj = parseTokenComponent( part.replace( /\:not\(|\)$/g, '' ) );
				obj.not = true;
			} 
			else if ( /^:/.test( part ) ) { 
				var _tmp = part.replace( /^:|\)$/g, '' ).split( '(' );
				obj.type = _pseudo_; 
				obj.kind = _tmp[0];
				obj.val = _tmp[1];
			} 
			return obj;
		},

		parseSelector = function ( selector ) {
			var result = [],
				// Seperate out the combinators + > ~, then split
				parts = normalize( selector.replace( /(>|~(?!=)|\+(?!\d))/g, ' $1 ' ) ).split( ' ' ),
				universal = { mode: _fetch_, type: _tag_, val: '*' },
				getByClass = 'getElementsByClassName' in doc,
				sibling = false;

			for ( var i = 0, tmp; i < parts.length; i++ ) { 
				tmp = parts[i].
						replace( /([^\(\#\.\[])(:)/g, '$1 $2' ).
						replace( /([^\(])(\[|\.)/g, '$1 $2' ).
						replace( /\:not\(\s*/g, ':not(' ).trim().split(' ');	
				for ( var j = 0, obj; j < tmp.length; j++ ) {
					obj = parseTokenComponent( tmp[j], !j );
					if ( sibling ) {
						obj.mode = _filter_;
					} 
					else if ( j === 0 && 
						( obj.type === _pseudo_ || obj.type === _attr_ || 
							( obj.type === _class_ && !getByClass ) || 
						obj.not ) ) {
						result.push( universal );
						obj.mode = _filter_;
					}
					if ( contains( tmp[j], uniqueKey ) ) {
						obj[obj.type === _attr_ ? 'spValue' : 'val'] = strings.shift();
					}
					result.push( obj );
					sibling = /^(~|\+)$/.test( obj.val );
				}
			}
			result.postFilter = !( parts.length === 1 || parts.length === 3 && /^[\+~]$/.test( parts[1] ) );
			return result;
		},	
		
		/*====================================
			ids
		=====================================*/
		
		mergeId = function ( tkn ) {
			var tag = tkn.val[0], 
				id = tkn.val[1];
				
			if ( tkn.mode === _filter_ ) { 
				for ( var i = 0, n = collection.length; i < n; i++) {
					if ( tag ) {
						if ( ( collection[i].tagName.toLowerCase() === tag && 
								collection[i].id === id ) !== tkn.not) {
							pushToStack( collection[i] );
						}
					} 
					else if ( ( collection[i].id === id ) !== tkn.not ) {
						pushToStack( collection[i] );
					}
					if ( !tkn.not && stack[0] ) {
						return;
					}
				}
			} 
			else {
				if ( !tag ) {
					stack[0] = getElement( id );
				} 
				else {
					var elem = getElement( id );
					if ( elem && elem.tagName.toLowerCase() === tag ) {
						stack[0] = elem;	
					}
				}
				if ( !firstRun && stack[0] ) {
					var flag = false;
					for ( var i = 0, n = collection.length; i < n; i++ ) {
						if ( stack[0].contains( collection[i] ) ) {
							flag = true;
							break;
						}
					}
					if ( !flag ) {
						stack[0] = null;
					}
				} 
			}
		}, 
		
		/*====================================
			tags
		=====================================*/
		
		mergeTags = function ( tkn ) {
			var extra = msie && tkn.val === '*';	
			if ( firstRun ) {
				for ( var i = 0, tags = getElements( tkn.val ), n = tags.length; i < n; i++) {
					if ( extra ) { 
						if ( tags[i].nodeType === 1 ) {
							pushToStack( tags[i] );
						}
					}
					else {
						pushToStack( tags[i] );
					}
				}
			} 
			else if ( tkn.not || tkn.mode === _filter_ ) {
				for ( var i = 0, test = tkn.val.toUpperCase(), n = collection.length; i < n; i++ ) {
					if ( ( collection[i].nodeName.toUpperCase() === test ) !== tkn.not ) {
						pushToStack( collection[i] );
					}
				}
			} 
			else {
				for ( var i = 0, n = collection.length; i < n; i++ ) {
					var tags = getElements( tkn.val, collection[i]  ), 
						n2 = tags.length, 
						j = 0;
					for ( j; j < n2; j++ ) {
						if ( extra ) {
							if ( tags[j].nodeType === 1 ) {
								pushToStack( tags[j] );
							}
						}
						else {
							pushToStack( tags[j] );
						}
					}
				}
			}
		},
		
		/*====================================
			class
		=====================================*/
		
		mergeClass = function ( tkn ) {
			var val = tkn.val, 
				not = tkn.not, 
				n = collection.length, 
				i = 0;
			if ( tkn.mode === _fetch_ ) {
				if ( firstRun ) {
					stack = toArray( doc.getElementsByClassName( val ) );
				} 
				else {
					for ( i; i < n; i++ ) {
						var tags = collection[i].getElementsByClassName( val ), 
							n2 = tags.length, 
							j = 0;
						for ( j; j < n2; j++ ) {
							pushToStack( tags[j] );
						}
					}
				}
			} 
			else {
				var patt = new RegExp( '(^|\\s)' + val + '(\\s|$)' ), 
					classname;
				for ( i; i < n; i++ ) {
					classname = collection[i].className;
					if ( !classname ) {
						if ( not ) {
							pushToStack( collection[i] );
						}
						continue;
					} 
					if ( patt.test( classname ) !== not ) {
						pushToStack( collection[i] );
					} 
				}
			}
		},		
		
		/*====================================
			attributes
		=====================================*/
	
		attributeTests = {
			'=': function ( attr, val ) { return attr === val; }, 
			'^=': function ( attr, val ) { return attr.indexOf( val ) === 0; }, 
			'$=': function ( attr, val ) { return attr.substr( attr.length - val.length ) === val; }, 
			'*=': function ( attr, val ) { return attr.indexOf( val ) !== -1; }, 
			'|=': function ( attr, val ) { return attr.indexOf( val ) === 0; }, 
			'~=': function ( attr, val ) { return (' ' + attr + ' ').indexOf(' ' + val + ' ') !== -1; } 
		},
		
		mergeAttribute = function ( tkn ) {
			var n = collection.length, 
				i = 0;
			if ( contains( tkn.val, '=' ) ) {
				var parts = /([\w-]+)([^=]?=)(.+)/.exec( tkn.val ), 
					attr, 
					val = isDefined( tkn.spValue ) ? tkn.spValue : parts[3];
				for ( i; i < n; i++ ) {
					attr = getAttribute( collection[i], parts[1] );
					if ( ( attr !== null && attributeTests[parts[2]]( attr, val ) ) !== tkn.not ) {
						pushToStack( collection[i] );
					}
				}
			} 
			else {
				for ( i; i < n; i++ ) {
					if ( ( getAttribute( collection[i], tkn.val ) !== null ) !== tkn.not ) {
						pushToStack( collection[i] );
					}
				}
			}
		},
		
		/*====================================
			pseudo classes
		=====================================*/
	
		mergePseudo = function ( tkn ) {
			var kind = tkn.kind,
				not = tkn.not;
			if ( /^(nth-|first-of|last-of)/.test( kind ) ) {
				stack = pseudoTests[kind]( collection, tkn ); 
			} 
			else if ( kind === 'root' && !not ) {
				stack[0] = rootElement;
			} 
			else if ( kind === 'target' && !not ) {
				var hash = win.location.href.split('#')[1] || null;
				stack[0] = getElement( hash ) || getElements( hash )[0];
			} 
			else {
				for ( var i = 0, n = collection.length; i < n; i++ ) {
					if ( pseudoTests[kind]( collection[i], tkn ) !== not ) {
						pushToStack( collection[i] );
					}
				}
			}
		},
		
		parseNthExpr = function ( expr ) {
			var obj = { mode: 'all' };
			obj.direction = expr.indexOf('-') === 0 ? 'neg' : 'pos';
			
			if ( expr === 'n' ) { 
				return obj;
			} 
			else if ( /^\d+$/.test( expr ) ) {
				obj.mode = 'child';
				obj.val = parseInt( expr, 10 );
				return obj;
			} 
			obj.mode = 'an+b';
			
			if ( /^(even|2n|2n\+2)$/.test( expr ) ) {
				obj.oddEven = 0;
			} 
			else if ( /^(odd|2n\+1)$/.test( expr ) ) {
				obj.oddEven = 1;
			}
			var pts = expr.split('n');
			obj.start = pts[1] ? parseInt( pts[1], 10 ) : 1;
			obj.jump = pts[0] && pts[0] !== '-'	? parseInt( pts[0].replace( /^\-/, '' ), 10 ) : 1;		
			return obj;
		},
		
		nthChildFilter = function ( collection, expr, ofType, last, not ) {
			expr = parseNthExpr( expr );
			if ( expr.mode === 'all' ) { return collection; }				
			var	result = [], 
				parentCache = [], 
				nodeName = collection[0].nodeName,
				testType = ofType ? 
					function (el) { return el.nodeType === 1 && el.nodeName === nodeName; } : 
					function (el) { return el.nodeType === 1; },		
				append = function ( cond ) { 
					if ( cond ) { 
						result.push( collection[i] ); 
					}
				};
			for ( var i = 0, n = collection.length, pnt, uid; i < n; i++ ) {
				pnt = collection[i].parentNode, 
				uid = 1;
				if ( !pnt[uniqueKey] ) {
					var el = pnt[ !last ? 'firstChild' : 'lastChild' ],
						direction = !last ? 'nextSibling' : 'previousSibling'; 
					for ( el; el; el = el[direction] ) {
						if ( testType( el ) ) {
							el.nodeIndex = uid++;
						}
					}
					pnt[uniqueKey] = 1;
					parentCache.push( pnt );
				}
				if ( expr.mode === 'child' ) { 
					append( ( ( collection[i].nodeIndex === expr.val ) !== not ) );
				} 
				else if ( isDefined( expr.oddEven ) ) { 
					append( ( collection[i].nodeIndex % 2 === expr.oddEven ) !== not );
				} 
				else {
					if ( expr.direction === 'pos' ) {
						if ( collection[i].nodeIndex < expr.start ) {
							if ( not ) {
								append( true );
							} 
						} 
						else { 
							append( ( ( collection[i].nodeIndex - expr.start ) % expr.jump === 0 ) !== not ); 
						}
					} 
					else {
						if ( collection[i].nodeIndex > expr.start ) {
							if ( not ) { 
								append( true ); 
							} 
						} 
						else { 
							append( ( ( expr.start - collection[i].nodeIndex ) % expr.jump === 0 ) !== not ); 
						}
					}
				}
			}
			unMark( parentCache );
			return expr.direction === 'neg' ? result.reverse() : result;
		},
		
		pseudoTests = {
			'nth-child': function ( tags, tkn ) {
				return nthChildFilter( tags, tkn.val, false, false, tkn.not );
			},
			'nth-of-type': function ( tags, tkn ) {
				return nthChildFilter( tags, tkn.val, true, false, tkn.not );
			},
			'nth-last-child': function ( tags, tkn ) {
				return nthChildFilter( tags, tkn.val, false, true, tkn.not );
			},
			'nth-last-of-type': function ( tags, tkn ) {
				return nthChildFilter( tags, tkn.val, true, true, tkn.not );
			},
			'first-of-type': function ( tags, tkn ) {
				return nthChildFilter( tags, '1', true, false, tkn.not );
			},
			'last-of-type': function ( tags, tkn ) {
				return nthChildFilter( tags, '1', true, true, tkn.not );
			},
			'only-child': function (el) {
				return !getNext(el) && !getPrevious(el);
			},
			'only-of-type': function (el) {
				var tags = getElements( el.nodeName, el.parentNode );
				if ( tags.length === 1 && tags[0].parentNode === el.parentNode ) {
					return true;
				} 
				else {
					for ( var bool = true, n = tags.length, i = 0, c = 0; i < n; i++ ) {
						if ( el.parentNode === tags[i].parentNode ) {
							c++; 
							if ( c > 1 ) {
								return false;
							}
						}
					}
					return true;
				}
			},
			'first-child': function (el) { return !getPrevious(el); },
			'last-child': function (el) { return !getNext(el); }, 
			'checked': function (el) { return el.checked; },
			'enabled': function (el) { return !el.disabled; },
			'disabled': function (el) { return el.disabled; },
			'empty': function (el) { return !el.firstChild;	},
			'lang': function ( el, tkn ) { return el.getAttribute('lang') === tkn.val; },
			'root': function (el) { return el === rootElement; },
			'target': function (el) {
				var hash = win.location.href.split('#')[1] || null;
				return el.id === hash || el.name === hash;
			}
		},
		
		/*====================================
			combinators
		=====================================*/

		mergeDirectSibling = function () {
			for ( var i = 0, n = collection.length, next; i < n; i++ ) {
				if ( next = getNext( collection[i] ) ) {
					pushToStack( next );
				}
			}
		},
		
		mergeAdjacentSibling = function () {
			var store = [], 
				sibs = [], 
				collectionLength = collection.length, 
				i = 0,
				next,
				parental; 
			for ( i; i < collectionLength; i++ ) {
				parental = collection[i].parentNode;
				parental[uniqueKey] = true;
				store.push({
					parent: parental, 
					child: collection[i]
				});
			}	
			for ( i = 0; i < store.length; i++ ) {
				if ( store[i].parent[uniqueKey] ) {
					store[i].parent[uniqueKey] = null;
					sibs.push( store[i].child );
				}
			}
			for ( i = 0; i < sibs.length; i++ ) {
				next = sibs[i].nextSibling;
				while ( next ) {
					if ( next.nodeType === 1 ) {
						pushToStack( next );
					}
					next = next.nextSibling;
				}
			}
		},
		
		filterChildren = function () {
			var result = [], 
				collectionLength = collection.length, 
				stackLength = stack.length, 
				parentElem,
				i = 0,
				j; 
			for ( i; i < stackLength; i++ ) {
				parentElem = stack[i].parentNode; 
				for ( j = 0; j < collectionLength; j++ ) {  
					if ( collection[j] === parentElem ) {
						result.push( stack[i] );
						break;
					}
				}
			}
			stack = result;
		},
		
		firstRun = 1,
		strings = [],
		uniqueKey = '__JELLY_Q__',
		stack = [],
		collection = [],
		
		_filter_ = 1,
		_fetch_ = 2,
		
		_id_ = 3,
		_tag_ = 4,
		_class_ = 5,
		_attr_ = 6,
		_pseudo_ = 7,
		_combi_ = 8,
		
		/*====================================
			execute
		=====================================*/
		
		execute = function ( a, b ) {
		
			var contextMode = !!b,
				selector = a;
			
			collection = contextMode ? [ getElement( b ) ] : [];

			if ( firstRun ) {
				var m;
				while ( m = /('|")([^\1]*?)\1/.exec( selector ) ) {
					strings.push( m[2] );
					selector = selector.split( m[0] );
					selector = [ selector[0], uniqueKey, selector[1] ].join('');   
				}
			}
			
			// Split and recurse for comma chained selectors
			if ( contains( selector, ',' ) ) {
				var combo = [],	
					parts = selector.split(','), 
					part;
				firstRun = 0;
				while ( part = parts.shift() ) {
					combo = combo.concat( contextMode ? execute( a, part ) : execute( part ) );
				}
				firstRun = 1;
				return filterUnique( combo );
			}
			
			//firstRun = !b;
			
			var tokens = parseSelector( selector ),
				children = null; 
				
			// log(tokens)
		
			for ( var i = 0, n = tokens.length, token; i < n; i++ ) {
			
				stack = []; 
				token = tokens[i];
				switch ( token.type ) {
					case _id_: 
						mergeId( token ); 
						break;
					case _tag_: 
						mergeTags( token ); 
						break;
					case _class_: 
						mergeClass( token ); 
						break;
					case _attr_: 
						mergeAttribute( token ); 
						break;
					case _pseudo_: 
						mergePseudo( token ); 
						break
					case _combi_: 
						if ( token.val === '+' ) {
							mergeDirectSibling( token );
						} 
						else if ( token.val === '~' ) {
							mergeAdjacentSibling( token );
						}
				}
				if ( children ) { 
					filterChildren(); 
				}
				if ( token.val === '>' ) {
					children = true;
					continue;
				}
				if ( !stack[0] ) {
					return addSugar( [] );
				}
				children = null;
				firstRun = 0;
				collection = stack;
			}
			if ( tokens.postFilter ) { 
				return addSugar( filterUnique( collection ) ); 
			}
			return addSugar( collection );
		},
		
		nativeSelectorEngine = function ( a, b ) {
			try { 
				return addSugar( toArray(  
					( b ? getElement( b ) : doc ).querySelectorAll( a )
				));
			} catch ( ex ) { 
				logWarn( ex ); 
			}
		},
		
		/*====================================
			sugar methods
		=====================================*/
		
		addSugar = function ( collection ) {
			for ( var meth in sugarMethods ) {
				collection[meth] = sugarMethods[meth];
			}
			return collection;
		},
		
		sugarMethods = {};
		
// Build our sugar methods object
[ 
	'addClass', 
	'removeClass', 
	'setStyle', 
	'addEvent', 
	['setAttribute', 
		function ( el, attr, value ) { return el.setAttribute( attr, value ); }],
	['removeAttribute', 
		function ( el, attr ) {	return el.removeAttribute( attr ); }],
	['remove', 
		removeElement]		
].each( function ( obj ) {
	
	var name = obj,
		method;

	if ( isArray( obj ) ) {
		name = obj[0];
		method = obj[1];
	}
	else {
		method = J[ obj ]
	}
	sugarMethods[name] = function () {
		var args = toArray( arguments ),
			n = this.length,
			i = 0;
		for ( i; i < n; i++ ) {
			method.apply( {}, [this[i]].concat( args ) );
		}  
		return this;
	} 
});

/*====================================
	external api
=====================================*/

J.Q = function () {
	if ( querySelectorAll ) {
		if ( !msie || msie > 8 ) { 
			return nativeSelectorEngine; 
		} 
		// IE8 special case
		return function ( a, b ) {
			if ( /\:(nth|las|onl|not|tar|roo|emp|ena|dis|che)/.test( b || a ) ) { 
				return execute( a, b ); 
			}
			return nativeSelectorEngine( a, b );
		}
	} 
	return execute;
}();
	
})();

var Q = J.Q;

/**

XMLHttpRequest wrapper

*/
(function () {

var Class = defineClass( 'Request', {
		
		__init: function ( obj ) {
			extend( this, obj );
		},
		
		__static: {
			timeout: 15000	
		},
		
		noCache: true,
		async: true,
		cleanUp: true,
		requestHeaders: {},
		
		send: function ( method, request, callback ) {
			var self = this,
			file = request,
			data = null,
			method = method.toUpperCase(),
			xhr = self.xhr ? self.xhr : self.getXHR();
			if ( self.inProgress || !xhr ) {
				return false;
			}
			if ( method === 'POST' ) {
				var tmp = request.split('?');
				file = tmp[0];
				data = tmp[1];
				self.requestHeaders['Content-Type'] = 'application/x-www-form-urlencoded; charset=UTF-8';
				self.requestHeaders['Content-length'] = data.length;
			}
			if ( method === 'GET' && self.noCache ) {
				self.requestHeaders['If-Modifed-Since'] = 'Sat, 1 Jan 2000 00:00:00 GMT';
			}
			
			xhr.open( method, file, self.async );
			
			xhr.onreadystatechange = function () {
				if ( xhr.readyState === 4 ) {
					self.fire( 'complete', xhr );
					self.fire( 'stop', xhr );
					clearTimeout(self.timer);
					var status = xhr.status,
						// credit jQuery
						statusOk = !xhr.status && location.protocol == "file:" ||
							( xhr.status >= 200 && xhr.status < 300 ) || xhr.status == 304 || xhr.status == 1223;
					if ( statusOk ) {
						self.fire( 'success', xhr );
						if ( callback ) {
							callback.call( self, xhr );
						}
					}
					else {
						self.fire( 'fail', xhr );
					}
					if ( self.cleanUp ) {
						self.xhr = null;
					}
					self.inProgress = false;
				}
			};
						
			for ( var key in self.requestHeaders ) {
				xhr.setRequestHeader( key, self.requestHeaders[ key ] );
			}
			xhr.setRequestHeader( 'X-Requested-With', 'XMLHttpRequest' );
			
			self.fire( 'start', xhr );
			self.timer = setTimeout( function () {
				xhr.abort();
				self.fire( 'timeout', xhr );               
				self.fire( 'stop', xhr );
				self.inProgress = false;
			}, self.timeout || Class.timeout );
			
			self.inProgress = true;
			xhr.send( data );
			self.fire( 'request', xhr );
			return true;
		},
		
		post: function ( file, data, callback ) {
			return this.send( 'post', file + '?' + ( data || 'empty' ), callback );
		},
		
		get: function ( request, callback ) {
			return this.send( 'get', request, callback );
		},
		
		getXHR: function () {
			if ( 'XMLHttpRequest' in win ) {
				return function () {
					return new XMLHttpRequest();
				};
			}
			return function () {
				var xhr = false;
				try { xhr = new ActiveXObject('Msxml2.XMLHTTP'); } catch (ex) {
					try { xhr = new ActiveXObject('Microsoft.XMLHTTP'); } catch (ex) {} 
				}
				return xhr;
			};
		}()
		
	});
	
})();

/**

Singleton for managing the loading of assets: scripts, stylesheets and images
	
*/
(function () {

var self = J.Load = {

	cache: {},
	
	js: function ( path, callback, opts ) {
		if ( self.cache[path] ) {
			return self.cache[path];
		}
		var attrs = extend( { type: 'text/javascript' }, opts || {} ),
			el = createElement( 'script', attrs ),
			onload = function () {
				self.cache[path] = el;
				if ( callback ) {
					defer( callback, el, el );
				}
			}, 
			onerror = function ( ex ) {
				logError( ex, ': "' + path + '"');
			};
		try {			
			if ( el.readyState ) {  
				el.onreadystatechange = function () {
					if ( /^(loaded|complete)$/.test( el.readyState ) ) {
						el.onreadystatechange = null;
						onload();
					}
				};
			} 
			else {  
				el.onload = onload;
				el.onerror = onerror;
			}
			el.src = path;
			insertElement( el, docHead );
			return el;
		} 
		catch ( ex ) {
			logError( ex );
		}
	},
	
	// No callbacks or error reporting is possible when loading css
	css: function ( path, opts ) {
		if ( self.cache[path] ) {
			return self.cache[path];
		}
		var attrs = extend( { type: 'text/css', media: 'screen', rel: 'stylesheet' }, opts || {} ),
			link = createElement( 'link', attrs );
		link.href = path;
		self.cache[path] = link;
		insertElement( link, docHead );
		return link;
	},
	
	img: function ( path, callback, opts ) {
		callback = callback || functionLit;
		var cached = self.cache[path];
		if ( cached ) {
			callback.call( cached, cached );
			return cached;
		}
		var img = createElement( 'img', opts || {} ),
			onload = function () {
				self.cache[path] = img;
				callback.call( img, img );
			},
			onerror = function (e) {
				logWarn( capitalize( e.type ) + ' loading image: "' + path + '"' );
			};
		img.onload = onload;
		img.onerror = img.onabort = onerror;
		img.src = path;
		return img;
	},
	
	purge: function ( path ) {
		var obj = self.cache[path];
		if ( !obj ) {
			return;
		}
		if ( docRoot.contains( obj ) ) {
			removeElement( obj );
		}
		obj.onload = obj.onerror = obj.onabort = null;
		delete self.cache[path];
	}
	
};
	
})();

var Load = J.Load;

/**

Class for creating polling objects that can manage multiple subscribed callbacks

@api
>> Instance mode
var obj = new Poll( (Integer) milliseconds )
** (Mixed) handle **  obj.subscribe( (Function) callback [, (String) handle] )
** void **  obj.unSubscribe( (Mixed) handle )

>> Static mode
** (Mixed) handle **  Poll.subscribe( (Integer|String) time/term , (Function) callback [, (String) handle] )
** void **  Poll.unSubscribe( (Integer|String) time/term , (Mixed) handle )
	
@example
// Create a new polling object
var poller = new Poll( 200 );

var handle = poller.subscribe( function () { ... } );
poller.unSubscribe( handle );

poller.subscribe( function () { ... }, 'myref' );
poller.unSubscribe( 'myref' );

@example 
// Subscribe to a global polling object
var handle = Poll.subscribe( 100, function () { ... } );
Poll.unSubscribe( 100, handle );

var handle = Poll.subscribe( 'fast', function () { ... } );
Poll.unSubscribe( 'fast', handle );

*/
(function () {

var Class = defineClass( 'Poll', {
		
	__static: {
		
		pollTime: 300,
		handlers: {},
		
		subscribe: function ( key, fn, ref ) {
			var keyStr = key+'', speed;
			if ( !( keyStr in Class.handlers ) ) {
				speed = isString( key ) ? Class.keywords[key] : key;
				Class.handlers[keyStr] = new Poll( speed );
			}
			return Class.handlers[keyStr].subscribe( fn, ref || null );
		},
		
		unSubscribe: function ( key, handlerId ) {
			Class.handlers[key].unsubscribe( handlerId );
		},

		keywords: {
			'vslow': 1000,
			'slow': 500,
			'fast': 100,
			'vfast': 50
		}
	},
	
	__init: function ( pollTime ) {
		extend( this, {
			handlers: {},
			pollTime: pollTime || Class.pollTime
		});
	},
	
	setPollTime: function ( ms ) {
		this.pollTime = ms;
		return this;
	},
	
	start: function () {
		var self = this;
		clearTimeout( self.timerHandle );
		clearTimeout( self.firstPoll );
		self.firstPoll = setTimeout( function () { 
			(function poll () {
				for ( var key in self.handlers ) {
					try {
						self.handlers[key]();
					}
					catch (ex) {
						logError(ex);
					}
				} 
				self.timerHandle = setTimeout( poll, self.pollTime );
			})()
		}, self.pollTime );
	}, 
	
	stop: function () {
		var self = this;
		clearTimeout( self.timerHandle );
		self.timerHandle = null;
		return self;
	},
	
	clear: function () {
		var self = this;
		self.stop();
		self.handlers = null;
		return self;
	},
	
	subscribe: function ( fn, ref ) {
		var self = this, handlerId;
		self.uid = self.uid || 0;
		handlerId = ref || ++self.uid;
		if ( self.handlers[handlerId] ) {
			return false;
		}
		self.handlers[handlerId] = fn;
		if ( !self.timerHandle ) {
			self.start();
		}
		return handlerId;
	},
	
	unSubscribe: function ( handlerId ) {
		var self = this;
		delete self.handlers[handlerId];
		if ( empty( self.handlers ) ) {
			self.stop();
		}
	}
});
	
})();

var Poll = J.Poll;

/**

Easing equations by Robert Penner 

*/
J.easings={linear:function(B,A,D,C){return D*B/C+A},quadIn:function(B,A,D,C){return D*(B/=C)*B+A},quadOut:function(B,A,D,C){return -D*(B/=C)*(B-2)+A},quadInOut:function(B,A,D,C){if((B/=C/2)<1){return D/2*B*B+A}return -D/2*((--B)*(B-2)-1)+A},cubicIn:function(B,A,D,C){return D*(B/=C)*B*B+A},cubicOut:function(B,A,D,C){return D*((B=B/C-1)*B*B+1)+A},cubicInOut:function(B,A,D,C){if((B/=C/2)<1){return D/2*B*B*B+A}return D/2*((B-=2)*B*B+2)+A},quartIn:function(B,A,D,C){return D*(B/=C)*B*B*B+A},quartOut:function(B,A,D,C){return -D*((B=B/C-1)*B*B*B-1)+A},quartInOut:function(B,A,D,C){if((B/=C/2)<1){return D/2*B*B*B*B+A}return -D/2*((B-=2)*B*B*B-2)+A},quintIn:function(B,A,D,C){return D*(B/=C)*B*B*B*B+A},quintOut:function(B,A,D,C){return D*((B=B/C-1)*B*B*B*B+1)+A},quintInOut:function(B,A,D,C){if((B/=C/2)<1){return D/2*B*B*B*B*B+A}return D/2*((B-=2)*B*B*B*B+2)+A},sineIn:function(B,A,D,C){return -D*Math.cos(B/C*(Math.PI/2))+D+A},sineOut:function(B,A,D,C){return D*Math.sin(B/C*(Math.PI/2))+A},sineInOut:function(B,A,D,C){return -D/2*(Math.cos(Math.PI*B/C)-1)+A},expoIn:function(B,A,D,C){return(B==0)?A:D*Math.pow(2,10*(B/C-1))+A},expoOut:function(B,A,D,C){return(B==C)?A+D:D*(-Math.pow(2,-10*B/C)+1)+A},expoInOut:function(B,A,D,C){if(B==0){return A}if(B==C){return A+D}if((B/=C/2)<1){return D/2*Math.pow(2,10*(B-1))+A}return D/2*(-Math.pow(2,-10*--B)+2)+A},circIn:function(B,A,D,C){return -D*(Math.sqrt(1-(B/=C)*B)-1)+A},circOut:function(B,A,D,C){return D*Math.sqrt(1-(B=B/C-1)*B)+A},circInOut:function(B,A,D,C){if((B/=C/2)<1){return -D/2*(Math.sqrt(1-B*B)-1)+A}return D/2*(Math.sqrt(1-(B-=2)*B)+1)+A},elasticIn:function(C,A,G,F,B,E){if(C==0){return A}if((C/=F)==1){return A+G}if(!E){E=F*0.3}if(!B){B=1}if(B<Math.abs(G)){B=G;var D=E/4}else{var D=E/(2*Math.PI)*Math.asin(G/B)}return -(B*Math.pow(2,10*(C-=1))*Math.sin((C*F-D)*(2*Math.PI)/E))+A},elasticOut:function(C,A,G,F,B,E){if(C==0){return A}if((C/=F)==1){return A+G}if(!E){E=F*0.3}if(!B){B=1}if(B<Math.abs(G)){B=G;var D=E/4}else{var D=E/(2*Math.PI)*Math.asin(G/B)}return B*Math.pow(2,-10*C)*Math.sin((C*F-D)*(2*Math.PI)/E)+G+A},elasticInOut:function(C,A,G,F,B,E){if(C==0){return A}if((C/=F/2)==2){return A+G}if(!E){E=F*(0.3*1.5)}if(!B){B=1}if(B<Math.abs(G)){B=G;var D=E/4}else{var D=E/(2*Math.PI)*Math.asin(G/B)}if(C<1){return -0.5*(B*Math.pow(2,10*(C-=1))*Math.sin((C*F-D)*(2*Math.PI)/E))+A}return B*Math.pow(2,-10*(C-=1))*Math.sin((C*F-D)*(2*Math.PI)/E)*0.5+G+A},backOffset:1.70158,backIn:function(B,A,E,D,C){if(!C){C=J.easings.backOffset}return E*(B/=D)*B*((C+1)*B-C)+A},backOut:function(B,A,E,D,C){if(!C){C=J.easings.backOffset}return E*((B=B/D-1)*B*((C+1)*B+C)+1)+A},backInOut:function(B,A,E,D,C){if(!C){C=J.easings.backOffset}if((B/=D/2)<1){return E/2*(B*B*(((C*=(1.525))+1)*B-C))+A}return E/2*((B-=2)*B*(((C*=(1.525))+1)*B+C)+2)+A},bounceIn:function(B,A,D,C){return D-J.easings.bounceOut(C-B,0,D,C)+A},bounceOut:function(B,A,D,C){if((B/=C)<(1/2.75)){return D*(7.5625*B*B)+A}else{if(B<(2/2.75)){return D*(7.5625*(B-=(1.5/2.75))*B+0.75)+A}else{if(B<(2.5/2.75)){return D*(7.5625*(B-=(2.25/2.75))*B+0.9375)+A}else{return D*(7.5625*(B-=(2.625/2.75))*B+0.984375)+A}}}},bounceInOut:function(B,A,D,C){if(B<C/2){return J.easings.bounceIn(B*2,0,D,C)*0.5+A}return J.easings.bounceOut(B*2-C,0,D,C)*0.5+D*0.5+A}};

/**

Tweening engine

@example
var tween = new Tween( Q( '.box' ), {
	duration: 1000,
	easing: 'sineInOut'
});

// Fade to 0
tween.start( 'opacity', 0 );  

// Fade from .5 to 1
tween.start( 'opacity', { from: .5, to: 1 }); 

// Multiple properties
tween.start({
	duration: 1000,
	marginTop: {
		to: 12,
		unit: 'em'
	},
	marginLeft: 100,
	'background-position': [100, 100],
	rotation: {
		to: 720,
		easing: 'bounceOut'
	},
	skew: 10,
	color: 'red'
});

// Sequences
tween.sequence({
	marginLeft: 120
},{
	delay: 400,
	marginLeft: 0
}, function ( tween ) {
	if ( confirm( 'Do you wish to continue?' ) ) {
		tween.callSequence();
	}
},{
	opacity: 0
});

*/
(function () {

var Class = defineClass( 'Tween', {

		__init: function ( element, opts ) {
			var self = this;
			self.setElement( element );
			self.set( opts || {} );
		},

		__static: {
			uid: 0,
			tweens: {},
			
			timerSpeed: 20,

			subscribe: function ( inst ) {
				Class.tweens[ inst.tweenId ] = function () {
						inst.step.call( inst );
					};
				if ( !Class.timerHandle ) {
					Class.startTimer();
				}
			},

			unSubscribe: function ( inst ) {
				delete Class.tweens[ inst.tweenId ];
				clearTimeout( Class.timeoutHandle );
				Class.timeoutHandle = setTimeout( function () {
						if ( empty( Class.tweens ) ) {
							Class.stopTimer();
						}
					}, 250);
			},

			startTimer: function () {
				var handler = function () {
						for ( var key in Class.tweens ) {
							Class.tweens[ key ]();
						}
					};
				Class.timerHandle = setInterval( handler, Class.timerSpeed );
			},

			stopTimer: function () {
				if ( Class.timerHandle ) {
					clearInterval( Class.timerHandle );
				}
				Class.timerHandle = null;
			}			
		},

		// Instance default values
		easing: J.easings.sineInOut,
		duration: 500,
		unit: 'px',
		
		__set: {
			easing: function ( val ) {
				this.easing = J.easings[ val ];
				return this;
			},
			
			duration: function ( val ) {
				this.duration = val;
				return this;
			},

			opacity: function ( val ) {
				this.element.each( function ( element ) {
					setOpacity( element, val );
				});
				return this;
			},

			origin: function ( val ) {
				this.element.each( function ( element ) {
					element.style[ transformProperty + 'Origin' ] = val;
				});
				return this;
			},
			
			element: function ( element ) {
				this.element = isArray( element ) ? element.map( getElement ) : [ getElement( element ) ];
				return this;
			}
		},

		sequence: function () {
			this.sequenceStack = toArray( arguments );
			this.callSequence();
			return this;
		},

		callSequence: function () {
			var self = this,
				next = isArray( self.sequenceStack ) ? self.sequenceStack.shift() : null;
			if ( next ) {
				if ( isFunction(next) ) {
					next.call( self, self );
				}
				else {
					self.start( next );
				}
			}
			else {
				self.fire( 'sequenceComplete' );
			}
		},

		cancel: function () {
			clearTimeout( self.delayTimer );
			Class.unSubscribe( this );
			return this;
		},

		start: function ( obj ) {
			var self = this,
				args = toArray( arguments );				

			if ( args.length > 1 ) {
				obj = {};
				obj[ args[0] ] = args[1];
			}
			self.cancel();
			self.stack = [];

			// Meta properties
			[ 'element', 
			  'duration', 
			  'easing',
			  'origin'
			].each( function ( prop ) {
				if ( prop in obj ) { 
					self.set( prop, obj[ prop ] )
					delete obj[ prop ];
				}
			});

			// Delay value for the current tween
			var delay = obj[ 'delay' ] || 0;
			delete obj[ 'delay' ];
			
			// Parse the start object to create the effect stack
			each( obj, function ( prop, value ) {
				var key = camelize( prop ), 
					// We pass in the first element as a basis for all unspecified start value calculations
					referenceElement = self.element[0],
					parsers = Class.parsers,
					parser = key in parsers && parsers[ key ].get ? key : '_default',
					feed = {};

				// Deal with object format arguments
				if ( isObject( value ) ) {
					feed = value;
				}
				else {
					feed.to = value; 
				}
				var parser = feed.parser = parsers[ parser ];
				feed.prop = parser.prop || key;
				feed.easing = feed.easing ? J.easings[ feed.easing ] : self.easing;
				feed.unit = isDefined( feed.unit ) ? feed.unit : self.unit;
				
				// Parse from/to values from the referenceElement 
				feed = parser.get( self, key, feed, referenceElement );
				
				self.stack.push( feed );
			});
						
			self.tweenId = ++( Class.uid );
			
			self.delayTimer = setTimeout( function () {
				self.startTime = +( new Date );
				Class.subscribe( self );
				self.fire( 'start' );
			}, delay );
			
			return self;
		},

		step: function () {
			var self = this,
				currentTime = +( new Date );
			if ( currentTime < self.startTime + self.duration ) {
				self.elapsedTime = currentTime - self.startTime;
			}
			else {
				self.cancel();
				self.tidyUp();
				setTimeout(	function () {
					self.fire( 'complete' );
					self.callSequence();
				}, 0 );
				return;
			}
			self.increase();
		},
		
		increase: function () {
			var self = this,
				collection = self.element,
				stackCounter = self.stack.length - 1,
				collectionCounter,
				item, 
				element;
			do {
				item = self.stack[ stackCounter ];
				collectionCounter = collection.length - 1;
				do {
					element = collection[ collectionCounter ]; 
					element.style[ item.prop ] = item.parser.step( self, item, element );
				} while ( collectionCounter-- );
			} while ( stackCounter-- );
		},
		
		tidyUp: function () {
			var self = this,
				collection = self.element,
				stackCounter = self.stack.length - 1,
				collectionCounter,
				item, 
				element;
			do {
				item = self.stack[ stackCounter ];
				collectionCounter = collection.length - 1;
				do {
					element = collection[ collectionCounter ]; 
					element.style[ item.prop ] = item.parser.finish( self, item, element );
				} while ( collectionCounter-- );
			} while ( stackCounter-- );
		},
		
		compute: function ( obj, from, to ) {
			return obj.easing( this.elapsedTime, from, ( to - from ), this.duration );
		}

	});


/* Parsers */

var	_default = {
		get: function ( self, key, feed, referenceElement ) {
			if ( isUndefined( feed.from ) ) {
				feed.from = parseFloat( getStyle( referenceElement, key ) ) || 0;
			}
			return feed;
		},
		step: function ( self, feed ) {	
			return self.compute( feed, feed.from, feed.to ) + feed.unit;
		},
		finish: function ( self, feed ) { 
			return feed.to + feed.unit;
		}
	},

	_unitless = {
		get: _default.get,
		step: function ( self, feed ) {
			return self.compute( feed, feed.from, feed.to );
		},
		finish: function ( self, feed ) {
			return feed.to;
		}
	},
	
	_color = {
		get: function ( self, key, feed, referenceElement ) {
			if ( isUndefined( feed.from ) ) {
				feed.from = parseColor( getStyle( referenceElement, key ), 'rgb-array' );
			} 
			feed.to = parseColor( feed.to, 'rgb-array' );
			return feed;
		},
		step: function ( self, feed, element ) {
			var round = Math.round;
			return 'rgb(' +
					round( self.compute( feed, feed.from[0], feed.to[0] ) ) + ',' +
					round( self.compute( feed, feed.from[1], feed.to[1] ) ) + ',' +
					round( self.compute( feed, feed.from[2], feed.to[2] ) ) + ')';
		},
		finish: function ( self, feed ) {
			return 'rgb(' + feed.to.join( ',' ) + ')';
		}
	},
	
	
	_colorRgba = {
		get: function ( self, key, feed, referenceElement ) {
			if ( isUndefined( feed.from ) ) {
				feed.from = getStyle( referenceElement, key );
			} 
			feed.from = parseColor( feed.from, 'rgb-array' ); 
			feed.to = parseColor( feed.to, 'rgb-array' );
			
			feed.from.push( feed.fromAlpha || 1 );
			feed.to.push( feed.toAlpha || 1 );
			return feed;
		},
		step: function ( self, feed, element ) {
			var round = Math.round;
			return 'rgba(' +
					round( self.compute( feed, feed.from[0], feed.to[0] ) ) + ',' +
					round( self.compute( feed, feed.from[1], feed.to[1] ) ) + ',' +
					round( self.compute( feed, feed.from[2], feed.to[2] ) ) + ',' + 
					round( self.compute( feed, feed.from[3], feed.to[3] ) ) + ')';;
		},
		finish: function ( self, feed ) {
			return 'rgba(' + feed.to.join( ',' ) + ')';
		}
	},
		
	_backgroundPosition = {
		get: function ( self, key, feed, referenceElement ) {
			if ( isUndefined( feed.from ) ) {
				var startX = 0,
					startY = 0,
					currentStyle = getStyle( referenceElement, key );
				if ( currentStyle ) {
					currentStyle = currentStyle.
						split( ' ' ).
						filter( negate( empty ) ).
						map( parseFloat );
					startX = currentStyle[0] || startX; 
					startY = currentStyle[1] || startY;
				}
				feed.from = [ startX, startY ]; 
			}
			return feed;
		},
		step: function ( self, feed ) {
			return 	self.compute( feed, feed.from[0], feed.to[0] ) + feed.unit + ' ' + 
					self.compute( feed, feed.from[1], feed.to[1] ) + feed.unit;
		},
		finish: function ( self, feed ) {
			return feed.to.join( feed.unit + ' ' ) + feed.unit;
		}
	},

	_opacity = function () {
		if ( 'opacity' in docRoot.style ) {
			return _unitless;
		}
		return {
			get: function ( self, key, feed, referenceElement ) {
				if ( isUndefined( feed.from ) ) {
					var elStyle = referenceElement.style;
					if ( elStyle.opacity === undefined ) {
						elStyle.opacity = 1;
						elStyle.zoom = 1;
					}
					feed.from = elStyle.opacity;
				}
				return feed;
			},
			step: function ( self, feed, element ) {
				var result = self.compute( feed, feed.from, feed.to );
				element.style.filter = result === 1 ? '' : 'alpha(opacity=' + ( result * 100 ) + ')';
				return result;
			},
			finish: function ( self, feed, element ) {
				element.style.filter = feed.to === 1 ? '' : 'alpha(opacity=' + ( feed.to * 100 ) + ')';
				return feed.to;
			}
		}
	}();
	
Class.parsers = {
	_default: _default,
	_unitless: _unitless,
	'opacity': _opacity,
	'color': _color,
	'backgroundColor': _color,
	'backgroundPosition': _backgroundPosition
};
	

/* 2D Transformation parsers */

var	getVendorProperty = function ( prop ) {
		var prop = camelize( prop ), 
			cases = [ prop ].concat( 
				[ 'Webkit', 'Moz', 'O', 'Ms' ].map( preset( String.concat, capitalize( prop ) ) ) );
		for ( var i = 0; i < cases.length; i++ ) {
			if ( cases[i] in docRoot.style ) {
				return cases[i];
			}
		}
		return cases[0];
	},
	
	transformProperty = getVendorProperty( 'transform' ),
	
	transformParser = function ( prop, obj ) {
		var unit = obj.unit || '',
			defaultValue = obj.def || 0,
			multiValue = isArray( defaultValue ),
			re = new RegExp( '((?:^|\\s)' + prop + '\\()([^\\)]+)(\\))' ),
			parseValue = function ( val ) {
				return val.split( ',' ).map( parseFloat );
			},
			joinValue = function ( val ) {
				return val.join( unit + ',' ) + unit;
			};
		return { 
			prop: transformProperty,

			// Return multi-dimensional array 
			// [ [ 1, 2.. ], [ 50, 100.. ] ]
			get: function ( self, key, feed, referenceElement ) {
				var elStyle = referenceElement.style,
					startValue = elStyle[ transformProperty ], 
					// Is the property already on the style attribute?
					m = re.exec( startValue );				
				
				// Pick a start value
				if ( isUndefined( feed.from ) ) {
					feed.from = m ? 
						parseValue( m[2] ) : 
						( isArray( feed.to ) ? 
							feed.to.map( function () {return defaultValue;} ) : 
							[ defaultValue ] );
				} 
				// Need to initialize the style attribute if it's not already
				if ( !m ) { 
					self.element.each( function ( el ) {
						el.style[ transformProperty ] += ' ' + prop + '(' + joinValue( feed.from ) + ')';
					});
				}
				if ( !isArray( feed.to ) ) {
					feed.to = [ feed.to ];
				}
				return feed;
			},
			
			step: function ( self, feed, element ) {	
				var result = [], i = 0;
				for ( ; i < feed.from.length; i++ ) {
					result.push( self.compute( feed, feed.from[i], feed.to[i] ) );
				} 
				return element.style[ transformProperty ].replace( re, '$1' + joinValue( result ) + '$3' ); 
			},
			
			finish: function ( self, feed, element ) { 
				return element.style[ transformProperty ].replace( re, '$1' + joinValue( feed.to ) + '$3' ); 
			}
		}
	};

each({
	'matrix': { },
	'translate': { unit: 'px' },
	'translateX': { unit: 'px' },
	'translateY': { unit: 'px' },
	'scale': { def: 1 },
	'scaleX': { def: 1 },
	'scaleY': { def: 1 },
	'rotate': { unit: 'deg', parserName: 'rotation' },
	'skew': { unit: 'deg' },
	'skewX': { unit: 'deg' },
	'skewY': { unit: 'deg' }
}, function ( prop, obj ) {
	Class.parsers[ obj.parserName || prop ] = transformParser( prop, obj );
})
	
})();

var Tween = J.Tween;

/**

Loop a <Tween> instance

*/
(function () {

var Class = defineClass( 'Tween.Loop', {
		
		__static: {
			loopCount: 2
		},
		
		__init: function ( element, opts ) {
			opts = opts || {};
			this.loopCount = opts.loopCount || Class.loopCount;
			delete opts.loopCount;
			this.tween = new J.Tween( element, opts );
        },
        
		setLoopCount: function ( integer ) {
			this.loopCount = integer;
			return this;
		},
		
        start: function () {
            var self = this,
				args = toArray( arguments ),
				tween = self.tween,
				onSequenceComplete = 'onSequenceComplete',
				loopCount = self.loopCount;
				
			if ( args.length < 2 ) {
				logWarn( Class.__name, ' :too few arguments' );
				return;
			}
			
			self.cancel = false;
			
			(function looper () {
				if ( self.cancel ) {
					delete tween[ onSequenceComplete ];
					return;
				}
				else if ( --loopCount ) {
					tween[ onSequenceComplete ] = looper;
				}				
				else {
					self.cancel = true;
					tween[ onSequenceComplete ] = function () {
						self.fire( 'complete' );
					};
				}
				tween.sequence.apply( self.tween, args );
			})();
        },
        
		stop: function ( stopCurrentLoop ) {
			var self = this;
			self.cancel = true;
			if ( stopCurrentLoop ) {
				self.tween.stop();	
			}
		}
		
    });

})();

/**

An 'onhashchange' event handler 
	
@dependencies   
Poll

@api
** handle **  HashChange.subscribe( <fn> handler )
** void **  HashChange.unsubscribe( handle )
	
@example
var handle = HashChange.subscribe( function () { ... } );
HashChange.unsubscribe( handle );

*/
(function () {

var self = J.HashChange = {

		subscribe: function ( fn ) {
			if ( !_nativeSupport ) {
				_handlers[ ++_uid ] = fn;
				_start();
				return _uid;
			}
			else {
				return addEvent( win, 'hashchange', fn );
			}
		},
		
		unSubscribe: function ( handle ) {
			var self = this;
			if ( !_nativeSupport ) { 
				delete _handlers[ handle ];
				if ( empty( _handlers ) ) {
					_stop();
				}
			}
			else {
				removeEvent( handle );
			}
		}	
	},
	
	_nativeSupport = 'onhashchange' in win,
	_uid = 0,
	_handlers = {},
	_hash = '',
	_poller = null,
	
	_cycle = function () {
		var hash = location.hash;
		if ( hash !== _hash ) { 
			for ( var id in _handlers ) {
				try {
					_handlers[ id ]();
				} catch ( ex ) {
					logError( ex );
				}
			}
			_hash = hash;
		}
	},
	
	_start = function () {
		if ( !_poller ) {
			_hash = location.hash;
			_poller = Poll.subscribe( 'fast', _cycle );
		}
	},
	
	_stop = function () {
		if ( _poller ) {
			Poll.unsubscribe( 'fast', _poller );
			_poller = null;
		}
	};
	
})();
})(); // End outer closure
