/**
 * jGrowl 1.2.0
 *
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php)
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * Written by Stan Lemon <stanlemon@mac.com>
 * Last updated: 2009.05.11
 *
 * jGrowl is a jQuery plugin implementing unobtrusive userland notifications.  These 
 * notifications function similarly to the Growl Framework available for
 * Mac OS X (http://growl.info).
 *
 * To Do:
 * - Move library settings to containers and allow them to be changed per container
 *
 * Changes in 1.2.0
 * - Added message pooling to limit the number of messages appearing at a given time.
 * - Closing a notification is now bound to the notification object and triggered by the close button.
 *
 * Changes in 1.1.2
 * - Added iPhone styled example
 * - Fixed possible IE7 bug when determining if the ie6 class shoudl be applied.
 * - Added template for the close button, so that it's content could be customized.
 *
 * Changes in 1.1.1
 * - Fixed CSS styling bug for ie6 caused by a mispelling
 * - Changes height restriction on default notifications to min-height
 * - Added skinned examples using a variety of images
 * - Added the ability to customize the content of the [close all] box
 * - Added jTweet, an example of using jGrowl + Twitter
 *
 * Changes in 1.1.0
 * - Multiple container and instances.
 * - Standard $.jGrowl() now wraps $.fn.jGrowl() by first establishing a generic jGrowl container.
 * - Instance methods of a jGrowl container can be called by $.fn.jGrowl(methodName)
 * - Added glue preferenced, which allows notifications to be inserted before or after nodes in the container
 * - Added new log callback which is called before anything is done for the notification
 * - Corner's attribute are now applied on an individual notification basis.
 *
 * Changes in 1.0.4
 * - Various CSS fixes so that jGrowl renders correctly in IE6.
 *
 * Changes in 1.0.3
 * - Fixed bug with options persisting across notifications
 * - Fixed theme application bug
 * - Simplified some selectors and manipulations.
 * - Added beforeOpen and beforeClose callbacks
 * - Reorganized some lines of code to be more readable
 * - Removed unnecessary this.defaults context
 * - If corners plugin is present, it's now customizable.
 * - Customizable open animation.
 * - Customizable close animation.
 * - Customizable animation easing.
 * - Added customizable positioning (top-left, top-right, bottom-left, bottom-right, center)
 *
 * Changes in 1.0.2
 * - All CSS styling is now external.
 * - Added a theme parameter which specifies a secondary class for styling, such
 *   that notifications can be customized in appearance on a per message basis.
 * - Notification life span is now customizable on a per message basis.
 * - Added the ability to disable the global closer, enabled by default.
 * - Added callbacks for when a notification is opened or closed.
 * - Added callback for the global closer.
 * - Customizable animation speed.
 * - jGrowl now set itself up and tears itself down.
 *
 * Changes in 1.0.1:
 * - Removed dependency on metadata plugin in favor of .data()
 * - Namespaced all events
 */
(function($) {

	/** jGrowl Wrapper - Establish a base jGrowl Container for compatibility with older releases. **/
	$.jGrowl = function( m , o ) {
		// To maintain compatibility with older version that only supported one instance we'll create the base container.
		if ( $('#jGrowl').size() == 0 ) $('<div id="jGrowl"></div>').addClass($.jGrowl.defaults.position).appendTo('body');
		// Create a notification on the container.
		$('#jGrowl').jGrowl(m,o);
	};


	/** Raise jGrowl Notification on a jGrowl Container **/
	$.fn.jGrowl = function( m , o ) {
		if ( $.isFunction(this.each) ) {
			var args = arguments;

			return this.each(function() {
				var self = this;

				/** Create a jGrowl Instance on the Container if it does not exist **/
				if ( $(this).data('jGrowl.instance') == undefined ) {
					$(this).data('jGrowl.instance', new $.fn.jGrowl());
					$(this).data('jGrowl.instance').startup( this );
				}

				/** Optionally call jGrowl instance methods, or just raise a normal notification **/
				if ( $.isFunction($(this).data('jGrowl.instance')[m]) ) {
					$(this).data('jGrowl.instance')[m].apply( $(this).data('jGrowl.instance') , $.makeArray(args).slice(1) );
				} else {
					$(this).data('jGrowl.instance').create( m , o );
				}
			});
		};
	};

	$.extend( $.fn.jGrowl.prototype , {

		/** Default JGrowl Settings **/
		defaults: {
			pool: 			5,
			header: 		'',
			group: 			'',
			sticky: 		true,
			position: 		'bottom-right', // Is this still needed?
			glue: 			'after',
			theme: 			'default',
			corners: 		'10px',
			check: 			250,
			life: 			5000,
			speed: 			'normal',
			easing: 		'swing',
			closer: 		false,
			closeTemplate: '&times;',
			closerTemplate: '<div>[ close all ]</div>',
			log: 			function(e,m,o) {},
			beforeOpen: 	function(e,m,o) {},
			open: 			function(e,m,o) {},
			beforeClose: 	function(e,m,o) {},
			close: 			function(e,m,o) {},
			animateOpen: 	{
				opacity: 	'show'
			},
			animateClose: 	{
				opacity: 	'hide'
			}
		},
		
		notifications: [],
		
		/** jGrowl Container Node **/
		element: 	null,
	
		/** Interval Function **/
		interval:   null,
		
		/** Create a Notification **/
		create: 	function( message , o ) {
			var o = $.extend({}, this.defaults, o);

			this.notifications[ this.notifications.length ] = { message: message , options: o };
			
			o.log.apply( this.element , [this.element,message,o] );
		},
		
		render: 		function( notification ) {
			var self = this;
			var message = notification.message;
			var o = notification.options;

			var notification = $('<div class="jGrowl-notification' + ((o.group != undefined && o.group != '') ? ' ' + o.group : '') + '"><div class="close">' + o.closeTemplate + '</div><div class="header">' + o.header + '</div><div class="message">' + message + '</div></div>')
				.data("jGrowl", o).addClass(o.theme).children('div.close').bind("click.jGrowl", function() {
					$(this).parent().trigger('jGrowl.close');
				}).parent();
				
			( o.glue == 'after' ) ? $('div.jGrowl-notification:last', this.element).after(notification) : $('div.jGrowl-notification:first', this.element).before(notification);

			/** Notification Actions **/
			$(notification).bind("mouseover.jGrowl", function() {
				$(this).data("jGrowl").pause = true;
			}).bind("mouseout.jGrowl", function() {
				$(this).data("jGrowl").pause = false;
			}).bind('jGrowl.beforeOpen', function() {
				o.beforeOpen.apply( self.element , [self.element,message,o] );
			}).bind('jGrowl.open', function() {
				o.open.apply( self.element , [self.element,message,o] );
			}).bind('jGrowl.beforeClose', function() {
				o.beforeClose.apply( self.element , [self.element,message,o] );
			}).bind('jGrowl.close', function() {
				$(this).trigger('jGrowl.beforeClose').animate(o.animateClose, o.speed, o.easing, function() {
					$(this).remove();
					o.close.apply( self.element , [self.element,message,o] );
				});
			}).trigger('jGrowl.beforeOpen').animate(o.animateOpen, o.speed, o.easing, function() {
				$(this).data("jGrowl").created = new Date();
			}).trigger('jGrowl.open');
		
			/** Optional Corners Plugin **/
			if ( $.fn.corner != undefined ) $(notification).corner( o.corners );

			/** Add a Global Closer if more than one notification exists **/
			if ( $('div.jGrowl-notification:parent', this.element).size() > 1 && $('div.jGrowl-closer', this.element).size() == 0 && this.defaults.closer != false ) {
				$(this.defaults.closerTemplate).addClass('jGrowl-closer').addClass(this.defaults.theme).appendTo(this.element).animate(this.defaults.animateOpen, this.defaults.speed, this.defaults.easing).bind("click.jGrowl", function() {
					$(this).siblings().children('div.close').trigger("click.jGrowl");

					if ( $.isFunction( self.defaults.closer ) ) self.defaults.closer.apply( $(this).parent()[0] , [$(this).parent()[0]] );
				});
			};
		},

		/** Update the jGrowl Container, removing old jGrowl notifications **/
		update:	 function() {
			$(this.element).find('div.jGrowl-notification:parent').each( function() {
				if ( $(this).data("jGrowl") != undefined && $(this).data("jGrowl").created != undefined && ($(this).data("jGrowl").created.getTime() + $(this).data("jGrowl").life)  < (new Date()).getTime() && $(this).data("jGrowl").sticky != true && 
					 ($(this).data("jGrowl").pause == undefined || $(this).data("jGrowl").pause != true) ) {
					$(this).trigger('jGrowl.close');
				}
			});

			if ( this.notifications.length > 0 && (this.defaults.pool == 0 || $(this.element).find('div.jGrowl-notification:parent').size() < this.defaults.pool) ) {
				this.render( this.notifications.shift() );
			}

			if ( $(this.element).find('div.jGrowl-notification:parent').size() < 2 ) {
				$(this.element).find('div.jGrowl-closer').animate(this.defaults.animateClose, this.defaults.speed, this.defaults.easing, function() {
					$(this).remove();
				});
			};
		},

		/** Setup the jGrowl Notification Container **/
		startup:	function(e) {
			this.element = $(e).addClass('jGrowl').append('<div class="jGrowl-notification"></div>');
			this.interval = setInterval( function() { 
				jQuery(e).data('jGrowl.instance').update(); 
			}, this.defaults.check);
			
			if ($.browser.msie && parseInt($.browser.version) < 7 && !window["XMLHttpRequest"]) $(this.element).addClass('ie6');
		},

		/** Shutdown jGrowl, removing it and clearing the interval **/
		shutdown:   function() {
			$(this.element).removeClass('jGrowl').find('div.jGrowl-notification').remove();
			clearInterval( this.interval );
		}
	});
	
	/** Reference the Defaults Object for compatibility with older versions of jGrowl **/
	$.jGrowl.defaults = $.fn.jGrowl.prototype.defaults;

})(jQuery);

/**
 * jQuery.ScrollTo
 * Copyright (c) 2007-2008 Ariel Flesler - aflesler(at)gmail(dot)com | http://flesler.blogspot.com
 * Dual licensed under MIT and GPL.
 * Date: 9/11/2008
 *
 * @projectDescription Easy element scrolling using jQuery.
 * http://flesler.blogspot.com/2007/10/jqueryscrollto.html
 * Tested with jQuery 1.2.6. On FF 2/3, IE 6/7, Opera 9.2/5 and Safari 3. on Windows.
 *
 * @author Ariel Flesler
 * @version 1.4
 *
 * @id jQuery.scrollTo
 * @id jQuery.fn.scrollTo
 * @param {String, Number, DOMElement, jQuery, Object} target Where to scroll the matched elements.
 *	  The different options for target are:
 *		- A number position (will be applied to all axes).
 *		- A string position ('44', '100px', '+=90', etc ) will be applied to all axes
 *		- A jQuery/DOM element ( logically, child of the element to scroll )
 *		- A string selector, that will be relative to the element to scroll ( 'li:eq(2)', etc )
 *		- A hash { top:x, left:y }, x and y can be any kind of number/string like above.
 * @param {Number} duration The OVERALL length of the animation, this argument can be the settings object instead.
 * @param {Object,Function} settings Optional set of settings or the onAfter callback.
 *	 @option {String} axis Which axis must be scrolled, use 'x', 'y', 'xy' or 'yx'.
 *	 @option {Number} duration The OVERALL length of the animation.
 *	 @option {String} easing The easing method for the animation.
 *	 @option {Boolean} margin If true, the margin of the target element will be deducted from the final position.
 *	 @option {Object, Number} offset Add/deduct from the end position. One number for both axes or { top:x, left:y }.
 *	 @option {Object, Number} over Add/deduct the height/width multiplied by 'over', can be { top:x, left:y } when using both axes.
 *	 @option {Boolean} queue If true, and both axis are given, the 2nd axis will only be animated after the first one ends.
 *	 @option {Function} onAfter Function to be called after the scrolling ends. 
 *	 @option {Function} onAfterFirst If queuing is activated, this function will be called after the first scrolling ends.
 * @return {jQuery} Returns the same jQuery object, for chaining.
 *
 * @desc Scroll to a fixed position
 * @example $('div').scrollTo( 340 );
 *
 * @desc Scroll relatively to the actual position
 * @example $('div').scrollTo( '+=340px', { axis:'y' } );
 *
 * @dec Scroll using a selector (relative to the scrolled element)
 * @example $('div').scrollTo( 'p.paragraph:eq(2)', 500, { easing:'swing', queue:true, axis:'xy' } );
 *
 * @ Scroll to a DOM element (same for jQuery object)
 * @example var second_child = document.getElementById('container').firstChild.nextSibling;
 *			$('#container').scrollTo( second_child, { duration:500, axis:'x', onAfter:function(){
 *				alert('scrolled!!');																   
 *			}});
 *
 * @desc Scroll on both axes, to different values
 * @example $('div').scrollTo( { top: 300, left:'+=200' }, { axis:'xy', offset:-20 } );
 */
;(function( $ ){
	
	var $scrollTo = $.scrollTo = function( target, duration, settings ){
		$(window).scrollTo( target, duration, settings );
	};

	$scrollTo.defaults = {
		axis:'y',
		duration:1
	};

	// Returns the element that needs to be animated to scroll the window.
	// Kept for backwards compatibility (specially for localScroll & serialScroll)
	$scrollTo.window = function( scope ){
		return $(window).scrollable();
	};

	// Hack, hack, hack... stay away!
	// Returns the real elements to scroll (supports window/iframes, documents and regular nodes)
	$.fn.scrollable = function(){
		return this.map(function(){
			// Just store it, we might need it
			var win = this.parentWindow || this.defaultView,
				// If it's a document, get its iframe or the window if it's THE document
				elem = this.nodeName == '#document' ? win.frameElement || win : this,
				// Get the corresponding document
				doc = elem.contentDocument || (elem.contentWindow || elem).document,
				isWin = elem.setInterval;

			return elem.nodeName == 'IFRAME' || isWin && $.browser.safari ? doc.body
				: isWin ? doc.documentElement
				: this;
		});
	};

	$.fn.scrollTo = function( target, duration, settings ){
		if( typeof duration == 'object' ){
			settings = duration;
			duration = 0;
		}
		if( typeof settings == 'function' )
			settings = { onAfter:settings };
			
		settings = $.extend( {}, $scrollTo.defaults, settings );
		// Speed is still recognized for backwards compatibility
		duration = duration || settings.speed || settings.duration;
		// Make sure the settings are given right
		settings.queue = settings.queue && settings.axis.length > 1;
		
		if( settings.queue )
			// Let's keep the overall duration
			duration /= 2;
		settings.offset = both( settings.offset );
		settings.over = both( settings.over );

		return this.scrollable().each(function(){
			var elem = this,
				$elem = $(elem),
				targ = target, toff, attr = {},
				win = $elem.is('html,body');

			switch( typeof targ ){
				// A number will pass the regex
				case 'number':
				case 'string':
					if( /^([+-]=)?\d+(px)?$/.test(targ) ){
						targ = both( targ );
						// We are done
						break;
					}
					// Relative selector, no break!
					targ = $(targ,this);
				case 'object':
					// DOMElement / jQuery
					if( targ.is || targ.style )
						// Get the real position of the target 
						toff = (targ = $(targ)).offset();
			}
			$.each( settings.axis.split(''), function( i, axis ){
				var Pos	= axis == 'x' ? 'Left' : 'Top',
					pos = Pos.toLowerCase(),
					key = 'scroll' + Pos,
					old = elem[key],
					Dim = axis == 'x' ? 'Width' : 'Height',
					dim = Dim.toLowerCase();

				if( toff ){// jQuery / DOMElement
					attr[key] = toff[pos] + ( win ? 0 : old - $elem.offset()[pos] );

					// If it's a dom element, reduce the margin
					if( settings.margin ){
						attr[key] -= parseInt(targ.css('margin'+Pos)) || 0;
						attr[key] -= parseInt(targ.css('border'+Pos+'Width')) || 0;
					}
					
					attr[key] += settings.offset[pos] || 0;
					
					if( settings.over[pos] )
						// Scroll to a fraction of its width/height
						attr[key] += targ[dim]() * settings.over[pos];
				}else
					attr[key] = targ[pos];

				// Number or 'number'
				if( /^\d+$/.test(attr[key]) )
					// Check the limits
					attr[key] = attr[key] <= 0 ? 0 : Math.min( attr[key], max(Dim) );

				// Queueing axes
				if( !i && settings.queue ){
					// Don't waste time animating, if there's no need.
					if( old != attr[key] )
						// Intermediate animation
						animate( settings.onAfterFirst );
					// Don't animate this axis again in the next iteration.
					delete attr[key];
				}
			});			
			animate( settings.onAfter );			

			function animate( callback ){
				$elem.animate( attr, duration, settings.easing, callback && function(){
					callback.call(this, target, settings);
				});
			};
			function max( Dim ){
				var attr ='scroll'+Dim,
					doc = elem.ownerDocument;
				
				return win
						? Math.max( doc.documentElement[attr], doc.body[attr]  )
						: elem[attr];
			};
		}).end();
	};

	function both( val ){
		return typeof val == 'object' ? val : { top:val, left:val };
	};

})( jQuery );

/* Copyright (c) 2007 Brandon Aaron (brandon.aaron@gmail.com || http://brandonaaron.net)
 * Dual licensed under the MIT (http://www.opensource.org/licenses/mit-license.php) 
 * and GPL (http://www.opensource.org/licenses/gpl-license.php) licenses.
 *
 * Version: 1.0.2
 * Requires jQuery 1.1.3+
 * Docs: http://docs.jquery.com/Plugins/livequery
 */

(function($) {
	
$.extend($.fn, {
	livequery: function(type, fn, fn2) {
		var self = this, q;
		
		// Handle different call patterns
		if ($.isFunction(type))
			fn2 = fn, fn = type, type = undefined;
			
		// See if Live Query already exists
		$.each( $.livequery.queries, function(i, query) {
			if ( self.selector == query.selector && self.context == query.context &&
				type == query.type && (!fn || fn.$lqguid == query.fn.$lqguid) && (!fn2 || fn2.$lqguid == query.fn2.$lqguid) )
					// Found the query, exit the each loop
					return (q = query) && false;
		});
		
		// Create new Live Query if it wasn't found
		q = q || new $.livequery(this.selector, this.context, type, fn, fn2);
		
		// Make sure it is running
		q.stopped = false;
		
		// Run it
		$.livequery.run( q.id );
		
		// Contnue the chain
		return this;
	},
	
	expire: function(type, fn, fn2) {
		var self = this;
		
		// Handle different call patterns
		if ($.isFunction(type))
			fn2 = fn, fn = type, type = undefined;
			
		// Find the Live Query based on arguments and stop it
		$.each( $.livequery.queries, function(i, query) {
			if ( self.selector == query.selector && self.context == query.context && 
				(!type || type == query.type) && (!fn || fn.$lqguid == query.fn.$lqguid) && (!fn2 || fn2.$lqguid == query.fn2.$lqguid) && !this.stopped )
					$.livequery.stop(query.id);
		});
		
		// Continue the chain
		return this;
	}
});

$.livequery = function(selector, context, type, fn, fn2) {
	this.selector = selector;
	this.context  = context || document;
	this.type     = type;
	this.fn       = fn;
	this.fn2      = fn2;
	this.elements = [];
	this.stopped  = false;
	
	// The id is the index of the Live Query in $.livequery.queries
	this.id = $.livequery.queries.push(this)-1;
	
	// Mark the functions for matching later on
	fn.$lqguid = fn.$lqguid || $.livequery.guid++;
	if (fn2) fn2.$lqguid = fn2.$lqguid || $.livequery.guid++;
	
	// Return the Live Query
	return this;
};

$.livequery.prototype = {
	stop: function() {
		var query = this;
		
		if ( this.type )
			// Unbind all bound events
			this.elements.unbind(this.type, this.fn);
		else if (this.fn2)
			// Call the second function for all matched elements
			this.elements.each(function(i, el) {
				query.fn2.apply(el);
			});
			
		// Clear out matched elements
		this.elements = [];
		
		// Stop the Live Query from running until restarted
		this.stopped = true;
	},
	
	run: function() {
		// Short-circuit if stopped
		if ( this.stopped ) return;
		var query = this;
		
		var oEls = this.elements,
			els  = $(this.selector, this.context),
			nEls = els.not(oEls);
		
		// Set elements to the latest set of matched elements
		this.elements = els;
		
		if (this.type) {
			// Bind events to newly matched elements
			nEls.bind(this.type, this.fn);
			
			// Unbind events to elements no longer matched
			if (oEls.length > 0)
				$.each(oEls, function(i, el) {
					if ( $.inArray(el, els) < 0 )
						$.event.remove(el, query.type, query.fn);
				});
		}
		else {
			// Call the first function for newly matched elements
			nEls.each(function() {
				query.fn.apply(this);
			});
			
			// Call the second function for elements no longer matched
			if ( this.fn2 && oEls.length > 0 )
				$.each(oEls, function(i, el) {
					if ( $.inArray(el, els) < 0 )
						query.fn2.apply(el);
				});
		}
	}
};

$.extend($.livequery, {
	guid: 0,
	queries: [],
	queue: [],
	running: false,
	timeout: null,
	
	checkQueue: function() {
		if ( $.livequery.running && $.livequery.queue.length ) {
			var length = $.livequery.queue.length;
			// Run each Live Query currently in the queue
			while ( length-- )
				$.livequery.queries[ $.livequery.queue.shift() ].run();
		}
	},
	
	pause: function() {
		// Don't run anymore Live Queries until restarted
		$.livequery.running = false;
	},
	
	play: function() {
		// Restart Live Queries
		$.livequery.running = true;
		// Request a run of the Live Queries
		$.livequery.run();
	},
	
	registerPlugin: function() {
		$.each( arguments, function(i,n) {
			// Short-circuit if the method doesn't exist
			if (!$.fn[n]) return;
			
			// Save a reference to the original method
			var old = $.fn[n];
			
			// Create a new method
			$.fn[n] = function() {
				// Call the original method
				var r = old.apply(this, arguments);
				
				// Request a run of the Live Queries
				$.livequery.run();
				
				// Return the original methods result
				return r;
			}
		});
	},
	
	run: function(id) {
		if (id != undefined) {
			// Put the particular Live Query in the queue if it doesn't already exist
			if ( $.inArray(id, $.livequery.queue) < 0 )
				$.livequery.queue.push( id );
		}
		else
			// Put each Live Query in the queue if it doesn't already exist
			$.each( $.livequery.queries, function(id) {
				if ( $.inArray(id, $.livequery.queue) < 0 )
					$.livequery.queue.push( id );
			});
		
		// Clear timeout if it already exists
		if ($.livequery.timeout) clearTimeout($.livequery.timeout);
		// Create a timeout to check the queue and actually run the Live Queries
		$.livequery.timeout = setTimeout($.livequery.checkQueue, 20);
	},
	
	stop: function(id) {
		if (id != undefined)
			// Stop are particular Live Query
			$.livequery.queries[ id ].stop();
		else
			// Stop all Live Queries
			$.each( $.livequery.queries, function(id) {
				$.livequery.queries[ id ].stop();
			});
	}
});

// Register core DOM manipulation methods
$.livequery.registerPlugin('append', 'prepend', 'after', 'before', 'wrap', 'attr', 'removeAttr', 'addClass', 'removeClass', 'toggleClass', 'empty', 'remove');

// Run Live Queries when the Document is ready
$(function() { $.livequery.play(); });


// Save a reference to the original init method
var init = $.prototype.init;

// Create a new init method that exposes two new properties: selector and context
$.prototype.init = function(a,c) {
	// Call the original init and save the result
	var r = init.apply(this, arguments);
	
	// Copy over properties if they exist already
	if (a && a.selector)
		r.context = a.context, r.selector = a.selector;
		
	// Set properties
	if ( typeof a == 'string' )
		r.context = c || document, r.selector = a;
	
	// Return the result
	return r;
};

// Give the init function the jQuery prototype for later instantiation (needed after Rev 4091)
$.prototype.init.prototype = $.prototype;
	
})(jQuery);

/**
* hoverIntent is similar to jQuery's built-in "hover" function except that
* instead of firing the onMouseOver event immediately, hoverIntent checks
* to see if the user's mouse has slowed down (beneath the sensitivity
* threshold) before firing the onMouseOver event.
* 
* hoverIntent r5 // 2007.03.27 // jQuery 1.1.2+
* <http://cherne.net/brian/resources/jquery.hoverIntent.html>
* 
* hoverIntent is currently available for use in all personal or commercial 
* projects under both MIT and GPL licenses. This means that you can choose 
* the license that best suits your project, and use it accordingly.
* 
* // basic usage (just like .hover) receives onMouseOver and onMouseOut functions
* $("ul li").hoverIntent( showNav , hideNav );
* 
* // advanced usage receives configuration object only
* $("ul li").hoverIntent({
*	sensitivity: 7, // number = sensitivity threshold (must be 1 or higher)
*	interval: 100,   // number = milliseconds of polling interval
*	over: showNav,  // function = onMouseOver callback (required)
*	timeout: 0,   // number = milliseconds delay before onMouseOut function call
*	out: hideNav    // function = onMouseOut callback (required)
* });
* 
* @param  f  onMouseOver function || An object with configuration options
* @param  g  onMouseOut function  || Nothing (use configuration options object)
* @author    Brian Cherne <brian@cherne.net>
*/
(function($) {
	$.fn.hoverIntent = function(f,g) {
		// default configuration options
		var cfg = {
			sensitivity: 7,
			interval: 100,
			timeout: 0
		};
		// override configuration options with user supplied object
		cfg = $.extend(cfg, g ? { over: f, out: g } : f );

		// instantiate variables
		// cX, cY = current X and Y position of mouse, updated by mousemove event
		// pX, pY = previous X and Y position of mouse, set by mouseover and polling interval
		var cX, cY, pX, pY;

		// A private function for getting mouse position
		var track = function(ev) {
			cX = ev.pageX;
			cY = ev.pageY;
		};

		// A private function for comparing current and previous mouse position
		var compare = function(ev,ob) {
			ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
			// compare mouse positions to see if they've crossed the threshold
			if ( ( Math.abs(pX-cX) + Math.abs(pY-cY) ) < cfg.sensitivity ) {
				$(ob).unbind("mousemove",track);
				// set hoverIntent state to true (so mouseOut can be called)
				ob.hoverIntent_s = 1;
				return cfg.over.apply(ob,[ev]);
			} else {
				// set previous coordinates for next time
				pX = cX; pY = cY;
				// use self-calling timeout, guarantees intervals are spaced out properly (avoids JavaScript timer bugs)
				ob.hoverIntent_t = setTimeout( function(){compare(ev, ob);} , cfg.interval );
			}
		};

		// A private function for delaying the mouseOut function
		var delay = function(ev,ob) {
			ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t);
			ob.hoverIntent_s = 0;
			return cfg.out.apply(ob,[ev]);
		};

		// A private function for handling mouse 'hovering'
		var handleHover = function(e) {
			// next three lines copied from jQuery.hover, ignore children onMouseOver/onMouseOut
			var p = (e.type == "mouseover" ? e.fromElement : e.toElement) || e.relatedTarget;
			while ( p && p != this ) { try { p = p.parentNode; } catch(e) { p = this; } }
			if ( p == this ) { return false; }

			// copy objects to be passed into t (required for event object to be passed in IE)
			var ev = jQuery.extend({},e);
			var ob = this;

			// cancel hoverIntent timer if it exists
			if (ob.hoverIntent_t) { ob.hoverIntent_t = clearTimeout(ob.hoverIntent_t); }

			// else e.type == "onmouseover"
			if (e.type == "mouseover") {
				// set "previous" X and Y position based on initial entry point
				pX = ev.pageX; pY = ev.pageY;
				// update "current" X and Y position based on mousemove
				$(ob).bind("mousemove",track);
				// start polling interval (self-calling timeout) to compare mouse coordinates over time
				if (ob.hoverIntent_s != 1) { ob.hoverIntent_t = setTimeout( function(){compare(ev,ob);} , cfg.interval );}

			// else e.type == "onmouseout"
			} else {
				// unbind expensive mousemove event
				$(ob).unbind("mousemove",track);
				// if hoverIntent state is true, then call the mouseOut function after the specified delay
				if (ob.hoverIntent_s == 1) { ob.hoverIntent_t = setTimeout( function(){delay(ev,ob);} , cfg.timeout );}
			}
		};

		// bind the function to the two event listeners
		return this.mouseover(handleHover).mouseout(handleHover);
	};
})(jQuery);

/*
 * jQuery clueTip plugin
 * Version 0.9.8  (05/22/2008)
 * @requires jQuery v1.1.4+
 * @requires Dimensions plugin (for jQuery versions < 1.2.5)
 *
 * Dual licensed under the MIT and GPL licenses:
 * http://www.opensource.org/licenses/mit-license.php
 * http://www.gnu.org/licenses/gpl.html
 *
 */
;(function($) { 
/*
 * @name clueTip
 * @type jQuery
 * @cat Plugins/tooltip
 * @return jQuery
 * @author Karl Swedberg
 *
 * @credit Inspired by Cody Lindley's jTip (http://www.codylindley.com)
 * @credit Thanks to the following people for their many and varied contributions:
      Shelane Enos, Glen Lipka, Hector Santos, Torben Schreiter, Dan G. Switzer, JÃ¶rn Zaefferer 
 * @credit Thanks to Jonathan Chaffer, as always, for help with the hard parts. :-)
 */

 /**
 * 
 * Displays a highly customizable tooltip when the user hovers (default) or clicks (optional) the matched element. 
 * By default, the clueTip plugin loads a page indicated by the "rel" attribute via ajax and displays its contents.
 * If a "title" attribute is specified, its value is used as the clueTip's heading.
 * The attribute to be used for both the body and the heading of the clueTip is user-configurable. 
 * Optionally, the clueTip's body can display content from an element on the same page.
 * * Just indicate the element's id (e.g. "#some-id") in the rel attribute.
 * Optionally, the clueTip's body can display content from the title attribute, when a delimiter is indicated. 
 * * The string before the first instance of the delimiter is set as the clueTip's heading.
 * * All subsequent strings are wrapped in separate DIVs and placed in the clueTip's body.
 * The clueTip plugin allows for many, many more options. Pleasee see the examples and the option descriptions below...
 * 
 * 
 * @example $('#tip).cluetip();
 * @desc This is the most basic clueTip. It displays a 275px-wide clueTip on mouseover of the element with an ID of "tip." On mouseout of the element, the clueTip is hidden.
 *
 *
 * @example $('a.clue').cluetip({
 *  hoverClass: 'highlight',
 *  sticky: true,
 *  closePosition: 'bottom',
 *  closeText: '<img src="cross.png" alt="close" />',
 *  truncate: 60,
 *  ajaxSettings: {
 *    type: 'POST'
 *  }
 * });
 * @desc Displays a clueTip on mouseover of all <a> elements with class="clue". The hovered element gets a class of "highlight" added to it (so that it can be styled appropriately. This is esp. useful for non-anchor elements.). The clueTip is "sticky," which means that it will not be hidden until the user either clicks on its "close" text/graphic or displays another clueTip. The "close" text/graphic is set to diplay at the bottom of the clueTip (default is top) and display an image rather than the default "Close" text. Moreover, the body of the clueTip is truncated to the first 60 characters, which are followed by an ellipsis (...). Finally, the clueTip retrieves the content using POST rather than the $.ajax method's default "GET."
 * 
 * More examples can be found at http://plugins.learningjquery.com/cluetip/demo/
 * 
 * Full list of options/settings can be found at the bottom of this file and at http://plugins.learningjquery.com/cluetip/
 */

  var $cluetip, $cluetipInner, $cluetipOuter, $cluetipTitle, $cluetipArrows, $dropShadow, imgCount;
  $.fn.cluetip = function(js, options) {
    if (typeof js == 'object') {
      options = js;
      js = null;
    }
    return this.each(function(index) {
      var $this = $(this);      
      
      // support metadata plugin (v1.0 and 2.0)
      var opts = $.extend(false, {}, $.fn.cluetip.defaults, options || {}, $.metadata ? $this.metadata() : $.meta ? $this.data() : {});

      // start out with no contents (for ajax activation)
      var cluetipContents = false;
      var cluezIndex = parseInt(opts.cluezIndex, 10)-1;
      var isActive = false, closeOnDelay = 0;

      // create the cluetip divs
      if (!$('#cluetip').length) {
        $cluetipInner = $('<div id="cluetip-inner"></div>');
        $cluetipTitle = $('<h3 id="cluetip-title"></h3>');        
        $cluetipOuter = $('<div id="cluetip-outer"></div>').append($cluetipInner).prepend($cluetipTitle);
        $cluetip = $('<div id="cluetip"></div>').css({zIndex: opts.cluezIndex})
        .append($cluetipOuter).append('<div id="cluetip-extra"></div>')[insertionType](insertionElement).hide();
        $('<div id="cluetip-waitimage"></div>').css({position: 'absolute', zIndex: cluezIndex-1})
        .insertBefore('#cluetip').hide();
        $cluetip.css({position: 'absolute', zIndex: cluezIndex});
        $cluetipOuter.css({position: 'relative', zIndex: cluezIndex+1});
        $cluetipArrows = $('<div id="cluetip-arrows" class="cluetip-arrows"></div>').css({zIndex: cluezIndex+1}).appendTo('#cluetip');
      }
      var dropShadowSteps = (opts.dropShadow) ? +opts.dropShadowSteps : 0;
      if (!$dropShadow) {
        $dropShadow = $([]);
        for (var i=0; i < dropShadowSteps; i++) {
          $dropShadow = $dropShadow.add($('<div></div>').css({zIndex: cluezIndex-i-1, opacity:.1, top: 1+i, left: 1+i}));
        };
        $dropShadow.css({position: 'absolute', backgroundColor: '#000'})
        .prependTo($cluetip);
      }
      var tipAttribute = $this.attr(opts.attribute), ctClass = opts.cluetipClass;
      if (!tipAttribute && !opts.splitTitle && !js) return true;
      // if hideLocal is set to true, on DOM ready hide the local content that will be displayed in the clueTip      
      if (opts.local && opts.hideLocal) { $(tipAttribute + ':first').hide(); }
      var tOffset = parseInt(opts.topOffset, 10), lOffset = parseInt(opts.leftOffset, 10);
      // vertical measurement variables
      var tipHeight, wHeight;
      var defHeight = isNaN(parseInt(opts.height, 10)) ? 'auto' : (/\D/g).test(opts.height) ? opts.height : opts.height + 'px';
      var sTop, linkTop, posY, tipY, mouseY, baseline;
      // horizontal measurement variables
      var tipInnerWidth = isNaN(parseInt(opts.width, 10)) ? 275 : parseInt(opts.width, 10);
      var tipWidth = tipInnerWidth + (parseInt($cluetip.css('paddingLeft'))||0) + (parseInt($cluetip.css('paddingRight'))||0) + dropShadowSteps;
      var linkWidth = this.offsetWidth;
      var linkLeft, posX, tipX, mouseX, winWidth;
            
      // parse the title
      var tipParts;
      var tipTitle = (opts.attribute != 'title') ? $this.attr(opts.titleAttribute) : '';
      if (opts.splitTitle) {
        if(tipTitle == undefined) {tipTitle = '';}
        tipParts = tipTitle.split(opts.splitTitle);
        tipTitle = tipParts.shift();
      }
      var localContent;

/***************************************      
* ACTIVATION
****************************************/
    
//activate clueTip
    var activate = function(event) {
      if (!opts.onActivate($this)) {
        return false;
      }
      isActive = true;
      $cluetip.removeClass().css({width: tipInnerWidth});
      if (tipAttribute == $this.attr('href')) {
        $this.css('cursor', opts.cursor);
      }
      $this.attr('title','');
      if (opts.hoverClass) {
        $this.addClass(opts.hoverClass);
      }
      linkTop = posY = $this.offset().top;
      linkLeft = $this.offset().left;
      mouseX = event.pageX;
      mouseY = event.pageY;
      if ($this[0].tagName.toLowerCase() != 'area') {
        sTop = $(document).scrollTop();
        winWidth = $(window).width();
      }
// position clueTip horizontally
      if (opts.positionBy == 'fixed') {
        posX = linkWidth + linkLeft + lOffset;
        $cluetip.css({left: posX});
      } else {
        posX = (linkWidth > linkLeft && linkLeft > tipWidth)
          || linkLeft + linkWidth + tipWidth + lOffset > winWidth 
          ? linkLeft - tipWidth - lOffset 
          : linkWidth + linkLeft + lOffset;
        if ($this[0].tagName.toLowerCase() == 'area' || opts.positionBy == 'mouse' || linkWidth + tipWidth > winWidth) { // position by mouse
          if (mouseX + 20 + tipWidth > winWidth) {  
            $cluetip.addClass(' cluetip-' + ctClass);
            posX = (mouseX - tipWidth - lOffset) >= 0 ? mouseX - tipWidth - lOffset - parseInt($cluetip.css('marginLeft'),10) + parseInt($cluetipInner.css('marginRight'),10) :  mouseX - (tipWidth/2);
          } else {
            posX = mouseX + lOffset;
          }
        }
        var pY = posX < 0 ? event.pageY + tOffset : event.pageY;
        $cluetip.css({left: (posX > 0 && opts.positionBy != 'bottomTop') ? posX : (mouseX + (tipWidth/2) > winWidth) ? winWidth/2 - tipWidth/2 : Math.max(mouseX - (tipWidth/2),0)});
      }
        wHeight = $(window).height();

/***************************************
* load a string from cluetip method's first argument
***************************************/
      if (js) {
        $cluetipInner.html(js);
        cluetipShow(pY);
      }
/***************************************
* load the title attribute only (or user-selected attribute). 
* clueTip title is the string before the first delimiter
* subsequent delimiters place clueTip body text on separate lines
***************************************/

      else if (tipParts) {
        var tpl = tipParts.length;
        for (var i=0; i < tpl; i++){
          if (i == 0) {
            $cluetipInner.html(tipParts[i]);
          } else { 
            $cluetipInner.append('<div class="split-body">' + tipParts[i] + '</div>');
          }            
        };
        cluetipShow(pY);
      }
/***************************************
* load external file via ajax          
***************************************/

      else if (!opts.local && tipAttribute.indexOf('#') != 0) {
        if (cluetipContents && opts.ajaxCache) {
          $cluetipInner.html(cluetipContents);
          cluetipShow(pY);
        }
        else {
          var ajaxSettings = opts.ajaxSettings;
          ajaxSettings.url = tipAttribute;
          ajaxSettings.beforeSend = function() {
            $cluetipOuter.children().empty();
            if (opts.waitImage) {
              $('#cluetip-waitimage')
              .css({top: mouseY+20, left: mouseX+20})
              .show();
            }
          };
         ajaxSettings.error = function() {
            if (isActive) {
              $cluetipInner.html('<i>sorry, the contents could not be loaded</i>');
            }
          };
          ajaxSettings.success = function(data) {
            cluetipContents = opts.ajaxProcess(data);
            if (isActive) {
              $cluetipInner.html(cluetipContents);
            }
          };
          ajaxSettings.complete = function() {
          	imgCount = $('#cluetip-inner img').length;
        		if (imgCount && !$.browser.opera) {
        		  $('#cluetip-inner img').load(function() {
          			imgCount--;
          			if (imgCount<1) {
          				$('#cluetip-waitimage').hide();
          			  if (isActive) cluetipShow(pY);
          			}
        		  }); 
        		} else {
      				$('#cluetip-waitimage').hide();
        		  if (isActive) cluetipShow(pY);    
        		} 
          };
          $.ajax(ajaxSettings);
        }

/***************************************
* load an element from the same page
***************************************/
      } else if (opts.local){
        var $localContent = $(tipAttribute + ':first');
        var localCluetip = $.fn.wrapInner ? $localContent.wrapInner('<div></div>').children().clone(true) : $localContent.html();
        $.fn.wrapInner ? $cluetipInner.empty().append(localCluetip) : $cluetipInner.html(localCluetip);
        cluetipShow(pY);
      }
    };

// get dimensions and options for cluetip and prepare it to be shown
    var cluetipShow = function(bpY) {
      $cluetip.addClass('cluetip-' + ctClass);
      
      if (opts.truncate) { 
        var $truncloaded = $cluetipInner.text().slice(0,opts.truncate) + '...';
        $cluetipInner.html($truncloaded);
      }
      function doNothing() {}; //empty function
      tipTitle ? $cluetipTitle.show().html(tipTitle) : (opts.showTitle) ? $cluetipTitle.show().html('&nbsp;') : $cluetipTitle.hide();
      if (opts.sticky) {
        var $closeLink = $('<div id="cluetip-close"><a href="#">' + opts.closeText + '</a></div>');
        (opts.closePosition == 'bottom') ? $closeLink.appendTo($cluetipInner) : (opts.closePosition == 'title') ? $closeLink.prependTo($cluetipTitle) : $closeLink.prependTo($cluetipInner);
        $closeLink.click(function() {
          cluetipClose();
          return false;
        });
        if (opts.mouseOutClose) {
          if ($.fn.hoverIntent && opts.hoverIntent) { 
            $cluetip.hoverIntent({
              over: doNothing, 
              timeout: opts.hoverIntent.timeout,  
              out: function() { $closeLink.trigger('click'); }
            });
          } else {
            $cluetip.hover(doNothing, 
            function() {$closeLink.trigger('click'); });
          }
        } else {
          $cluetip.unbind('mouseout');
        }
      }
// now that content is loaded, finish the positioning 
      var direction = '';
      $cluetipOuter.css({overflow: defHeight == 'auto' ? 'visible' : 'auto', height: defHeight});
      tipHeight = defHeight == 'auto' ? Math.max($cluetip.outerHeight(),$cluetip.height()) : parseInt(defHeight,10);   
      tipY = posY;
      baseline = sTop + wHeight;
      if (opts.positionBy == 'fixed') {
        tipY = posY - opts.dropShadowSteps + tOffset;
      } else if ( (posX < mouseX && Math.max(posX, 0) + tipWidth > mouseX) || opts.positionBy == 'bottomTop') {
        if (posY + tipHeight + tOffset > baseline && mouseY - sTop > tipHeight + tOffset) { 
          tipY = mouseY - tipHeight - tOffset;
          direction = 'top';
        } else { 
          tipY = mouseY + tOffset;
          direction = 'bottom';
        }
      } else if ( posY + tipHeight + tOffset > baseline ) {
        tipY = (tipHeight >= wHeight) ? sTop : baseline - tipHeight - tOffset;
      } else if ($this.css('display') == 'block' || $this[0].tagName.toLowerCase() == 'area' || opts.positionBy == "mouse") {
        tipY = bpY - tOffset;
      } else {
        tipY = posY - opts.dropShadowSteps;
      }
      if (direction == '') {
        posX < linkLeft ? direction = 'left' : direction = 'right';
      }
      $cluetip.css({top: tipY + 'px'}).removeClass().addClass('clue-' + direction + '-' + ctClass).addClass(' cluetip-' + ctClass);
      if (opts.arrows) { // set up arrow positioning to align with element
        var bgY = (posY - tipY - opts.dropShadowSteps);
        $cluetipArrows.css({top: (/(left|right)/.test(direction) && posX >=0 && bgY > 0) ? bgY + 'px' : /(left|right)/.test(direction) ? 0 : ''}).show();
      } else {
        $cluetipArrows.hide();
      }

// (first hide, then) ***SHOW THE CLUETIP***
      $dropShadow.hide();
      $cluetip.hide()[opts.fx.open](opts.fx.open != 'show' && opts.fx.openSpeed);
      if (opts.dropShadow) $dropShadow.css({height: tipHeight, width: tipInnerWidth}).show();
      if ($.fn.bgiframe) { $cluetip.bgiframe(); }
      // trigger the optional onShow function
      if (opts.delayedClose > 0) {
        closeOnDelay = setTimeout(cluetipClose, opts.delayedClose);
      }
      opts.onShow($cluetip, $cluetipInner);
      
    };

/***************************************
   =INACTIVATION
-------------------------------------- */
    var inactivate = function() {
      isActive = false;
      $('#cluetip-waitimage').hide();
      if (!opts.sticky || (/click|toggle/).test(opts.activation) ) {
        cluetipClose();
clearTimeout(closeOnDelay);        
      };
      if (opts.hoverClass) {
        $this.removeClass(opts.hoverClass);
      }
      $('.cluetip-clicked').removeClass('cluetip-clicked');
    };
// close cluetip and reset some things
    var cluetipClose = function() {
      $cluetipOuter 
      .parent().hide().removeClass().end()
      .children().empty();
      if (tipTitle) {
        $this.attr(opts.titleAttribute, tipTitle);
      }
      $this.css('cursor','');
      if (opts.arrows) $cluetipArrows.css({top: ''});
    };

/***************************************
   =BIND EVENTS
-------------------------------------- */
  // activate by click
      if ( (/click|toggle/).test(opts.activation) ) {
        $this.click(function(event) {
          if ($cluetip.is(':hidden') || !$this.is('.cluetip-clicked')) {
            activate(event);
            $('.cluetip-clicked').removeClass('cluetip-clicked');
            $this.addClass('cluetip-clicked');

          } else {
            inactivate(event);

          }
          this.blur();
          return false;
        });
  // activate by focus; inactivate by blur    
      } else if (opts.activation == 'focus') {
        $this.focus(function(event) {
          activate(event);
        });
        $this.blur(function(event) {
          inactivate(event);
        });
  // activate by hover
    // clicking is returned false if cluetip url is same as href url
      } else {
        $this.click(function() {
          if ($this.attr('href') && $this.attr('href') == tipAttribute && !opts.clickThrough) {
            return false;
          }
        });
        //set up mouse tracking
        var mouseTracks = function(evt) {
          if (opts.tracking == true) {
            var trackX = posX - evt.pageX;
            var trackY = tipY ? tipY - evt.pageY : posY - evt.pageY;
            $this.mousemove(function(evt) {
              $cluetip.css({left: evt.pageX + trackX, top: evt.pageY + trackY });
            });
          }
        };
        if ($.fn.hoverIntent && opts.hoverIntent) {
          $this.mouseover(function() {$this.attr('title',''); })
          .hoverIntent({
            sensitivity: opts.hoverIntent.sensitivity,
            interval: opts.hoverIntent.interval,  
            over: function(event) {
              activate(event);
              mouseTracks(event);
            }, 
            timeout: opts.hoverIntent.timeout,  
            out: function(event) {inactivate(event); $this.unbind('mousemove');}
          });           
        } else {
          $this.hover(function(event) {
            activate(event);
            mouseTracks(event);
          }, function(event) {
            inactivate(event);
            $this.unbind('mousemove');
          });
        }
      }
    });
  };
  
/*
 * options for clueTip
 *
 * each one can be explicitly overridden by changing its value. 
 * for example: $.fn.cluetip.defaults.width = 200; 
 * would change the default width for all clueTips to 200. 
 *
 * each one can also be overridden by passing an options map to the cluetip method.
 * for example: $('a.example').cluetip({width: 200}); 
 * would change the default width to 200 for clueTips invoked by a link with class of "example"
 *
 */
  
  $.fn.cluetip.defaults = {  // set up default options
    width:            275,      // The width of the clueTip
    height:           'auto',   // The height of the clueTip
    cluezIndex:       97,       // Sets the z-index style property of the clueTip
    positionBy:       'mouse',   // Sets the type of positioning: 'auto', 'mouse','bottomTop', 'fixed'
    topOffset:        15,       // Number of px to offset clueTip from top of invoking element
    leftOffset:       15,       // Number of px to offset clueTip from left of invoking element
    local:            false,    // Whether to use content from the same page for the clueTip's body
    hideLocal:        true,     // If local option is set to true, this determines whether local content
                                // to be shown in clueTip should be hidden at its original location
    attribute:        'rel',    // the attribute to be used for fetching the clueTip's body content
    titleAttribute:   'title',  // the attribute to be used for fetching the clueTip's title
    splitTitle:       '',       // A character used to split the title attribute into the clueTip title and divs
                                // within the clueTip body. more info below [6]
    showTitle:        false,     // show title bar of the clueTip, even if title attribute not set
    cluetipClass:     'default',// class added to outermost clueTip div in the form of 'cluetip-' + clueTipClass.
    hoverClass:       '',       // class applied to the invoking element onmouseover and removed onmouseout
    waitImage:        true,     // whether to show a "loading" img, which is set in jquery.cluetip.css
    cursor:           'help',
    arrows:           false,    // if true, displays arrow on appropriate side of clueTip
    dropShadow:       true,     // set to false if you don't want the drop-shadow effect on the clueTip
    dropShadowSteps:  6,        // adjusts the size of the drop shadow
    sticky:           false,    // keep visible until manually closed
    mouseOutClose:    false,    // close when clueTip is moused out
    activation:       'hover',  // set to 'click' to force user to click to show clueTip
                                // set to 'focus' to show on focus of a form element and hide on blur
    clickThrough:     false,    // if true, and activation is not 'click', then clicking on link will take user to the link's href,
                                // even if href and tipAttribute are equal
    tracking:         true,    // if true, clueTip will track mouse movement (experimental)
    delayedClose:     0,        // close clueTip on a timed delay (experimental)
    closePosition:    'top',    // location of close text for sticky cluetips; can be 'top' or 'bottom' or 'title'
    closeText:        'Close',  // text (or HTML) to to be clicked to close sticky clueTips
    truncate:         0,        // number of characters to truncate clueTip's contents. if 0, no truncation occurs

    // effect and speed for opening clueTips
    fx: {             
                      open:       'fadeIn', // can be 'show' or 'slideDown' or 'fadeIn'
                      openSpeed:  ''
    },     

    // settings for when hoverIntent plugin is used             
    hoverIntent: {    
                      sensitivity:  3,
              			  interval:     50,
              			  timeout:      0
    },

    // function to run just before clueTip is shown.           
    onActivate:       function(e) {return true;},

    // function to run just after clueTip is shown.
    onShow:           function(ct, c){},
    
    // whether to cache results of ajax request to avoid unnecessary hits to server    
    ajaxCache:        true,  

    // process data retrieved via xhr before it's displayed
    ajaxProcess:      function(data) {
                        data = data.replace(/<s(cript|tyle)(.|\s)*?\/s(cript|tyle)>/g, '').replace(/<(link|title)(.|\s)*?\/(link|title)>/g,'');
                        return data;
    },                

    // can pass in standard $.ajax() parameters, not including error, complete, success, and url
    ajaxSettings: {   
                      dataType: 'html'
    },
    debug: false
  };


/*
 * Global defaults for clueTips. Apply to all calls to the clueTip plugin.
 *
 * @example $.cluetip.setup({
 *   insertionType: 'prependTo',
 *   insertionElement: '#container'
 * });
 * 
 * @property
 * @name $.cluetip.setup
 * @type Map
 * @cat Plugins/tooltip
 * @option String insertionType: Default is 'appendTo'. Determines the method to be used for inserting the clueTip into the DOM. Permitted values are 'appendTo', 'prependTo', 'insertBefore', and 'insertAfter'
 * @option String insertionElement: Default is 'body'. Determines which element in the DOM the plugin will reference when inserting the clueTip.
 *
 */
   
  var insertionType = 'appendTo', insertionElement = 'body';
  $.cluetip = {};
  $.cluetip.setup = function(options) {
    if (options && options.insertionType && (options.insertionType).match(/appendTo|prependTo|insertBefore|insertAfter/)) {
      insertionType = options.insertionType;
    }
    if (options && options.insertionElement) {
      insertionElement = options.insertionElement;
    }
  };
  
})(jQuery);

/*****************************************************************************
scalable Inman Flash Replacement (sIFR) version 3, revision 436.

Copyright 2006 – 2008 Mark Wubben, <http://novemberborn.net/>

Older versions:
* IFR by Shaun Inman
* sIFR 1.0 by Mike Davidson, Shaun Inman and Tomas Jogin
* sIFR 2.0 by Mike Davidson, Shaun Inman, Tomas Jogin and Mark Wubben

See also <http://novemberborn.net/sifr3> and <http://wiki.novemberborn.net/sifr3>.

This software is licensed and provided under the CC-GNU LGPL.
See <http://creativecommons.org/licenses/LGPL/2.1/>
*****************************************************************************/

var sIFR = new function() {
  var self = this;

  var ClassNames  = {
    ACTIVE    : 'sIFR-active',
    REPLACED  : 'sIFR-replaced',
    IGNORE    : 'sIFR-ignore',
    ALTERNATE : 'sIFR-alternate',
    CLASS     : 'sIFR-class',
    LAYOUT    : 'sIFR-layout',
    FLASH     : 'sIFR-flash',
    FIX_FOCUS : 'sIFR-fixfocus',
    DUMMY     : 'sIFR-dummy'
  };
  
  ClassNames.IGNORE_CLASSES = [ClassNames.REPLACED, ClassNames.IGNORE, ClassNames.ALTERNATE];
  
  this.MIN_FONT_SIZE        = 6;
  this.MAX_FONT_SIZE        = 126;
  this.FLASH_PADDING_BOTTOM = 5;
  this.VERSION              = '436';

  this.isActive             = false;
  this.isEnabled            = true;
  this.fixHover             = true;
  this.autoInitialize       = true;
  this.setPrefetchCookie    = true;
  this.cookiePath           = '/';
  this.domains              = [];
  this.forceWidth           = true;
  this.fitExactly           = false;
  this.forceTextTransform   = true;
  this.useDomLoaded         = true;
  this.useStyleCheck        = false;
  this.hasFlashClassSet     = false;
  this.repaintOnResize      = true;
  this.replacements         = [];
  
  var elementCount          = 0; // The number of replaced elements.
  var isInitialized         = false;

  function Errors() {
    this.fire = function(id) {
      if(this[id + 'Alert']) alert(this[id + 'Alert']);
      throw new Error(this[id]);
    };
  
    this.isFile      = 'sIFR: Did not activate because the page is being loaded from the filesystem.';
    this.isFileAlert = 'Hi!\n\nThanks for using sIFR on your page. Unfortunately sIFR couldn\'t activate, because it was loaded '
                        + 'directly from your computer.\nDue to Flash security restrictions, you need to load sIFR through a web'
                        + ' server.\n\nWe apologize for the inconvenience.';
  };
  
  function Util(sIFR) {
    function capitalize($) {
      return $.toLocaleUpperCase();
    }
    
    this.normalize = function(str) {
      // Replace linebreaks and &nbsp; by whitespace, then normalize.
      // Flash doesn't support no-breaking space characters, hence they're replaced by a normal space.
      return str.replace(/\n|\r|\xA0/g, Util.SINGLE_WHITESPACE).replace(/\s+/g, Util.SINGLE_WHITESPACE);
    };
    
    this.textTransform = function(type, str) {
      switch(type) {
        case 'uppercase':
          return str.toLocaleUpperCase();
        case 'lowercase':
          return str.toLocaleLowerCase();
        case 'capitalize':
          return str.replace(/^\w|\s\w/g, capitalize);
      }
      return str;
    };
    
    this.toHexString = function(str) {
      if(str.charAt(0) != '#' || str.length != 4 && str.length != 7) return str;
      
      str = str.substring(1);
      return '0x' + (str.length == 3 ? str.replace(/(.)(.)(.)/, '$1$1$2$2$3$3') : str);
    };
    
    this.toJson = function(obj, strFunc) {
      var json = '';
  
      switch(typeof(obj)) {
        case 'string':
          json = '"' + strFunc(obj) + '"';
          break;
        case 'number':
        case 'boolean':
          json = obj.toString();
          break;
        case 'object':
          json = [];
          for(var prop in obj) {
            if(obj[prop] == Object.prototype[prop]) continue;
            json.push('"' + prop + '":' + this.toJson(obj[prop]));
          }
          json = '{' + json.join(',') + '}';
          break;
      }
  
      return json;
    };
  
    this.convertCssArg = function(arg) {
      if(!arg) return {};
      if(typeof(arg) == 'object') {
        if(arg.constructor == Array) arg = arg.join('');
        else return arg;
      }
  
      var obj = {};
      var rules = arg.split('}');
  
      for(var i = 0; i < rules.length; i++) {
        var $ = rules[i].match(/([^\s{]+)\s*\{(.+)\s*;?\s*/);
        if(!$ || $.length != 3) continue;
  
        if(!obj[$[1]]) obj[$[1]] = {};
  
        var properties = $[2].split(';');
        for(var j = 0; j < properties.length; j++) {
          var $2 = properties[j].match(/\s*([^:\s]+)\s*\:\s*([^;]+)/);
          if(!$2 || $2.length != 3) continue;
          obj[$[1]][$2[1]] = $2[2].replace(/\s+$/, '');
        }
      }
  
      return obj;
    };
  
    this.extractFromCss = function(css, selector, property, remove) {
      var value = null;
  
      if(css && css[selector] && css[selector][property]) {
        value = css[selector][property];
        if(remove) delete css[selector][property];
      }
  
      return value;
    };
    
    this.cssToString = function(arg) {
      var css = [];
      for(var selector in arg) {
        var rule = arg[selector];
        if(rule == Object.prototype[selector]) continue;
  
        css.push(selector, '{');
        for(var property in rule) {
          if(rule[property] == Object.prototype[property]) continue;
          var value = rule[property];
          if(Util.UNIT_REMOVAL_PROPERTIES[property]) value = parseInt(value, 10);
          css.push(property, ':', value, ';');
        }
        css.push('}');
      }
  
      return css.join('');
    };
  
    this.escape = function(str) {
      return escape(str).replace(/\+/g, '%2B');
    };
    
    this.encodeVars = function(vars) {
      return vars.join('&').replace(/%/g, '%25');
    };
    
    this.copyProperties = function(from, to) {
      for(var property in from) {
        if(to[property] === undefined) to[property] = from[property];
      }
      return to;
    };
    
    this.domain = function() {
      var domain = '';
      // When trying to access document.domain on a Google-translated page with Firebug, I got an exception. 
      // Try/catch to be safe.
      try { domain = document.domain } catch(e) {};
      return domain;
    };
    
    this.domainMatches = function(domain, match) {
      if(match == '*' || match == domain) return true;
  
      var wildcard = match.lastIndexOf('*');
      if(wildcard > -1) {
        match = match.substr(wildcard + 1);
        var matchPosition = domain.lastIndexOf(match);
        if(matchPosition > -1 && (matchPosition + match.length) == domain.length) return true;
      }
      
      return false;
    };
    
    this.uriEncode = function(s) {
      return encodeURI(decodeURIComponent(s));  // Decode first, in case the URI was already encoded.
    };
    
    this.delay = function(ms, func, scope) {
      var args = Array.prototype.slice.call(arguments, 3);
      setTimeout(function() { func.apply(scope, args) }, ms);
    };
  };
  
  Util.UNIT_REMOVAL_PROPERTIES = {leading: true, 'margin-left': true, 'margin-right': true, 'text-indent': true};
  Util.SINGLE_WHITESPACE       = ' ';
  
  
  function DomUtil(sIFR) {
    var self = this;
    
    function getDimensionFromStyle(node, property, offsetProperty)
    {
      var dimension = self.getStyleAsInt(node, property, sIFR.ua.ie);
      if(dimension == 0) {
        dimension = node[offsetProperty];
        for(var i = 3; i < arguments.length; i++) dimension -= self.getStyleAsInt(node, arguments[i], true);
      }
      return dimension;
    }
    
    this.getBody = function() {
      return document.getElementsByTagName('body')[0] || null;
    };
    
    this.querySelectorAll = function(selector) {
      return window.parseSelector(selector);
    };
  
    this.addClass = function(name, node) {
      if(node) node.className = ((node.className || '') == '' ? '' : node.className + ' ') + name;
    };
    
    this.removeClass = function(name, node) {
      if(node) node.className = node.className.replace(new RegExp('(^|\\s)' + name + '(\\s|$)'), '').replace(/^\s+|(\s)\s+/g, '$1');
    };
  
    this.hasClass = function(name, node) {
      return new RegExp('(^|\\s)' + name + '(\\s|$)').test(node.className);
    };
    
    this.hasOneOfClassses = function(names, node) {
      for(var i = 0; i < names.length; i++) {
        if(this.hasClass(names[i], node)) return true;
      }
      return false;
    };
    
    this.ancestorHasClass = function(node, name) {
      node = node.parentNode;
      while(node && node.nodeType == 1) {
        if(this.hasClass(name, node)) return true;
        node = node.parentNode;
      }
      return false;
    };
  
    this.create = function(name, className) {
      var node = document.createElementNS ? document.createElementNS(DomUtil.XHTML_NS, name) : document.createElement(name);
      if(className) node.className = className;
      return node;
    };
    
    this.getComputedStyle = function(node, property) {
      var result;
      if(document.defaultView && document.defaultView.getComputedStyle) {
        var style = document.defaultView.getComputedStyle(node, null);
        result = style ? style[property] : null;
      } else {
        if(node.currentStyle) result = node.currentStyle[property];
      }
      return result || ''; // Ensuring a string.
    };
  
    this.getStyleAsInt = function(node, property, requirePx) {
      var value = this.getComputedStyle(node, property);
      if(requirePx && !/px$/.test(value)) return 0;
      return parseInt(value) || 0;
    };
    
    this.getWidthFromStyle = function(node) {
      return getDimensionFromStyle(node, 'width', 'offsetWidth', 'paddingRight', 'paddingLeft', 'borderRightWidth', 'borderLeftWidth');
    };
  
    this.getHeightFromStyle = function(node) {
      return getDimensionFromStyle(node, 'height', 'offsetHeight', 'paddingTop', 'paddingBottom', 'borderTopWidth', 'borderBottomWidth');
    };
  
    this.getDimensions = function(node) {
      var width  = node.offsetWidth;
      var height = node.offsetHeight;
      
      if(width == 0 || height == 0) {
        for(var i = 0; i < node.childNodes.length; i++) {
          var child = node.childNodes[i];
          if(child.nodeType != 1) continue;
          width  = Math.max(width, child.offsetWidth);
          height = Math.max(height, child.offsetHeight);
        }
      }
      
      return {width: width, height: height};
    };
    
    this.getViewport = function() {
      return {
        width:  window.innerWidth  || document.documentElement.clientWidth  || this.getBody().clientWidth,
        height: window.innerHeight || document.documentElement.clientHeight || this.getBody().clientHeight
      };
    };
    
    this.blurElement = function(element) {
      try {
        element.blur();
        return;
      } catch(e) {};
      
      // Move the focus to an input element, and then destroy it.
      var input = this.create('input');
      input.style.width  = '0px';
      input.style.height = '0px';
      element.parentNode.appendChild(input);
      input.focus();
      input.blur();
      input.parentNode.removeChild(input);
    };
  };
  
  DomUtil.XHTML_NS = 'http://www.w3.org/1999/xhtml';
  
  function UserAgentDetection(sIFR) {
    var ua              = navigator.userAgent.toLowerCase();
    var product         = (navigator.product || '').toLowerCase();
    var platform        = navigator.platform.toLowerCase();
  
    this.parseVersion = UserAgentDetection.parseVersion;
  
    this.macintosh        = /^mac/.test(platform);
    this.windows          = /^win/.test(platform);
    this.linux            = /^linux/.test(platform);
    this.quicktime        = false;
  
    this.opera            = /opera/.test(ua);
    this.konqueror        = /konqueror/.test(ua);
    this.ie               = false/*@cc_on || true @*/;
    this.ieSupported      = this.ie         && !/ppc|smartphone|iemobile|msie\s5\.5/.test(ua)/*@cc_on && @_jscript_version >= 5.5 @*/
    this.ieWin            = this.windows    && this.ie/*@cc_on && @_jscript_version >= 5.1 @*/;
    this.windows          = this.windows    && (!this.ie || this.ieWin);
    this.ieMac            = this.macintosh  && this.ie/*@cc_on && @_jscript_version < 5.1 @*/;
    this.macintosh        = this.macintosh  && (!this.ie || this.ieMac);
    this.safari           = /safari/.test(ua);
    this.webkit           = !this.konqueror && /applewebkit/.test(ua);
    this.khtml            = this.webkit     || this.konqueror;
    this.gecko            = !this.khtml     && product == 'gecko';
                          
    this.ieVersion        = this.ie         && /.*msie\s(\d\.\d)/.exec(ua)           ? this.parseVersion(RegExp.$1) : '0';
    this.operaVersion     = this.opera      && /.*opera(\s|\/)(\d+\.\d+)/.exec(ua)   ? this.parseVersion(RegExp.$2) : '0';
    this.webkitVersion    = this.webkit     && /.*applewebkit\/(\d+).*/.exec(ua)     ? this.parseVersion(RegExp.$1) : '0';
    this.geckoVersion     = this.gecko      && /.*rv:\s*([^\)]+)\)\s+gecko/.exec(ua) ? this.parseVersion(RegExp.$1) : '0';
    this.konquerorVersion = this.konqueror  && /.*konqueror\/([\d\.]+).*/.exec(ua)   ? this.parseVersion(RegExp.$1) : '0';
  
    this.flashVersion   = 0;
  
    if(this.ieWin) {
      var axo;
      var stop = false;
      try {
        axo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash.7');
      } catch(e) {
        // In case the Flash 7 registry key does not exist, we need to test for specific 
        // Flash 6 installs before we can use the general key. 
        // See also <http://blog.deconcept.com/2006/01/11/getvariable-setvariable-crash-internet-explorer-flash-6/>.
        // Many thanks to Geoff Stearns and Bobby van der Sluis for clarifying the problem and providing
        // examples of non-crashing code.
        try {
          axo                   = new ActiveXObject('ShockwaveFlash.ShockwaveFlash.6');
          this.flashVersion     = this.parseVersion('6');
          axo.AllowScriptAccess = 'always';
        } catch(e) { stop = this.flashVersion == this.parseVersion('6'); }
        
        if(!stop) try { axo = new ActiveXObject('ShockwaveFlash.ShockwaveFlash'); } catch(e) {}
      }
      
      if(!stop && axo) {
        this.flashVersion = this.parseVersion((axo.GetVariable('$version') || '').replace(/^\D+(\d+)\D+(\d+)\D+(\d+).*/g, '$1.$2.$3'));
      }
    } else if(navigator.plugins && navigator.plugins['Shockwave Flash']) {
      var d = navigator.plugins['Shockwave Flash'].description.replace(/^.*\s+(\S+\s+\S+$)/, '$1');
      var v = d.replace(/^\D*(\d+\.\d+).*$/, '$1');
      if(/r/.test(d)) v += d.replace(/^.*r(\d*).*$/, '.$1');
      else if(/d/.test(d)) v += '.0';
      this.flashVersion = this.parseVersion(v);
      
      // Watch out for QuickTime, which could be stealing the Flash handling! Also check to make sure the plugin for the Flash
      // MIMEType is enabled.
      var foundEnabled = false;
      for(var i = 0, valid = this.flashVersion >= UserAgentDetection.MIN_FLASH_VERSION; valid && i < navigator.mimeTypes.length; i++) {
        var mime = navigator.mimeTypes[i];
        if(mime.type != 'application/x-shockwave-flash') continue;
        if(mime.enabledPlugin) {
          foundEnabled = true;
          if(mime.enabledPlugin.description.toLowerCase().indexOf('quicktime') > -1) {
            valid = false;
            this.quicktime = true;
          }
        }
      }
      
      if(this.quicktime || !foundEnabled) this.flashVersion = this.parseVersion('0');
    }
    this.flash = this.flashVersion >= UserAgentDetection.MIN_FLASH_VERSION;
    this.transparencySupport  = this.macintosh || this.windows
                                               || this.linux && (
                                                    this.flashVersion >= this.parseVersion('10')
                                                    && (
                                                      this.gecko && this.geckoVersion >= this.parseVersion('1.9')
                                                      || this.opera
                                                    )
                                                  );
    this.computedStyleSupport = this.ie || !!document.defaultView.getComputedStyle;
    this.fixFocus             = this.gecko  && this.windows;
    this.nativeDomLoaded      = this.gecko  || this.webkit    && this.webkitVersion  >= this.parseVersion('525')
                                            || this.konqueror && this.konquerorMajor >  this.parseVersion('03') || this.opera;
    this.mustCheckStyle       = this.khtml  || this.opera;
    this.forcePageLoad        = this.webkit && this.webkitVersion < this.parseVersion('523')
    this.properDocument       = typeof(document.location) == 'object';
  
    this.supported            = this.flash  && this.properDocument && (!this.ie || this.ieSupported) && this.computedStyleSupport
                                            && (!this.opera  || this.operaVersion  >= this.parseVersion('9.61')) 
                                            && (!this.webkit || this.webkitVersion >= this.parseVersion('412'))
                                            && (!this.gecko  || this.geckoVersion  >= this.parseVersion('1.8.0.12'))
                                            && (!this.konqueror/* || this.konquerorVersion >= this.parseVersion('4.1')*/);
  };
  
  UserAgentDetection.parseVersion = function(s) {
    return s.replace(
      /(^|\D)(\d+)(?=\D|$)/g,
      function(s, nonDigit, digits) {
        s = nonDigit;
        for(var i = 4 - digits.length; i >= 0; i--) s += '0';
        return s + digits;
      }
    );
  };
  UserAgentDetection.MIN_FLASH_VERSION = UserAgentDetection.parseVersion('8');
  
  function FragmentIdentifier(sIFR) {
    this.fix = sIFR.ua.ieWin && window.location.hash != '';
  
    var cachedTitle;
    this.cache = function() {
      cachedTitle = document.title;
    };
  
    function doFix() {
      document.title = cachedTitle;
    }
  
    this.restore = function() {
      if(this.fix) setTimeout(doFix, 0);
    };
  };
  function PageLoad(sIFR) {
    var dummy = null;
    
    function pollLoad() {
      try {
        // IE hack courtesy of Diego Perini – <http://javascript.nwbox.com/IEContentLoaded/>.
        // Merged polling taken from jQuery – <http://dev.jquery.com/browser/trunk/jquery/src/event.js>
        if(sIFR.ua.ie || document.readyState != 'loaded' && document.readyState != 'complete') {
          document.documentElement.doScroll('left');
        }
      } catch(e) {
        return setTimeout(pollLoad, 10);
      }
      afterDomLoad();
    };
    
    function afterDomLoad() {
      if(sIFR.useStyleCheck) checkStyle();
      else if(!sIFR.ua.mustCheckStyle) fire(null, true);
    };
    
    function checkStyle() {
      dummy = sIFR.dom.create("div", ClassNames.DUMMY);
      sIFR.dom.getBody().appendChild(dummy);
      pollStyle();
    };
    
    function pollStyle() {
      if(sIFR.dom.getComputedStyle(dummy, 'marginLeft') == '42px') afterStyle();
      else setTimeout(pollStyle, 10);
    };
    
    function afterStyle() {
      if(dummy && dummy.parentNode) dummy.parentNode.removeChild(dummy);
      dummy = null;
      fire(null, true);
    };
    
    function fire(evt, preserveReplacements) {
      sIFR.initialize(preserveReplacements);
      
      // Remove handlers to prevent memory leak in Firefox 1.5, but only after onload.
      if(evt && evt.type == 'load') {
        if(document.removeEventListener) document.removeEventListener('DOMContentLoaded', fire, false);
        if(window.removeEventListener) window.removeEventListener('load', fire, false);
      }
    };
    
    // Unload detection based on the research from Moxiecode. <http://blog.moxiecode.com/2008/04/08/unload-event-never-fires-in-ie/>
    function verifyUnload() {
      sIFR.prepareClearReferences();
      if(document.readyState == 'interactive') {
        document.attachEvent('onstop', unloadByStop);
        setTimeout(function() { document.detachEvent('onstop', unloadByStop) }, 0);
      }
    };
    
    function unloadByStop() {
      document.detachEvent('onstop', unloadByStop);
      fireUnload();
    };
    
    function fireUnload() {
      sIFR.clearReferences();
    };
    
    this.attach = function() {
      if(window.addEventListener) window.addEventListener('load', fire, false);
      else window.attachEvent('onload', fire);
      
      if(!sIFR.useDomLoaded || sIFR.ua.forcePageLoad || sIFR.ua.ie && window.top != window) return;
      
      if(sIFR.ua.nativeDomLoaded) {
        document.addEventListener('DOMContentLoaded', afterDomLoad, false);
      } else if(sIFR.ua.ie || sIFR.ua.khtml) {
        pollLoad();
      } 
    };
    
    this.attachUnload = function() {
      if(!sIFR.ua.ie) return;
      window.attachEvent('onbeforeunload', verifyUnload);
      window.attachEvent('onunload', fireUnload);
    }
  };
  
  var PREFETCH_COOKIE = 'sifrFetch';
  
  function Prefetch(sIFR) {
    var hasPrefetched = false;
    
    this.fetchMovies = function(movies) {
      if(sIFR.setPrefetchCookie && new RegExp(';?' + PREFETCH_COOKIE + '=true;?').test(document.cookie)) return;
  
      try { // We don't know which DOM actions the user agent will allow
        hasPrefetched = true;
        prefetch(movies);
      } catch(e) {}
  
      if(sIFR.setPrefetchCookie) document.cookie = PREFETCH_COOKIE + '=true;path=' + sIFR.cookiePath;
    };
  
    this.clear = function() {
      if(!hasPrefetched) return;
  
      try {
        var nodes = document.getElementsByTagName('script');
        for(var i = nodes.length - 1; i >= 0; i--) {
          var node = nodes[i];
          if(node.type == 'sifr/prefetch') node.parentNode.removeChild(node);
        }
      } catch(e) {}
    };
    
    function prefetch(args) {
      for(var i = 0; i < args.length; i++) {
        document.write('<scr' + 'ipt defer type="sifr/prefetch" src="' + args[i].src + '"></' + 'script>');
      }
    }
  };
  
  function BrokenFlashIE(sIFR) {
    var active      = sIFR.ua.ie;
    var fixFlash    = active && sIFR.ua.flashVersion < sIFR.ua.parseVersion('9.0.115');
    var resetMovies = {};
    var registry    = {};
    
    this.fixFlash = fixFlash;
    
    this.register = function(flashNode) {
      if(!active) return;
      
      var id = flashNode.getAttribute('id');
      // Try cleaning up previous Flash <object>
      this.cleanup(id, false);
      
      registry[id] = flashNode;
      delete resetMovies[id];
      
      if(fixFlash) window[id] = flashNode;
    };
    
    this.reset = function() {
      if(!active) return false;
      
      for(var i = 0; i < sIFR.replacements.length; i++) {
        var flash = sIFR.replacements[i];
        var flashNode = registry[flash.id];
        if(!resetMovies[flash.id] && (!flashNode.parentNode || flashNode.parentNode.nodeType == 11)) {
          flash.resetMovie();
          resetMovies[flash.id] = true;
        }
      }
      
      return true;
    };
    
    this.cleanup = function(id, usePlaceholder) {
      var node = registry[id];
      if(!node) return;
      
      for(var expando in node) {
        if(typeof(node[expando]) == 'function') node[expando] = null;
      }
      registry[id] = null;
      if(fixFlash) window[id] = null;
      
      if(node.parentNode) {
        if(usePlaceholder && node.parentNode.nodeType == 1) {
          // Replace the Flash node by a placeholde element with the same dimensions. This stops the page from collapsing
          // when the Flash movies are removed.
          var placeholder          = document.createElement('div');
          placeholder.style.width  = node.offsetWidth  + 'px';
          placeholder.style.height = node.offsetHeight + 'px';
          node.parentNode.replaceChild(placeholder, node);
        } else {
          node.parentNode.removeChild(node);
        }
      }
    };
    
    this.prepareClearReferences = function() {
      if(!fixFlash) return;
      
      // Disable Flash cleanup, see <http://blog.deconcept.com/2006/05/18/flash-player-bug-streaming-content-innerhtml-ie/>
      // for more info.
      __flash_unloadHandler      = function(){};
      __flash_savedUnloadHandler = function(){};
    };
    
    this.clearReferences = function() {
      // Since we've disabled Flash' own cleanup, add all objects on the page to our registry so they can be cleaned up.
      if(fixFlash) {
        var objects = document.getElementsByTagName('object');
        for(var i = objects.length - 1; i >= 0; i--) registry[objects[i].getAttribute('id')] = objects[i];
      }
      
      for(var id in registry) {
        if(Object.prototype[id] != registry[id]) this.cleanup(id, true);
      }
    };
  }
  
  function FlashInteractor(sIFR, id, vars, forceWidth, events) {
    this.sIFR         = sIFR;
    this.id           = id;
    this.vars         = vars;
    // Type of value depends on SWF builder. This could use some improvement!
    this.movie        = null;
    
    this.__forceWidth = forceWidth;
    this.__events     = events;
    this.__resizing   = 0;
  }
  
  FlashInteractor.prototype = {
    getFlashElement: function() {
      return document.getElementById(this.id);
    },
    
    getAlternate: function() {
      return document.getElementById(this.id + '_alternate');
    },
    
    getAncestor: function() {
      var ancestor = this.getFlashElement().parentNode;
      return !this.sIFR.dom.hasClass(ClassNames.FIX_FOCUS, ancestor) ? ancestor : ancestor.parentNode;
    },
    
    available: function() {
      var flashNode = this.getFlashElement();
      return flashNode && flashNode.parentNode;
    },
    
    call: function(type) {
      var flashNode = this.getFlashElement();
      
      if (!flashNode[type]) {
        return false;
      }
      // In Firefox 2, exposed Flash methods aren't proper functions, there's no `apply()` method! This workaround
      // does work, though.
      return Function.prototype.apply.call(flashNode[type], flashNode, Array.prototype.slice.call(arguments, 1));
    },
    
    attempt: function() {
      if(!this.available()) return false;
      
      try {
        this.call.apply(this, arguments);
      } catch(e) {
        if(this.sIFR.debug) throw e;
        return false;
      }
      
      return true;
    },
    
    updateVars: function(name, value) {
      for(var i = 0; i < this.vars.length; i++) {
        if (this.vars[i].split('=')[0] == name) {
          this.vars[i] = name + '=' + value;
          break;
        }
      }
      
      var vars = this.sIFR.util.encodeVars(this.vars);
      this.movie.injectVars(this.getFlashElement(), vars);
      this.movie.injectVars(this.movie.html, vars);
    },
    
    storeSize: function(type, value) {
      this.movie.setSize(type, value);
      this.updateVars(type, value);
    },
    
    fireEvent: function(name) {
      if(this.available() && this.__events[name]) this.sIFR.util.delay(0, this.__events[name], this, this);
    },
    
    resizeFlashElement: function(height, width, firstResize) {
      if(!this.available()) return;
      
      this.__resizing++;
      
      var flashNode = this.getFlashElement();
      flashNode.setAttribute('height', height);
      
      // Reset element height as declared by `MovieCreator`
      this.getAncestor().style.minHeight = '';
      
      this.updateVars('renderheight', height);
      this.storeSize('height', height);
      if(width !== null) {
        flashNode.setAttribute('width', width);
        // Don't store the size, it may cause Flash to wrap the text when the movie is reset.
        this.movie.setSize('width', width);
      }
      if(this.__events.onReplacement) {
        this.sIFR.util.delay(0, this.__events.onReplacement, this, this);
        delete this.__events.onReplacement;
      }
      
      if(firstResize) {
        this.sIFR.util.delay(0, function() {
          this.attempt('scaleMovie');
          this.__resizing--;
        }, this);
      } else {
        this.__resizing--;
      }
    },
    
    blurFlashElement: function() {
      if(this.available()) this.sIFR.dom.blurElement(this.getFlashElement());
    },
    
    resetMovie: function() {
      this.sIFR.util.delay(0, this.movie.reset, this.movie, this.getFlashElement(), this.getAlternate());
    },
    
    resizeAfterScale: function() {
      if(this.available() && this.__resizing == 0) this.sIFR.util.delay(0, this.resize, this);
    },
    
    resize: function() {
      if(!this.available()) return;
      
      this.__resizing++;
      
      var flashNode      = this.getFlashElement();
      var currentWidth   = flashNode.offsetWidth;
      
      // The Flash movie has no dimensions, which means it's not visible anyway. No need to recalculate.
      if(currentWidth == 0) return;
      
      var originalWidth  = flashNode.getAttribute('width');
      var originalHeight = flashNode.getAttribute('height');
      
      var ancestor       = this.getAncestor();
      var minHeight      = this.sIFR.dom.getHeightFromStyle(ancestor);
      
      // Remove Flash movie from flow
      flashNode.style.width  = '1px';
      flashNode.style.height = '1px';
      
      // Set a minimal height on the flashNode's parent, to stop a reflow
      ancestor.style.minHeight = minHeight + 'px';
      
      // Restore original content
      var nodes = this.getAlternate().childNodes;
      var clones = [];
      for(var i = 0; i < nodes.length; i++) {
        var node = nodes[i].cloneNode(true);
        clones.push(node);
        ancestor.appendChild(node);
      }
      
      // Calculate width
      var width = this.sIFR.dom.getWidthFromStyle(ancestor);
      
      // Remove original content again
      for(var i = 0; i < clones.length; i++) ancestor.removeChild(clones[i]);
      
      // Reset Flash movie flow
      flashNode.style.width = flashNode.style.height = ancestor.style.minHeight = '';
      flashNode.setAttribute('width', this.__forceWidth ? width : originalWidth);
      flashNode.setAttribute('height', originalHeight);
      
      // IE can get mightily confused about where to draw the Flash <object>. This is a workaround to force IE to repaint
      // the <object>.
      if(sIFR.ua.ie) {
        flashNode.style.display = 'none';
        var repaint = flashNode.offsetHeight;
        flashNode.style.display = '';
      }
      
      // Resize!
      if(width != currentWidth) {
        if(this.__forceWidth) this.storeSize('width', width);
        this.attempt('resize', width);
      }
      
      this.__resizing--;
    },
    
    // `content` must not be util.escaped when passed in.
    // alternate may be an array of nodes to be appended to the alternate content, use this
    // in XHTML documents.
    replaceText: function(content, alternate) {
      var escapedContent = this.sIFR.util.escape(content);
      if(!this.attempt('replaceText', escapedContent)) return false;
      
      this.updateVars('content', escapedContent);
      var node = this.getAlternate();
      if(alternate) {
        while(node.firstChild) node.removeChild(node.firstChild);
        for(var i = 0; i < alternate.length; i++) node.appendChild(alternate[i]);
      } else {
        try { node.innerHTML = content; } catch(e) {};
      }
      
      return true;
    },
    
    changeCSS: function(css) {
      css = this.sIFR.util.escape(this.sIFR.util.cssToString(this.sIFR.util.convertCssArg(css)));
      this.updateVars('css', css);
      return this.attempt('changeCSS', css);
    },
    
    remove: function() {
      if(this.movie && this.available()) this.movie.remove(this.getFlashElement(), this.id);
    }
  };
  
  var MovieCreator = new function() {
    this.create = function(sIFR, brokenFlash, node, fixFocus, id, src, width, height, vars, wmode, backgroundColor) {
      var klass = sIFR.ua.ie ? IEFlashMovie : FlashMovie;
      return new klass(
        sIFR, brokenFlash, node, fixFocus, 
        id, src, width, height, 
        ['flashvars', vars, 'wmode', wmode, 'bgcolor', backgroundColor, 'allowScriptAccess', 'always', 'quality', 'best']
      );
    }
    
    function FlashMovie(sIFR, brokenFlash, node, fixFocus, id, src, width, height, params) {
      var object = sIFR.dom.create('object', ClassNames.FLASH);
      var attrs  = ['type', 'application/x-shockwave-flash', 'id', id, 'name', id, 'data', src, 'width', width, 'height', height];
      for(var i = 0; i < attrs.length; i += 2) object.setAttribute(attrs[i], attrs[i + 1]);
      
      var insertion = object;
      if(fixFocus) {
        insertion = dom.create("div", ClassNames.FIX_FOCUS);
        insertion.appendChild(object);
      }
      
      for(var i = 0; i < params.length; i+=2) {
        if(params[i] == 'name') continue;
        
        var param = dom.create('param');
        param.setAttribute('name', params[i]);
        param.setAttribute('value', params[i + 1]);
        object.appendChild(param);
      }
      
      // Before removing the existing content, set its height such that the element
      // does not collapse. Height is restored in `FlashInteractor#resizeFlashElement`.
      node.style.minHeight = height + 'px';
      
      while(node.firstChild) node.removeChild(node.firstChild);
      node.appendChild(insertion);
      
      this.html = insertion.cloneNode(true);
    }
    
    FlashMovie.prototype = {
      reset: function(flashNode, alternate) {
        flashNode.parentNode.replaceChild(this.html.cloneNode(true), flashNode);
      },
      
      remove: function(flashNode, id) {
        flashNode.parentNode.removeChild(flashNode);
      },
      
      setSize: function(type, value) {
        this.html.setAttribute(type, value);
      },
      
      injectVars: function(flash, encodedVars) {
        var params = flash.getElementsByTagName('param');
        for(var i = 0; i < params.length; i++) {
          if(params[i].getAttribute('name') == 'flashvars') {
            params[i].setAttribute('value', encodedVars);
            break;
          }
        }
      }
    };
    
    function IEFlashMovie(sIFR, brokenFlash, node, fixFocus, id, src, width, height, params) {
      this.dom    = sIFR.dom;
      this.broken = brokenFlash;
      
      this.html = '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" id="' + id +
        '" width="' + width + '" height="' + height + '" class="' + ClassNames.FLASH + '">' +
        '<param name="movie" value="' + src + '"></param></object>'
        ;
      var paramsHtml = '';
      for(var i = 0; i < params.length; i+=2) {
        paramsHtml += '<param name="' + params[i] + '" value="' + params[i + 1] + '"></param>';
      }
      this.html      = this.html.replace(/(<\/object>)/, paramsHtml + '$1');
      
      // Before removing the existing content, set its height such that the element
      // does not collapse. Height is restored in `FlashInteractor#resizeFlashElement`.
      node.style.minHeight = height + 'px';
      
      node.innerHTML = this.html;
      
      this.broken.register(node.firstChild);
    }
    
    IEFlashMovie.prototype = {
      reset: function(flashNode, alternate) {
        alternate            = alternate.cloneNode(true);
        var parent           = flashNode.parentNode;
        parent.innerHTML     = this.html;
        this.broken.register(parent.firstChild);
        parent.appendChild(alternate);
      },
      
      remove: function(flashNode, id) {
        this.broken.cleanup(id);
      },
      
      setSize: function(type, value) {
         this.html = this.html.replace(type == 'height' ? /(height)="\d+"/ : /(width)="\d+"/, '$1="' + value + '"');
      },
      
      injectVars: function(flash, encodedVars) {
        if(flash != this.html) return;
        this.html = this.html.replace(/(flashvars(=|\"\svalue=)\")[^\"]+/, '$1' + encodedVars);
      }
    };
  }
  
  this.errors =             new Errors(self);
  var util    = this.util = new Util(self);
  var dom     = this.dom  = new DomUtil(self);
  var ua      = this.ua   = new UserAgentDetection(self);
  var hacks   = {
    fragmentIdentifier:     new FragmentIdentifier(self),
    pageLoad:               new PageLoad(self),
    prefetch:               new Prefetch(self),
    brokenFlashIE:          new BrokenFlashIE(self)
  };
  this.__resetBrokenMovies = hacks.brokenFlashIE.reset;
  
  var replaceKwargsStore = {
    kwargs: [],
    replaceAll:  function(preserve) {
      for(var i = 0; i < this.kwargs.length; i++) self.replace(this.kwargs[i]);
      if(!preserve) this.kwargs = [];
    }
  };
  
  this.activate = function(/* … */) {
    if(!ua.supported || !this.isEnabled || this.isActive || !isValidDomain() || isFile()) return;
    hacks.prefetch.fetchMovies(arguments);
    
    this.isActive = true;
    this.setFlashClass();
    hacks.fragmentIdentifier.cache();
    hacks.pageLoad.attachUnload();
    
    if(!this.autoInitialize) return;
    
    hacks.pageLoad.attach();
  };
  
  this.setFlashClass = function() {
    if(this.hasFlashClassSet) return;
    
    dom.addClass(ClassNames.ACTIVE, dom.getBody() || document.documentElement);
    this.hasFlashClassSet = true;
  };
  
  this.removeFlashClass = function() {
    if(!this.hasFlashClassSet) return;
    
    dom.removeClass(ClassNames.ACTIVE, dom.getBody());
    dom.removeClass(ClassNames.ACTIVE, document.documentElement);
    this.hasFlashClassSet = false;
  };
  
  this.initialize = function(preserveReplacements) {
    if(!this.isActive || !this.isEnabled) return;
    if(isInitialized) {
      if(!preserveReplacements) replaceKwargsStore.replaceAll(false);
      return;
    }
    
    isInitialized = true;
    replaceKwargsStore.replaceAll(preserveReplacements);
    
    if(self.repaintOnResize) {
      if(window.addEventListener) window.addEventListener('resize', resize, false);
      else window.attachEvent('onresize', resize);
    }
    
    hacks.prefetch.clear();
  };
  
  this.replace = function(kwargs, mergeKwargs) {
    if(!ua.supported) return;
    
    // This lets you specify two kwarg objects so you don't have to repeat common settings.
    // The first object will be merged with the second, while properties in the second 
    // object have priority over those in the first. The first object is unmodified
    // for further use, the resulting second object will be used in the replacement.
    if(mergeKwargs) kwargs = util.copyProperties(kwargs, mergeKwargs);
    
    if(!isInitialized) return replaceKwargsStore.kwargs.push(kwargs);
    
    if(this.onReplacementStart) this.onReplacementStart(kwargs);
    
    var nodes = kwargs.elements || dom.querySelectorAll(kwargs.selector);
    if(nodes.length == 0) return;
    
    var src             = getSource(kwargs.src);
    var css             = util.convertCssArg(kwargs.css);
    var filters         = getFilters(kwargs.filters);
    
    var forceSingleLine = kwargs.forceSingleLine === true;
    var preventWrap     = kwargs.preventWrap === true && !forceSingleLine;
    var fitExactly      = forceSingleLine || (kwargs.fitExactly == null ? this.fitExactly : kwargs.fitExactly) === true;
    var forceWidth      = fitExactly || (kwargs.forceWidth == null ? this.forceWidth : kwargs.forceWidth) === true;
    var ratios          = kwargs.ratios || [];
    var pixelFont       = kwargs.pixelFont === true;
    var tuneHeight      = parseInt(kwargs.tuneHeight) || 0;
    var events          = !!kwargs.onRelease || !!kwargs.onRollOver || !!kwargs.onRollOut;
    
    // Alignment should be handled by the browser in this case.
    if(fitExactly) util.extractFromCss(css, '.sIFR-root', 'text-align', true);
    
    var fontSize        = util.extractFromCss(css, '.sIFR-root', 'font-size', true)        || '0';
    var backgroundColor = util.extractFromCss(css, '.sIFR-root', 'background-color', true) || '#FFFFFF';
    var kerning         = util.extractFromCss(css, '.sIFR-root', 'kerning', true)          || '';
    var opacity         = util.extractFromCss(css, '.sIFR-root', 'opacity', true)          || '100';
    var cursor          = util.extractFromCss(css, '.sIFR-root', 'cursor', true)           || 'default';
    var leading         = parseInt(util.extractFromCss(css, '.sIFR-root', 'leading'))      || 0;
    var gridFitType     = kwargs.gridFitType ||
                            (util.extractFromCss(css, '.sIFR-root', 'text-align') == 'right') ? 'subpixel' : 'pixel';
    var textTransform   = this.forceTextTransform === false 
                            ? 'none'
                            : util.extractFromCss(css, '.sIFR-root', 'text-transform', true) || 'none';
    // Only font sizes specified in pixels are supported.
    fontSize            = /^\d+(px)?$/.test(fontSize) ? parseInt(fontSize) : 0;
    // Make sure to support percentages and decimals
    opacity             = parseFloat(opacity) < 1 ? 100 * parseFloat(opacity) : opacity;
    
    var cssText = kwargs.modifyCss ? '' : util.cssToString(css);
    var wmode   = kwargs.wmode || '';
    if(!wmode) {
      if(kwargs.transparent) wmode = 'transparent';
      else if(kwargs.opaque) wmode = 'opaque';
    } 
    if(wmode == 'transparent') {
      if(!ua.transparencySupport) wmode = 'opaque';
      else backgroundColor = 'transparent';
    } else if(backgroundColor == 'transparent') {
      backgroundColor = '#FFFFFF';
    }
    
    for(var i = 0; i < nodes.length; i++) {
      var node = nodes[i];
      
      if(dom.hasOneOfClassses(ClassNames.IGNORE_CLASSES, node) || dom.ancestorHasClass(node, ClassNames.ALTERNATE)) continue;
      
      // Opera does not allow communication with hidden Flash movies. Visibility is tackled by sIFR itself, but
      // `display:none` isn't. Additionally, WebKit does not return computed style information for elements with
      // `display:none`. We'll prevent elements which have `display:none` or are contained in such an element from
      // being replaced. It's a bit hard to detect this, but we'll check for the dimensions of the element and its
      // `display` property.
      
      var dimensions = dom.getDimensions(node);
      var height     = dimensions.height;
      var width      = dimensions.width;
      var display    = dom.getComputedStyle(node, 'display');
      
      if(!height || !width || !display || display == 'none') continue;
      
      // Get the width (to approximate the final size).
      width = dom.getWidthFromStyle(node);
      
      var size, lines;
      if(!fontSize) {
        var calculation    = calculate(node);
        size               = Math.min(this.MAX_FONT_SIZE, Math.max(this.MIN_FONT_SIZE, calculation.fontSize));
        if(pixelFont) size = Math.max(8, 8 * Math.round(size / 8));
        
        lines = calculation.lines;
      } else {
        size  = fontSize;
        lines = 1;
      }
      
      var alternate = dom.create('span', ClassNames.ALTERNATE);
      // Clone the original content to the alternate element.
      var contentNode = node.cloneNode(true);
      // Temporarily append the contentNode to the document, to get around IE problems with resolved hrefs
      node.parentNode.appendChild(contentNode);
      for(var j = 0, l = contentNode.childNodes.length; j < l; j++) {
        var child = contentNode.childNodes[j];
        // Let's not keep <style> or <script> in the alternate content, since it may be
        // reintroduced to the DOM after resizing.
        if (!/^(style|script)$/i.test(child.nodeName)) {
          alternate.appendChild(child.cloneNode(true));
        }
      }
      
      // Allow the sIFR content to be modified
      if(kwargs.modifyContent) kwargs.modifyContent(contentNode, kwargs.selector);
      if(kwargs.modifyCss) cssText = kwargs.modifyCss(css, contentNode, kwargs.selector);
      
      var content = parseContent(contentNode, textTransform, kwargs.uriEncode);
      // Remove the contentNode again
      contentNode.parentNode.removeChild(contentNode);
      if(kwargs.modifyContentString) content.text = kwargs.modifyContentString(content.text, kwargs.selector);
      if(content.text == '') continue;
      
      // Approximate the final height to avoid annoying movements of the page
      var renderHeight = Math.round(lines * getRatio(size, ratios) * size) + this.FLASH_PADDING_BOTTOM + tuneHeight;
      if (lines > 1 && leading) {
        renderHeight += Math.round((lines - 1) * leading);
      }
      
      var forcedWidth = forceWidth ? width : '100%';
      
      var id   = 'sIFR_replacement_' + elementCount++;
      var vars = ['id='              + id,
                  'content='         + util.escape(content.text),
                  'width='           + width,
                  'renderheight='    + renderHeight, 
                  'link='            + util.escape(content.primaryLink.href   || ''),
                  'target='          + util.escape(content.primaryLink.target || ''),
                  'size='            + size, 
                  'css='             + util.escape(cssText),
                  'cursor='          + cursor,
                  'tunewidth='       + (kwargs.tuneWidth || 0),
                  'tuneheight='      + tuneHeight,
                  'offsetleft='      + (kwargs.offsetLeft || ''),
                  'offsettop='       + (kwargs.offsetTop  || ''),
                  'fitexactly='      + fitExactly,
                  'preventwrap='     + preventWrap,
                  'forcesingleline=' + forceSingleLine,
                  'antialiastype='   + (kwargs.antiAliasType || ''),
                  'thickness='       + (kwargs.thickness || ''),
                  'sharpness='       + (kwargs.sharpness || ''),
                  'kerning='         + kerning,
                  'gridfittype='     + gridFitType,
                  'flashfilters='    + filters,
                  'opacity='         + opacity,
                  'blendmode='       + (kwargs.blendMode || ''), 
                  'selectable='      + (kwargs.selectable == null || wmode != '' && !sIFR.ua.macintosh && sIFR.ua.gecko && sIFR.ua.geckoVersion >= sIFR.ua.parseVersion('1.9')
                                         ? 'true' 
                                         : kwargs.selectable === true
                                       ),
                  'fixhover='        + (this.fixHover === true),
                  'events='          + events,
                  'delayrun='        + hacks.brokenFlashIE.fixFlash,
                  'version='         + this.VERSION];
      
      var encodedVars = util.encodeVars(vars);
      var interactor  = new FlashInteractor(self, id, vars, forceWidth, {
        onReplacement: kwargs.onReplacement,
        onRollOver: kwargs.onRollOver,
        onRollOut: kwargs.onRollOut,
        onRelease: kwargs.onRelease
      });
      interactor.movie = MovieCreator.create(
        sIFR, hacks.brokenFlashIE, node, ua.fixFocus && kwargs.fixFocus, 
        id, src, forcedWidth, renderHeight, 
        encodedVars, wmode, backgroundColor
      );
      this.replacements.push(interactor);
      this.replacements[id] = interactor;
      if(kwargs.selector) {
        if(!this.replacements[kwargs.selector]) this.replacements[kwargs.selector] = [interactor];
        else this.replacements[kwargs.selector].push(interactor);
      }
      alternate.setAttribute('id', id + '_alternate');
      node.appendChild(alternate);
      dom.addClass(ClassNames.REPLACED, node);
    }
    
    hacks.fragmentIdentifier.restore();
  };
  
  this.getReplacementByFlashElement = function(node) {
    for(var i = 0; i < self.replacements.length; i++) {
      if(self.replacements[i].id == node.getAttribute('id')) return self.replacements[i];
    }
  };
  
  this.redraw = function() {
    for(var i = 0; i < self.replacements.length; i++) self.replacements[i].resetMovie();
  };
  
  this.prepareClearReferences = function() {
    hacks.brokenFlashIE.prepareClearReferences();
  };
  
  this.clearReferences = function() {
    hacks.brokenFlashIE.clearReferences();
    hacks = null;
    replaceKwargsStore = null;
    delete self.replacements;
  };
  
  // The goal here is not to prevent usage of the Flash movie, but running sIFR on possibly translated pages
  function isValidDomain() {
    if(self.domains.length == 0) return true;
    
    var domain = util.domain();
    for(var i = 0; i < self.domains.length; i++) {
      if(util.domainMatches(domain, self.domains[i])) {
        return true;
      }
    }
    
    return false;
  }
  
  function isFile() {
    if(document.location.protocol == 'file:') {
      if(self.debug) self.errors.fire('isFile');
      return true;
    }
    return false;
  }
  
  function getSource(src) {
    if(ua.ie && src.charAt(0) == '/') {
      src = window.location.toString().replace(/([^:]+)(:\/?\/?)([^\/]+).*/, '$1$2$3') + src;
    }
    
    return src;
  }
  
  // Gives a font-size to required vertical space ratio
  function getRatio(size, ratios) {
    for(var i = 0; i < ratios.length; i += 2) {
      if(size <= ratios[i]) return ratios[i + 1];
    }
    return ratios[ratios.length - 1] || 1;
  }
  
  function getFilters(obj) {
    var filters = [];
    for(var filter in obj) {
      if(obj[filter] == Object.prototype[filter]) continue;
      
      var properties = obj[filter];
      filter = [filter.replace(/filter/i, '') + 'Filter'];
      
      for(var property in properties) {
        if(properties[property] == Object.prototype[property]) continue;
        // Double-escaping (see end of function) makes it easier to parse the resulting string
        // in AS.
        filter.push(property + ':' + util.escape(util.toJson(properties[property], util.toHexString)));
      }
      
      filters.push(filter.join(','));
    }
    
    return util.escape(filters.join(';'));
  }
  
  function resize(evt) {
    var current  = resize.viewport;
    var viewport = dom.getViewport();
    
    if(current && viewport.width == current.width && viewport.height == current.height) return;
    resize.viewport = viewport;
    
    if(self.replacements.length == 0) return; // Nothing replaced yet, resize event is not important.
    
    if(resize.timer) clearTimeout(resize.timer);
    resize.timer = setTimeout(function() {
      delete resize.timer;
      for(var i = 0; i < self.replacements.length; i++) self.replacements[i].resize();
    }, 200);
  }
  
  function calculate(node) {
    var fontSize = dom.getComputedStyle(node, 'fontSize');
    var deduce = fontSize.indexOf('px') == -1;
    
    var html = node.innerHTML;
    if (deduce) {
      node.innerHTML = 'X';
    }
    
    // Reset padding and border, so offsetHeight works properly
    node.style.paddingTop = node.style.paddingBottom = node.style.borderTopWidth = node.style.borderBottomWidth = '0px';
    // 2em magically makes offsetHeight correct in IE
    node.style.lineHeight = '2em';
    // Provided display is block
    node.style.display = 'block';
    
    fontSize = deduce ? node.offsetHeight / 2 : parseInt(fontSize, 10);
    
    if (deduce) {
      node.innerHTML = html;
    }
    
    var lines = Math.round(node.offsetHeight / (2 * fontSize));
    
    node.style.paddingTop = node.style.paddingBottom = node.style.borderTopWidth = node.style.borderBottomWidth
                          = node.style.lineHeight = node.style.display = '';
    
    if (isNaN(lines) || !isFinite(lines) || lines == 0) {
      lines = 1;
    }
    
    return {fontSize: fontSize, lines: lines};
  }
  
  function parseContent(source, textTransform, uriEncode) {
    uriEncode = uriEncode || util.uriEncode;
    var stack = [], content = [];
    var primaryLink = null;
    var nodes = source.childNodes;
    var whiteSpaceEnd = false, firstText = false;
    
    var i = 0;
    while(i < nodes.length) {
      var node = nodes[i];
      
      if(node.nodeType == 3) {
        var text = util.textTransform(textTransform, util.normalize(node.nodeValue)).replace(/</g, '&lt;');
        if(whiteSpaceEnd && firstText) text = text.replace(/^\s+/, '');
        content.push(text);
        whiteSpaceEnd = /\s$/.test(text);
        firstText = false;
      }
      
      if(node.nodeType == 1 && !/^(style|script)$/i.test(node.nodeName)) {
        var attributes = [];
        var nodeName   = node.nodeName.toLowerCase();
        var className  = node.className || '';
        // If there are multiple classes, look for the specified sIFR class
        if(/\s+/.test(className)) {
          if(className.indexOf(ClassNames.CLASS) > -1) className = className.match('(\\s|^)' + ClassNames.CLASS + '-([^\\s$]*)(\\s|$)')[2];
          // or use the first class. This is because Flash does not support the use of multiple class names.
          // Flash doesn't support IDs either.
          else className = className.match(/^([^\s]+)/)[1];
        }
        if(className != '') attributes.push('class="' + className + '"');
        
        if(nodeName == 'a') {
          var href   = uriEncode(node.getAttribute('href') || '');
          var target = node.getAttribute('target') || '';
          attributes.push('href="' + href + '"', 'target="' + target + '"');
          
          if(!primaryLink) {
            primaryLink = {
              href: href,
              target: target
            };
          }
        }
        
        content.push('<' + nodeName + (attributes.length > 0 ? ' ' : '') + attributes.join(' ') + '>');
        firstText = true;
        
        if(node.hasChildNodes()) {
          // Push the current index to the stack and prepare to iterate
          // over the childNodes.
          stack.push(i);
          i = 0;
          nodes = node.childNodes;
          continue;
        } else if(!/^(br|img)$/i.test(node.nodeName)) content.push('</', node.nodeName.toLowerCase(), '>');
      }
      
      if(stack.length > 0 && !node.nextSibling) {
        // Iterating the childNodes has been completed. Go back to the position
        // before we started the iteration. If that position was the last child,
        // go back even further.
        do {
          i = stack.pop();
          nodes = node.parentNode.parentNode.childNodes;
          node = nodes[i];
          if(node) content.push('</', node.nodeName.toLowerCase(), '>');
        } while(i == nodes.length - 1 && stack.length > 0);
      }
      
      i++;
    }
    
    return {text: content.join('').replace(/^\s+|\s+$|\s*(<br>)\s*/g, '$1'), primaryLink: primaryLink || {}};
  }
};

/*=:project
    parseSelector 2.0.2
    
  =:description
    Provides an extensible way of parsing CSS selectors against a DOM in 
    JavaScript.

  =:file
    Copyright: 2006-2008 Mark Wubben.
    Author: Mark Wubben, <http://novemberborn.net/>
       
  =:license
    This software is licensed and provided under the CC-GNU LGPL. 
    See <http://creativecommons.org/licenses/LGPL/2.1/>
    
  =:support
    parseSelector supports the following user agents:
      * Internet Explorer 6 and above
      * Firefox 1.0 and above, and equivalent Gecko engine versions
      * Safari 2.0 and above
      * Opera 8.0 and above
      * Konqueror 3.5.5 and above
    It might work in other browsers and versions, but there are no guarantees. There is
    no verification made when parseSelector is run to ascertain the browser is supported.
  
  =:notes
    The parsing of CSS selectors as streams has been based on Dean Edwards
    excellent work with cssQuery. See <http://dean.edwards.name/my/cssQuery/>
    for more info.
*/

var parseSelector = (function() {
  var SEPERATOR       = /\s*,\s*/
  var WHITESPACE      = /\s*([\s>+~(),]|^|$)\s*/g;
  var IMPLIED_ALL     = /([\s>+~,]|[^(]\+|^)([#.:@])/g;
  var STANDARD_SELECT = /(^|\))[^\s>+~]/g;
  var INSERT_SPACE    = /(\)|^)/;
  var STREAM          = /[\s#.:>+~()@]|[^\s#.:>+~()@]+/g;
  
  function parseSelector(selector, node) {
    node = node || document.documentElement;
    var argSelectors = selector.split(SEPERATOR), result = [];
    
    for(var i = 0; i < argSelectors.length; i++) {
      var nodes = [node], stream = toStream(argSelectors[i]);
      for(var j = 0; j < stream.length;) {
        var token = stream[j++], filter = stream[j++], args = '';
        if(stream[j] == '(') {
          while(stream[j++] != ')' && j < stream.length) args += stream[j];
          args = args.slice(0, -1);
        }
        nodes = select(nodes, token, filter, args);
      }
      result = result.concat(nodes);
    }
    
    return result;
  }

  function toStream(selector) {
    var stream = selector.replace(WHITESPACE, '$1').replace(IMPLIED_ALL, '$1*$2').replace(STANDARD_SELECT, insertSpaces);
    return stream.match(STREAM) || [];
  }
  
  function insertSpaces(str) {
    return str.replace(INSERT_SPACE, '$1 ');
  }
  
  function select(nodes, token, filter, args) {
    return (parseSelector.selectors[token]) ? parseSelector.selectors[token](nodes, filter, args) : [];
  }
  
  var util = {
    toArray: function(enumerable) {
      var a = [];
      for(var i = 0; i < enumerable.length; i++) a.push(enumerable[i]);
      return a;
    }
  };
  
  var dom = {
    isTag: function(node, tag) {
      return (tag == '*') || (tag.toLowerCase() == node.nodeName.toLowerCase());
    },
  
    previousSiblingElement: function(node) {
      do node = node.previousSibling; while(node && node.nodeType != 1);
      return node;
    },
  
    nextSiblingElement: function(node) {
      do node = node.nextSibling; while(node && node.nodeType != 1);
      return node;
    },
  
    hasClass: function(name, node) {
      return (node.className || '').match('(^|\\s)'+name+'(\\s|$)');
    },
  
    getByTag: function(tag, node) {
      return node.getElementsByTagName(tag);
    }
  };

  var selectors = {
    '#': function(nodes, filter) {
      for(var i = 0; i < nodes.length; i++) {
        if(nodes[i].getAttribute('id') == filter) return [nodes[i]];
      }
      return [];
    },

    ' ': function(nodes, filter) {
      var result = [];
      for(var i = 0; i < nodes.length; i++) {
        result = result.concat(util.toArray(dom.getByTag(filter, nodes[i])));
      }
      return result;
    },
    
    '>': function(nodes, filter) {
      var result = [];
      for(var i = 0, node; i < nodes.length; i++) {
        node = nodes[i];
        for(var j = 0, child; j < node.childNodes.length; j++) {
          child = node.childNodes[j];
          if(child.nodeType == 1 && dom.isTag(child, filter)) result.push(child);
        }
      }
      return result;
    },

    '.': function(nodes, filter) {
      var result = [];
      for(var i = 0, node; i < nodes.length; i++) {
        node = nodes[i];
        if(dom.hasClass([filter], node)) result.push(node);
      }
      return result;
    }, 
        
    ':': function(nodes, filter, args) {
      return (parseSelector.pseudoClasses[filter]) ? parseSelector.pseudoClasses[filter](nodes, args) : [];
    }
    
  };

  parseSelector.selectors     = selectors;
  parseSelector.pseudoClasses = {};
  parseSelector.util          = util;
  parseSelector.dom           = dom;

  return parseSelector;
})();

var sifrH3 = { src: '/swf/sifrH3.swf' };
var sifrH3Prop = {
  wmode: 'transparent',
  selector: '#content h3 span',
  css: [
  	'.sIFR-root { margin-left: 8px; color: #FFFFFF; font-size: 21px; letter-spacing: 1.1; }',
  	'a { margin-left: 8px; color: #FFFFFF; font-size: 21px; letter-spacing: 1.1; text-decoration: none; }',
  	'a:link { margin-left: 8px; color: #FFFFFF; font-size: 21px; letter-spacing: 1.1; text-decoration: none; }',
  	'a:hover { margin-left: 8px; color: #FFFFFF; font-size: 21px; letter-spacing: 1.1; text-decoration: none; }'
  ],
  filters: {
	Glow: {
   		color: '#6b340b',
	    strength: 30,
	    alpha: 100,
	    blurX: 1.3,
	    blurY: 1.3,
	    quality: 100
	}
  }
};
sIFR.activate(sifrH3);
sIFR.replace(sifrH3, sifrH3Prop);

// KONAMI CODE ;)
// up, up, down, down, left, right, left, right, b, a
// $(document).konami(function(){ alert('Konami Code Activated!')});
// $(document).konami(function(){ alert('NMC Code Activated!')}, "78,77,67");
(function($) {

	$.fn.konami = function(callback, code) {
        if(code == undefined) code = "38,38,40,40,37,39,37,39,66,65";
		
		return this.each(function() {
			var kkeys = [];
			$(this).keydown(function(e){
				kkeys.push( e.keyCode );
				if ( kkeys.toString().indexOf( code ) >= 0 ){
					$(this).unbind('keydown', arguments.callee);
					callback(e);
				}
			}, true);
		});
	}

})(jQuery);

/**
 *	Tarschool main javascript
 */
$(document).ready(function(){
	$('#menu .menuSuperFish li.index a').css('border-left', 'none');
	$('#menu .menuSuperFish li.forums a').css('border-right', 'none');
	
	swfobject.embedSWF(
		"/swf/header.swf", 
		"flashHeader", 
		"648", 
		"149", 
		"9.0.0", 
		false, 
		{}, 
		{
			wmode: 'transparent'
		}
	);
	$('#header h1, #header h1 a').css('text-indent', 0);

	/**
	 *	Tooltip
	 */
	$('.tooltip').livequery(function(){
		$(this).cluetip(); 
	});
});

$(document).ready(function(){
	var smsHTML = $(".marqueeMe").html();
	$(".marqueeMe").html("<marquee behavior=\"scroll\" scrollamount=\"3\" direction=\"left\" style=\"width: 510px; height: 16px; line-height: 16px;\">" + smsHTML + "</marquee>");
});

$(window).load(function(){
	/**
	 *	Gestion des hauteurs de blocs
	 */
	setTimeout(function(){
		var heightLeft = $('.thirdBoxedLTR .left').height();
		var heightRight = $('.thirdBoxedLTR .right').height();
		
		if(heightLeft > heightRight)
		{
			$last = $('.thirdBoxedLTR .right .block:last .content:last');
			$last.css('height', $last.height() + heightLeft - heightRight + 'px');
		}
		if(heightRight > heightLeft)
		{
			$last = $('.thirdBoxedLTR .left .block:last .content:last');
			$last.css('height', $last.height() + heightRight - heightLeft + 'px');
		}
	}, 500);
});