/*!!!!jquery-1.3.2.js!!!!*/
/*!
 * jQuery JavaScript Library v1.3.2
 * http://jquery.com/
 *
 * Copyright (c) 2009 John Resig
 * Dual licensed under the MIT and GPL licenses.
 * http://docs.jquery.com/License
 *
 * Date: 2009-02-19 17:34:21 -0500 (Thu, 19 Feb 2009)
 * Revision: 6246
 */
(function(){

var 
	// Will speed up references to window, and allows munging its name.
	window = this,
	// Will speed up references to undefined, and allows munging its name.
	undefined,
	// Map over jQuery in case of overwrite
	_jQuery = window.jQuery,
	// Map over the $ in case of overwrite
	_$ = window.$,

	jQuery = window.jQuery = window.$ = function( selector, context ) {
		// The jQuery object is actually just the init constructor 'enhanced'
		return new jQuery.fn.init( selector, context );
	},

	// A simple way to check for HTML strings or ID strings
	// (both of which we optimize for)
	quickExpr = /^[^<]*(<(.|\s)+>)[^>]*$|^#([\w-]+)$/,
	// Is it a simple selector
	isSimple = /^.[^:#\[\.,]*$/;

jQuery.fn = jQuery.prototype = {
	init: function( selector, context ) {
		// Make sure that a selection was provided
		selector = selector || document;

		// Handle $(DOMElement)
		if ( selector.nodeType ) {
			this[0] = selector;
			this.length = 1;
			this.context = selector;
			return this;
		}
		// Handle HTML strings
		if ( typeof selector === "string" ) {
			// Are we dealing with HTML string or an ID?
			var match = quickExpr.exec( selector );

			// Verify a match, and that no context was specified for #id
			if ( match && (match[1] || !context) ) {

				// HANDLE: $(html) -> $(array)
				if ( match[1] )
					selector = jQuery.clean( [ match[1] ], context );

				// HANDLE: $("#id")
				else {
					var elem = document.getElementById( match[3] );

					// Handle the case where IE and Opera return items
					// by name instead of ID
					if ( elem && elem.id != match[3] )
						return jQuery().find( selector );

					// Otherwise, we inject the element directly into the jQuery object
					var ret = jQuery( elem || [] );
					ret.context = document;
					ret.selector = selector;
					return ret;
				}

			// HANDLE: $(expr, [context])
			// (which is just equivalent to: $(content).find(expr)
			} else
				return jQuery( context ).find( selector );

		// HANDLE: $(function)
		// Shortcut for document ready
		} else if ( jQuery.isFunction( selector ) )
			return jQuery( document ).ready( selector );

		// Make sure that old selector state is passed along
		if ( selector.selector && selector.context ) {
			this.selector = selector.selector;
			this.context = selector.context;
		}

		return this.setArray(jQuery.isArray( selector ) ?
			selector :
			jQuery.makeArray(selector));
	},

	// Start with an empty selector
	selector: "",

	// The current version of jQuery being used
	jquery: "1.3.2",

	// The number of elements contained in the matched element set
	size: function() {
		return this.length;
	},

	// Get the Nth element in the matched element set OR
	// Get the whole matched element set as a clean array
	get: function( num ) {
		return num === undefined ?

			// Return a 'clean' array
			Array.prototype.slice.call( this ) :

			// Return just the object
			this[ num ];
	},

	// Take an array of elements and push it onto the stack
	// (returning the new matched element set)
	pushStack: function( elems, name, selector ) {
		// Build a new jQuery matched element set
		var ret = jQuery( elems );

		// Add the old object onto the stack (as a reference)
		ret.prevObject = this;

		ret.context = this.context;

		if ( name === "find" )
			ret.selector = this.selector + (this.selector ? " " : "") + selector;
		else if ( name )
			ret.selector = this.selector + "." + name + "(" + selector + ")";

		// Return the newly-formed element set
		return ret;
	},

	// Force the current matched set of elements to become
	// the specified array of elements (destroying the stack in the process)
	// You should use pushStack() in order to do this, but maintain the stack
	setArray: function( elems ) {
		// Resetting the length to 0, then using the native Array push
		// is a super-fast way to populate an object with array-like properties
		this.length = 0;
		Array.prototype.push.apply( this, elems );

		return this;
	},

	// Execute a callback for every element in the matched set.
	// (You can seed the arguments with an array of args, but this is
	// only used internally.)
	each: function( callback, args ) {
		return jQuery.each( this, callback, args );
	},

	// Determine the position of an element within
	// the matched set of elements
	index: function( elem ) {
		// Locate the position of the desired element
		return jQuery.inArray(
			// If it receives a jQuery object, the first element is used
			elem && elem.jquery ? elem[0] : elem
		, this );
	},

	attr: function( name, value, type ) {
		var options = name;

		// Look for the case where we're accessing a style value
		if ( typeof name === "string" )
			if ( value === undefined )
				return this[0] && jQuery[ type || "attr" ]( this[0], name );

			else {
				options = {};
				options[ name ] = value;
			}

		// Check to see if we're setting style values
		return this.each(function(i){
			// Set all the styles
			for ( name in options )
				jQuery.attr(
					type ?
						this.style :
						this,
					name, jQuery.prop( this, options[ name ], type, i, name )
				);
		});
	},

	css: function( key, value ) {
		// ignore negative width and height values
		if ( (key == 'width' || key == 'height') && parseFloat(value) < 0 )
			value = undefined;
		return this.attr( key, value, "curCSS" );
	},

	text: function( text ) {
		if ( typeof text !== "object" && text != null )
			return this.empty().append( (this[0] && this[0].ownerDocument || document).createTextNode( text ) );

		var ret = "";

		jQuery.each( text || this, function(){
			jQuery.each( this.childNodes, function(){
				if ( this.nodeType != 8 )
					ret += this.nodeType != 1 ?
						this.nodeValue :
						jQuery.fn.text( [ this ] );
			});
		});

		return ret;
	},

	wrapAll: function( html ) {
		if ( this[0] ) {
			// The elements to wrap the target around
			var wrap = jQuery( html, this[0].ownerDocument ).clone();

			if ( this[0].parentNode )
				wrap.insertBefore( this[0] );

			wrap.map(function(){
				var elem = this;

				while ( elem.firstChild )
					elem = elem.firstChild;

				return elem;
			}).append(this);
		}

		return this;
	},

	wrapInner: function( html ) {
		return this.each(function(){
			jQuery( this ).contents().wrapAll( html );
		});
	},

	wrap: function( html ) {
		return this.each(function(){
			jQuery( this ).wrapAll( html );
		});
	},

	append: function() {
		return this.domManip(arguments, true, function(elem){
			if (this.nodeType == 1)
				this.appendChild( elem );
		});
	},

	prepend: function() {
		return this.domManip(arguments, true, function(elem){
			if (this.nodeType == 1)
				this.insertBefore( elem, this.firstChild );
		});
	},

	before: function() {
		return this.domManip(arguments, false, function(elem){
			this.parentNode.insertBefore( elem, this );
		});
	},

	after: function() {
		return this.domManip(arguments, false, function(elem){
			this.parentNode.insertBefore( elem, this.nextSibling );
		});
	},

	end: function() {
		return this.prevObject || jQuery( [] );
	},

	// For internal use only.
	// Behaves like an Array's method, not like a jQuery method.
	push: [].push,
	sort: [].sort,
	splice: [].splice,

	find: function( selector ) {
		if ( this.length === 1 ) {
			var ret = this.pushStack( [], "find", selector );
			ret.length = 0;
			jQuery.find( selector, this[0], ret );
			return ret;
		} else {
			return this.pushStack( jQuery.unique(jQuery.map(this, function(elem){
				return jQuery.find( selector, elem );
			})), "find", selector );
		}
	},

	clone: function( events ) {
		// Do the clone
		var ret = this.map(function(){
			if ( !jQuery.support.noCloneEvent && !jQuery.isXMLDoc(this) ) {
				// IE copies events bound via attachEvent when
				// using cloneNode. Calling detachEvent on the
				// clone will also remove the events from the orignal
				// In order to get around this, we use innerHTML.
				// Unfortunately, this means some modifications to
				// attributes in IE that are actually only stored
				// as properties will not be copied (such as the
				// the name attribute on an input).
				var html = this.outerHTML;
				if ( !html ) {
					var div = this.ownerDocument.createElement("div");
					div.appendChild( this.cloneNode(true) );
					html = div.innerHTML;
				}

				return jQuery.clean([html.replace(/ jQuery\d+="(?:\d+|null)"/g, "").replace(/^\s*/, "")])[0];
			} else
				return this.cloneNode(true);
		});

		// Copy the events from the original to the clone
		if ( events === true ) {
			var orig = this.find("*").andSelf(), i = 0;

			ret.find("*").andSelf().each(function(){
				if ( this.nodeName !== orig[i].nodeName )
					return;

				var events = jQuery.data( orig[i], "events" );

				for ( var type in events ) {
					for ( var handler in events[ type ] ) {
						jQuery.event.add( this, type, events[ type ][ handler ], events[ type ][ handler ].data );
					}
				}

				i++;
			});
		}

		// Return the cloned set
		return ret;
	},

	filter: function( selector ) {
		return this.pushStack(
			jQuery.isFunction( selector ) &&
			jQuery.grep(this, function(elem, i){
				return selector.call( elem, i );
			}) ||

			jQuery.multiFilter( selector, jQuery.grep(this, function(elem){
				return elem.nodeType === 1;
			}) ), "filter", selector );
	},

	closest: function( selector ) {
		var pos = jQuery.expr.match.POS.test( selector ) ? jQuery(selector) : null,
			closer = 0;

		return this.map(function(){
			var cur = this;
			while ( cur && cur.ownerDocument ) {
				if ( pos ? pos.index(cur) > -1 : jQuery(cur).is(selector) ) {
					jQuery.data(cur, "closest", closer);
					return cur;
				}
				cur = cur.parentNode;
				closer++;
			}
		});
	},

	not: function( selector ) {
		if ( typeof selector === "string" )
			// test special case where just one selector is passed in
			if ( isSimple.test( selector ) )
				return this.pushStack( jQuery.multiFilter( selector, this, true ), "not", selector );
			else
				selector = jQuery.multiFilter( selector, this );

		var isArrayLike = selector.length && selector[selector.length - 1] !== undefined && !selector.nodeType;
		return this.filter(function() {
			return isArrayLike ? jQuery.inArray( this, selector ) < 0 : this != selector;
		});
	},

	add: function( selector ) {
		return this.pushStack( jQuery.unique( jQuery.merge(
			this.get(),
			typeof selector === "string" ?
				jQuery( selector ) :
				jQuery.makeArray( selector )
		)));
	},

	is: function( selector ) {
		return !!selector && jQuery.multiFilter( selector, this ).length > 0;
	},

	hasClass: function( selector ) {
		return !!selector && this.is( "." + selector );
	},

	val: function( value ) {
		if ( value === undefined ) {			
			var elem = this[0];

			if ( elem ) {
				if( jQuery.nodeName( elem, 'option' ) )
					return (elem.attributes.value || {}).specified ? elem.value : elem.text;
				
				// We need to handle select boxes special
				if ( jQuery.nodeName( elem, "select" ) ) {
					var index = elem.selectedIndex,
						values = [],
						options = elem.options,
						one = elem.type == "select-one";

					// Nothing was selected
					if ( index < 0 )
						return null;

					// Loop through all the selected options
					for ( var i = one ? index : 0, max = one ? index + 1 : options.length; i < max; i++ ) {
						var option = options[ i ];

						if ( option.selected ) {
							// Get the specifc value for the option
							value = jQuery(option).val();

							// We don't need an array for one selects
							if ( one )
								return value;

							// Multi-Selects return an array
							values.push( value );
						}
					}

					return values;				
				}

				// Everything else, we just grab the value
				return (elem.value || "").replace(/\r/g, "");

			}

			return undefined;
		}

		if ( typeof value === "number" )
			value += '';

		return this.each(function(){
			if ( this.nodeType != 1 )
				return;

			if ( jQuery.isArray(value) && /radio|checkbox/.test( this.type ) )
				this.checked = (jQuery.inArray(this.value, value) >= 0 ||
					jQuery.inArray(this.name, value) >= 0);

			else if ( jQuery.nodeName( this, "select" ) ) {
				var values = jQuery.makeArray(value);

				jQuery( "option", this ).each(function(){
					this.selected = (jQuery.inArray( this.value, values ) >= 0 ||
						jQuery.inArray( this.text, values ) >= 0);
				});

				if ( !values.length )
					this.selectedIndex = -1;

			} else
				this.value = value;
		});
	},

	html: function( value ) {
		return value === undefined ?
			(this[0] ?
				this[0].innerHTML.replace(/ jQuery\d+="(?:\d+|null)"/g, "") :
				null) :
			this.empty().append( value );
	},

	replaceWith: function( value ) {
		return this.after( value ).remove();
	},

	eq: function( i ) {
		return this.slice( i, +i + 1 );
	},

	slice: function() {
		return this.pushStack( Array.prototype.slice.apply( this, arguments ),
			"slice", Array.prototype.slice.call(arguments).join(",") );
	},

	map: function( callback ) {
		return this.pushStack( jQuery.map(this, function(elem, i){
			return callback.call( elem, i, elem );
		}));
	},

	andSelf: function() {
		return this.add( this.prevObject );
	},

	domManip: function( args, table, callback ) {
		if ( this[0] ) {
			var fragment = (this[0].ownerDocument || this[0]).createDocumentFragment(),
				scripts = jQuery.clean( args, (this[0].ownerDocument || this[0]), fragment ),
				first = fragment.firstChild;

			if ( first )
				for ( var i = 0, l = this.length; i < l; i++ )
					callback.call( root(this[i], first), this.length > 1 || i > 0 ?
							fragment.cloneNode(true) : fragment );
		
			if ( scripts )
				jQuery.each( scripts, evalScript );
		}

		return this;
		
		function root( elem, cur ) {
			return table && jQuery.nodeName(elem, "table") && jQuery.nodeName(cur, "tr") ?
				(elem.getElementsByTagName("tbody")[0] ||
				elem.appendChild(elem.ownerDocument.createElement("tbody"))) :
				elem;
		}
	}
};

// Give the init function the jQuery prototype for later instantiation
jQuery.fn.init.prototype = jQuery.fn;

function evalScript( i, elem ) {
	if ( elem.src )
		jQuery.ajax({
			url: elem.src,
			async: false,
			dataType: "script"
		});

	else
		jQuery.globalEval( elem.text || elem.textContent || elem.innerHTML || "" );

	if ( elem.parentNode )
		elem.parentNode.removeChild( elem );
}

function now(){
	return +new Date;
}

jQuery.extend = jQuery.fn.extend = function() {
	// copy reference to target object
	var target = arguments[0] || {}, i = 1, length = arguments.length, deep = false, options;

	// Handle a deep copy situation
	if ( typeof target === "boolean" ) {
		deep = target;
		target = arguments[1] || {};
		// skip the boolean and the target
		i = 2;
	}

	// Handle case when target is a string or something (possible in deep copy)
	if ( typeof target !== "object" && !jQuery.isFunction(target) )
		target = {};

	// extend jQuery itself if only one argument is passed
	if ( length == i ) {
		target = this;
		--i;
	}

	for ( ; i < length; i++ )
		// Only deal with non-null/undefined values
		if ( (options = arguments[ i ]) != null )
			// Extend the base object
			for ( var name in options ) {
				var src = target[ name ], copy = options[ name ];

				// Prevent never-ending loop
				if ( target === copy )
					continue;

				// Recurse if we're merging object values
				if ( deep && copy && typeof copy === "object" && !copy.nodeType )
					target[ name ] = jQuery.extend( deep, 
						// Never move original objects, clone them
						src || ( copy.length != null ? [ ] : { } )
					, copy );

				// Don't bring in undefined values
				else if ( copy !== undefined )
					target[ name ] = copy;

			}

	// Return the modified object
	return target;
};

// exclude the following css properties to add px
var	exclude = /z-?index|font-?weight|opacity|zoom|line-?height/i,
	// cache defaultView
	defaultView = document.defaultView || {},
	toString = Object.prototype.toString;

jQuery.extend({
	noConflict: function( deep ) {
		window.$ = _$;

		if ( deep )
			window.jQuery = _jQuery;

		return jQuery;
	},

	// See test/unit/core.js for details concerning isFunction.
	// Since version 1.3, DOM methods and functions like alert
	// aren't supported. They return false on IE (#2968).
	isFunction: function( obj ) {
		return toString.call(obj) === "[object Function]";
	},

	isArray: function( obj ) {
		return toString.call(obj) === "[object Array]";
	},

	// check if an element is in a (or is an) XML document
	isXMLDoc: function( elem ) {
		return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
			!!elem.ownerDocument && jQuery.isXMLDoc( elem.ownerDocument );
	},

	// Evalulates a script in a global context
	globalEval: function( data ) {
		if ( data && /\S/.test(data) ) {
			// Inspired by code by Andrea Giammarchi
			// http://webreflection.blogspot.com/2007/08/global-scope-evaluation-and-dom.html
			var head = document.getElementsByTagName("head")[0] || document.documentElement,
				script = document.createElement("script");

			script.type = "text/javascript";
			if ( jQuery.support.scriptEval )
				script.appendChild( document.createTextNode( data ) );
			else
				script.text = data;

			// Use insertBefore instead of appendChild  to circumvent an IE6 bug.
			// This arises when a base node is used (#2709).
			head.insertBefore( script, head.firstChild );
			head.removeChild( script );
		}
	},

	nodeName: function( elem, name ) {
		return elem.nodeName && elem.nodeName.toUpperCase() == name.toUpperCase();
	},

	// args is for internal usage only
	each: function( object, callback, args ) {
		var name, i = 0, length = object.length;

		if ( args ) {
			if ( length === undefined ) {
				for ( name in object )
					if ( callback.apply( object[ name ], args ) === false )
						break;
			} else
				for ( ; i < length; )
					if ( callback.apply( object[ i++ ], args ) === false )
						break;

		// A special, fast, case for the most common use of each
		} else {
			if ( length === undefined ) {
				for ( name in object )
					if ( callback.call( object[ name ], name, object[ name ] ) === false )
						break;
			} else
				for ( var value = object[0];
					i < length && callback.call( value, i, value ) !== false; value = object[++i] ){}
		}

		return object;
	},

	prop: function( elem, value, type, i, name ) {
		// Handle executable functions
		if ( jQuery.isFunction( value ) )
			value = value.call( elem, i );

		// Handle passing in a number to a CSS property
		return typeof value === "number" && type == "curCSS" && !exclude.test( name ) ?
			value + "px" :
			value;
	},

	className: {
		// internal only, use addClass("class")
		add: function( elem, classNames ) {
			jQuery.each((classNames || "").split(/\s+/), function(i, className){
				if ( elem.nodeType == 1 && !jQuery.className.has( elem.className, className ) )
					elem.className += (elem.className ? " " : "") + className;
			});
		},

		// internal only, use removeClass("class")
		remove: function( elem, classNames ) {
			if (elem.nodeType == 1)
				elem.className = classNames !== undefined ?
					jQuery.grep(elem.className.split(/\s+/), function(className){
						return !jQuery.className.has( classNames, className );
					}).join(" ") :
					"";
		},

		// internal only, use hasClass("class")
		has: function( elem, className ) {
			return elem && jQuery.inArray( className, (elem.className || elem).toString().split(/\s+/) ) > -1;
		}
	},

	// A method for quickly swapping in/out CSS properties to get correct calculations
	swap: function( elem, options, callback ) {
		var old = {};
		// Remember the old values, and insert the new ones
		for ( var name in options ) {
			old[ name ] = elem.style[ name ];
			elem.style[ name ] = options[ name ];
		}

		callback.call( elem );

		// Revert the old values
		for ( var name in options )
			elem.style[ name ] = old[ name ];
	},

	css: function( elem, name, force, extra ) {
		if ( name == "width" || name == "height" ) {
			var val, props = { position: "absolute", visibility: "hidden", display:"block" }, which = name == "width" ? [ "Left", "Right" ] : [ "Top", "Bottom" ];

			function getWH() {
				val = name == "width" ? elem.offsetWidth : elem.offsetHeight;

				if ( extra === "border" )
					return;

				jQuery.each( which, function() {
					if ( !extra )
						val -= parseFloat(jQuery.curCSS( elem, "padding" + this, true)) || 0;
					if ( extra === "margin" )
						val += parseFloat(jQuery.curCSS( elem, "margin" + this, true)) || 0;
					else
						val -= parseFloat(jQuery.curCSS( elem, "border" + this + "Width", true)) || 0;
				});
			}

			if ( elem.offsetWidth !== 0 )
				getWH();
			else
				jQuery.swap( elem, props, getWH );

			return Math.max(0, Math.round(val));
		}

		return jQuery.curCSS( elem, name, force );
	},

	curCSS: function( elem, name, force ) {
		var ret, style = elem.style;

		// We need to handle opacity special in IE
		if ( name == "opacity" && !jQuery.support.opacity ) {
			ret = jQuery.attr( style, "opacity" );

			return ret == "" ?
				"1" :
				ret;
		}

		// Make sure we're using the right name for getting the float value
		if ( name.match( /float/i ) )
			name = styleFloat;

		if ( !force && style && style[ name ] )
			ret = style[ name ];

		else if ( defaultView.getComputedStyle ) {

			// Only "float" is needed here
			if ( name.match( /float/i ) )
				name = "float";

			name = name.replace( /([A-Z])/g, "-$1" ).toLowerCase();

			var computedStyle = defaultView.getComputedStyle( elem, null );

			if ( computedStyle )
				ret = computedStyle.getPropertyValue( name );

			// We should always get a number back from opacity
			if ( name == "opacity" && ret == "" )
				ret = "1";

		} else if ( elem.currentStyle ) {
			var camelCase = name.replace(/\-(\w)/g, function(all, letter){
				return letter.toUpperCase();
			});

			ret = elem.currentStyle[ name ] || elem.currentStyle[ camelCase ];

			// From the awesome hack by Dean Edwards
			// http://erik.eae.net/archives/2007/07/27/18.54.15/#comment-102291

			// If we're not dealing with a regular pixel number
			// but a number that has a weird ending, we need to convert it to pixels
			if ( !/^\d+(px)?$/i.test( ret ) && /^\d/.test( ret ) ) {
				// Remember the original values
				var left = style.left, rsLeft = elem.runtimeStyle.left;

				// Put in the new values to get a computed value out
				elem.runtimeStyle.left = elem.currentStyle.left;
				style.left = ret || 0;
				ret = style.pixelLeft + "px";

				// Revert the changed values
				style.left = left;
				elem.runtimeStyle.left = rsLeft;
			}
		}

		return ret;
	},

	clean: function( elems, context, fragment ) {
		context = context || document;

		// !context.createElement fails in IE with an error but returns typeof 'object'
		if ( typeof context.createElement === "undefined" )
			context = context.ownerDocument || context[0] && context[0].ownerDocument || document;

		// If a single string is passed in and it's a single tag
		// just do a createElement and skip the rest
		if ( !fragment && elems.length === 1 && typeof elems[0] === "string" ) {
			var match = /^<(\w+)\s*\/?>$/.exec(elems[0]);
			if ( match )
				return [ context.createElement( match[1] ) ];
		}

		var ret = [], scripts = [], div = context.createElement("div");

		jQuery.each(elems, function(i, elem){
			if ( typeof elem === "number" )
				elem += '';

			if ( !elem )
				return;

			// Convert html string into DOM nodes
			if ( typeof elem === "string" ) {
				// Fix "XHTML"-style tags in all browsers
				elem = elem.replace(/(<(\w+)[^>]*?)\/>/g, function(all, front, tag){
					return tag.match(/^(abbr|br|col|img|input|link|meta|param|hr|area|embed)$/i) ?
						all :
						front + "></" + tag + ">";
				});

				// Trim whitespace, otherwise indexOf won't work as expected
				var tags = elem.replace(/^\s+/, "").substring(0, 10).toLowerCase();

				var wrap =
					// option or optgroup
					!tags.indexOf("<opt") &&
					[ 1, "<select multiple='multiple'>", "</select>" ] ||

					!tags.indexOf("<leg") &&
					[ 1, "<fieldset>", "</fieldset>" ] ||

					tags.match(/^<(thead|tbody|tfoot|colg|cap)/) &&
					[ 1, "<table>", "</table>" ] ||

					!tags.indexOf("<tr") &&
					[ 2, "<table><tbody>", "</tbody></table>" ] ||

				 	// <thead> matched above
					(!tags.indexOf("<td") || !tags.indexOf("<th")) &&
					[ 3, "<table><tbody><tr>", "</tr></tbody></table>" ] ||

					!tags.indexOf("<col") &&
					[ 2, "<table><tbody></tbody><colgroup>", "</colgroup></table>" ] ||

					// IE can't serialize <link> and <script> tags normally
					!jQuery.support.htmlSerialize &&
					[ 1, "div<div>", "</div>" ] ||

					[ 0, "", "" ];

				// Go to html and back, then peel off extra wrappers
				div.innerHTML = wrap[1] + elem + wrap[2];

				// Move to the right depth
				while ( wrap[0]-- )
					div = div.lastChild;

				// Remove IE's autoinserted <tbody> from table fragments
				if ( !jQuery.support.tbody ) {

					// String was a <table>, *may* have spurious <tbody>
					var hasBody = /<tbody/i.test(elem),
						tbody = !tags.indexOf("<table") && !hasBody ?
							div.firstChild && div.firstChild.childNodes :

						// String was a bare <thead> or <tfoot>
						wrap[1] == "<table>" && !hasBody ?
							div.childNodes :
							[];

					for ( var j = tbody.length - 1; j >= 0 ; --j )
						if ( jQuery.nodeName( tbody[ j ], "tbody" ) && !tbody[ j ].childNodes.length )
							tbody[ j ].parentNode.removeChild( tbody[ j ] );

					}

				// IE completely kills leading whitespace when innerHTML is used
				if ( !jQuery.support.leadingWhitespace && /^\s/.test( elem ) )
					div.insertBefore( context.createTextNode( elem.match(/^\s*/)[0] ), div.firstChild );
				
				elem = jQuery.makeArray( div.childNodes );
			}

			if ( elem.nodeType )
				ret.push( elem );
			else
				ret = jQuery.merge( ret, elem );

		});

		if ( fragment ) {
			for ( var i = 0; ret[i]; i++ ) {
				if ( jQuery.nodeName( ret[i], "script" ) && (!ret[i].type || ret[i].type.toLowerCase() === "text/javascript") ) {
					scripts.push( ret[i].parentNode ? ret[i].parentNode.removeChild( ret[i] ) : ret[i] );
				} else {
					if ( ret[i].nodeType === 1 )
						ret.splice.apply( ret, [i + 1, 0].concat(jQuery.makeArray(ret[i].getElementsByTagName("script"))) );
					fragment.appendChild( ret[i] );
				}
			}
			
			return scripts;
		}

		return ret;
	},

	attr: function( elem, name, value ) {
		// don't set attributes on text and comment nodes
		if (!elem || elem.nodeType == 3 || elem.nodeType == 8)
			return undefined;

		var notxml = !jQuery.isXMLDoc( elem ),
			// Whether we are setting (or getting)
			set = value !== undefined;

		// Try to normalize/fix the name
		name = notxml && jQuery.props[ name ] || name;

		// Only do all the following if this is a node (faster for style)
		// IE elem.getAttribute passes even for style
		if ( elem.tagName ) {

			// These attributes require special treatment
			var special = /href|src|style/.test( name );

			// Safari mis-reports the default selected property of a hidden option
			// Accessing the parent's selectedIndex property fixes it
			if ( name == "selected" && elem.parentNode )
				elem.parentNode.selectedIndex;

			// If applicable, access the attribute via the DOM 0 way
			if ( name in elem && notxml && !special ) {
				if ( set ){
					// We can't allow the type property to be changed (since it causes problems in IE)
					if ( name == "type" && jQuery.nodeName( elem, "input" ) && elem.parentNode )
						throw "type property can't be changed";

					elem[ name ] = value;
				}

				// browsers index elements by id/name on forms, give priority to attributes.
				if( jQuery.nodeName( elem, "form" ) && elem.getAttributeNode(name) )
					return elem.getAttributeNode( name ).nodeValue;

				// elem.tabIndex doesn't always return the correct value when it hasn't been explicitly set
				// http://fluidproject.org/blog/2008/01/09/getting-setting-and-removing-tabindex-values-with-javascript/
				if ( name == "tabIndex" ) {
					var attributeNode = elem.getAttributeNode( "tabIndex" );
					return attributeNode && attributeNode.specified
						? attributeNode.value
						: elem.nodeName.match(/(button|input|object|select|textarea)/i)
							? 0
							: elem.nodeName.match(/^(a|area)$/i) && elem.href
								? 0
								: undefined;
				}

				return elem[ name ];
			}

			if ( !jQuery.support.style && notxml &&  name == "style" )
				return jQuery.attr( elem.style, "cssText", value );

			if ( set )
				// convert the value to a string (all browsers do this but IE) see #1070
				elem.setAttribute( name, "" + value );

			var attr = !jQuery.support.hrefNormalized && notxml && special
					// Some attributes require a special call on IE
					? elem.getAttribute( name, 2 )
					: elem.getAttribute( name );

			// Non-existent attributes return null, we normalize to undefined
			return attr === null ? undefined : attr;
		}

		// elem is actually elem.style ... set the style

		// IE uses filters for opacity
		if ( !jQuery.support.opacity && name == "opacity" ) {
			if ( set ) {
				// IE has trouble with opacity if it does not have layout
				// Force it by setting the zoom level
				elem.zoom = 1;

				// Set the alpha filter to set the opacity
				elem.filter = (elem.filter || "").replace( /alpha\([^)]*\)/, "" ) +
					(parseInt( value ) + '' == "NaN" ? "" : "alpha(opacity=" + value * 100 + ")");
			}

			return elem.filter && elem.filter.indexOf("opacity=") >= 0 ?
				(parseFloat( elem.filter.match(/opacity=([^)]*)/)[1] ) / 100) + '':
				"";
		}

		name = name.replace(/-([a-z])/ig, function(all, letter){
			return letter.toUpperCase();
		});

		if ( set )
			elem[ name ] = value;

		return elem[ name ];
	},

	trim: function( text ) {
		return (text || "").replace( /^\s+|\s+$/g, "" );
	},

	makeArray: function( array ) {
		var ret = [];

		if( array != null ){
			var i = array.length;
			// The window, strings (and functions) also have 'length'
			if( i == null || typeof array === "string" || jQuery.isFunction(array) || array.setInterval )
				ret[0] = array;
			else
				while( i )
					ret[--i] = array[i];
		}

		return ret;
	},

	inArray: function( elem, array ) {
		for ( var i = 0, length = array.length; i < length; i++ )
		// Use === because on IE, window == document
			if ( array[ i ] === elem )
				return i;

		return -1;
	},

	merge: function( first, second ) {
		// We have to loop this way because IE & Opera overwrite the length
		// expando of getElementsByTagName
		var i = 0, elem, pos = first.length;
		// Also, we need to make sure that the correct elements are being returned
		// (IE returns comment nodes in a '*' query)
		if ( !jQuery.support.getAll ) {
			while ( (elem = second[ i++ ]) != null )
				if ( elem.nodeType != 8 )
					first[ pos++ ] = elem;

		} else
			while ( (elem = second[ i++ ]) != null )
				first[ pos++ ] = elem;

		return first;
	},

	unique: function( array ) {
		var ret = [], done = {};

		try {

			for ( var i = 0, length = array.length; i < length; i++ ) {
				var id = jQuery.data( array[ i ] );

				if ( !done[ id ] ) {
					done[ id ] = true;
					ret.push( array[ i ] );
				}
			}

		} catch( e ) {
			ret = array;
		}

		return ret;
	},

	grep: function( elems, callback, inv ) {
		var ret = [];

		// Go through the array, only saving the items
		// that pass the validator function
		for ( var i = 0, length = elems.length; i < length; i++ )
			if ( !inv != !callback( elems[ i ], i ) )
				ret.push( elems[ i ] );

		return ret;
	},

	map: function( elems, callback ) {
		var ret = [];

		// Go through the array, translating each of the items to their
		// new value (or values).
		for ( var i = 0, length = elems.length; i < length; i++ ) {
			var value = callback( elems[ i ], i );

			if ( value != null )
				ret[ ret.length ] = value;
		}

		return ret.concat.apply( [], ret );
	}
});

// Use of jQuery.browser is deprecated.
// It's included for backwards compatibility and plugins,
// although they should work to migrate away.

var userAgent = navigator.userAgent.toLowerCase();

// Figure out what browser is being used
jQuery.browser = {
	version: (userAgent.match( /.+(?:rv|it|ra|ie)[\/: ]([\d.]+)/ ) || [0,'0'])[1],
	safari: /webkit/.test( userAgent ),
	opera: /opera/.test( userAgent ),
	msie: /msie/.test( userAgent ) && !/opera/.test( userAgent ),
	mozilla: /mozilla/.test( userAgent ) && !/(compatible|webkit)/.test( userAgent )
};

jQuery.each({
	parent: function(elem){return elem.parentNode;},
	parents: function(elem){return jQuery.dir(elem,"parentNode");},
	next: function(elem){return jQuery.nth(elem,2,"nextSibling");},
	prev: function(elem){return jQuery.nth(elem,2,"previousSibling");},
	nextAll: function(elem){return jQuery.dir(elem,"nextSibling");},
	prevAll: function(elem){return jQuery.dir(elem,"previousSibling");},
	siblings: function(elem){return jQuery.sibling(elem.parentNode.firstChild,elem);},
	children: function(elem){return jQuery.sibling(elem.firstChild);},
	contents: function(elem){return jQuery.nodeName(elem,"iframe")?elem.contentDocument||elem.contentWindow.document:jQuery.makeArray(elem.childNodes);}
}, function(name, fn){
	jQuery.fn[ name ] = function( selector ) {
		var ret = jQuery.map( this, fn );

		if ( selector && typeof selector == "string" )
			ret = jQuery.multiFilter( selector, ret );

		return this.pushStack( jQuery.unique( ret ), name, selector );
	};
});

jQuery.each({
	appendTo: "append",
	prependTo: "prepend",
	insertBefore: "before",
	insertAfter: "after",
	replaceAll: "replaceWith"
}, function(name, original){
	jQuery.fn[ name ] = function( selector ) {
		var ret = [], insert = jQuery( selector );

		for ( var i = 0, l = insert.length; i < l; i++ ) {
			var elems = (i > 0 ? this.clone(true) : this).get();
			jQuery.fn[ original ].apply( jQuery(insert[i]), elems );
			ret = ret.concat( elems );
		}

		return this.pushStack( ret, name, selector );
	};
});

jQuery.each({
	removeAttr: function( name ) {
		jQuery.attr( this, name, "" );
		if (this.nodeType == 1)
			this.removeAttribute( name );
	},

	addClass: function( classNames ) {
		jQuery.className.add( this, classNames );
	},

	removeClass: function( classNames ) {
		jQuery.className.remove( this, classNames );
	},

	toggleClass: function( classNames, state ) {
		if( typeof state !== "boolean" )
			state = !jQuery.className.has( this, classNames );
		jQuery.className[ state ? "add" : "remove" ]( this, classNames );
	},

	remove: function( selector ) {
		if ( !selector || jQuery.filter( selector, [ this ] ).length ) {
			// Prevent memory leaks
			jQuery( "*", this ).add([this]).each(function(){
				jQuery.event.remove(this);
				jQuery.removeData(this);
			});
			if (this.parentNode)
				this.parentNode.removeChild( this );
		}
	},

	empty: function() {
		// Remove element nodes and prevent memory leaks
		jQuery(this).children().remove();

		// Remove any remaining nodes
		while ( this.firstChild )
			this.removeChild( this.firstChild );
	}
}, function(name, fn){
	jQuery.fn[ name ] = function(){
		return this.each( fn, arguments );
	};
});

// Helper function used by the dimensions and offset modules
function num(elem, prop) {
	return elem[0] && parseInt( jQuery.curCSS(elem[0], prop, true), 10 ) || 0;
}
var expando = "jQuery" + now(), uuid = 0, windowData = {};

jQuery.extend({
	cache: {},

	data: function( elem, name, data ) {
		elem = elem == window ?
			windowData :
			elem;

		var id = elem[ expando ];

		// Compute a unique ID for the element
		if ( !id )
			id = elem[ expando ] = ++uuid;

		// Only generate the data cache if we're
		// trying to access or manipulate it
		if ( name && !jQuery.cache[ id ] )
			jQuery.cache[ id ] = {};

		// Prevent overriding the named cache with undefined values
		if ( data !== undefined )
			jQuery.cache[ id ][ name ] = data;

		// Return the named cache data, or the ID for the element
		return name ?
			jQuery.cache[ id ][ name ] :
			id;
	},

	removeData: function( elem, name ) {
		elem = elem == window ?
			windowData :
			elem;

		var id = elem[ expando ];

		// If we want to remove a specific section of the element's data
		if ( name ) {
			if ( jQuery.cache[ id ] ) {
				// Remove the section of cache data
				delete jQuery.cache[ id ][ name ];

				// If we've removed all the data, remove the element's cache
				name = "";

				for ( name in jQuery.cache[ id ] )
					break;

				if ( !name )
					jQuery.removeData( elem );
			}

		// Otherwise, we want to remove all of the element's data
		} else {
			// Clean up the element expando
			try {
				delete elem[ expando ];
			} catch(e){
				// IE has trouble directly removing the expando
				// but it's ok with using removeAttribute
				if ( elem.removeAttribute )
					elem.removeAttribute( expando );
			}

			// Completely remove the data cache
			delete jQuery.cache[ id ];
		}
	},
	queue: function( elem, type, data ) {
		if ( elem ){
	
			type = (type || "fx") + "queue";
	
			var q = jQuery.data( elem, type );
	
			if ( !q || jQuery.isArray(data) )
				q = jQuery.data( elem, type, jQuery.makeArray(data) );
			else if( data )
				q.push( data );
	
		}
		return q;
	},

	dequeue: function( elem, type ){
		var queue = jQuery.queue( elem, type ),
			fn = queue.shift();
		
		if( !type || type === "fx" )
			fn = queue[0];
			
		if( fn !== undefined )
			fn.call(elem);
	}
});

jQuery.fn.extend({
	data: function( key, value ){
		var parts = key.split(".");
		parts[1] = parts[1] ? "." + parts[1] : "";

		if ( value === undefined ) {
			var data = this.triggerHandler("getData" + parts[1] + "!", [parts[0]]);

			if ( data === undefined && this.length )
				data = jQuery.data( this[0], key );

			return data === undefined && parts[1] ?
				this.data( parts[0] ) :
				data;
		} else
			return this.trigger("setData" + parts[1] + "!", [parts[0], value]).each(function(){
				jQuery.data( this, key, value );
			});
	},

	removeData: function( key ){
		return this.each(function(){
			jQuery.removeData( this, key );
		});
	},
	queue: function(type, data){
		if ( typeof type !== "string" ) {
			data = type;
			type = "fx";
		}

		if ( data === undefined )
			return jQuery.queue( this[0], type );

		return this.each(function(){
			var queue = jQuery.queue( this, type, data );
			
			 if( type == "fx" && queue.length == 1 )
				queue[0].call(this);
		});
	},
	dequeue: function(type){
		return this.each(function(){
			jQuery.dequeue( this, type );
		});
	}
});/*!
 * Sizzle CSS Selector Engine - v0.9.3
 *  Copyright 2009, The Dojo Foundation
 *  Released under the MIT, BSD, and GPL Licenses.
 *  More information: http://sizzlejs.com/
 */
(function(){

var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?/g,
	done = 0,
	toString = Object.prototype.toString;

var Sizzle = function(selector, context, results, seed) {
	results = results || [];
	context = context || document;

	if ( context.nodeType !== 1 && context.nodeType !== 9 )
		return [];
	
	if ( !selector || typeof selector !== "string" ) {
		return results;
	}

	var parts = [], m, set, checkSet, check, mode, extra, prune = true;
	
	// Reset the position of the chunker regexp (start from head)
	chunker.lastIndex = 0;
	
	while ( (m = chunker.exec(selector)) !== null ) {
		parts.push( m[1] );
		
		if ( m[2] ) {
			extra = RegExp.rightContext;
			break;
		}
	}

	if ( parts.length > 1 && origPOS.exec( selector ) ) {
		if ( parts.length === 2 && Expr.relative[ parts[0] ] ) {
			set = posProcess( parts[0] + parts[1], context );
		} else {
			set = Expr.relative[ parts[0] ] ?
				[ context ] :
				Sizzle( parts.shift(), context );

			while ( parts.length ) {
				selector = parts.shift();

				if ( Expr.relative[ selector ] )
					selector += parts.shift();

				set = posProcess( selector, set );
			}
		}
	} else {
		var ret = seed ?
			{ expr: parts.pop(), set: makeArray(seed) } :
			Sizzle.find( parts.pop(), parts.length === 1 && context.parentNode ? context.parentNode : context, isXML(context) );
		set = Sizzle.filter( ret.expr, ret.set );

		if ( parts.length > 0 ) {
			checkSet = makeArray(set);
		} else {
			prune = false;
		}

		while ( parts.length ) {
			var cur = parts.pop(), pop = cur;

			if ( !Expr.relative[ cur ] ) {
				cur = "";
			} else {
				pop = parts.pop();
			}

			if ( pop == null ) {
				pop = context;
			}

			Expr.relative[ cur ]( checkSet, pop, isXML(context) );
		}
	}

	if ( !checkSet ) {
		checkSet = set;
	}

	if ( !checkSet ) {
		throw "Syntax error, unrecognized expression: " + (cur || selector);
	}

	if ( toString.call(checkSet) === "[object Array]" ) {
		if ( !prune ) {
			results.push.apply( results, checkSet );
		} else if ( context.nodeType === 1 ) {
			for ( var i = 0; checkSet[i] != null; i++ ) {
				if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) {
					results.push( set[i] );
				}
			}
		} else {
			for ( var i = 0; checkSet[i] != null; i++ ) {
				if ( checkSet[i] && checkSet[i].nodeType === 1 ) {
					results.push( set[i] );
				}
			}
		}
	} else {
		makeArray( checkSet, results );
	}

	if ( extra ) {
		Sizzle( extra, context, results, seed );

		if ( sortOrder ) {
			hasDuplicate = false;
			results.sort(sortOrder);

			if ( hasDuplicate ) {
				for ( var i = 1; i < results.length; i++ ) {
					if ( results[i] === results[i-1] ) {
						results.splice(i--, 1);
					}
				}
			}
		}
	}

	return results;
};

Sizzle.matches = function(expr, set){
	return Sizzle(expr, null, null, set);
};

Sizzle.find = function(expr, context, isXML){
	var set, match;

	if ( !expr ) {
		return [];
	}

	for ( var i = 0, l = Expr.order.length; i < l; i++ ) {
		var type = Expr.order[i], match;
		
		if ( (match = Expr.match[ type ].exec( expr )) ) {
			var left = RegExp.leftContext;

			if ( left.substr( left.length - 1 ) !== "\\" ) {
				match[1] = (match[1] || "").replace(/\\/g, "");
				set = Expr.find[ type ]( match, context, isXML );
				if ( set != null ) {
					expr = expr.replace( Expr.match[ type ], "" );
					break;
				}
			}
		}
	}

	if ( !set ) {
		set = context.getElementsByTagName("*");
	}

	return {set: set, expr: expr};
};

Sizzle.filter = function(expr, set, inplace, not){
	var old = expr, result = [], curLoop = set, match, anyFound,
		isXMLFilter = set && set[0] && isXML(set[0]);

	while ( expr && set.length ) {
		for ( var type in Expr.filter ) {
			if ( (match = Expr.match[ type ].exec( expr )) != null ) {
				var filter = Expr.filter[ type ], found, item;
				anyFound = false;

				if ( curLoop == result ) {
					result = [];
				}

				if ( Expr.preFilter[ type ] ) {
					match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter );

					if ( !match ) {
						anyFound = found = true;
					} else if ( match === true ) {
						continue;
					}
				}

				if ( match ) {
					for ( var i = 0; (item = curLoop[i]) != null; i++ ) {
						if ( item ) {
							found = filter( item, match, i, curLoop );
							var pass = not ^ !!found;

							if ( inplace && found != null ) {
								if ( pass ) {
									anyFound = true;
								} else {
									curLoop[i] = false;
								}
							} else if ( pass ) {
								result.push( item );
								anyFound = true;
							}
						}
					}
				}

				if ( found !== undefined ) {
					if ( !inplace ) {
						curLoop = result;
					}

					expr = expr.replace( Expr.match[ type ], "" );

					if ( !anyFound ) {
						return [];
					}

					break;
				}
			}
		}

		// Improper expression
		if ( expr == old ) {
			if ( anyFound == null ) {
				throw "Syntax error, unrecognized expression: " + expr;
			} else {
				break;
			}
		}

		old = expr;
	}

	return curLoop;
};

var Expr = Sizzle.selectors = {
	order: [ "ID", "NAME", "TAG" ],
	match: {
		ID: /#((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
		CLASS: /\.((?:[\w\u00c0-\uFFFF_-]|\\.)+)/,
		NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF_-]|\\.)+)['"]*\]/,
		ATTR: /\[\s*((?:[\w\u00c0-\uFFFF_-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/,
		TAG: /^((?:[\w\u00c0-\uFFFF\*_-]|\\.)+)/,
		CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/,
		POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/,
		PSEUDO: /:((?:[\w\u00c0-\uFFFF_-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/
	},
	attrMap: {
		"class": "className",
		"for": "htmlFor"
	},
	attrHandle: {
		href: function(elem){
			return elem.getAttribute("href");
		}
	},
	relative: {
		"+": function(checkSet, part, isXML){
			var isPartStr = typeof part === "string",
				isTag = isPartStr && !/\W/.test(part),
				isPartStrNotTag = isPartStr && !isTag;

			if ( isTag && !isXML ) {
				part = part.toUpperCase();
			}

			for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) {
				if ( (elem = checkSet[i]) ) {
					while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {}

					checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ?
						elem || false :
						elem === part;
				}
			}

			if ( isPartStrNotTag ) {
				Sizzle.filter( part, checkSet, true );
			}
		},
		">": function(checkSet, part, isXML){
			var isPartStr = typeof part === "string";

			if ( isPartStr && !/\W/.test(part) ) {
				part = isXML ? part : part.toUpperCase();

				for ( var i = 0, l = checkSet.length; i < l; i++ ) {
					var elem = checkSet[i];
					if ( elem ) {
						var parent = elem.parentNode;
						checkSet[i] = parent.nodeName === part ? parent : false;
					}
				}
			} else {
				for ( var i = 0, l = checkSet.length; i < l; i++ ) {
					var elem = checkSet[i];
					if ( elem ) {
						checkSet[i] = isPartStr ?
							elem.parentNode :
							elem.parentNode === part;
					}
				}

				if ( isPartStr ) {
					Sizzle.filter( part, checkSet, true );
				}
			}
		},
		"": function(checkSet, part, isXML){
			var doneName = done++, checkFn = dirCheck;

			if ( !part.match(/\W/) ) {
				var nodeCheck = part = isXML ? part : part.toUpperCase();
				checkFn = dirNodeCheck;
			}

			checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML);
		},
		"~": function(checkSet, part, isXML){
			var doneName = done++, checkFn = dirCheck;

			if ( typeof part === "string" && !part.match(/\W/) ) {
				var nodeCheck = part = isXML ? part : part.toUpperCase();
				checkFn = dirNodeCheck;
			}

			checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML);
		}
	},
	find: {
		ID: function(match, context, isXML){
			if ( typeof context.getElementById !== "undefined" && !isXML ) {
				var m = context.getElementById(match[1]);
				return m ? [m] : [];
			}
		},
		NAME: function(match, context, isXML){
			if ( typeof context.getElementsByName !== "undefined" ) {
				var ret = [], results = context.getElementsByName(match[1]);

				for ( var i = 0, l = results.length; i < l; i++ ) {
					if ( results[i].getAttribute("name") === match[1] ) {
						ret.push( results[i] );
					}
				}

				return ret.length === 0 ? null : ret;
			}
		},
		TAG: function(match, context){
			return context.getElementsByTagName(match[1]);
		}
	},
	preFilter: {
		CLASS: function(match, curLoop, inplace, result, not, isXML){
			match = " " + match[1].replace(/\\/g, "") + " ";

			if ( isXML ) {
				return match;
			}

			for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) {
				if ( elem ) {
					if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) {
						if ( !inplace )
							result.push( elem );
					} else if ( inplace ) {
						curLoop[i] = false;
					}
				}
			}

			return false;
		},
		ID: function(match){
			return match[1].replace(/\\/g, "");
		},
		TAG: function(match, curLoop){
			for ( var i = 0; curLoop[i] === false; i++ ){}
			return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase();
		},
		CHILD: function(match){
			if ( match[1] == "nth" ) {
				// parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6'
				var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec(
					match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" ||
					!/\D/.test( match[2] ) && "0n+" + match[2] || match[2]);

				// calculate the numbers (first)n+(last) including if they are negative
				match[2] = (test[1] + (test[2] || 1)) - 0;
				match[3] = test[3] - 0;
			}

			// TODO: Move to normal caching system
			match[0] = done++;

			return match;
		},
		ATTR: function(match, curLoop, inplace, result, not, isXML){
			var name = match[1].replace(/\\/g, "");
			
			if ( !isXML && Expr.attrMap[name] ) {
				match[1] = Expr.attrMap[name];
			}

			if ( match[2] === "~=" ) {
				match[4] = " " + match[4] + " ";
			}

			return match;
		},
		PSEUDO: function(match, curLoop, inplace, result, not){
			if ( match[1] === "not" ) {
				// If we're dealing with a complex expression, or a simple one
				if ( match[3].match(chunker).length > 1 || /^\w/.test(match[3]) ) {
					match[3] = Sizzle(match[3], null, null, curLoop);
				} else {
					var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not);
					if ( !inplace ) {
						result.push.apply( result, ret );
					}
					return false;
				}
			} else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) {
				return true;
			}
			
			return match;
		},
		POS: function(match){
			match.unshift( true );
			return match;
		}
	},
	filters: {
		enabled: function(elem){
			return elem.disabled === false && elem.type !== "hidden";
		},
		disabled: function(elem){
			return elem.disabled === true;
		},
		checked: function(elem){
			return elem.checked === true;
		},
		selected: function(elem){
			// Accessing this property makes selected-by-default
			// options in Safari work properly
			elem.parentNode.selectedIndex;
			return elem.selected === true;
		},
		parent: function(elem){
			return !!elem.firstChild;
		},
		empty: function(elem){
			return !elem.firstChild;
		},
		has: function(elem, i, match){
			return !!Sizzle( match[3], elem ).length;
		},
		header: function(elem){
			return /h\d/i.test( elem.nodeName );
		},
		text: function(elem){
			return "text" === elem.type;
		},
		radio: function(elem){
			return "radio" === elem.type;
		},
		checkbox: function(elem){
			return "checkbox" === elem.type;
		},
		file: function(elem){
			return "file" === elem.type;
		},
		password: function(elem){
			return "password" === elem.type;
		},
		submit: function(elem){
			return "submit" === elem.type;
		},
		image: function(elem){
			return "image" === elem.type;
		},
		reset: function(elem){
			return "reset" === elem.type;
		},
		button: function(elem){
			return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON";
		},
		input: function(elem){
			return /input|select|textarea|button/i.test(elem.nodeName);
		}
	},
	setFilters: {
		first: function(elem, i){
			return i === 0;
		},
		last: function(elem, i, match, array){
			return i === array.length - 1;
		},
		even: function(elem, i){
			return i % 2 === 0;
		},
		odd: function(elem, i){
			return i % 2 === 1;
		},
		lt: function(elem, i, match){
			return i < match[3] - 0;
		},
		gt: function(elem, i, match){
			return i > match[3] - 0;
		},
		nth: function(elem, i, match){
			return match[3] - 0 == i;
		},
		eq: function(elem, i, match){
			return match[3] - 0 == i;
		}
	},
	filter: {
		PSEUDO: function(elem, match, i, array){
			var name = match[1], filter = Expr.filters[ name ];

			if ( filter ) {
				return filter( elem, i, match, array );
			} else if ( name === "contains" ) {
				return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0;
			} else if ( name === "not" ) {
				var not = match[3];

				for ( var i = 0, l = not.length; i < l; i++ ) {
					if ( not[i] === elem ) {
						return false;
					}
				}

				return true;
			}
		},
		CHILD: function(elem, match){
			var type = match[1], node = elem;
			switch (type) {
				case 'only':
				case 'first':
					while (node = node.previousSibling)  {
						if ( node.nodeType === 1 ) return false;
					}
					if ( type == 'first') return true;
					node = elem;
				case 'last':
					while (node = node.nextSibling)  {
						if ( node.nodeType === 1 ) return false;
					}
					return true;
				case 'nth':
					var first = match[2], last = match[3];

					if ( first == 1 && last == 0 ) {
						return true;
					}
					
					var doneName = match[0],
						parent = elem.parentNode;
	
					if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) {
						var count = 0;
						for ( node = parent.firstChild; node; node = node.nextSibling ) {
							if ( node.nodeType === 1 ) {
								node.nodeIndex = ++count;
							}
						} 
						parent.sizcache = doneName;
					}
					
					var diff = elem.nodeIndex - last;
					if ( first == 0 ) {
						return diff == 0;
					} else {
						return ( diff % first == 0 && diff / first >= 0 );
					}
			}
		},
		ID: function(elem, match){
			return elem.nodeType === 1 && elem.getAttribute("id") === match;
		},
		TAG: function(elem, match){
			return (match === "*" && elem.nodeType === 1) || elem.nodeName === match;
		},
		CLASS: function(elem, match){
			return (" " + (elem.className || elem.getAttribute("class")) + " ")
				.indexOf( match ) > -1;
		},
		ATTR: function(elem, match){
			var name = match[1],
				result = Expr.attrHandle[ name ] ?
					Expr.attrHandle[ name ]( elem ) :
					elem[ name ] != null ?
						elem[ name ] :
						elem.getAttribute( name ),
				value = result + "",
				type = match[2],
				check = match[4];

			return result == null ?
				type === "!=" :
				type === "=" ?
				value === check :
				type === "*=" ?
				value.indexOf(check) >= 0 :
				type === "~=" ?
				(" " + value + " ").indexOf(check) >= 0 :
				!check ?
				value && result !== false :
				type === "!=" ?
				value != check :
				type === "^=" ?
				value.indexOf(check) === 0 :
				type === "$=" ?
				value.substr(value.length - check.length) === check :
				type === "|=" ?
				value === check || value.substr(0, check.length + 1) === check + "-" :
				false;
		},
		POS: function(elem, match, i, array){
			var name = match[2], filter = Expr.setFilters[ name ];

			if ( filter ) {
				return filter( elem, i, match, array );
			}
		}
	}
};

var origPOS = Expr.match.POS;

for ( var type in Expr.match ) {
	Expr.match[ type ] = RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source );
}

var makeArray = function(array, results) {
	array = Array.prototype.slice.call( array );

	if ( results ) {
		results.push.apply( results, array );
		return results;
	}
	
	return array;
};

// Perform a simple check to determine if the browser is capable of
// converting a NodeList to an array using builtin methods.
try {
	Array.prototype.slice.call( document.documentElement.childNodes );

// Provide a fallback method if it does not work
} catch(e){
	makeArray = function(array, results) {
		var ret = results || [];

		if ( toString.call(array) === "[object Array]" ) {
			Array.prototype.push.apply( ret, array );
		} else {
			if ( typeof array.length === "number" ) {
				for ( var i = 0, l = array.length; i < l; i++ ) {
					ret.push( array[i] );
				}
			} else {
				for ( var i = 0; array[i]; i++ ) {
					ret.push( array[i] );
				}
			}
		}

		return ret;
	};
}

var sortOrder;

if ( document.documentElement.compareDocumentPosition ) {
	sortOrder = function( a, b ) {
		var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1;
		if ( ret === 0 ) {
			hasDuplicate = true;
		}
		return ret;
	};
} else if ( "sourceIndex" in document.documentElement ) {
	sortOrder = function( a, b ) {
		var ret = a.sourceIndex - b.sourceIndex;
		if ( ret === 0 ) {
			hasDuplicate = true;
		}
		return ret;
	};
} else if ( document.createRange ) {
	sortOrder = function( a, b ) {
		var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange();
		aRange.selectNode(a);
		aRange.collapse(true);
		bRange.selectNode(b);
		bRange.collapse(true);
		var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange);
		if ( ret === 0 ) {
			hasDuplicate = true;
		}
		return ret;
	};
}

// Check to see if the browser returns elements by name when
// querying by getElementById (and provide a workaround)
(function(){
	// We're going to inject a fake input element with a specified name
	var form = document.createElement("form"),
		id = "script" + (new Date).getTime();
	form.innerHTML = "<input name='" + id + "'/>";

	// Inject it into the root element, check its status, and remove it quickly
	var root = document.documentElement;
	root.insertBefore( form, root.firstChild );

	// The workaround has to do additional checks after a getElementById
	// Which slows things down for other browsers (hence the branching)
	if ( !!document.getElementById( id ) ) {
		Expr.find.ID = function(match, context, isXML){
			if ( typeof context.getElementById !== "undefined" && !isXML ) {
				var m = context.getElementById(match[1]);
				return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : [];
			}
		};

		Expr.filter.ID = function(elem, match){
			var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id");
			return elem.nodeType === 1 && node && node.nodeValue === match;
		};
	}

	root.removeChild( form );
})();

(function(){
	// Check to see if the browser returns only elements
	// when doing getElementsByTagName("*")

	// Create a fake element
	var div = document.createElement("div");
	div.appendChild( document.createComment("") );

	// Make sure no comments are found
	if ( div.getElementsByTagName("*").length > 0 ) {
		Expr.find.TAG = function(match, context){
			var results = context.getElementsByTagName(match[1]);

			// Filter out possible comments
			if ( match[1] === "*" ) {
				var tmp = [];

				for ( var i = 0; results[i]; i++ ) {
					if ( results[i].nodeType === 1 ) {
						tmp.push( results[i] );
					}
				}

				results = tmp;
			}

			return results;
		};
	}

	// Check to see if an attribute returns normalized href attributes
	div.innerHTML = "<a href='#'></a>";
	if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" &&
			div.firstChild.getAttribute("href") !== "#" ) {
		Expr.attrHandle.href = function(elem){
			return elem.getAttribute("href", 2);
		};
	}
})();

if ( document.querySelectorAll ) (function(){
	var oldSizzle = Sizzle, div = document.createElement("div");
	div.innerHTML = "<p class='TEST'></p>";

	// Safari can't handle uppercase or unicode characters when
	// in quirks mode.
	if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) {
		return;
	}
	
	Sizzle = function(query, context, extra, seed){
		context = context || document;

		// Only use querySelectorAll on non-XML documents
		// (ID selectors don't work in non-HTML documents)
		if ( !seed && context.nodeType === 9 && !isXML(context) ) {
			try {
				return makeArray( context.querySelectorAll(query), extra );
			} catch(e){}
		}
		
		return oldSizzle(query, context, extra, seed);
	};

	Sizzle.find = oldSizzle.find;
	Sizzle.filter = oldSizzle.filter;
	Sizzle.selectors = oldSizzle.selectors;
	Sizzle.matches = oldSizzle.matches;
})();

if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){
	var div = document.createElement("div");
	div.innerHTML = "<div class='test e'></div><div class='test'></div>";

	// Opera can't find a second classname (in 9.6)
	if ( div.getElementsByClassName("e").length === 0 )
		return;

	// Safari caches class attributes, doesn't catch changes (in 3.2)
	div.lastChild.className = "e";

	if ( div.getElementsByClassName("e").length === 1 )
		return;

	Expr.order.splice(1, 0, "CLASS");
	Expr.find.CLASS = function(match, context, isXML) {
		if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) {
			return context.getElementsByClassName(match[1]);
		}
	};
})();

function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
	var sibDir = dir == "previousSibling" && !isXML;
	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
		var elem = checkSet[i];
		if ( elem ) {
			if ( sibDir && elem.nodeType === 1 ){
				elem.sizcache = doneName;
				elem.sizset = i;
			}
			elem = elem[dir];
			var match = false;

			while ( elem ) {
				if ( elem.sizcache === doneName ) {
					match = checkSet[elem.sizset];
					break;
				}

				if ( elem.nodeType === 1 && !isXML ){
					elem.sizcache = doneName;
					elem.sizset = i;
				}

				if ( elem.nodeName === cur ) {
					match = elem;
					break;
				}

				elem = elem[dir];
			}

			checkSet[i] = match;
		}
	}
}

function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) {
	var sibDir = dir == "previousSibling" && !isXML;
	for ( var i = 0, l = checkSet.length; i < l; i++ ) {
		var elem = checkSet[i];
		if ( elem ) {
			if ( sibDir && elem.nodeType === 1 ) {
				elem.sizcache = doneName;
				elem.sizset = i;
			}
			elem = elem[dir];
			var match = false;

			while ( elem ) {
				if ( elem.sizcache === doneName ) {
					match = checkSet[elem.sizset];
					break;
				}

				if ( elem.nodeType === 1 ) {
					if ( !isXML ) {
						elem.sizcache = doneName;
						elem.sizset = i;
					}
					if ( typeof cur !== "string" ) {
						if ( elem === cur ) {
							match = true;
							break;
						}

					} else if ( Sizzle.filter( cur, [elem] ).length > 0 ) {
						match = elem;
						break;
					}
				}

				elem = elem[dir];
			}

			checkSet[i] = match;
		}
	}
}

var contains = document.compareDocumentPosition ?  function(a, b){
	return a.compareDocumentPosition(b) & 16;
} : function(a, b){
	return a !== b && (a.contains ? a.contains(b) : true);
};

var isXML = function(elem){
	return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" ||
		!!elem.ownerDocument && isXML( elem.ownerDocument );
};

var posProcess = function(selector, context){
	var tmpSet = [], later = "", match,
		root = context.nodeType ? [context] : context;

	// Position selectors must be done after the filter
	// And so must :not(positional) so we move all PSEUDOs to the end
	while ( (match = Expr.match.PSEUDO.exec( selector )) ) {
		later += match[0];
		selector = selector.replace( Expr.match.PSEUDO, "" );
	}

	selector = Expr.relative[selector] ? selector + "*" : selector;

	for ( var i = 0, l = root.length; i < l; i++ ) {
		Sizzle( selector, root[i], tmpSet );
	}

	return Sizzle.filter( later, tmpSet );
};

// EXPOSE
jQuery.find = Sizzle;
jQuery.filter = Sizzle.filter;
jQuery.expr = Sizzle.selectors;
jQuery.expr[":"] = jQuery.expr.filters;

Sizzle.selectors.filters.hidden = function(elem){
	return elem.offsetWidth === 0 || elem.offsetHeight === 0;
};

Sizzle.selectors.filters.visible = function(elem){
	return elem.offsetWidth > 0 || elem.offsetHeight > 0;
};

Sizzle.selectors.filters.animated = function(elem){
	return jQuery.grep(jQuery.timers, function(fn){
		return elem === fn.elem;
	}).length;
};

jQuery.multiFilter = function( expr, elems, not ) {
	if ( not ) {
		expr = ":not(" + expr + ")";
	}

	return Sizzle.matches(expr, elems);
};

jQuery.dir = function( elem, dir ){
	var matched = [], cur = elem[dir];
	while ( cur && cur != document ) {
		if ( cur.nodeType == 1 )
			matched.push( cur );
		cur = cur[dir];
	}
	return matched;
};

jQuery.nth = function(cur, result, dir, elem){
	result = result || 1;
	var num = 0;

	for ( ; cur; cur = cur[dir] )
		if ( cur.nodeType == 1 && ++num == result )
			break;

	return cur;
};

jQuery.sibling = function(n, elem){
	var r = [];

	for ( ; n; n = n.nextSibling ) {
		if ( n.nodeType == 1 && n != elem )
			r.push( n );
	}

	return r;
};

return;

window.Sizzle = Sizzle;

})();
/*
 * A number of helper functions used for managing events.
 * Many of the ideas behind this code originated from
 * Dean Edwards' addEvent library.
 */
jQuery.event = {

	// Bind an event to an element
	// Original by Dean Edwards
	add: function(elem, types, handler, data) {
		if ( elem.nodeType == 3 || elem.nodeType == 8 )
			return;

		// For whatever reason, IE has trouble passing the window object
		// around, causing it to be cloned in the process
		if ( elem.setInterval && elem != window )
			elem = window;

		// Make sure that the function being executed has a unique ID
		if ( !handler.guid )
			handler.guid = this.guid++;

		// if data is passed, bind to handler
		if ( data !== undefined ) {
			// Create temporary function pointer to original handler
			var fn = handler;

			// Create unique handler function, wrapped around original handler
			handler = this.proxy( fn );

			// Store data in unique handler
			handler.data = data;
		}

		// Init the element's event structure
		var events = jQuery.data(elem, "events") || jQuery.data(elem, "events", {}),
			handle = jQuery.data(elem, "handle") || jQuery.data(elem, "handle", function(){
				// Handle the second event of a trigger and when
				// an event is called after a page has unloaded
				return typeof jQuery !== "undefined" && !jQuery.event.triggered ?
					jQuery.event.handle.apply(arguments.callee.elem, arguments) :
					undefined;
			});
		// Add elem as a property of the handle function
		// This is to prevent a memory leak with non-native
		// event in IE.
		handle.elem = elem;

		// Handle multiple events separated by a space
		// jQuery(...).bind("mouseover mouseout", fn);
		jQuery.each(types.split(/\s+/), function(index, type) {
			// Namespaced event handlers
			var namespaces = type.split(".");
			type = namespaces.shift();
			handler.type = namespaces.slice().sort().join(".");

			// Get the current list of functions bound to this event
			var handlers = events[type];
			
			if ( jQuery.event.specialAll[type] )
				jQuery.event.specialAll[type].setup.call(elem, data, namespaces);

			// Init the event handler queue
			if (!handlers) {
				handlers = events[type] = {};

				// Check for a special event handler
				// Only use addEventListener/attachEvent if the special
				// events handler returns false
				if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem, data, namespaces) === false ) {
					// Bind the global event handler to the element
					if (elem.addEventListener)
						elem.addEventListener(type, handle, false);
					else if (elem.attachEvent)
						elem.attachEvent("on" + type, handle);
				}
			}

			// Add the function to the element's handler list
			handlers[handler.guid] = handler;

			// Keep track of which events have been used, for global triggering
			jQuery.event.global[type] = true;
		});

		// Nullify elem to prevent memory leaks in IE
		elem = null;
	},

	guid: 1,
	global: {},

	// Detach an event or set of events from an element
	remove: function(elem, types, handler) {
		// don't do events on text and comment nodes
		if ( elem.nodeType == 3 || elem.nodeType == 8 )
			return;

		var events = jQuery.data(elem, "events"), ret, index;

		if ( events ) {
			// Unbind all events for the element
			if ( types === undefined || (typeof types === "string" && types.charAt(0) == ".") )
				for ( var type in events )
					this.remove( elem, type + (types || "") );
			else {
				// types is actually an event object here
				if ( types.type ) {
					handler = types.handler;
					types = types.type;
				}

				// Handle multiple events seperated by a space
				// jQuery(...).unbind("mouseover mouseout", fn);
				jQuery.each(types.split(/\s+/), function(index, type){
					// Namespaced event handlers
					var namespaces = type.split(".");
					type = namespaces.shift();
					var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");

					if ( events[type] ) {
						// remove the given handler for the given type
						if ( handler )
							delete events[type][handler.guid];

						// remove all handlers for the given type
						else
							for ( var handle in events[type] )
								// Handle the removal of namespaced events
								if ( namespace.test(events[type][handle].type) )
									delete events[type][handle];
									
						if ( jQuery.event.specialAll[type] )
							jQuery.event.specialAll[type].teardown.call(elem, namespaces);

						// remove generic event handler if no more handlers exist
						for ( ret in events[type] ) break;
						if ( !ret ) {
							if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem, namespaces) === false ) {
								if (elem.removeEventListener)
									elem.removeEventListener(type, jQuery.data(elem, "handle"), false);
								else if (elem.detachEvent)
									elem.detachEvent("on" + type, jQuery.data(elem, "handle"));
							}
							ret = null;
							delete events[type];
						}
					}
				});
			}

			// Remove the expando if it's no longer used
			for ( ret in events ) break;
			if ( !ret ) {
				var handle = jQuery.data( elem, "handle" );
				if ( handle ) handle.elem = null;
				jQuery.removeData( elem, "events" );
				jQuery.removeData( elem, "handle" );
			}
		}
	},

	// bubbling is internal
	trigger: function( event, data, elem, bubbling ) {
		// Event object or event type
		var type = event.type || event;

		if( !bubbling ){
			event = typeof event === "object" ?
				// jQuery.Event object
				event[expando] ? event :
				// Object literal
				jQuery.extend( jQuery.Event(type), event ) :
				// Just the event type (string)
				jQuery.Event(type);

			if ( type.indexOf("!") >= 0 ) {
				event.type = type = type.slice(0, -1);
				event.exclusive = true;
			}

			// Handle a global trigger
			if ( !elem ) {
				// Don't bubble custom events when global (to avoid too much overhead)
				event.stopPropagation();
				// Only trigger if we've ever bound an event for it
				if ( this.global[type] )
					jQuery.each( jQuery.cache, function(){
						if ( this.events && this.events[type] )
							jQuery.event.trigger( event, data, this.handle.elem );
					});
			}

			// Handle triggering a single element

			// don't do events on text and comment nodes
			if ( !elem || elem.nodeType == 3 || elem.nodeType == 8 )
				return undefined;
			
			// Clean up in case it is reused
			event.result = undefined;
			event.target = elem;
			
			// Clone the incoming data, if any
			data = jQuery.makeArray(data);
			data.unshift( event );
		}

		event.currentTarget = elem;

		// Trigger the event, it is assumed that "handle" is a function
		var handle = jQuery.data(elem, "handle");
		if ( handle )
			handle.apply( elem, data );

		// Handle triggering native .onfoo handlers (and on links since we don't call .click() for links)
		if ( (!elem[type] || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false )
			event.result = false;

		// Trigger the native events (except for clicks on links)
		if ( !bubbling && elem[type] && !event.isDefaultPrevented() && !(jQuery.nodeName(elem, 'a') && type == "click") ) {
			this.triggered = true;
			try {
				elem[ type ]();
			// prevent IE from throwing an error for some hidden elements
			} catch (e) {}
		}

		this.triggered = false;

		if ( !event.isPropagationStopped() ) {
			var parent = elem.parentNode || elem.ownerDocument;
			if ( parent )
				jQuery.event.trigger(event, data, parent, true);
		}
	},

	handle: function(event) {
		// returned undefined or false
		var all, handlers;

		event = arguments[0] = jQuery.event.fix( event || window.event );
		event.currentTarget = this;
		
		// Namespaced event handlers
		var namespaces = event.type.split(".");
		event.type = namespaces.shift();

		// Cache this now, all = true means, any handler
		all = !namespaces.length && !event.exclusive;
		
		var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)");

		handlers = ( jQuery.data(this, "events") || {} )[event.type];

		for ( var j in handlers ) {
			var handler = handlers[j];

			// Filter the functions by class
			if ( all || namespace.test(handler.type) ) {
				// Pass in a reference to the handler function itself
				// So that we can later remove it
				event.handler = handler;
				event.data = handler.data;

				var ret = handler.apply(this, arguments);

				if( ret !== undefined ){
					event.result = ret;
					if ( ret === false ) {
						event.preventDefault();
						event.stopPropagation();
					}
				}

				if( event.isImmediatePropagationStopped() )
					break;

			}
		}
	},

	props: "altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue originalTarget pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which".split(" "),

	fix: function(event) {
		if ( event[expando] )
			return event;

		// store a copy of the original event object
		// and "clone" to set read-only properties
		var originalEvent = event;
		event = jQuery.Event( originalEvent );

		for ( var i = this.props.length, prop; i; ){
			prop = this.props[ --i ];
			event[ prop ] = originalEvent[ prop ];
		}

		// Fix target property, if necessary
		if ( !event.target )
			event.target = event.srcElement || document; // Fixes #1925 where srcElement might not be defined either

		// check if target is a textnode (safari)
		if ( event.target.nodeType == 3 )
			event.target = event.target.parentNode;

		// Add relatedTarget, if necessary
		if ( !event.relatedTarget && event.fromElement )
			event.relatedTarget = event.fromElement == event.target ? event.toElement : event.fromElement;

		// Calculate pageX/Y if missing and clientX/Y available
		if ( event.pageX == null && event.clientX != null ) {
			var doc = document.documentElement, body = document.body;
			event.pageX = event.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0) - (doc.clientLeft || 0);
			event.pageY = event.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0) - (doc.clientTop || 0);
		}

		// Add which for key events
		if ( !event.which && ((event.charCode || event.charCode === 0) ? event.charCode : event.keyCode) )
			event.which = event.charCode || event.keyCode;

		// Add metaKey to non-Mac browsers (use ctrl for PC's and Meta for Macs)
		if ( !event.metaKey && event.ctrlKey )
			event.metaKey = event.ctrlKey;

		// Add which for click: 1 == left; 2 == middle; 3 == right
		// Note: button is not normalized, so don't use it
		if ( !event.which && event.button )
			event.which = (event.button & 1 ? 1 : ( event.button & 2 ? 3 : ( event.button & 4 ? 2 : 0 ) ));

		return event;
	},

	proxy: function( fn, proxy ){
		proxy = proxy || function(){ return fn.apply(this, arguments); };
		// Set the guid of unique handler to the same of original handler, so it can be removed
		proxy.guid = fn.guid = fn.guid || proxy.guid || this.guid++;
		// So proxy can be declared as an argument
		return proxy;
	},

	special: {
		ready: {
			// Make sure the ready event is setup
			setup: bindReady,
			teardown: function() {}
		}
	},
	
	specialAll: {
		live: {
			setup: function( selector, namespaces ){
				jQuery.event.add( this, namespaces[0], liveHandler );
			},
			teardown:  function( namespaces ){
				if ( namespaces.length ) {
					var remove = 0, name = RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)");
					
					jQuery.each( (jQuery.data(this, "events").live || {}), function(){
						if ( name.test(this.type) )
							remove++;
					});
					
					if ( remove < 1 )
						jQuery.event.remove( this, namespaces[0], liveHandler );
				}
			}
		}
	}
};

jQuery.Event = function( src ){
	// Allow instantiation without the 'new' keyword
	if( !this.preventDefault )
		return new jQuery.Event(src);
	
	// Event object
	if( src && src.type ){
		this.originalEvent = src;
		this.type = src.type;
	// Event type
	}else
		this.type = src;

	// timeStamp is buggy for some events on Firefox(#3843)
	// So we won't rely on the native value
	this.timeStamp = now();
	
	// Mark it as fixed
	this[expando] = true;
};

function returnFalse(){
	return false;
}
function returnTrue(){
	return true;
}

// jQuery.Event is based on DOM3 Events as specified by the ECMAScript Language Binding
// http://www.w3.org/TR/2003/WD-DOM-Level-3-Events-20030331/ecma-script-binding.html
jQuery.Event.prototype = {
	preventDefault: function() {
		this.isDefaultPrevented = returnTrue;

		var e = this.originalEvent;
		if( !e )
			return;
		// if preventDefault exists run it on the original event
		if (e.preventDefault)
			e.preventDefault();
		// otherwise set the returnValue property of the original event to false (IE)
		e.returnValue = false;
	},
	stopPropagation: function() {
		this.isPropagationStopped = returnTrue;

		var e = this.originalEvent;
		if( !e )
			return;
		// if stopPropagation exists run it on the original event
		if (e.stopPropagation)
			e.stopPropagation();
		// otherwise set the cancelBubble property of the original event to true (IE)
		e.cancelBubble = true;
	},
	stopImmediatePropagation:function(){
		this.isImmediatePropagationStopped = returnTrue;
		this.stopPropagation();
	},
	isDefaultPrevented: returnFalse,
	isPropagationStopped: returnFalse,
	isImmediatePropagationStopped: returnFalse
};
// Checks if an event happened on an element within another element
// Used in jQuery.event.special.mouseenter and mouseleave handlers
var withinElement = function(event) {
	// Check if mouse(over|out) are still within the same parent element
	var parent = event.relatedTarget;
	// Traverse up the tree
	while ( parent && parent != this )
		try { parent = parent.parentNode; }
		catch(e) { parent = this; }
	
	if( parent != this ){
		// set the correct event type
		event.type = event.data;
		// handle event if we actually just moused on to a non sub-element
		jQuery.event.handle.apply( this, arguments );
	}
};
	
jQuery.each({ 
	mouseover: 'mouseenter', 
	mouseout: 'mouseleave'
}, function( orig, fix ){
	jQuery.event.special[ fix ] = {
		setup: function(){
			jQuery.event.add( this, orig, withinElement, fix );
		},
		teardown: function(){
			jQuery.event.remove( this, orig, withinElement );
		}
	};			   
});

jQuery.fn.extend({
	bind: function( type, data, fn ) {
		return type == "unload" ? this.one(type, data, fn) : this.each(function(){
			jQuery.event.add( this, type, fn || data, fn && data );
		});
	},

	one: function( type, data, fn ) {
		var one = jQuery.event.proxy( fn || data, function(event) {
			jQuery(this).unbind(event, one);
			return (fn || data).apply( this, arguments );
		});
		return this.each(function(){
			jQuery.event.add( this, type, one, fn && data);
		});
	},

	unbind: function( type, fn ) {
		return this.each(function(){
			jQuery.event.remove( this, type, fn );
		});
	},

	trigger: function( type, data ) {
		return this.each(function(){
			jQuery.event.trigger( type, data, this );
		});
	},

	triggerHandler: function( type, data ) {
		if( this[0] ){
			var event = jQuery.Event(type);
			event.preventDefault();
			event.stopPropagation();
			jQuery.event.trigger( event, data, this[0] );
			return event.result;
		}		
	},

	toggle: function( fn ) {
		// Save reference to arguments for access in closure
		var args = arguments, i = 1;

		// link all the functions, so any of them can unbind this click handler
		while( i < args.length )
			jQuery.event.proxy( fn, args[i++] );

		return this.click( jQuery.event.proxy( fn, function(event) {
			// Figure out which function to execute
			this.lastToggle = ( this.lastToggle || 0 ) % i;

			// Make sure that clicks stop
			event.preventDefault();

			// and execute the function
			return args[ this.lastToggle++ ].apply( this, arguments ) || false;
		}));
	},

	hover: function(fnOver, fnOut) {
		return this.mouseenter(fnOver).mouseleave(fnOut);
	},

	ready: function(fn) {
		// Attach the listeners
		bindReady();

		// If the DOM is already ready
		if ( jQuery.isReady )
			// Execute the function immediately
			fn.call( document, jQuery );

		// Otherwise, remember the function for later
		else
			// Add the function to the wait list
			jQuery.readyList.push( fn );

		return this;
	},
	
	live: function( type, fn ){
		var proxy = jQuery.event.proxy( fn );
		proxy.guid += this.selector + type;

		jQuery(document).bind( liveConvert(type, this.selector), this.selector, proxy );

		return this;
	},
	
	die: function( type, fn ){
		jQuery(document).unbind( liveConvert(type, this.selector), fn ? { guid: fn.guid + this.selector + type } : null );
		return this;
	}
});

function liveHandler( event ){
	var check = RegExp("(^|\\.)" + event.type + "(\\.|$)"),
		stop = true,
		elems = [];

	jQuery.each(jQuery.data(this, "events").live || [], function(i, fn){
		if ( check.test(fn.type) ) {
			var elem = jQuery(event.target).closest(fn.data)[0];
			if ( elem )
				elems.push({ elem: elem, fn: fn });
		}
	});

	elems.sort(function(a,b) {
		return jQuery.data(a.elem, "closest") - jQuery.data(b.elem, "closest");
	});
	
	jQuery.each(elems, function(){
		if ( this.fn.call(this.elem, event, this.fn.data) === false )
			return (stop = false);
	});

	return stop;
}

function liveConvert(type, selector){
	return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "|")].join(".");
}

jQuery.extend({
	isReady: false,
	readyList: [],
	// Handle when the DOM is ready
	ready: function() {
		// Make sure that the DOM is not already loaded
		if ( !jQuery.isReady ) {
			// Remember that the DOM is ready
			jQuery.isReady = true;

			// If there are functions bound, to execute
			if ( jQuery.readyList ) {
				// Execute all of them
				jQuery.each( jQuery.readyList, function(){
					this.call( document, jQuery );
				});

				// Reset the list of functions
				jQuery.readyList = null;
			}

			// Trigger any bound ready events
			jQuery(document).triggerHandler("ready");
		}
	}
});

var readyBound = false;

function bindReady(){
	if ( readyBound ) return;
	readyBound = true;

	// Mozilla, Opera and webkit nightlies currently support this event
	if ( document.addEventListener ) {
		// Use the handy event callback
		document.addEventListener( "DOMContentLoaded", function(){
			document.removeEventListener( "DOMContentLoaded", arguments.callee, false );
			jQuery.ready();
		}, false );

	// If IE event model is used
	} else if ( document.attachEvent ) {
		// ensure firing before onload,
		// maybe late but safe also for iframes
		document.attachEvent("onreadystatechange", function(){
			if ( document.readyState === "complete" ) {
				document.detachEvent( "onreadystatechange", arguments.callee );
				jQuery.ready();
			}
		});

		// If IE and not an iframe
		// continually check to see if the document is ready
		if ( document.documentElement.doScroll && window == window.top ) (function(){
			if ( jQuery.isReady ) return;

			try {
				// If IE is used, use the trick by Diego Perini
				// http://javascript.nwbox.com/IEContentLoaded/
				document.documentElement.doScroll("left");
			} catch( error ) {
				setTimeout( arguments.callee, 0 );
				return;
			}

			// and execute any waiting functions
			jQuery.ready();
		})();
	}

	// A fallback to window.onload, that will always work
	jQuery.event.add( window, "load", jQuery.ready );
}

jQuery.each( ("blur,focus,load,resize,scroll,unload,click,dblclick," +
	"mousedown,mouseup,mousemove,mouseover,mouseout,mouseenter,mouseleave," +
	"change,select,submit,keydown,keypress,keyup,error").split(","), function(i, name){

	// Handle event binding
	jQuery.fn[name] = function(fn){
		return fn ? this.bind(name, fn) : this.trigger(name);
	};
});

// Prevent memory leaks in IE
// And prevent errors on refresh with events like mouseover in other browsers
// Window isn't included so as not to unbind existing unload events
jQuery( window ).bind( 'unload', function(){ 
	for ( var id in jQuery.cache )
		// Skip the window
		if ( id != 1 && jQuery.cache[ id ].handle )
			jQuery.event.remove( jQuery.cache[ id ].handle.elem );
}); 
(function(){

	jQuery.support = {};

	var root = document.documentElement,
		script = document.createElement("script"),
		div = document.createElement("div"),
		id = "script" + (new Date).getTime();

	div.style.display = "none";
	div.innerHTML = '   <link/><table></table><a href="/a" style="color:red;float:left;opacity:.5;">a</a><select><option>text</option></select>';

	var all = div.getElementsByTagName("*"),
		a = div.getElementsByTagName("a")[0];

	// Can't get basic test support
	if ( !all || !all.length || !a ) {
		return;
	}

	jQuery.support = {
		// IE strips leading whitespace when .innerHTML is used
		leadingWhitespace: div.firstChild.nodeType == 3,
		
		// Make sure that tbody elements aren't automatically inserted
		// IE will insert them into empty tables
		tbody: !div.getElementsByTagName("tbody").length,
		
		// Make sure that link elements get serialized correctly by innerHTML
		// This requires a wrapper element in IE
		htmlSerialize: !!div.getElementsByTagName("link").length,
		
		// Get the style information from getAttribute
		// (IE uses .cssText insted)
		style: /red/.test( a.getAttribute("style") ),
		
		// Make sure that URLs aren't manipulated
		// (IE normalizes it by default)
		hrefNormalized: a.getAttribute("href") === "/a",
		
		// Make sure that element opacity exists
		// (IE uses filter instead)
		opacity: a.style.opacity === "0.5",
		
		// Verify style float existence
		// (IE uses styleFloat instead of cssFloat)
		cssFloat: !!a.style.cssFloat,

		// Will be defined later
		scriptEval: false,
		noCloneEvent: true,
		boxModel: null
	};
	
	script.type = "text/javascript";
	try {
		script.appendChild( document.createTextNode( "window." + id + "=1;" ) );
	} catch(e){}

	root.insertBefore( script, root.firstChild );
	
	// Make sure that the execution of code works by injecting a script
	// tag with appendChild/createTextNode
	// (IE doesn't support this, fails, and uses .text instead)
	if ( window[ id ] ) {
		jQuery.support.scriptEval = true;
		delete window[ id ];
	}

	root.removeChild( script );

	if ( div.attachEvent && div.fireEvent ) {
		div.attachEvent("onclick", function(){
			// Cloning a node shouldn't copy over any
			// bound event handlers (IE does this)
			jQuery.support.noCloneEvent = false;
			div.detachEvent("onclick", arguments.callee);
		});
		div.cloneNode(true).fireEvent("onclick");
	}

	// Figure out if the W3C box model works as expected
	// document.body must exist before we can do this
	jQuery(function(){
		var div = document.createElement("div");
		div.style.width = div.style.paddingLeft = "1px";

		document.body.appendChild( div );
		jQuery.boxModel = jQuery.support.boxModel = div.offsetWidth === 2;
		document.body.removeChild( div ).style.display = 'none';
	});
})();

var styleFloat = jQuery.support.cssFloat ? "cssFloat" : "styleFloat";

jQuery.props = {
	"for": "htmlFor",
	"class": "className",
	"float": styleFloat,
	cssFloat: styleFloat,
	styleFloat: styleFloat,
	readonly: "readOnly",
	maxlength: "maxLength",
	cellspacing: "cellSpacing",
	rowspan: "rowSpan",
	tabindex: "tabIndex"
};
jQuery.fn.extend({
	// Keep a copy of the old load
	_load: jQuery.fn.load,

	load: function( url, params, callback ) {
		if ( typeof url !== "string" )
			return this._load( url );

		var off = url.indexOf(" ");
		if ( off >= 0 ) {
			var selector = url.slice(off, url.length);
			url = url.slice(0, off);
		}

		// Default to a GET request
		var type = "GET";

		// If the second parameter was provided
		if ( params )
			// If it's a function
			if ( jQuery.isFunction( params ) ) {
				// We assume that it's the callback
				callback = params;
				params = null;

			// Otherwise, build a param string
			} else if( typeof params === "object" ) {
				params = jQuery.param( params );
				type = "POST";
			}

		var self = this;

		// Request the remote document
		jQuery.ajax({
			url: url,
			type: type,
			dataType: "html",
			data: params,
			complete: function(res, status){
				// If successful, inject the HTML into all the matched elements
				if ( status == "success" || status == "notmodified" )
					// See if a selector was specified
					self.html( selector ?
						// Create a dummy div to hold the results
						jQuery("<div/>")
							// inject the contents of the document in, removing the scripts
							// to avoid any 'Permission Denied' errors in IE
							.append(res.responseText.replace(/<script(.|\s)*?\/script>/g, ""))

							// Locate the specified elements
							.find(selector) :

						// If not, just inject the full result
						res.responseText );

				if( callback )
					self.each( callback, [res.responseText, status, res] );
			}
		});
		return this;
	},

	serialize: function() {
		return jQuery.param(this.serializeArray());
	},
	serializeArray: function() {
		return this.map(function(){
			return this.elements ? jQuery.makeArray(this.elements) : this;
		})
		.filter(function(){
			return this.name && !this.disabled &&
				(this.checked || /select|textarea/i.test(this.nodeName) ||
					/text|hidden|password|search/i.test(this.type));
		})
		.map(function(i, elem){
			var val = jQuery(this).val();
			return val == null ? null :
				jQuery.isArray(val) ?
					jQuery.map( val, function(val, i){
						return {name: elem.name, value: val};
					}) :
					{name: elem.name, value: val};
		}).get();
	}
});

// Attach a bunch of functions for handling common AJAX events
jQuery.each( "ajaxStart,ajaxStop,ajaxComplete,ajaxError,ajaxSuccess,ajaxSend".split(","), function(i,o){
	jQuery.fn[o] = function(f){
		return this.bind(o, f);
	};
});

var jsc = now();

jQuery.extend({
  
	get: function( url, data, callback, type ) {
		// shift arguments if data argument was ommited
		if ( jQuery.isFunction( data ) ) {
			callback = data;
			data = null;
		}

		return jQuery.ajax({
			type: "GET",
			url: url,
			data: data,
			success: callback,
			dataType: type
		});
	},

	getScript: function( url, callback ) {
		return jQuery.get(url, null, callback, "script");
	},

	getJSON: function( url, data, callback ) {
		return jQuery.get(url, data, callback, "json");
	},

	post: function( url, data, callback, type ) {
		if ( jQuery.isFunction( data ) ) {
			callback = data;
			data = {};
		}

		return jQuery.ajax({
			type: "POST",
			url: url,
			data: data,
			success: callback,
			dataType: type
		});
	},

	ajaxSetup: function( settings ) {
		jQuery.extend( jQuery.ajaxSettings, settings );
	},

	ajaxSettings: {
		url: location.href,
		global: true,
		type: "GET",
		contentType: "application/x-www-form-urlencoded",
		processData: true,
		async: true,
		/*
		timeout: 0,
		data: null,
		username: null,
		password: null,
		*/
		// Create the request object; Microsoft failed to properly
		// implement the XMLHttpRequest in IE7, so we use the ActiveXObject when it is available
		// This function can be overriden by calling jQuery.ajaxSetup
		xhr:function(){
			return window.ActiveXObject ? new ActiveXObject("Microsoft.XMLHTTP") : new XMLHttpRequest();
		},
		accepts: {
			xml: "application/xml, text/xml",
			html: "text/html",
			script: "text/javascript, application/javascript",
			json: "application/json, text/javascript",
			text: "text/plain",
			_default: "*/*"
		}
	},

	// Last-Modified header cache for next request
	lastModified: {},

	ajax: function( s ) {
		// Extend the settings, but re-extend 's' so that it can be
		// checked again later (in the test suite, specifically)
		s = jQuery.extend(true, s, jQuery.extend(true, {}, jQuery.ajaxSettings, s));

		var jsonp, jsre = /=\?(&|$)/g, status, data,
			type = s.type.toUpperCase();

		// convert data if not already a string
		if ( s.data && s.processData && typeof s.data !== "string" )
			s.data = jQuery.param(s.data);

		// Handle JSONP Parameter Callbacks
		if ( s.dataType == "jsonp" ) {
			if ( type == "GET" ) {
				if ( !s.url.match(jsre) )
					s.url += (s.url.match(/\?/) ? "&" : "?") + (s.jsonp || "callback") + "=?";
			} else if ( !s.data || !s.data.match(jsre) )
				s.data = (s.data ? s.data + "&" : "") + (s.jsonp || "callback") + "=?";
			s.dataType = "json";
		}

		// Build temporary JSONP function
		if ( s.dataType == "json" && (s.data && s.data.match(jsre) || s.url.match(jsre)) ) {
			jsonp = "jsonp" + jsc++;

			// Replace the =? sequence both in the query string and the data
			if ( s.data )
				s.data = (s.data + "").replace(jsre, "=" + jsonp + "$1");
			s.url = s.url.replace(jsre, "=" + jsonp + "$1");

			// We need to make sure
			// that a JSONP style response is executed properly
			s.dataType = "script";

			// Handle JSONP-style loading
			window[ jsonp ] = function(tmp){
				data = tmp;
				success();
				complete();
				// Garbage collect
				window[ jsonp ] = undefined;
				try{ delete window[ jsonp ]; } catch(e){}
				if ( head )
					head.removeChild( script );
			};
		}

		if ( s.dataType == "script" && s.cache == null )
			s.cache = false;

		if ( s.cache === false && type == "GET" ) {
			var ts = now();
			// try replacing _= if it is there
			var ret = s.url.replace(/(\?|&)_=.*?(&|$)/, "$1_=" + ts + "$2");
			// if nothing was replaced, add timestamp to the end
			s.url = ret + ((ret == s.url) ? (s.url.match(/\?/) ? "&" : "?") + "_=" + ts : "");
		}

		// If data is available, append data to url for get requests
		if ( s.data && type == "GET" ) {
			s.url += (s.url.match(/\?/) ? "&" : "?") + s.data;

			// IE likes to send both get and post data, prevent this
			s.data = null;
		}

		// Watch for a new set of requests
		if ( s.global && ! jQuery.active++ )
			jQuery.event.trigger( "ajaxStart" );

		// Matches an absolute URL, and saves the domain
		var parts = /^(\w+:)?\/\/([^\/?#]+)/.exec( s.url );

		// If we're requesting a remote document
		// and trying to load JSON or Script with a GET
		if ( s.dataType == "script" && type == "GET" && parts
			&& ( parts[1] && parts[1] != location.protocol || parts[2] != location.host )){

			var head = document.getElementsByTagName("head")[0];
			var script = document.createElement("script");
			script.src = s.url;
			if (s.scriptCharset)
				script.charset = s.scriptCharset;

			// Handle Script loading
			if ( !jsonp ) {
				var done = false;

				// Attach handlers for all browsers
				script.onload = script.onreadystatechange = function(){
					if ( !done && (!this.readyState ||
							this.readyState == "loaded" || this.readyState == "complete") ) {
						done = true;
						success();
						complete();

						// Handle memory leak in IE
						script.onload = script.onreadystatechange = null;
						head.removeChild( script );
					}
				};
			}

			head.appendChild(script);

			// We handle everything using the script element injection
			return undefined;
		}

		var requestDone = false;

		// Create the request object
		var xhr = s.xhr();

		// Open the socket
		// Passing null username, generates a login popup on Opera (#2865)
		if( s.username )
			xhr.open(type, s.url, s.async, s.username, s.password);
		else
			xhr.open(type, s.url, s.async);

		// Need an extra try/catch for cross domain requests in Firefox 3
		try {
			// Set the correct header, if data is being sent
			if ( s.data )
				xhr.setRequestHeader("Content-Type", s.contentType);

			// Set the If-Modified-Since header, if ifModified mode.
			if ( s.ifModified )
				xhr.setRequestHeader("If-Modified-Since",
					jQuery.lastModified[s.url] || "Thu, 01 Jan 1970 00:00:00 GMT" );

			// Set header so the called script knows that it's an XMLHttpRequest
			xhr.setRequestHeader("X-Requested-With", "XMLHttpRequest");

			// Set the Accepts header for the server, depending on the dataType
			xhr.setRequestHeader("Accept", s.dataType && s.accepts[ s.dataType ] ?
				s.accepts[ s.dataType ] + ", */*" :
				s.accepts._default );
		} catch(e){}

		// Allow custom headers/mimetypes and early abort
		if ( s.beforeSend && s.beforeSend(xhr, s) === false ) {
			// Handle the global AJAX counter
			if ( s.global && ! --jQuery.active )
				jQuery.event.trigger( "ajaxStop" );
			// close opended socket
			xhr.abort();
			return false;
		}

		if ( s.global )
			jQuery.event.trigger("ajaxSend", [xhr, s]);

		// Wait for a response to come back
		var onreadystatechange = function(isTimeout){
			// The request was aborted, clear the interval and decrement jQuery.active
			if (xhr.readyState == 0) {
				if (ival) {
					// clear poll interval
					clearInterval(ival);
					ival = null;
					// Handle the global AJAX counter
					if ( s.global && ! --jQuery.active )
						jQuery.event.trigger( "ajaxStop" );
				}
			// The transfer is complete and the data is available, or the request timed out
			} else if ( !requestDone && xhr && (xhr.readyState == 4 || isTimeout == "timeout") ) {
				requestDone = true;

				// clear poll interval
				if (ival) {
					clearInterval(ival);
					ival = null;
				}

				status = isTimeout == "timeout" ? "timeout" :
					!jQuery.httpSuccess( xhr ) ? "error" :
					s.ifModified && jQuery.httpNotModified( xhr, s.url ) ? "notmodified" :
					"success";

				if ( status == "success" ) {
					// Watch for, and catch, XML document parse errors
					try {
						// process the data (runs the xml through httpData regardless of callback)
						data = jQuery.httpData( xhr, s.dataType, s );
					} catch(e) {
						status = "parsererror";
					}
				}

				// Make sure that the request was successful or notmodified
				if ( status == "success" ) {
					// Cache Last-Modified header, if ifModified mode.
					var modRes;
					try {
						modRes = xhr.getResponseHeader("Last-Modified");
					} catch(e) {} // swallow exception thrown by FF if header is not available

					if ( s.ifModified && modRes )
						jQuery.lastModified[s.url] = modRes;

					// JSONP handles its own success callback
					if ( !jsonp )
						success();
				} else
					jQuery.handleError(s, xhr, status);

				// Fire the complete handlers
				complete();

				if ( isTimeout )
					xhr.abort();

				// Stop memory leaks
				if ( s.async )
					xhr = null;
			}
		};

		if ( s.async ) {
			// don't attach the handler to the request, just poll it instead
			var ival = setInterval(onreadystatechange, 13);

			// Timeout checker
			if ( s.timeout > 0 )
				setTimeout(function(){
					// Check to see if the request is still happening
					if ( xhr && !requestDone )
						onreadystatechange( "timeout" );
				}, s.timeout);
		}

		// Send the data
		try {
			xhr.send(s.data);
		} catch(e) {
			jQuery.handleError(s, xhr, null, e);
		}

		// firefox 1.5 doesn't fire statechange for sync requests
		if ( !s.async )
			onreadystatechange();

		function success(){
			// If a local callback was specified, fire it and pass it the data
			if ( s.success )
				s.success( data, status );

			// Fire the global callback
			if ( s.global )
				jQuery.event.trigger( "ajaxSuccess", [xhr, s] );
		}

		function complete(){
			// Process result
			if ( s.complete )
				s.complete(xhr, status);

			// The request was completed
			if ( s.global )
				jQuery.event.trigger( "ajaxComplete", [xhr, s] );

			// Handle the global AJAX counter
			if ( s.global && ! --jQuery.active )
				jQuery.event.trigger( "ajaxStop" );
		}

		// return XMLHttpRequest to allow aborting the request etc.
		return xhr;
	},

	handleError: function( s, xhr, status, e ) {
		// If a local callback was specified, fire it
		if ( s.error ) s.error( xhr, status, e );

		// Fire the global callback
		if ( s.global )
			jQuery.event.trigger( "ajaxError", [xhr, s, e] );
	},

	// Counter for holding the number of active queries
	active: 0,

	// Determines if an XMLHttpRequest was successful or not
	httpSuccess: function( xhr ) {
		try {
			// IE error sometimes returns 1223 when it should be 204 so treat it as success, see #1450
			return !xhr.status && location.protocol == "file:" ||
				( xhr.status >= 200 && xhr.status < 300 ) || xhr.status == 304 || xhr.status == 1223;
		} catch(e){}
		return false;
	},

	// Determines if an XMLHttpRequest returns NotModified
	httpNotModified: function( xhr, url ) {
		try {
			var xhrRes = xhr.getResponseHeader("Last-Modified");

			// Firefox always returns 200. check Last-Modified date
			return xhr.status == 304 || xhrRes == jQuery.lastModified[url];
		} catch(e){}
		return false;
	},

	httpData: function( xhr, type, s ) {
		var ct = xhr.getResponseHeader("content-type"),
			xml = type == "xml" || !type && ct && ct.indexOf("xml") >= 0,
			data = xml ? xhr.responseXML : xhr.responseText;

		if ( xml && data.documentElement.tagName == "parsererror" )
			throw "parsererror";
			
		// Allow a pre-filtering function to sanitize the response
		// s != null is checked to keep backwards compatibility
		if( s && s.dataFilter )
			data = s.dataFilter( data, type );

		// The filter can actually parse the response
		if( typeof data === "string" ){

			// If the type is "script", eval it in global context
			if ( type == "script" )
				jQuery.globalEval( data );

			// Get the JavaScript object, if JSON is used.
			if ( type == "json" )
				data = window["eval"]("(" + data + ")");
		}
		
		return data;
	},

	// Serialize an array of form elements or a set of
	// key/values into a query string
	param: function( a ) {
		var s = [ ];

		function add( key, value ){
			s[ s.length ] = encodeURIComponent(key) + '=' + encodeURIComponent(value);
		};

		// If an array was passed in, assume that it is an array
		// of form elements
		if ( jQuery.isArray(a) || a.jquery )
			// Serialize the form elements
			jQuery.each( a, function(){
				add( this.name, this.value );
			});

		// Otherwise, assume that it's an object of key/value pairs
		else
			// Serialize the key/values
			for ( var j in a )
				// If the value is an array then the key names need to be repeated
				if ( jQuery.isArray(a[j]) )
					jQuery.each( a[j], function(){
						add( j, this );
					});
				else
					add( j, jQuery.isFunction(a[j]) ? a[j]() : a[j] );

		// Return the resulting serialization
		return s.join("&").replace(/%20/g, "+");
	}

});
var elemdisplay = {},
	timerId,
	fxAttrs = [
		// height animations
		[ "height", "marginTop", "marginBottom", "paddingTop", "paddingBottom" ],
		// width animations
		[ "width", "marginLeft", "marginRight", "paddingLeft", "paddingRight" ],
		// opacity animations
		[ "opacity" ]
	];

function genFx( type, num ){
	var obj = {};
	jQuery.each( fxAttrs.concat.apply([], fxAttrs.slice(0,num)), function(){
		obj[ this ] = type;
	});
	return obj;
}

jQuery.fn.extend({
	show: function(speed,callback){
		if ( speed ) {
			return this.animate( genFx("show", 3), speed, callback);
		} else {
			for ( var i = 0, l = this.length; i < l; i++ ){
				var old = jQuery.data(this[i], "olddisplay");
				
				this[i].style.display = old || "";
				
				if ( jQuery.css(this[i], "display") === "none" ) {
					var tagName = this[i].tagName, display;
					
					if ( elemdisplay[ tagName ] ) {
						display = elemdisplay[ tagName ];
					} else {
						var elem = jQuery("<" + tagName + " />").appendTo("body");
						
						display = elem.css("display");
						if ( display === "none" )
							display = "block";
						
						elem.remove();
						
						elemdisplay[ tagName ] = display;
					}
					
					jQuery.data(this[i], "olddisplay", display);
				}
			}

			// Set the display of the elements in a second loop
			// to avoid the constant reflow
			for ( var i = 0, l = this.length; i < l; i++ ){
				this[i].style.display = jQuery.data(this[i], "olddisplay") || "";
			}
			
			return this;
		}
	},

	hide: function(speed,callback){
		if ( speed ) {
			return this.animate( genFx("hide", 3), speed, callback);
		} else {
			for ( var i = 0, l = this.length; i < l; i++ ){
				var old = jQuery.data(this[i], "olddisplay");
				if ( !old && old !== "none" )
					jQuery.data(this[i], "olddisplay", jQuery.css(this[i], "display"));
			}

			// Set the display of the elements in a second loop
			// to avoid the constant reflow
			for ( var i = 0, l = this.length; i < l; i++ ){
				this[i].style.display = "none";
			}

			return this;
		}
	},

	// Save the old toggle function
	_toggle: jQuery.fn.toggle,

	toggle: function( fn, fn2 ){
		var bool = typeof fn === "boolean";

		return jQuery.isFunction(fn) && jQuery.isFunction(fn2) ?
			this._toggle.apply( this, arguments ) :
			fn == null || bool ?
				this.each(function(){
					var state = bool ? fn : jQuery(this).is(":hidden");
					jQuery(this)[ state ? "show" : "hide" ]();
				}) :
				this.animate(genFx("toggle", 3), fn, fn2);
	},

	fadeTo: function(speed,to,callback){
		return this.animate({opacity: to}, speed, callback);
	},

	animate: function( prop, speed, easing, callback ) {
		var optall = jQuery.speed(speed, easing, callback);

		return this[ optall.queue === false ? "each" : "queue" ](function(){
		
			var opt = jQuery.extend({}, optall), p,
				hidden = this.nodeType == 1 && jQuery(this).is(":hidden"),
				self = this;
	
			for ( p in prop ) {
				if ( prop[p] == "hide" && hidden || prop[p] == "show" && !hidden )
					return opt.complete.call(this);

				if ( ( p == "height" || p == "width" ) && this.style ) {
					// Store display property
					opt.display = jQuery.css(this, "display");

					// Make sure that nothing sneaks out
					opt.overflow = this.style.overflow;
				}
			}

			if ( opt.overflow != null )
				this.style.overflow = "hidden";

			opt.curAnim = jQuery.extend({}, prop);

			jQuery.each( prop, function(name, val){
				var e = new jQuery.fx( self, opt, name );

				if ( /toggle|show|hide/.test(val) )
					e[ val == "toggle" ? hidden ? "show" : "hide" : val ]( prop );
				else {
					var parts = val.toString().match(/^([+-]=)?([\d+-.]+)(.*)$/),
						start = e.cur(true) || 0;

					if ( parts ) {
						var end = parseFloat(parts[2]),
							unit = parts[3] || "px";

						// We need to compute starting value
						if ( unit != "px" ) {
							self.style[ name ] = (end || 1) + unit;
							start = ((end || 1) / e.cur(true)) * start;
							self.style[ name ] = start + unit;
						}

						// If a +=/-= token was provided, we're doing a relative animation
						if ( parts[1] )
							end = ((parts[1] == "-=" ? -1 : 1) * end) + start;

						e.custom( start, end, unit );
					} else
						e.custom( start, val, "" );
				}
			});

			// For JS strict compliance
			return true;
		});
	},

	stop: function(clearQueue, gotoEnd){
		var timers = jQuery.timers;

		if (clearQueue)
			this.queue([]);

		this.each(function(){
			// go in reverse order so anything added to the queue during the loop is ignored
			for ( var i = timers.length - 1; i >= 0; i-- )
				if ( timers[i].elem == this ) {
					if (gotoEnd)
						// force the next step to be the last
						timers[i](true);
					timers.splice(i, 1);
				}
		});

		// start the next in the queue if the last step wasn't forced
		if (!gotoEnd)
			this.dequeue();

		return this;
	}

});

// Generate shortcuts for custom animations
jQuery.each({
	slideDown: genFx("show", 1),
	slideUp: genFx("hide", 1),
	slideToggle: genFx("toggle", 1),
	fadeIn: { opacity: "show" },
	fadeOut: { opacity: "hide" }
}, function( name, props ){
	jQuery.fn[ name ] = function( speed, callback ){
		return this.animate( props, speed, callback );
	};
});

jQuery.extend({

	speed: function(speed, easing, fn) {
		var opt = typeof speed === "object" ? speed : {
			complete: fn || !fn && easing ||
				jQuery.isFunction( speed ) && speed,
			duration: speed,
			easing: fn && easing || easing && !jQuery.isFunction(easing) && easing
		};

		opt.duration = jQuery.fx.off ? 0 : typeof opt.duration === "number" ? opt.duration :
			jQuery.fx.speeds[opt.duration] || jQuery.fx.speeds._default;

		// Queueing
		opt.old = opt.complete;
		opt.complete = function(){
			if ( opt.queue !== false )
				jQuery(this).dequeue();
			if ( jQuery.isFunction( opt.old ) )
				opt.old.call( this );
		};

		return opt;
	},

	easing: {
		linear: function( p, n, firstNum, diff ) {
			return firstNum + diff * p;
		},
		swing: function( p, n, firstNum, diff ) {
			return ((-Math.cos(p*Math.PI)/2) + 0.5) * diff + firstNum;
		}
	},

	timers: [],

	fx: function( elem, options, prop ){
		this.options = options;
		this.elem = elem;
		this.prop = prop;

		if ( !options.orig )
			options.orig = {};
	}

});

jQuery.fx.prototype = {

	// Simple function for setting a style value
	update: function(){
		if ( this.options.step )
			this.options.step.call( this.elem, this.now, this );

		(jQuery.fx.step[this.prop] || jQuery.fx.step._default)( this );

		// Set display property to block for height/width animations
		if ( ( this.prop == "height" || this.prop == "width" ) && this.elem.style )
			this.elem.style.display = "block";
	},

	// Get the current size
	cur: function(force){
		if ( this.elem[this.prop] != null && (!this.elem.style || this.elem.style[this.prop] == null) )
			return this.elem[ this.prop ];

		var r = parseFloat(jQuery.css(this.elem, this.prop, force));
		return r && r > -10000 ? r : parseFloat(jQuery.curCSS(this.elem, this.prop)) || 0;
	},

	// Start an animation from one number to another
	custom: function(from, to, unit){
		this.startTime = now();
		this.start = from;
		this.end = to;
		this.unit = unit || this.unit || "px";
		this.now = this.start;
		this.pos = this.state = 0;

		var self = this;
		function t(gotoEnd){
			return self.step(gotoEnd);
		}

		t.elem = this.elem;

		if ( t() && jQuery.timers.push(t) && !timerId ) {
			timerId = setInterval(function(){
				var timers = jQuery.timers;

				for ( var i = 0; i < timers.length; i++ )
					if ( !timers[i]() )
						timers.splice(i--, 1);

				if ( !timers.length ) {
					clearInterval( timerId );
					timerId = undefined;
				}
			}, 13);
		}
	},

	// Simple 'show' function
	show: function(){
		// Remember where we started, so that we can go back to it later
		this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
		this.options.show = true;

		// Begin the animation
		// Make sure that we start at a small width/height to avoid any
		// flash of content
		this.custom(this.prop == "width" || this.prop == "height" ? 1 : 0, this.cur());

		// Start by showing the element
		jQuery(this.elem).show();
	},

	// Simple 'hide' function
	hide: function(){
		// Remember where we started, so that we can go back to it later
		this.options.orig[this.prop] = jQuery.attr( this.elem.style, this.prop );
		this.options.hide = true;

		// Begin the animation
		this.custom(this.cur(), 0);
	},

	// Each step of an animation
	step: function(gotoEnd){
		var t = now();

		if ( gotoEnd || t >= this.options.duration + this.startTime ) {
			this.now = this.end;
			this.pos = this.state = 1;
			this.update();

			this.options.curAnim[ this.prop ] = true;

			var done = true;
			for ( var i in this.options.curAnim )
				if ( this.options.curAnim[i] !== true )
					done = false;

			if ( done ) {
				if ( this.options.display != null ) {
					// Reset the overflow
					this.elem.style.overflow = this.options.overflow;

					// Reset the display
					this.elem.style.display = this.options.display;
					if ( jQuery.css(this.elem, "display") == "none" )
						this.elem.style.display = "block";
				}

				// Hide the element if the "hide" operation was done
				if ( this.options.hide )
					jQuery(this.elem).hide();

				// Reset the properties, if the item has been hidden or shown
				if ( this.options.hide || this.options.show )
					for ( var p in this.options.curAnim )
						jQuery.attr(this.elem.style, p, this.options.orig[p]);
					
				// Execute the complete function
				this.options.complete.call( this.elem );
			}

			return false;
		} else {
			var n = t - this.startTime;
			this.state = n / this.options.duration;

			// Perform the easing function, defaults to swing
			this.pos = jQuery.easing[this.options.easing || (jQuery.easing.swing ? "swing" : "linear")](this.state, n, 0, 1, this.options.duration);
			this.now = this.start + ((this.end - this.start) * this.pos);

			// Perform the next step of the animation
			this.update();
		}

		return true;
	}

};

jQuery.extend( jQuery.fx, {
	speeds:{
		slow: 600,
 		fast: 200,
 		// Default speed
 		_default: 400
	},
	step: {

		opacity: function(fx){
			jQuery.attr(fx.elem.style, "opacity", fx.now);
		},

		_default: function(fx){
			if ( fx.elem.style && fx.elem.style[ fx.prop ] != null )
				fx.elem.style[ fx.prop ] = fx.now + fx.unit;
			else
				fx.elem[ fx.prop ] = fx.now;
		}
	}
});
if ( document.documentElement["getBoundingClientRect"] )
	jQuery.fn.offset = function() {
		if ( !this[0] ) return { top: 0, left: 0 };
		if ( this[0] === this[0].ownerDocument.body ) return jQuery.offset.bodyOffset( this[0] );
		var box  = this[0].getBoundingClientRect(), doc = this[0].ownerDocument, body = doc.body, docElem = doc.documentElement,
			clientTop = docElem.clientTop || body.clientTop || 0, clientLeft = docElem.clientLeft || body.clientLeft || 0,
			top  = box.top  + (self.pageYOffset || jQuery.boxModel && docElem.scrollTop  || body.scrollTop ) - clientTop,
			left = box.left + (self.pageXOffset || jQuery.boxModel && docElem.scrollLeft || body.scrollLeft) - clientLeft;
		return { top: top, left: left };
	};
else 
	jQuery.fn.offset = function() {
		if ( !this[0] ) return { top: 0, left: 0 };
		if ( this[0] === this[0].ownerDocument.body ) return jQuery.offset.bodyOffset( this[0] );
		jQuery.offset.initialized || jQuery.offset.initialize();

		var elem = this[0], offsetParent = elem.offsetParent, prevOffsetParent = elem,
			doc = elem.ownerDocument, computedStyle, docElem = doc.documentElement,
			body = doc.body, defaultView = doc.defaultView,
			prevComputedStyle = defaultView.getComputedStyle(elem, null),
			top = elem.offsetTop, left = elem.offsetLeft;

		while ( (elem = elem.parentNode) && elem !== body && elem !== docElem ) {
			computedStyle = defaultView.getComputedStyle(elem, null);
			top -= elem.scrollTop, left -= elem.scrollLeft;
			if ( elem === offsetParent ) {
				top += elem.offsetTop, left += elem.offsetLeft;
				if ( jQuery.offset.doesNotAddBorder && !(jQuery.offset.doesAddBorderForTableAndCells && /^t(able|d|h)$/i.test(elem.tagName)) )
					top  += parseInt( computedStyle.borderTopWidth,  10) || 0,
					left += parseInt( computedStyle.borderLeftWidth, 10) || 0;
				prevOffsetParent = offsetParent, offsetParent = elem.offsetParent;
			}
			if ( jQuery.offset.subtractsBorderForOverflowNotVisible && computedStyle.overflow !== "visible" )
				top  += parseInt( computedStyle.borderTopWidth,  10) || 0,
				left += parseInt( computedStyle.borderLeftWidth, 10) || 0;
			prevComputedStyle = computedStyle;
		}

		if ( prevComputedStyle.position === "relative" || prevComputedStyle.position === "static" )
			top  += body.offsetTop,
			left += body.offsetLeft;

		if ( prevComputedStyle.position === "fixed" )
			top  += Math.max(docElem.scrollTop, body.scrollTop),
			left += Math.max(docElem.scrollLeft, body.scrollLeft);

		return { top: top, left: left };
	};

jQuery.offset = {
	initialize: function() {
		if ( this.initialized ) return;
		var body = document.body, container = document.createElement('div'), innerDiv, checkDiv, table, td, rules, prop, bodyMarginTop = body.style.marginTop,
			html = '<div style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;"><div></div></div><table style="position:absolute;top:0;left:0;margin:0;border:5px solid #000;padding:0;width:1px;height:1px;" cellpadding="0" cellspacing="0"><tr><td></td></tr></table>';

		rules = { position: 'absolute', top: 0, left: 0, margin: 0, border: 0, width: '1px', height: '1px', visibility: 'hidden' };
		for ( prop in rules ) container.style[prop] = rules[prop];

		container.innerHTML = html;
		body.insertBefore(container, body.firstChild);
		innerDiv = container.firstChild, checkDiv = innerDiv.firstChild, td = innerDiv.nextSibling.firstChild.firstChild;

		this.doesNotAddBorder = (checkDiv.offsetTop !== 5);
		this.doesAddBorderForTableAndCells = (td.offsetTop === 5);

		innerDiv.style.overflow = 'hidden', innerDiv.style.position = 'relative';
		this.subtractsBorderForOverflowNotVisible = (checkDiv.offsetTop === -5);

		body.style.marginTop = '1px';
		this.doesNotIncludeMarginInBodyOffset = (body.offsetTop === 0);
		body.style.marginTop = bodyMarginTop;

		body.removeChild(container);
		this.initialized = true;
	},

	bodyOffset: function(body) {
		jQuery.offset.initialized || jQuery.offset.initialize();
		var top = body.offsetTop, left = body.offsetLeft;
		if ( jQuery.offset.doesNotIncludeMarginInBodyOffset )
			top  += parseInt( jQuery.curCSS(body, 'marginTop',  true), 10 ) || 0,
			left += parseInt( jQuery.curCSS(body, 'marginLeft', true), 10 ) || 0;
		return { top: top, left: left };
	}
};


jQuery.fn.extend({
	position: function() {
		var left = 0, top = 0, results;

		if ( this[0] ) {
			// Get *real* offsetParent
			var offsetParent = this.offsetParent(),

			// Get correct offsets
			offset       = this.offset(),
			parentOffset = /^body|html$/i.test(offsetParent[0].tagName) ? { top: 0, left: 0 } : offsetParent.offset();

			// Subtract element margins
			// note: when an element has margin: auto the offsetLeft and marginLeft 
			// are the same in Safari causing offset.left to incorrectly be 0
			offset.top  -= num( this, 'marginTop'  );
			offset.left -= num( this, 'marginLeft' );

			// Add offsetParent borders
			parentOffset.top  += num( offsetParent, 'borderTopWidth'  );
			parentOffset.left += num( offsetParent, 'borderLeftWidth' );

			// Subtract the two offsets
			results = {
				top:  offset.top  - parentOffset.top,
				left: offset.left - parentOffset.left
			};
		}

		return results;
	},

	offsetParent: function() {
		var offsetParent = this[0].offsetParent || document.body;
		while ( offsetParent && (!/^body|html$/i.test(offsetParent.tagName) && jQuery.css(offsetParent, 'position') == 'static') )
			offsetParent = offsetParent.offsetParent;
		return jQuery(offsetParent);
	}
});


// Create scrollLeft and scrollTop methods
jQuery.each( ['Left', 'Top'], function(i, name) {
	var method = 'scroll' + name;
	
	jQuery.fn[ method ] = function(val) {
		if (!this[0]) return null;

		return val !== undefined ?

			// Set the scroll offset
			this.each(function() {
				this == window || this == document ?
					window.scrollTo(
						!i ? val : jQuery(window).scrollLeft(),
						 i ? val : jQuery(window).scrollTop()
					) :
					this[ method ] = val;
			}) :

			// Return the scroll offset
			this[0] == window || this[0] == document ?
				self[ i ? 'pageYOffset' : 'pageXOffset' ] ||
					jQuery.boxModel && document.documentElement[ method ] ||
					document.body[ method ] :
				this[0][ method ];
	};
});
// Create innerHeight, innerWidth, outerHeight and outerWidth methods
jQuery.each([ "Height", "Width" ], function(i, name){

	var tl = i ? "Left"  : "Top",  // top or left
		br = i ? "Right" : "Bottom", // bottom or right
		lower = name.toLowerCase();

	// innerHeight and innerWidth
	jQuery.fn["inner" + name] = function(){
		return this[0] ?
			jQuery.css( this[0], lower, false, "padding" ) :
			null;
	};

	// outerHeight and outerWidth
	jQuery.fn["outer" + name] = function(margin) {
		return this[0] ?
			jQuery.css( this[0], lower, false, margin ? "margin" : "border" ) :
			null;
	};
	
	var type = name.toLowerCase();

	jQuery.fn[ type ] = function( size ) {
		// Get window width or height
		return this[0] == window ?
			// Everyone else use document.documentElement or document.body depending on Quirks vs Standards mode
			document.compatMode == "CSS1Compat" && document.documentElement[ "client" + name ] ||
			document.body[ "client" + name ] :

			// Get document width or height
			this[0] == document ?
				// Either scroll[Width/Height] or offset[Width/Height], whichever is greater
				Math.max(
					document.documentElement["client" + name],
					document.body["scroll" + name], document.documentElement["scroll" + name],
					document.body["offset" + name], document.documentElement["offset" + name]
				) :

				// Get or set width or height on the element
				size === undefined ?
					// Get width or height on the element
					(this.length ? jQuery.css( this[0], type ) : null) :

					// Set the width or height on the element (default to pixels if value is unitless)
					this.css( type, typeof size === "string" ? size : size + "px" );
	};

});
})();






/*!!!!0.js and more!!!!*/

if (!self[/*{*/"_loadhandlers"/*}*/])
{
	// Try to fix IE image caching issues. (IE7 doesn't need it but doesn't mind. Anything else before IE6SP1 doesn't understand it.)
	try { document.execCommand("BackgroundImageCache",false,true); } catch(e){};
	
	document.write("<style>.hideforjs"+(document.createElement?",.hidefordom":"")+"{position:absolute;left:-249em;width:1em;}</style>");

	///////////////////////////////////////////////////////////////////////
	// Obfuscation and utility functions
	///////////////////////////////////////////////////////////////////////
	function $_(){} // $_ is defined for backwards-compatibility; it used to be part of a page-loading trick.
	var _isIE=$.browser.msie;
	var _browserVersion=$.browser.version;
	var _sizelocked=false; // Is the page size locked at some previous size (because we're performing a drag that shouldn't alter anything)
	var keyAfterHB=false; // Has a key been pressed since the last heartbeat? (might affect session timeout)
	var _bwsnavig=null; // The button or such that was clicked recently (used by navigation warnings)
	var _clickhandlers={};
	var _overhandlers={};
	var _outhandlers={};
	var _bwswatchnavig=[];
	if (!self.hbloc) hbloc = null;
	var controldest={};
	var controlsrc={};
	var ignorehbfail=false;
	
	function parLoc(url,par)
	{
		var pos = url.indexOf("?")+1;
		if (!pos) return;
		
		url = url.substr(pos);
		url = url.split('&');
		for (var i=0;i<url.length;++i)
		{
			var bits = url[i].split("=");
			if (bits[0]==par)
				return pos;
			pos=pos+url[i].length+1;
		}
	}
	
	function arraypush(a,o)
	{
		var r = a.length;
		a[r]=o;
		return r;
	}
	
	function $log(s)
	{
		try{if (parent!=self) {parent.$log("Ch: "+s);return;}}catch(e){}
		$log2(s);
		//-----test include
		$logarray(s);
	}
	
	function $log2(){}
	
	function $logtitle(s)
	{
		document.title=s+" "+document.title;
	}
	
	var loglines=[];
	function $logarray(s)
	{
		loglines.push(s);
	}
	var logdiv;
	function $showlog()
	{
		if (!logdiv) document.body.appendChild(logdiv=document.createElement("div"));
		logdiv.innerHTML="<div>"+loglines.join('<br />')+"</div>";
}
	
	
	function ISOWithMS(d)
	{
		return d.getFullYear()+'-'+twodig(d.getMonth())+'-'+twodig(d.getDate())+' '+twodig(d.getHours())+':'+twodig(d.getMinutes())+':'+twodig(d.getSeconds())+'.'+threedig(d.getMilliseconds());
	}
	
	function threedig(n)
	{
		return (n<10&&n>=-10?'00':n<100&&n>=-100?'0':'')+n;
	}
	
	function twodig(n)
	{
		return (n<10&&n>-10?'0':'')+n;
	}
	
	function $logwindow(s)
	{
		try{
			var div = logwindow.document.createElement("div");
			var time = logwindow.document.createElement("tt");
			time.appendChild(logwindow.document.createTextNode(ISOWithMS(new Date())));
			div.appendChild(time);
			div.appendChild(logwindow.document.createTextNode(' - '+s));
			logwindow.document.body.appendChild(div);
		}catch(e){}
	}
	
	var logwindow;
	function $logtowindow()
	{
		document.cookie='fs2debug=window';
		if (!logwindow)
		{
			var m=/^fs2(main|debug)(\d+)$/.exec(window.name||'');
			if (!m)
			{
				var num = Math.floor(Math.random()*100000);
				window.name='fs2main'+num;
				m=[0,'main',num];
			}
			if (m[1]=='main')
				logwindow = window.open('','fs2debug'+m[2]);
			if (!logwindow.init)
			{
				logwindow.document.open();
				logwindow.document.write("<html><head><title></title></head><body></body></html>");
				logwindow.document.close();
				logwindow.init=true;
			}
			logwindow.document.title='FS2 Debug for '+location;
			$log2=$logwindow;
			$log("Loaded "+location);
		}
    }
    function $logtoarray() {
        document.cookie = 'fs2debug=array';        
        
        var m = /^fs2(main|debug)(\d+)$/.exec(window.name || '');
        if (!m) {
            var num = Math.floor(Math.random() * 100000);
            window.name = 'fs2main' + num;
            m = [0, 'main', num];
        }
        if (m[1] == 'main')
            logwindow = window.open('', 'fs2debug' + m[2]);
        if (!logwindow.init) {
            logwindow.document.open();
            logwindow.document.write("<html><head><title></title></head><body></body></html>");
            logwindow.document.close();
            logwindow.init = true;
        }
        logwindow.document.title = 'FS2 Debug for ' + location;
        $log2 = $logwindow;
        $log("Loaded " + location);
        
        $log2(loglines.join('\n'));
    }
    if (document.cookie.indexOf('fs2debug=window') != -1) $logtowindow();
    if (document.cookie.indexOf('fs2debug=array') != -1) $logtoarray();
	
	///////////////////////////////////////////////////////////////////////
	// DHTML Helper functions
	///////////////////////////////////////////////////////////////////////
	
	function ge(o,n)
	{
		return o.getAttribute?o.getAttribute(n):o[n];
	}
	// Get an element, by name
	function getel(n)
	{
		var o=document.getElementById?document.getElementById(n):(document.all||document.layers)[n];
		if (!o) o=document.getElementsByName(n)[0];
		return o;
	}
	// Get the parent element/node of an element/node
	function getParent(o)
	{
		return o.parentNode||o.parentElement;
	}
	// Returns a closure (that merely calls the given closure) that can be
	// assigned to DOM objects without IE leaking memory.
	// Using the undoer correctly will prevent significant memory leaks.
	_closures = {};
	function closure2(closure)
	{
		var name = "bws"+Math.random();
		_closures[name] = closure;
		closure = null;
		return {
		maybefn:function()
		{
			if (_closures[name])
				return _closures[name].apply(this, arguments);
		},
		fn:function()
		{
			return _closures[name].apply(this, arguments);
		},
		undo:function()
		{
			delete _closures[name];
		}};
	}
	// Please use closure2 and propagate/use undo functions correctly, instead.
	function closure(fn) { return closure2(fn).fn; }
	// Get information about the document and its visible area
	// _x, _y are the scroll left-top coords
	// _w, _h are the full document width/height
	// _cw, _ch are the visible area's width/height
	// _x2, _y2 are the scroll bottom-right coords
	function getScroll()
	{
		if (!document.all||document.compatMode=='CSS1Compat') var o=document.documentElement;
		if (!o||o.scrollWidth==null) o=document.body||{};
		var $w = jQuery(window);
		var $d = jQuery(document);
		var r={_o:o, _x:$w.scrollLeft(), _y:$w.scrollTop(), _w:$d.width(), _h:$d.height(), _cw:$w.width(), _ch:$w.height()};
		if (!_sizelocked) {rightx=r._w;bottomy=r._h;}
		r._x2=r._x+r._cw;
		r._y2=r._y+r._ch;
		return r;
	}
	// To allow the clicking of multiple submits...
	var eventCount = 0;
	var logevents = document.cookie.indexOf("fs2logevents")!=-1;
	function eventStarted(r)
	{
		++eventCount;
		if (logevents) $log('eS('+r+','+eventCount+')');
	}
	var clickingButtons = false;
	function eventFinished(r)
	{
		if (logevents) $log('eF('+r+','+eventCount+')');
		if (eventCount>1) return --eventCount;
		if (rescanNames) FullNameRescan();
		--eventCount;
		
		if (buttonsclicked.length>0)
		{
			eventStarted('$c');
			clickingButtons=true;
			setTimeout(function(){
				if (eventCount!=1) {eventFinished('$c'); return;}
				var b = buttonsclicked;
				buttonsclicked=[];
				for (var i=0;i<b.length;++i)
				{
					var e = b[i];
					if (e.reset)
					{
						var f = e;
						continue;
					}
					if (!e.click||!e.form)
						continue;
					var f=e.form;
					var name = e.name;
					var olde = e;
					e = document.createElement("input");
					e.type = "text";
					e.name = name;
					e.value = "1";
					e.style.display = "none";
					e._fakeSubmit = true;
					olde.parentNode.insertBefore(e,olde);
					_bwsnavig = olde;
				}
				if (f)
				{
					// Browsers try to prevent us from clicking buttons, for some reason, but
					// we can submit the form...
					if (f.onsubmit) f.onsubmit();
					if (!window.SubmitPartial) f.submit();
				}
				clickingButtons=false;
				eventFinished('$c');
			}, 1);
		}
		
		if (eventCount!=0) return;
		
		var runon=_loadhandlers.length>0;
		if (runon) $log("Running OnLoads");
		// If we end up terminating early, due to exception, we don't want to stop it but we do want to continue running onloads.
		var thingsbreak = setTimeout(function(){eventStarted();eventFinished();},1);
		while (_loadhandlers.length>0)
		{
			var lh = _loadhandlers[0];
			_loadhandlers=_loadhandlers.slice(1);
			lh();
			if (eventCount) {clearTimeout(thingsbreak);$log("Paused OnLoads");return;}
		}
		clearTimeout(thingsbreak);
		if (runon) $log("Done running OnLoads");
	}
	// Click a submit or submit a form (returning true on success)
	// Also avoids nav nags.
	var buttonsclicked=[];
	function $c(o, isTargetTop)
	{
		$log("$c("+(o.id||o.name||o)+")");
		if (typeof o=='string') o = getel(o);
		if (!o.click&&!o.submit) return false;
		if (eventCount>0||buttonsclicked.length>0)
			arraypush(buttonsclicked, o);
		else
		{
			_bwsnavig = o;
			if (o.click)
			{
				if (window.SubmitPartial) SubmitPartial(null,o, isTargetTop);
				else
				{
					if (o.onclick) o.onclick();
					o.click();
				}
			}
			else if (o.onsubmit)
			{
				o.onsubmit();
				if (!window.SubmitPartial) o.submit();
			}
		}
		return true;
	}
	
	///////////////////////////////////////////////////////////////////////
	// Heartbeat functionality - Lets the server know we're still here.
	///////////////////////////////////////////////////////////////////////
	function genhbloc(force)
	{
		if (!force&&hbloc) return;
		var f = document.forms;
		for (var i=f.length;i-->0;)
		{
			var pos;
			if (pos=parLoc(f[i].action,"fs2s"))
				hbloc=f[i].action.substr(0,pos)+"fs2hb="+f[i].action.substr(pos+5);
		}
	}
	_hbfailed = false;
	var _timeoutat;
	var _to;
	function hb()
	{
		genhbloc();
		if (hbloc)
		{
			var _started = +new Date();
			getXML(hbloc+(keyAfterHB?"&keypressed=1":"")+"&random="+Math.random(),function(xml)
			{
				if(xml&&xml.lastChild&&xml.lastChild.nodeName=="redirect")
					location=xml.lastChild.lastChild.nodeValue;
				else if (xml&&xml.firstChild&&xml.firstChild.nodeName=="failure")
					hbfailed();
				else if (xml&&xml.firstChild&&xml.firstChild.nodeName=="success")
				{
					if (_to) {clearTimeout(_to);_to=null;}
					var delay = _started+60*1000-new Date();
					_to = setTimeout(hb, Math.max(0,delay));
					_timeoutat = _started + 120*1000;
				}
				else if (!_timeoutat&&!ignorehbfail)
					alert("Javascript failure");
			});
			keyAfterHB=false;
			if (_timeoutat && +new Date()>_timeoutat)
				setTimeout(function(){if (_timeoutat && +new Date()>_timeoutat) hbfailed();}, 1000);
			else if (!_hbfailed)
				_to = setTimeout(hb,10000);
		}
	}
	
	function hbfailed()
	{
		if (!_hbfailed)
		{
			_hbfailed = true;
			if (window._hbredirect) 
				window.location=_hbredirect; 
			else
				alert("Your session has expired. Choose a toolbar item to start again.");
		}
	}
	
	///////////////////////////////////////////////////////////////////////
	// Page-load handling - tries to run delayed JS functions on page-load,
	//                      but before in-page objects necessarily have.
	//                      (now just uses jQuery)
	///////////////////////////////////////////////////////////////////////
	_loadhandlers = [];
	_LoadDone = _LoadTimer = 0;
	eventStarted('init');
	_LoadObject = document;
	function init()
	{
		if (_LoadDone++) return;
		document.body.className=(document.body.className||'')+' bwsjsisactive';
		getScroll()._o.onkeypress=ddkeypress;
		
		hb();
		
		eventFinished('init');
		
		OnLoad(function(){
			var o={};
			for (var i=0;i<pending.length;++i)
				o[pending[i][0]]=1;
			for (i in o) loadjs(i);
			nowloaded("$");
		});
	}
	$(init);
	
	if (_isIE) document.documentElement.className+=" isie";
	
	// Runs the provided function after the page has finished loading.
	function OnLoad(fn)
	{
		if (!eventCount) fn();
		else arraypush(_loadhandlers, fn);
	}
	
	///////////////////////////////////////////////////////////////////////
	// Easy part of Request-based dynamic HTML
	///////////////////////////////////////////////////////////////////////
	
	// Gets an XMLHTTPRequest object (can reuse old ones)
	_prevXHR=[];
	function geXHR()
	{
		if (_prevXHR.length>0) return _prevXHR.pop();
		try{ return new XMLHttpRequest(); }catch(e){}
		try{ return new ActiveXObject("Msxml2.XMLHTTP"); }catch(e){}
		try{ return new ActiveXObject("Microsoft.XMLHTTP"); }catch(e){}
	}
	// Accepts an XMLHTTPRequest object for reuse
	function puXHR(o){arraypush(_prevXHR,o);}
	
	
	// Do a GET, using XMLHTTPRequest, calling fn(resp, code) on
	// completion or failure, where resp is the response XML (or
	// null, on failure) and code is the status-code received
	// from the server.
	function getText(URL, fn, data, ctype){return getData(URL, fn, data, ctype, "responseText");}
	function getXML(URL, fn, data, ctype){return getData(URL, fn, data, ctype, "responseXML");}
	function getData(URL, fn, data, ctype, propname)
	{
		var XHR = geXHR();
		XHR.open(data?"POST":"GET", URL, true);
		XHR.setRequestHeader('X-FS2JS', '1.0');
		if (data||ctype)
			XHR.setRequestHeader('Content-Type', ctype||'application/x-www-form-urlencoded');
		var ch = closure2(function()
		{
			if (XHR.readyState!=4) return;
			ch.undo(); // This stops the event handler being run again. Simply reassigning onreadystatechange would make IE crash.
			fn&&fn(XHR.status==200?XHR[propname]:null,XHR.status);
			puXHR(XHR);
		});
		XHR.onreadystatechange=ch.maybefn;
		XHR.send(data);
	}
	_timedthings={};
	function runfunctionafter(fn,o,t)
	{
		setTimeout(function(){fn(o);}, t||1);
	}
	_loaded=0;
	_basefns={'0':setaction00, '1':setaction01, '1a':JS01aAction, '2':setaction02, '3':null, '4':JS04setaction, '5':JS05Action, '6':setaction06, '7':setaction07, '9':JS09setaction, 'a':setaction0a, 'b':setaction0b, 'd':setaction0d, 'e':setaction0e, 'f':setaction0f, 'g':setaction0g, 'h':setaction0h, 's':setaction0s, 'v':setaction0v};
	_loadcount=1;
	var iframeid;
	function nowloaded(a,b)
	{
		if (js[a]==1)
		{
			_loadcount--;
			js[a]=2;
			_basefns[a]=b;
		}
		if (_loadcount==0)
		{
			if (!iframeid&&parent!=window)
			{
				try
				{
					parent[/*{*/"JS07Refresh"/*}*/]();
					checkSize();
					setInterval(/*{*/"checkSize()"/*}*/,55);
				}catch(e){}
				if (iframeid) parent[/*{*/"JS07LoadComplete"/*}*/](iframeid);
			}
			$log("Running $$ JS");
			// If we end up terminating early, due to exception, we don't want to stop it but we do want to continue running onloads.
			var thingsbreak = setTimeout(function(){eventStarted();eventFinished();},1);
			for (var i=0;i<pending.length;++i)
			{
				var s = pending[i];
				if (!undoers[s.s]) undoers[s.s]=[];
				arraypush(undoers[s.s], _basefns[s[0]](s));
			}
			pending=[];
			clearTimeout(thingsbreak);
			$log("Done $$ JS");
		}
	}

	var js={$:1, '0':2, '1':2, '1a':2, '2':2, '3':2, '4':2, '5':2, '6':2, '7':2, '9':2, 'a':2, 'b':2, 'd':2, 'e':2, 'f':2, 'g':2, 'h':2, 's':2, 'v':2};
	OnLoad(function(){
		if (_isIE6)
			document.body.onclick = IE6LabelHandler;
	});
	
	var _isIE6 = !!(window.VBArray&&!window.XMLHttpRequest)
	if (_isIE6) document.documentElement.className+=" isie6";
		
	function IE6LabelHandler()
	{
		if (window.lastwashandled) return false;
		var ev = window.event;
		var el = ev.targetElement||ev.srcElement;
		while (el&&!(el.tagName&&el.tagName.toLowerCase()=="label")) el=el.parentElement;
		el=el&&getel(el.htmlFor);
		if (!el||el.tagName.toLowerCase()!="select") return true;
		el.focus();
		return false;
	}
	function ddkeypress(ev)
	{
		$log("keypress!");
		keyAfterHB=true;
		var evv=ev||event;
		var el=evv.srcElement||evv.target;
		
		if ((evv.charCode||evv.keyCode||evv.which)==13&&(el.type=="text"||el.type=="password"))
		{
			if (el.ignoreEnter) return false;
			var f=document.all;
			var i=el.sourceIndex;
			var dest = controldest[el.id]||controldest[el.name];
			if (dest)
			{
				var del = getel(dest);
				if (del.type=='text'||del.type=='password'||del.tagName.toLowerCase()=='textarea') {del.focus();return false;}
			if (del.type=='submit'||del.type=='image') {return !$c(del);}
			}
			if (!f||!i)
			{
				f=el.form.elements;
				for (i=0;i<f.length;++i)
					if (f[i]==el) break;
			}
			while (++i<f.length)
			{
				if (controldest[f[i].id||f[i].name]||controlsrc[f[i].id||f[i].name]||$(f[i]).is(":hidden")) continue;
				if (f[i].type=='text'||f[i].type=='password'||f[i].tagName.toLowerCase()=='textarea') {f[i].focus();return false;}
			if ((f[i].type=='submit'||f[i].type=='image')&&!ge(f[i],'ignore')) {return !$c(f[i]);}
			}
		}
		else if (window[/*{*/"ddkeypress2"/*}*/]) return ddkeypress2(ev);
	}
	// This function adds pending events
	var pending=[];
	var nowloadedwaiting=false;
	function nowloadedload()
	{
		nowloadedwaiting=false;
		nowloaded();
	}
	function $$(s)
	{
		// Allow people to call with as many instructions as they like
		for (var i=1;i<arguments.length;++i) $$(arguments[i]);
		var p = s.split("~");p.s=s;
		pending[pending.length]=p;
		if (!js[p[0]]) loadjs(p[0]);
		if (_loadcount==0&&!nowloadedwaiting)
		{
			nowloadedwaiting=true;
			OnLoad(nowloadedload);
		}
	}
	//$$=function(){}
	var undoers={};
	function $$undo(s)
	{
		// Allow people to call with as many instructions as they like
		for (var i=1;i<arguments.length;++i) $$undo(arguments[i]);
		
		var o = undoers[s];
		if (o==null||!o.length) return;
		o = o.pop();
		if (o) o();
	}
	function loadjs(s)
	{
		if (js[s]) return;
		var o=getel("bwsjs");
		if (!o) return;
		_loadcount+=1;
		js[s]=1;
		var s2=o.src.replace("0.js","0"+s+".js");
		if (document.createElement)
		{
			if (_LoadDone)
			{
				var el=document.createElement('script');
				if (!_isIE)
				{
					el.defer=true;
					el.src=s2;
				}
				el.bws=1;
				document.body.appendChild(el);
				// If we insert a deferred script, here, IE will lose the history
				// when we navigate away from this page (if we're using dynamic
				// updates). Instead, we use a hack.
				if (_isIE) el.src=s2;
			}
			else
				document.write("<script bws=bws src='"+s2+"' type='text/javascript'></script>");
		}
		else o.src=s2;
	}
	var lastsize;
	function checkSize()
	{
		var thissize={};
		var o=getScroll()._o;
		thissize._w=o.scrollWidth;
		thissize._h=Math.min(o.scrollHeight, document.body.offsetHeight+15);
		if (!lastsize||lastsize._w!=thissize._w||lastsize._h!=thissize._h)
			parent["JS07SetSize"](iframeid,thissize._w,thissize._h);
		lastsize=thissize;
	}
	
	function ElsByName(name)
	{
		var a=document.getElementsByName(name);
		var ret = {};
		for (var i=0;i<a.length;++i)
		{
			if (!a[i]._nw)
				a[i]._nw = "nw"+Math.random();
			ret[a[i]._nw] = a[i];
		}
		return ret;
	}
	
	function ElByID(id)
	{
		var o = document.getElementById(id);
		if (o&&!o._nw) o._nw="nw"+Math.random();
		return o;
	}
	
	function getels(n, norecurse)
	{
		var all = {};
		if (norecurse)
			all[n] = 1;
		else
			all = AddDests(n);
		var ret = {};
		for (var i in all)
		{
			var o = ElByID(i);
			if (o)
				ret[o._nw] = o;
			var a = ElsByName(i);
			for (var j in a)
				ret[a[j]._nw] = a[j];
		}
		return ret;
	}
	
	var namemap={};
	function AddNameMap(from, to)
	{
		if (!namemap[from]) namemap[from]={};
		if (namemap[from][to])
			namemap[from][to]++;
		else
			namemap[from][to]=1;
	}

	function RemNameMap(from, to)
	{
		if (!--namemap[from][to])
			delete namemap[from][to];
	}
	
	function AddDests(name, currentset)
	{
		currentset = currentset||{};
		currentset[name]=1;
		if (namemap[name])
			for (var i in namemap[name])
			{
				if (currentset[i]) continue;
				AddDests(i, currentset);
			}
		return currentset;
	}

	var namewatchers = {};
	function AddNameWatch(name, addfn)
	{
		var thisname = "bws"+Math.random();
		if (!namewatchers[name]) namewatchers[name]={};
		
		var rem={};
		
		namewatchers[name][thisname] = function()
		{
			var cur = getels(name);
			
			for (var i in rem)
				if (!cur[rem[i]._nw])
				{
					rem[i].undo();
					delete rem[i];
				}
			for (var i in cur)
				if (!rem[cur[i]._nw])
					rem[cur[i]._nw] = {undo: addfn(cur[i]), _nw:cur[i]._nw};
		};
		
		namewatchers[name][thisname]();
		
		return function()
		{
			for (var i in rem)
				rem[i].undo();
			rem={};
			delete namewatchers[name][thisname];
		};
	}
	
	var rescanNames = false;
	function ElementsUpdated()
	{
		rescanNames = true;
		if (eventCount==0)
		{
			eventStarted('trickupdate');
			eventFinished('trickupdate');
		}
	}
	
	function FullNameRescan()
	{
		rescanNames = false;
		$log("Watchers Begin");
		for (var i in namewatchers)
		{
			var oi = namewatchers[i];
			for (var j in oi) oi[j]();
		}
		$log("Watchers End");
	}
	
	function undo2(fn1, fn2){return function(){fn1();fn2();};}
	
	function AddToObject(obj, thing)
	{
		var id = "ato"+Math.random();
		obj[id]=thing;
		return function(){delete obj[id]};
	}
}












































// This is the base JS that all of the dynamic JS files use
// It adds custom event-handlers and provides other useful
// cross-platform functionality.

//TODO: Replace all underscore variables
//TODO: thingsavoidme->_thingsavoidme

function setaction00(s)
{
	if (!start00done) start00();
	
	if (s[1]!="")
	{
		// 0~parentname~childname~~childname~~childname~~...
		for (var i=2;s[i];i+=2)
			AddNameMap(s[1],s[i]);
		return function(){
			for (var i=2;s[i];i+=2)
				RemNameMap(s[1],s[i]);
		};
	}
	
	if (s[2]!="")
	{
		controlsrc[s[3]]=s[2];
		controldest[s[2]]=s[3];
		return;
	}
	
	return fns00[s[3]](s);
}

var fns00 =
{
	// A field that contains the current/expected scroll position (and names/ids of fields to scroll into view)
	"0":function(s)
	{
		maybeforcescroll();
		return AddToObject(forcescrolls,s[4]);
	},
	// The id of something to scroll into view
	"Vis":function(s)
	{
		maybeforcescroll();
		return AddToObject(scrollobjs,s[4]);
	},
	// selshate
	"SH":function(s)
	{
		if (!_isIE) return function(){};
		//TODO: Make maybehidesels get called more often. Whenever any change happens that might make the size/position of a hidesel change. It should be fairly cheap, at least, though obviously never as cheap as not doing it.
		return AddNameWatch(s[4],function(el)
		{
			selshate(el);
			return function(){selsunhate(el);}
		});
	},
	// Something that tries to scroll with the page when forcescrolls happen, but not otherwise.
	"SW":function(s)
	{
		return AddNameWatch(s[4],function(el)
		{
			var setx = parseFloat(s[5])||0;
			var sety = parseFloat(s[6])||0;
			var $el=$(el);
			var origtop = el.style.top;
			var origleft = el.style.left;
			var offset=setx||sety?{left:setx,top:sety}:$el.offset();
			var posat = function(x,y)
			{
				x = offset.left+x;
				y = offset.top+y;
				objpos(el, x, y);
			};
			var r = getScroll();
			posat(r._x,r._y);
			var undo = ForceScrollWatch(function(ev)
			{
				posat(ev._new._x,ev._new._y);
			});
			return function(){undo();el.style.top=origtop;el.style.left=origleft;};
		});
	},
	// Helptext - prevent clicks and make focus display it.
	"h":function(s)
	{
		var el = getel(s[4]);
		if (el)
		{
			var img = getel(s[5]);
			img.tabIndex = 0;
			var o = {el:el, img:img};
			addobjref(o);
			addEvent(img, "_onfocusin", function(){el.style.display="";});
			addEvent(img, "_onfocusout", function(){el.style.display="none";});
			addEvent(img, "_onmouseclick", function(){return true;});
			el.parentNode.insertBefore(img, el);
			el.style.display="none";
			el.className = el.className.replace("hidefordom","helptext");
		}
	},
	// One. Focus a field and click a button if it gets unfocused?! Seems insane.
	"1":function(s)
	{
		if (s[4]=getel(s[4])) s[4].focus();
		if (s[5])
		{
			var sub = getel(s[5]);
			sub.style.position="absolute";
			sub.style.top="-1000em";
			s[4].onblur = closure(function(){sub.click()});
		}
	},
	// L. Fake-links. That is, buttons (styled like a link) that become a link for those who have JS.
	"l":function(s)
	{
		var link = document.createElement("a");
		link.sub = getel(s[4]);
		link.href="javascript:void $c(getel('"+s[4].replace(/(['\\])/g,"\\$1")+"'))";
		link.className = link.sub.className.split("fakelink").join("").split("genericsubmit").join("");
		link.appendChild(document.createTextNode(link.sub.value));
		link.sub.parentNode.insertBefore(link, link.sub);
		link.sub.style.position="absolute";
		link.sub.style.left="-10000em";
		link.sub.style.top="-20em";
		link.sub.tabIndex=-1;
	},
	// Character counts for textareas. TODO: Fix the detect-welsh-by-Dileu hack
	"5":function(s)
	{
		var ta = getel(s[4]);
		var len = parseInt(s[5]);
		
		// IE does some awful relayout whenever we change the text. The only way to avoid it seems to be to absolutely position the text into place from elsewhere and change that.
		if (_isIE)
		{
			// A gap for the count to be positioned into
			var gap = document.createElement("div");
			gap.innerHTML='&#160;';
			if (ta.nextSibling) ta.parentNode.insertBefore(gap, ta.nextSibling);
			else ta.parentNode.appendChild(gap);
			
			var text = document.createElement("div");
			text.style.position="absolute";
			document.body.appendChild(text);
			var sizeandpos = function(){
				moveobj(text,objleft(gap),objtop(gap));
				gap.style.height = text.offsetHeight+"px";
			};
			var timer = setInterval(sizeandpos,300);
		}
		else
		{
			var text = document.createElement("div");
			if (ta.nextSibling) ta.parentNode.insertBefore(text, ta.nextSibling);
			else ta.parentNode.appendChild(text);
		}
		
		var text = document.createElement("div");
		if (ta.nextSibling) ta.parentNode.insertBefore(text, ta.nextSibling);
		else ta.parentNode.appendChild(text);
		var isWelsh = false;
		for (var i=ta.form.elements.length;i-->0;)
		{
			var te=ta.form.elements[i];
			if (te.type=="submit"&&te.value.substr(0,5)=="Dileu")
				isWelsh=true;
		}
		ta.onchange = ta.onpropertychange = ta.onkeyup = ta.onkeypress = closure(function()
		{
			var tatext = ta.value;
			tatext=tatext.replace(/\r\n|\r|\n/g, "\r\n");
			var left = len-tatext.length;
			if (len>=tatext.length)
			{
				text.className = "textarealength";
				text.innerHTML = isWelsh?
					(left?left+' cymeriad':'Dim cymeriadau')+" (o "+len+") ar &#244;l.":
					((left||'No')+" character"+(left==1?"":"s")+" (of "+len+") left.");
			}
			else
			{
				text.className = "textarealength warning";
				text.innerHTML = isWelsh?
					len+" o gymeriadau a ganiateir. Tynnwch "+-left+" cymeriad"+(left!=-1?"au":"")+" os gwelwch yn dda.":
					(len+" characters allowed. Please remove "+-left+" character"+(left==-1?"":"s")+".");
			}
			if (_isIE) sizeandpos();
		});
		ta.onchange();
		return function(){text.parentNode.removeChild(text);if (gap) gap.parentNode.removeChild(gap);if (timer) clearInterval(timer);};
	},
	// Warns the user when they're navigating away. All parameters optional. First is message. Second is the div within which changes to fields have to occur in order for us to be interested. Third is the div within which pressing a button is entirely uninteresting to us.
	"W":function(s)
	{
		var msg = s[4]||"If you navigate away, your changes will be lost.";
		var div = s[5];
		var par = s[6];
		var rnd = Math.random();
		_bwswatchnavig[_bwswatchnavig.length] = {msg:msg,rnd:rnd,div:div,par:par};
		return function()
		{
			var newnavig = [];
			for (var i=0;i<_bwswatchnavig.length;++i)
				if (_bwswatchnavig[i].rnd!=rnd) newnavig[newnavig.length]=_bwswatchnavig[i];
			_bwswatchnavig = newnavig;
		};
	},
	// Watch a control. If it changes, submit the form. For textboxes and textareas, 1.5s of no typing is required. (0.5s if you unfocus the field)
	"T":function(s)
	{
		// If we're not AJAXy, we're not interested.
		if (!window.SubmitPartial) return function(){};
		return AddNameWatch(s[4], function(el)
		{
			var oldval = ControlValue(el);
			var to;
			var undof;
			var go = function()
			{
				if (to) {clearTimeout(to); to=null;}
				if (oldval==ControlValue(el)) return;
				oldval=ControlValue(el);
				$log("T caused refresh" + ControlValue(el));
				if (undof) {undof(); undof=null;}
				if (el.form) $c(el.form);
			};
			var undoch = OnChange(el, function()
			{
				if (el.tagName=="TEXTAREA"||el.tagName=="textarea"||el.type=="text"||el.type=="secret")
				{
					if (to) clearTimeout(to);
					if (!undof) undof = addEvent(el, "_onfocusout", function(){if (to) clearTimeout(to);to=setTimeout(go,500);});
					to = setTimeout(go, 1500);
				}
				else
					go();
			});
			return function(){
				if (to) clearTimeout(to);
				if (undof) undof();
				undoch();
			};
		});
	},
	
	"Tm":function(s)
	{
		// Since the behaviour is broken, we'll not use it.
		return function(){};
		// If we're not AJAXy, we're not interested.
		if (!window.SubmitPartial) return function(){};
		return AddNameWatch(s[4], function(el)
		{
			var go = function()
			{
				$c(el.form);
			};
			addEvent(el, "_onfocusout", go);
			return function(){
				//addEvent(el, "_onfocusout", go);
			};
		});
	},
	
	// Watching for time fields - only causes update when the apparent value has changed.
	"Tt":function(s){
		// If we're not AJAXy, we're not interested.
		if (!window.SubmitPartial) return function(){};
		return AddNameWatch(s[4], function(elh)
		{
			return AddNameWatch(s[5], function(elm)
			{
				var getVal = function()
				{
					var h = parseInt(ControlValue(elh));
					var m = parseInt(ControlValue(elm));
					if (isNaN(h)||isNaN(m)) return null;
					return h+","+m; // Doesn't have to be human-readable, but has to be identifying.
				};
				var lastVal = getVal();
				var change = function()
				{
					var newVal = getVal();
					if (newVal==lastVal) return;
					$log("Auto-submit because time field changed from "+lastVal+" to "+newVal);
					lastVal=newVal;
					$c(elh.form);
				};
				var undo1 = OnChange(elh, change);
				var undo2 = OnChange(elm, change);
				return function(){undo1();undo2();};
			});
		});
	},
	// Double-click on the first thing to click the second thing (a submit, really).
	"D":function(s)
	{
		var sub = getel(s[5]);
		getel(s[4]).ondblclick=closure(function(){sub.click();return false;});
		sub.style.position = "absolute";
		sub.style.top = "-1000em";
	},
	// Hide the first thing if the second thing has a value or has focus.
	"in":function(s)
	{
		var lbl = getel(s[4]);
		var ctrl = getel(s[5]);
		var chk = closure(function()
		{
			lbl.style.display=(ControlValue(ctrl)||ctrl._hasFocus)?"none":"";
		});

		addEvent(ctrl, "_onfocusin", chk);
		addEvent(ctrl, "_onfocusout", chk);
		OnChange(ctrl, chk);
		chk();
	},
	"tinyMCE":function(s)
	{
		if (!window.tinyMCEInited)
		{
			// TODO: TinyMCE still currently disables AJAXy updates, because we need to call the undoer before the page elements are removed. The change required for that may affect other things, so it's postponed for now. (Maybe if the name-watching stuff gets that feature, we can use name-watches, instead).
			tinyMCEInited=true;
			tinyMCE.init({
				mode : "none",
				plugins : "advimage",
				theme : "advanced", 
				theme_advanced_buttons1 :"bold,italic,underline,numlist,bullist,outdent,indent,separator,link,unlink,image,code",
				theme_advanced_buttons2 : "",
				theme_advanced_buttons3 : "",
				theme_advanced_toolbar_location : "top",
				theme_advanced_toolbar_align : "left",
				theme_advanced_path_location : "bottom",
				content_css : "<%{jsfolder}%>/tiny_mce/css/example_full.css",
				plugin_insertdate_dateFormat : "%Y-%m-%d",
				plugin_insertdate_timeFormat : "%H:%M:%S",
				extended_valid_elements : "hr[class|width|size|noshade],font[face|size|color|style],span[class|align|style]",
				external_link_list_url : "example_link_list.js",
				external_image_list_url : "example_image_list.js",
				flash_external_list_url : "example_flash_list.js",
				media_external_list_url : "example_media_list.js",
				template_external_list_url : "example_template_list.js",
				theme_advanced_resize_horizontal : false,
				theme_advanced_resizing : true,
				nonbreaking_force_tab : true,
				apply_source_formatting : true,
				relative_urls : false,
				convert_urls : false,
				remove_script_host : true,
				strict_loading_mode : true
			});
		}
		
		tinyMCE.execCommand('mceAddControl',true,s[4]);
		// This isn't yet used, since tinyMCE still inhibits AJAXiness, but hey.
		return function(){tinyMCE.execCommand('mceRemoveControl',true,s[4])};
	},
	// Xoomer. Click on the first thing to set the second thing's value to the third parameter. Moves currentxoom class accordingly and alters the fourth and fifth things classes to xoomed+third parameter, to alter page accordingly.
	"X":function(s)
	{
		return AddNameWatch(s[4], function(el)
		{
			return addEvent(el, "_onmouseclick", function(ev)
			{
				getel(s[5]).value=s[6];
				for (var o=el.parentNode.firstChild;o;o=o.nextSibling) o.className=o.className.replace("currentxoom","");
				el.className+=" currentxoom";
				getel(s[8]).className=getel(s[7]).className="xoomed"+s[6];
				return true;
			});
		});
	}
};

function NavigateAway(ev)
{
	// If someone's trying to navigate away, we want to ignore heartbeat failures
	ignorehbfail = true;
	if (_hbfailed) {$log("Navigating away (hb had failed)");return;}
	for (var i=_bwswatchnavig.length;i-->0;)
	{
		var o = _bwswatchnavig[i];
		var isDiff=true;
		var div;
		if (div=getel(o.div))
		{
			var valsbefore = FormValsFrom(div,null,true,true);
			var valsafter = FormValsFrom(div,null,false,true);
			var diff=DiffFromFormVals(valsafter, valsbefore);
			var scrollers = {};
			for (var j in forcescrolls) {if(!forcescrolls.hasOwnProperty(j)) continue;scrollers[forcescrolls[j]]=1;}
			for (var j in diff) {if (!diff.hasOwnProperty(j)||scrollers[j]) delete diff[j];}
			$log("NavCheck: "+ValsAsPostData(diff));
			isDiff=false;
			for (var j in diff) {isDiff=true;break;}
		}
		var p = o.par?getel(o.par):document.body;
		var found = false;
		for (var el=_bwsnavig;el;el=el.parentNode)
			if (el==p)
				found=true;
		if (isDiff&&!found) {$log("Navigating away, maybe");return (ev||event).returnValue=o.msg;}
	}
	$log("Navigating away");
}

function CheckNavig(ev, evo, o)
{
	var el = ev._el;
	if (el.tagName.toLowerCase()=="a" && el.sub)
	{
		el = el.sub;
	}
	_bwsnavig=(el.tagName.toLowerCase()=="input"||el.tagName.toLowerCase()=="submit")?el:null;
}

var scrollobjs = {};
var forcescrolls = {};
var scrollnewx;
var scrollnewy;
var scrolldo = true;
function wantedscrollpos()
{
	for (var i in forcescrolls)
	{
		if (!forcescrolls.hasOwnProperty(i)) continue;
		var o = getel(forcescrolls[i]);
		if (!o) continue;
		var s = o.value.split("~");
		if (s.length>2)
		{
			for (var j=0;j<s.length-2;++j)
				AddToObject(scrollobjs,s[j]);
			o.value=s[s.length-2]+"~"+s[s.length-1];
		}
	}
	var x = null;
	var y = null;
	for (var i in forcescrolls)
	{
		if (!forcescrolls.hasOwnProperty(i)) continue;
		var o = getel(forcescrolls[i]);
		if (!o) continue;
		var s = o.value.split("~");
		x = +s[0]||x;
		y = +s[1]||y;
	}
	
	var r=getScroll();
	x=x||r._x;y=y||r._y;
	// We've got a candidate x,y - now let's go through all of the
	// referenced objects and find their current position and try
	// to make sure they're on-screen.
	for (var i in scrollobjs)
	{
		if (!scrollobjs.hasOwnProperty(i)) continue;
		var o = getel(scrollobjs[i]);
		if (!o) continue;
		var ox = objleft(o);
		var oy = objtop(o);
		var ox2 = ox + (o.offsetWidth||0);
		var oy2 = oy + (o.offsetHeight||100);
		$log("Scrolling "+scrollobjs[i]+" into view, at ("+ox+","+oy+"), from ("+x+","+y+") ");
		
		if (x+r._cw<ox2) x=ox2-r._cw;
		if (x>ox) x=ox;
		if (y+r._ch<oy2) y=oy2-r._ch;
		if (y>oy) y=oy;
	}
	
	$log("Final estimated scrollpos: "+x+","+y);
	return {_x:x,_y:y,_r:r};
}

var forcing=null;
function maybeforcescroll()
{
	if (forcing) return;
	$log("Forcing scroll in 100ms");
	forcing = setTimeout("forcescroll()",100);
}

function forcescroll()
{
	if (eventCount>0)
	{
		$log("Forced scrolling delayed for 100ms");
		setTimeout("forcescroll()",100);
		return;
	}
	forcing=null;
	if (!scrolldo) {$log("Forced scrolling cancelled");return;}
	
	var pos = wantedscrollpos();
	if (pos._x!=pos._r._x||pos._y!=pos._r._y)
	{
		scrollnewx=pos._x;scrollnewy=pos._y;
		scrollTo(pos._x,pos._y);
	}
}

_cancelotherdrag=null;

function replaceEv(el, name, fn)
{
	if (el[name]==fn) return;
	if (el[name])
	{
		if (!el["bwsold"+name]) el["bwsold"+name]=[el[name]];
		else arraypush(el["bwsold"+name], el[name]);
	}
	el[name]=fn;
}

function ddchange(el, force)
{
	if (!el._ddchange) return;
	var newval =  ControlValue(el);
	if (newval==el._lastval&&!force) return;
	var oldval = el._lastval;
	el._lastval=newval;
	
	for (var i in el._ddchange)
		el._ddchange[i](el, oldval, el._lastval, force);
}

function ddglobalchange(ev)
{
	ev = ev || event;
	var targ = ev.srcElement||ev.target;
	if (!targ._ddchange) return;
	
	if (targ.type!="radio")
		return ddchange(targ);
	
	if (targ.form)
	{
		var els = targ.form.elements[targ.name];
		for (var i=0;i<els.length;++i)
			ddchange(els[i]);
	}
	else
	{
		var t = document.getElementsByName(targ.name);
		for (var i=0;i<t.length;++i)
			if (t[i].form==null)
				ddchange(t[i]);
	}
}

function Changed(el)
{
	ddchange(el, true);
}

function iechange()
{
	var ths=this;
	eventStarted();
	setTimeout(function(){ddchange(ths);eventFinished();},1);
}

function InitOnChange(el)
{
	if (el._ddchange) return;
	el._ddchange={};
	el._lastval = ControlValue(el);
	// IE doesn't bubble onchange events but does support onpropertychange
	replaceEv(el,"onpropertychange",iechange);
}

function OnChange(el, fn)
{
	InitOnChange(el);
	
	var name = "bws"+Math.random();
	var clos = closure2(fn);
	el._ddchange[name] = clos.fn;
	return function(){clos.undo();delete el._ddchange[name];};
}

//////////////////////////////////////////////////////////////////////////////////
//Initialisation and behaviour stuff
//////////////////////////////////////////////////////////////////////////////////
var start00done = false;
if (window.addEventListener)
	window.addEventListener("beforeunload", NavigateAway, true);
else
	window.onbeforeunload = NavigateAway;
function start00()
{
	if (start00done) return;
	start00done = true;
	var dbs=getScroll();

	dbs._o.onmousedown=ddmousedown;
	dbs._o.onmouseup=ddmouseup;
	dbs._o.onmousemove=ddmousemove;
	dbs._o.onmouseover=ddmouseover;
	dbs._o.onscroll=ddscroll;
	if (dbs._o.addEventListener)
	{
		dbs._o.addEventListener("focus", ddfocus, true);
		dbs._o.addEventListener("blur", ddblur, true);
	}
	else
	{
		dbs._o.onfocusin = ddfocus;
		dbs._o.onfocusout = ddblur;
	}
//	dbs._o.onresize=ddscroll;
	document.onselectionchange=ddselchange;
	dbs._o.oncontextmenu=ddcancelifhandled;
	dbs._o.onclick=ddcancelifhandled;
	dbs._o.onchange=ddglobalchange;
	addEvent(dbs._o, "_onmouseclick", CheckNavig, 0);
	ddscrolllastx=dbs._x;
	ddscrolllasty=dbs._y;
}

var menus={};
var menusrc={};
var richeds=[];
var sels=[];
var nextactive=null;
var lastactive=null;
var mustval=0;

// attachmenu -> onmouseover, onmouseout

//////////////////////////////////////////////////////////////////////////////////
//Base DHTML stuff
//
//Custom event-handling model (to get around some issues with the various
//browsers), style access and cross-platform object access and movement
//////////////////////////////////////////////////////////////////////////////////

var lastmousestate={};
var lastev={};
var dragdrop=null;
var dragmove=null;

// Remove an object from an array
function arrayremove(a,o)
{
	var ret=[];
	for (var i=0;i<a.length;++i)
		if (a[i]!=o)
			arraypush(ret,a[i]);
	return ret;
}

// Add a class to an object
function addclass(o,c){remclass(o,c,c);}
// Remove a class from an object
function remclass(o,c,c2)
{
	var a=o.className.split(" ");
	a=arrayremove(a,c);
	if (c2) arraypush(a,c2);
	o.className=a.join(" ");
}

var eventobjs=[{}];
// This is used to provide an indirection to get around the IE GC issues.
function addobjref(o)
{
	o._id=eventobjs.length;
	arraypush(eventobjs,o);
}
// Add an event handler to an object
function addEvent(oo,t,fn,o)
{
	if (!oo[t]) oo[t]={};
	var id = "ev"+Math.random();
	oo[t][id]={fn:fn,o:o};
	fn=o=null;
	return function(){delete oo[t][id];}
}
var mmc=0;
function addEventMouseMove(oo,fn,o)
{
	++mmc;
	return undo2(function(){--mmc},addEvent(oo,/*{*/"_onmousemove"/*}*/,fn,o));
}
// Fix the position of an element, after a style change (when display:none, position doesn't get updated properly)
function fixpos(o){
	if (o._x!=null) {(o.style||o).left=o._x-objleft(o.offsetParent)+"px";(o.style||o).top=o._y-objtop(o.offsetParent)+"px";}}
// Move an object
function moveobj(o,x,y) { if (o._x==x&&o._y==y) return; o._x=x; o._y=y; fixpos(o); }
// Move an object, but keep it in the visible area of the window
function objpos(o,x,y)
{
	var r=getScroll();
	if (iframeid)
	{
		r=parent[/*{*/"getScroll"/*}*/]();
		var io=parent[/*{*/"JS07GetPos"/*}*/](iframeid);
		r._x-=io._x;
		r._y-=io._y;
		r._x2-=io._x;
		r._y2-=io._y;
	}
	if (x+o.offsetWidth>r._x2) x=r._x2-o.offsetWidth;
	if (y+o.offsetHeight>r._y2) y=r._y2-o.offsetHeight;
	if (x<r._x) x=r._x;
	if (y<r._y) y=r._y;
	moveobj(o,x,y);
	if (o._selid) hidesels();
}
// Get or set a style
function setstyle(o,st,s){if (o._propertyCache&&o._propertyCache.style[st]===void 0) o._propertyCache.style[st]=(o.style||o)[st]||"";(o.style||o)[st]=s;fixpos(o);if (st=='visibility') if (s=='hidden') hidden(o); else visible(o);}
// Calculate the position of the left of an object
function objleft(o){if (!o) return 0;var x=0;if (o.offsetLeft==null) return o.x||0;while (o){x+=o._buggyLeft||o.offsetLeft;o=o.offsetParent;}return x;}
// Calculate the position of the top of an object
function objtop(o){if (!o) return 0;var y=0;if (o.offsetTop==null) return o.y||0;while (o){y+=o._buggyTop||o.offsetTop;o=o.offsetParent;}return y;}

//TODO:
function hidden(){}function visible(){}

// Special IE select-box handling (to make them wide enough to use)
function IESelIn(el)
{
	return;
	if (!el||!el.tagName||el.tagName.toLowerCase()!='select') return;
	var w = el.offsetWidth;
	// Using a class for all this does not work in IE6.
	el.style.width='auto';
	// Absolute positioning to avoid extra layout issues.
    el.style.position='absolute';
	// If we didn't usefully change the width, undo it.
	if (w>=el.offsetWidth)
		IESelOut(el,true);
}
function IESelOut(el,force)
{
	return;
	if (!el||!el.tagName||el.tagName.toLowerCase()!='select') return;
	if (el._hasFocus&&!force) return;
    el.style.width='';
    el.style.position='';
}

function doEvent(ev,els,evname,others)
{
	eventStarted('ev'+evname);
	for (var i=0;i<els.length;++i)
	{
		var ia=els[i];
		var o = others&&(others[ia.id]||others[ia.name]);
		if (o)
			for (var j in o)
				if (o[j]._fn(ev, ia, o[j]._o))
				{
					eventFinished('ev'+evname);
					return false;
				}
		ia=ia[evname];
		if (ia)
			for (var j in ia)
				if (ia[j].fn(ev,els[i],eventobjs[ia[j].o]))
				{
					eventFinished('ev'+evname);
					return false;
				}
	}
	eventFinished('ev'+evname);
	return true;
}


function StartCache(o)
{
	o._propertycache={_propertycache:o._propertycache,_style:{}};
	for (var i in {width:0,height:0,visibility:0,position:0,top:0,left:0}) o._propertycache._style[i]=o.style[i]||"";
	o._propertycache._x=o._x||null;
	o._propertycache._y=o._y||null;
	return function(){EndCache(o);};
}

function EndCache(o)
{
	var pc=o._propertycache;
	o._propertycache=null;
	for (var i in pc) if (i!="_style"&&i!="_propertycache") o[i]=pc[i];
	for (var i in pc._style) setstyle(o,i,pc._style[i]);
	o._propertycache=pc._propertycache;
}

// Sets a fake parent
var fakePars=[];
function setFakeParent(o,par)
{
	var r=fakePars.length;
	fakePars[r]=par;
	if (o._propertyCache&&o._propertyCache._fakeParent===void 0) o._propertyCache._fakeParent=o._fakeParent||null;
	o._fakeParent=r;
	return r;
}

// Fill the object chain in an event (only done when the event is being passed to a handler)
function getparels(ev,disallowfake)
{
	if (ev._els) return;
	ev._els=[];
	var o=ev._el;
	while (o)
	{
		arraypush(ev._els,o);
		if (o._fakeParent&&disallowfake) return;
		o=o._fakeParent!=null?fakePars[o._fakeParent]:(o.parentNode||o.parentElement);
	}
}
// Try to create a consistent event object
function myevent(ev,ismup)
{
	ev=ev||event;
	var modifiers=ev.modifiers;
	var r={};
	r._el=ev.srcElement||ev.target;
	r._kctrl=modifiers&1==1||ev.ctrlKey;
	r._kalt=modifiers&2==2||ev.altKey;
	r._kshift=modifiers&4==4||ev.shiftKey;
	r._kmeta=modifiers&8==8||ev.metaKey;
	var mx=ev.pageX;
	var my=ev.pageY;
	if (mx==null)
	{
		mx=ev.clientX;my=ev.clientY;
		if (window.navigator.userAgent.indexOf('Opera')==-1&&(!window.ScriptEngine||ScriptEngine().indexOf('InScript')==-1)&&window.navigator.vendor!='KDE')
		{
			var o=getScroll();
			mx+=o._x;my+=o._y;
		}
	}
	r._mx=mx;
	r._my=my;
	var which=ev.which;
	var button=ev.button;
	var keyCode=ev.charCode||ev.keyCode;
	if (which==null)
		if (ismup&&!button) r._button=lastmousestate._button; // Workaround for MacIE bug (mouseup never has a button)
		else r._button=button==1?1:button==2?3:2;
	else if (which==65536) // Workaround for Safari bug (mouseup on submits gives 65536)
		r,_button=lastmousestate._button;
	else if (which-button==1)
		r._button=which;
	else
		r._button=which>2?2:which<2?1:3;
	r._key=keyCode||which;
	return r;
}
function ddmousedown(ev)
{
	// Ignore scrollbar clicks in Firefox
	if (ev&&ev.originalTarget&&window.XULElement&&ev.originalTarget instanceof XULElement) return;
	
	ev=myevent(ev);
	// If the browser supports componentFromPoint (probably IE), this might be a scrollbar event
	// We actually assume there's not going to be a mouseup, which is true for IE, at least.
	if (ev._el.componentFromPoint&&window.event)
	{
		var loc = ev._el.componentFromPoint(event.clientX,event.clientY);
		if (loc!=""&&loc!="outside") return;
	}
	
	if (_isIE) IESelIn(ev._el);
	
	var o=getScroll();
	if (ev._mx>o._x2||ev._my>o._y2) return;
	// TODO: Fix this for IE's multi-button-combination
	if (lastmousestate._button) {lastmousestate._button=ev._button;return;}
	ddrealmousedown(ev);
}
function arrcontains(arr,el)
{
	for (var i=arr.length-1;i>=0;--i)
		if (arr[i]==el) return true;
}
function ddrealmousedown(ev)
{
	eventStarted('ddMouse');
	lastmousestate._button=ev._button;
	lastmousestate._downevent=ev;
	lastmousestate._mx=ev._mx;
	lastmousestate._my=ev._my;
	lastev=ev;
	getparels(ev,true);
	addjssmouseclick(ev);
	var al=captures.length;
	var arr=[];
	while (captured&&!arrcontains(ev._els,captured))
		endovercapture(ev);
	if (al!=captures.length)
	{
		lastmousestate._button=0;
		fakemouseover(ev);
		getparels(ev,true);
		lastmousestate._button=ev._button;
	}

	return doEvent(ev,ev._els,/*{*/"_onmousedown"/*}*/);
}
function ddmouseup(ev)
{
	// Ignore scrollbar clicks in Firefox
	if (ev&&ev.originalTarget&&window.XULElement&&ev.originalTarget instanceof XULElement) return;
	
	lastwashandled=false;
	ev=myevent(ev,true);
	getparels(ev,true);
	addjssmouseclick(ev);
	// We need this because IE has a very strange sequence of events for double-clicks
	// for which the only work around is to turn the second mouseup into a mouseclick.
	if (!lastmousestate._button) ddrealmousedown(ev);

	// This fixes some bug, but it causes another. We need to correctly handle IE's multi-button-combination behaviour. (TODO:!)
	if (ev._button&&lastmousestate._button!=ev._button) {lastwashandled=true;return;}
	if (lastmousestate._dragging)
	{
		doEvent(ev,[lastmousestate._dragging],/*{*/"_ondragdrop"/*}*/);
		if (scrollinterval) scrollinterval=clearInterval(scrollinterval)&&null;
		lastmousestate._button=null;
		lastmousestate._dragging=false;
		lastwashandled=true;
		eventFinished('ddMouse');
		return false;
	}
	else
	{
		lastmousestate._button=null;
		var ret = !(lastwashandled=!doEvent(ev,ev._els,/*{*/"_onmouseup"/*}*/));
		if (ret) ret = !(lastwashandled=!doEvent(ev,ev._els,/*{*/"_onmouseclick"/*}*/,_clickhandlers));
		eventFinished('ddMouse');
		return ret;
	}
}
var lastwashandled=false;
function ddcancelifhandled(ev)
{
	ev=ev||event;
	if (lastwashandled) return false;
	return ddmouseup(ev);
}
var lastoverlist=[];
var nextoverlist=[];
function ddmouseover(ev)
{
	ev=myevent(ev);
	ev._button=null;
	getparels(ev);
	fakemouseover(ev);
}
function fakemouseover(ev)
{
	var inside=!captured;
	if (captured)
		for (var i=ev._els.length;i>=0;--i)
			if (ev._els[i]==captured) inside=true;
	if (inside)
		if (!lastmousestate._dragging&&!lastmousestate._button)
			changeover(ev);
	else
	{
		for (var i=lastoverlist.length-2;i>=0;--i)
		{
			if (lastoverlist[i]==captured)
			{
				loseover(ev,i+1);
				return;
			}
		}
	}
}
function loseover(ev,n)
{
	var to=[];
	for (var i=n;i<lastoverlist.length;++i)
		arraypush(to,lastoverlist[i]);
	changeover(ev,to);
}
function changeover(ev,to)
{
	var from=lastoverlist;
	to=to||ev._els;

	var d=to.length-from.length;
	var els=[];
	for (var i=0;from[i]!=to[d+i];++i)
		arraypush(els,from[i]);
	if (_isIE) IESelOut(els[0]);
	doEvent(ev,els,/*{*/"_onmouseout"/*}*/,_outhandlers);
	els=[];
	for (i=i+d-1;i>=0;--i)
		arraypush(els,to[i]);
	if (_isIE) IESelIn(els[0]);
	doEvent(ev,els,/*{*/"_onmouseover"/*}*/,_overhandlers);

	lastoverlist=to;
}

var ohcount = 0;
function AddHandlerByName(o, n, fn, p)
{
	var id = "oh"+ohcount++;
	if (!o[n]) o[n] = {};
	o[n][id] = {_fn:fn, _o:p};
	return function(){delete o[n][id];};
}

// TODO: Should 'out' be the undo for 'over'?
function AddOverHandlerByName(n, fn, p)
{
	return AddHandlerByName(_overhandlers, n, fn, p);
}

function AddOutHandlerByName(n, fn, p)
{
	return AddHandlerByName(_outhandlers, n, fn, p);
}

function AddClickHandlerByName(n, fn, p)
{
	return AddHandlerByName(_clickhandlers, n, fn, p);
}

var captures=[];
var captured;
function overcapture(ev,par,el,fn)
{
	for (var i=0;i<lastoverlist.length;++i)
		if (lastoverlist[i]==par)
			loseover(ev,i);
	setFakeParent(el,par);
	arraypush(captures,{_o:el,_fn:fn,_d:lastoverlist.length});
	var to=[el];
	for (var i=0;i<lastoverlist.length;++i)
		arraypush(to,lastoverlist[i]);
	changeover(ev,to);
	captured=el;
}
function endovercapture(ev,skipfn)
{
	var capt = captures[captures.length-1];
	var fn=capt._fn;
	var d=capt._d;
	var o=capt._o;
	captures.length--;
	captured=captures[captures.length-1];
	if (captured) captured=captured._o;
	if (fn&&!skipfn) fn(o);
	loseover(ev,lastoverlist.length-d);
}
var scrollinterval=null;
var drageltx=0;
var dragelty=0;
function scrollagain(dx,dy)
{
	var tx=Math.round(drageltx+=dx);
	var ty=Math.round(dragelty+=dy);
	if (tx||ty)
	{
		var ddss=getScroll();
		var ddx=ddss._x;
		var ddy=ddss._y;
		if (_sizelocked&&ddss._x+ddss._cw+tx>rightx) tx=rightx-ddss._cw-ddss._x;
		if (_sizelocked&&ddss._y+ddss._ch+ty>bottomy) ty=bottomy-ddss._ch-ddss._y;
		window.scrollBy(tx,ty);
		drageltx-=tx;
		dragelty-=ty;
		if (ddx==ddss._x&&ddy==ddss._y) scrollinterval=clearInterval(scrollinterval)&&null;
	}
}
var dragscrolls;
function ddmousemove(ev)
{
	ev=myevent(ev);
	getparels(ev);
	if (lastmousestate._dragging&&lastmousestate._dragging._ondragmove)
	{
		if (doEvent(ev,[lastmousestate._dragging],/*{*/"_ondragmove"/*}*/)) return;
		var cx=ev._mx;
		var cy=ev._my;
		var dx=0;
		var dy=0;
		var r=getScroll();
		if (cx>r._x2-150)
		{
			var ddx=r._x2-cx;
			if (ddx<1) ddx=1;
			dx+=(1/ddx)*75;
		}
		if (cx<r._x+150)
		{
			var ddx=cx-r._x;
			if (ddx<1) ddx=1;
			dx-=(1/ddx)*75;
		}
		if (cy>r._y2-150)
		{
			var ddy=r._y2-cy;
			if (ddy<1) ddy=1;
			dy+=(1/ddy)*75;
		}
		if (cy<r._y+150)
		{
			var ddy=cy-r._y;
			if (ddy<1) ddy=1;
			dy-=(1/ddy)*75;
		}
		if (scrollinterval) scrollinterval=clearInterval(scrollinterval)&&null;
		if (dx||dy) {scrollinterval=setInterval("scrollagain("+dx+","+dy+")",1);}
		return false;
	}
	else if (lastmousestate._button&&lastmousestate._downevent)
	{
		var dmx=lastmousestate._mx-ev._mx;
		var dmy=lastmousestate._my-ev._my;
		if (dmx<-2||dmx>2||dmy<-2||dmy>2)
		{
			var lev=lastmousestate._downevent;
			lastmousestate._dragging=lev;

			var handled=false;
			if (lev._el.type!="text"&&lev._el.type!="password")
			{
				doEvent(lev,lev._els,/*{*/"_ondragstart"/*}*/);
			}
			if (!lev._ondragdrop) lastmousestate._downevent=lastmousestate._dragging=null;
		}
		return false;
	}
	else if (mmc)
		doEvent(ev, ev._els, /*{*/"_onmousemove"/*}*/);
}
function ddkeypress2(ev)
{
	ev=myevent(ev);
	getparels(ev);
	return doEvent(ev,ev._els,/*{*/"_onkeypress"/*}*/);
}
function ddselchange(ev)
{
	if (!document.selection) return;
	ev=myevent(ev);
	ev._sel=document.selection.createRange();
	ev._el=ev._sel.parentElement();
	getparels(ev);
	return doEvent(ev,ev._els,/*{*/"_onselchange"/*}*/);
}
var ddscrolllastx;
var ddscrolllasty;
var scrollinputs;
var lastnotedscroll={_x:0,_y:0};
var forcescrollwatch={};
function ForceScrollWatch(fn)
{
	return addEvent(forcescrollwatch,/*{*/"_onforcescroll"/*}*/, fn);
}
function ddscroll()
{
	var r=getScroll();
	var mvx=r._x-ddscrolllastx;
	var mvy=r._y-ddscrolllasty;
	ddscrolllastx=r._x;
	ddscrolllasty=r._y;
	if (scrollnewx!=null)
	{
		var lx = scrollnewx;
		var ly = scrollnewy;
		scrollnewx=scrollnewy=null;
		var oldlns=lastnotedscroll;
		lastnotedscroll={_x:r._x,_y:r._y};
		doEvent({_old:oldlns,_new:lastnotedscroll},[forcescrollwatch],/*{*/"_onforcescroll"/*}*/);
		if (r._x!=lx||r._y!=ly) {setTimeout("forcescroll()",100);return;}
	}
	
	if (eventCount==0)
	{
		scrolldo = false;
		for (var i in forcescrolls)
			getel(forcescrolls[i]).value = r._x+"~"+r._y;
	}
}

_blurry = null;
// Focus events give us most of the information we need. Most browsers allow capturing
// of onfocus; IE allows bubbling of onfocusin. These are nearly equivalent and are
// handled by this function.
function ddfocus(ev)
{
	_blurev = null;
	ev = myevent(ev);
	getparels(ev);
	SetFocus(ev, ev._els);
}
// Most browsers won't give us any way of detecting whether we're losing focus entirely
// or whether it's just moving to another element (IE will). However, we can detect a
// blur without a corresponding focus by using this hack:
function ddblur(ev)
{
	ev = myevent(ev);
	_blurev = ev;
	/*!*/setTimeout("ddlostfocus()",1);/*!*/
}
function ddlostfocus()
{
	if (_blurev) SetFocus(_blurev, []);
}

_lastfocus=[];
_infocus = null;
function SetFocus(ev, els)
{
	// If the focus changes while we're handling focus-changes, we just
	// store the newer focus-chain and the code below will start working
	// towards that, instead.
	_newfocus=els;
	if (_infocus) return;
	_infocus = true;
	
	
	// Handle focus-changes on an element-by-element basis, making sure
	// that focusouts are checked for, first.
	while (_lastfocus[0]!=_newfocus[0])
	{
		var d = _newfocus.length-_lastfocus.length;
		if (_lastfocus[0]!=_newfocus[d])
		{
			_lastfocus[0]._hasFocus = false;
			if (_isIE) IESelOut(_lastfocus[0]);
			doEvent(ev, [_lastfocus[0]], "_onfocusout");
			_lastfocus = _lastfocus.slice(1);
		}
		else if (d>0)
		{
			_lastfocus = [_newfocus[d-1]].concat(_lastfocus);
			_lastfocus[0]._hasFocus = true;
			if (_isIE) IESelIn(_lastfocus[0]);
			doEvent(ev, [_lastfocus[0]], "_onfocusin");
		}
	}
	
	_infocus = false;
}
function GetFocusIDs(upto)
{
	var ret = [];
	for (var i=0;i<_lastfocus.length;++i)
	{
		if (_lastfocus[i].id)
			ret[ret.length]=_lastfocus[i].id;
		if (_lastfocus[i]==upto) return;
	}
	return ret;
}
function TrySetFocus(ids)
{
	var el;
	for (var i=0;i<ids.length;++i)
	{
		var el = getel(ids[i]);
		if (!el) continue;
		try { el.focus(); return el; } catch(e) {}
	}
}

var hackhiders=[];
function hidesels()
{
	for (var i=0;i<hackhiders.length;++i)
	{	
		var o = hackhiders[i];
		o._div.style.width=o.offsetWidth;
		o._div.style.height=o.offsetHeight;
		o._div.style.top=o.offsetTop;
		o._div.style.left=o.offsetLeft;
	}
}
function maybehidesels(){}
var maybehideselswillrun;
if (_isIE) maybehidesels = function()
{
	if (maybehideselswillrun) return;
	maybehideselswillrun=1;
	OnLoad(function(){hidesels();maybehideselswillrun=0;});
};

function selshate(o)
{
	o._selshate=(o._selshate||0)+1;
	if (o._selshate!=1) return;
	if (o._ifr||!_isIE) return;
	o._div = document.createElement("div");
	o._ifr = document.createElement("iframe");
	o._div.style.position="absolute";
	o._div.style.left="0";
	o._div.style.top="0";
	o._div.style.width="1px";
	o._div.style.height="1px";
	o._div.style.filter="alpha(opacity=0)";
	o._ifr.style.width="100%";
	o._ifr.style.height="100%";
	o._ifr.src="javascript:0";
	o._div.appendChild(o._ifr);
	o.parentNode.insertBefore(o._div,o);
	arraypush(hackhiders,o);
	hidesels();
}

function selsunhate(o)
{
	if (--o._selshate) return;
	if (!o._ifr||!_isIE) return;
	o._ifr.parentNode.removeChild(o._ifr);
	o._div.parentNode.removeChild(o._div);
	o._div=o._ifr=null;
	hackhiders=arrayremove(hackhiders,o);
	hidesels()
}

function JS00StandardHide()
{
	if (!this._hidecount++)
	{
		this.style.display="none";
		this._hidecount = 1;
	}
}

function JS00StandardShow()
{
	if (!--this._hidecount)
		this.style.display="";
}

function JS00StandardFocus()
{
	this.focus();
	if (this.type=="text") this.select();
}

function IsControl(el)
{
	return el._getValue&&1||el.tagName=="SELECT"||el.tagName=="TEXTAREA"||el.tagName=="INPUT";
}

function ControlValue(el)
{
	if (el._getValue) return el._getValue();
	if (el.type=="checkbox"||el.type=="radio") return el.checked?el.value:'';
	if (el.tagName!="SELECT"&&el.tagName!="select") return el.value;
	var val = el.selectedIndex==-1?null:el.options[el.selectedIndex].value;
	if (val=="-1"&&el.selectedIndex==0) return '';
	return val;
}

function ControlText(el)
{
	return el._getValue?el._getText():
		el.tagName=="SELECT"?el.options[el.selectedIndex].text:
		el.type=="checkbox"||el.type=="radio"?el.checked?el.value:'':
		el.value;
}

function HideControl(el)
{
	if (!el._hide) el._hide = JS00StandardHide;
	el._hide();
}

function ShowControl(el)
{
	if (!el._show) el._show = JS00StandardShow;
	el._show();
}

function FocusControl(el, backwards)
{
	if (!el._focus) el._focus = JS00StandardFocus;
	el._focus(backwards);
}

function FindControls(el, aout)
{
	aout=aout||[];
	if (!IsControl(el))
		for (var i=0;i<el.childNodes.length;++i)
			FindControls(el.childNodes[i], aout);
	else
		arraypush(aout, el);
	return aout;
}

OnLoad(start00);















































//<!--// Stick something like the following in 0.js:
// if (browser definitely supports AJAT) location.replace(aspxpart+"/FJ/#"+rest);
// (Hopefully with an fs2, so the server doesn't need to think)-->

// Also TODO: these
// Upload fields missing
// FieldPopup return seems to fail.
// Page Timeouts need handling.
// Response timeouts and other errors need handling.

_historyiframe=null;
_historytext=null;
_secrettoken="FS2S"+Math.floor(Math.random()*100000000000.0)+"FS2S";
_watchint=null;
_forcerefresh=false;
_Uploads=[];
_UploadCount=0;
_UploadTokens='';
__UploadDoneFn=null;
_action=null;
_actionel=null;

function StartAJAT(mode)
{
	if (mode=="None") return;
	_forceloaded=1;
	_ignorelocation=1;
	eventStarted('startAJAT');
	if (mode=="OnePageNoBack")
	{
		var mainform;
	
		for (var i=0;i<document.forms.length;++i)
		{
			var f = document.forms[i];
			var els = f.elements;
			if (parLoc(f.action,"fs2s"))
			{
				_action = f.action;
				_actionel = f;
				FixNode(f, true);
				f.onsubmit=SubmitPartial2;
				servervals=lastvals=FormValsFrom(f,null,true);
				mainform = f;
				break;
			}
		}
		if (!_action)
		{
			eventFinished('startAJAT');
			return;
		}
		_action=_action.split("&amp;").join("&");
		var fs2sloc = parLoc(_action,"fs2s")+5;
		_fs2s = _action.substr(fs2sloc,11);
		_action = _action.substr(0, fs2sloc)+_secrettoken+_action.substr(fs2sloc+11);
	}
	SubmitPartial = SubmitPartial2;
	AddNextState("<FS2P fs2s='"+_fs2s+"' page='"+(window.fs2pageid||'initial')+"' responseid='AAAAAAAAAAA'><html><head><title>No title</title><script type='text/javascript' src='blah/0.js' id='bwsjs'></script></head><body></body></html></FS2P>");
	GoToHistory(histpos+1, null, true);
	_abortonpagechange=1;
	eventFinished('startAJAT');
}

var ajaxdone;
if (!ajaxdone)
{
	ajaxdone=true;
	OnLoad(function()
	{
		if (window._ajaxmode) return StartAJAT(_ajaxmode);
	});
	
	var ajatdone=false;
	function $bwsajat()
	{
		$log("Queued bwsajat");
		OnLoad(bwsajatgo);
	}
	
	function bwsajatgo()
	{
		$log("Attempted bwsajat");
		if (ajatdone) return;
		ajatdone=true;
		genhbloc();
		if ((document.cookie+"").indexOf("NoAJAT")>=0||!hbloc) return;
		$log("Accepted bwsajat");
		_fsurl = hbloc+"/FJ/";
		_fsurlhash = "/Panel/";
		_forceloaded=1;
		SubmitPartial=SubmitPartial2;
		eventStarted('AJAXLoad');
		_historyiframe = getel('fs2jsifr');
		_historytext = getel('fs2jsta');
		if (_historytext&&_historytext.value&&!window._firstpage)
		{
			var o = RestoreState();
			if (self._forceloaded) {if (InitialiseExistingHTML()==false) return false;}
			var url = getAJAXURL();
			if (!o._url&&urlhist[url.num-1]==url.rel)
			{
				GoToHistory(url.num,{});
				eventFinished('AJAXLoad');
				_watchint=setInterval(CheckPageURL, 55);
				return;
			}
			if (self._forceloaded) return;
			history.go(-1);
			GoToHistory(url.num,{},true);
			eventFinished('AJAXLoad');
			_watchint=setInterval(CheckPageURL, 55);
			LinkHit(o._url);
			return;
		}
		else if (self._forceloaded)
		{
			if (InitialiseExistingHTML()==false) return false;
			ExtractInitialState();
			return;
		}
		var url = getAJAXURL();
		if (url&&url.abs)
		getText(url.abs, function(txt,code)
		{
			if (txt==null)
			{
				document.cookie = "NoAJAT=1";
				location.refresh();
				//TODO: Support non-forced mode for this
				//location.replace((location+"").split("#").join(""));
				return;
			}
			AddNextState(txt);
			GoToHistory(histpos+1);
			AddToHistory(url.rel, url.preFJ);
			StoreAllState();
			eventFinished('AJAXLoad');
			if (_historyiframe)
				_watchint=setInterval(CheckPageURL, 55);
		});
	};
}

_firstfs2s=null;
_fs2s=null;
//TODO: Test this or remove it. It's for the AJAX mode for rendering a form, in which it can handle in-render page transitions but no other kind.
function InitialiseExistingHTML()
{
	var cause = "This is normally caused by invalid HTML in a branding, a raw HTML field or the page otherwise surrounding the form.";
	var el = getel('bwsjs');
	while (el&&(el.nodeType!=8||el.nodeValue!="FS2PS: Begin head"))
	{
		if (el.tagName!='link'&&el.tagName!='LINK')
			el._donoterase = true;
		el = el.previousSibling;
	}
	if (!el)
	{
		alert("AJAX cannot find start of head. "+cause);
		return false;
	}
	el.parentNode.insertBefore(getel('bwsjs'),el);
	
	var el = getel('bwsjs');
	while (el&&(el.nodeType!=8||el.nodeValue!="FS2PS: End head"))
	{
		if (el.tagName!='link'&&el.tagName!='LINK')
			el._donoterase = true;
		el = el.nextSibling;
	}
	if (!el)
	{
		alert("AJAX cannot find end of head. "+cause);
		return false;
	}
	
	var beginb = [];
	forEveryNode(document.documentElement, function(el){if (el.nodeValue=='FS2PS: Begin body') beginb.push(el);});
	var endb = beginb[0];
	while (endb&&endb.nodeValue!='FS2PS: End body') endb=endb.nextSibling;
	if (beginb.length!=1||!endb)
	{
		debugger;
		alert("AJAX cannot initialise because the DOM is invalid. "+cause);
		return false;
	}

	for (var i=0;i<document.forms.length;++i)
	{
		var f = document.forms[i];
		var els = f.elements;
		for (var j=0;j<els.length;++j)
		{
			if (!els[j].name) continue;
			var n=els[j].name.indexOf("FS2ActionUrl");
			if (n==-1||n!=els[j].name.length-"FS2ActionUrl".length) continue;
			_actionel = els[j];
			_action = _actionel.value;
			break;
		}
		if (!_action&&parLoc(f.action,"fs2s"))
		{
			_action = f.action;
			_actionel = f;
		}
		if (_action)
		{
			for (var k=0;k<els.length;++k)
				FixNode(els[k], true);
			f.onsubmit=SubmitPartial2;
			servervals=lastvals=FormValsFrom(f);
			break;
		}
	}
	_action=_action.split("&amp;").join("&");
	var fs2sloc = parLoc(_action,"fs2s")+5;
	_firstfs2s = _fs2s = _action.substr(fs2sloc,11);
	_action = _action.substr(0, fs2sloc)+_secrettoken+_action.substr(fs2sloc+11);
}

function escapeHTML(txt)
{
	return txt.replace(/&/g,"&amp;").replace(/</g,"&lt;");
}

function escapeAttrSingle(txt)
{
	return escapeHTML(txt).replace(/'/g,"&apos;");
}

function GetOuterHTML(el)
{
	var html = el.outerHTML;
	if (html) return html;
	
	if (el.nodeType==8) return "<!--"+el.nodeValue+"-->";
	if (el.nodeType==3) return escapeHTML(el.nodeValue);
	var elname = el.nodeName.toLowerCase();
	var html = "<"+el.nodeName;
	var attrs = el.attributes;
	var isTA = elname=="textarea";
	for (var i=0;i<attrs.length;++i)
	{
		var attr=attrs[i];
		if (!isTA||attr.nodeName!='value')
			html+=" "+attr.nodeName+"='"+escapeAttrSingle(attr.nodeValue)+"'";
	}
	if ({hr:1,br:1,img:1,input:1}[elname]) return html+" />";
	return html+">"+(isTA?el.value:el.innerHTML)+"</"+el.nodeName+">";
}
	
function ExtractInitialState()
{
	// NOTE: This method has to run eventFinished('AJAXLoad') before returning.
	var head = "";
	var body = "";
	var begin = /FS2PS: Begin (.+)/;
	forEveryNode(document.documentElement, function(el)
	{
		if (el.nodeType!=8) return;
		var m = el.nodeValue.match(begin);
		if (!m||m[1]!='head'&&m[1]!='body') return;
		
		for (var end=el.nextSibling; end&&end.nodeValue!='FS2PS: End '+m[1]; end=end.nextSibling)
		{
			FixNode(end, true);
			if (!end.bws&&end.id!="fs2jsdiv"&&end.id!='bwsjs'&&end.id!="bwsload"&&(!end.text||end.text.indexOf("bws_oldonload")==-1))
				if (m[1]=='head')
					head+=GetOuterHTML(end);
				else
					body+=GetOuterHTML(end);
		}
	});
	
	AddNextState("<FS2P fs2s='"+_fs2s+"' page='initial'><html><head><title>"+document.title+"</title><script type='text/javascript' src='blah/0.js' id='bwsjs'></script>"+head+"</head><body>"+body+"</body></html></FS2P>");
	GoToHistory(histpos+1, null, true);
	_forcerefresh=false;
	var url = getAJAXURL();
	AddToHistory(url.rel);
	StoreAllState();
	eventFinished('AJAXLoad');
	if (_historyiframe)
		_watchint=setInterval(CheckPageURL, 55);
}

function UploadError(type, text, ifr)
{
	alert("Upload error of type "+type+":\n"+text);
	UploadDone(null,ifr);
}

function UploadDone(id, ifr)
{
	if (!_Uploads[ifr._upid]) return;
	if (id)
		_UploadTokens=(_UploadTokens?_UploadTokens+","+id:id);
	_UploadCount--;
	delete _Uploads[ifr._upid];
	//if (_isIE) history.go(-1);
	if (_UploadCount!=0) return;
	eventFinished('Upload');
	_UploadDoneFn();
}

function MaybeClickButton(frm, ths)
{
	if (ths.disabled) return;
	return !SubmitPartial(frm,ths);
}

function KeySubmitPartial(ev, evo)
{
	if (ev._key!=13&&ev._key!=32) return;
	return !SubmitPartial(null, evo);
}

function ForceIERefresh()
{
	var myfr = document.createElement('iframe');
	myfr.src='javascript:""';
	myfr.style.position='absolute';
	myfr.style.left=myfr.style.top='0';
	myfr.style.width=myfr.style.height='100%';
	document.body.appendChild(myfr);
	document.body.removeChild(myfr);
}

var AJAXCallbacks={done:{}};

var SubmitPartial;
// Submit a form, AJAT-style (only send changed items)
function SubmitPartial2(frm, ths, isTargetTop)
{
	$log("AJAX starting");
	if (window.tinyMCE&&tinyMCE.triggerSave) try{tinyMCE.triggerSave();}catch(e){}
	// If we're called by Sharepoint, we don't want to handle it.
	if ((arguments.callee.caller+'').indexOf('__doPostBack')!=-1) return;
	ths=ths||this;
	if (eventCount>(clickingButtons?1:0))
	{
		$log("AJAX postponed, JS busy");
		$c(ths);
		return false;
	}
	eventStarted('AJAXSubmit');
	// What form changes are there?
	frm = frm||ths.form||ths;
	var origurl = ((_action||(frm.getAttribute?frm.getAttribute("action"):frm.action))+"").split(_secrettoken).join(getContent('fs2s'));
	var url = getAJAXURL(origurl)||{abs:origurl,rel:origurl};
	
	document.body.className+=" ajaxbusy";
	if (_isIE) ForceIERefresh();
	
	_UploadCount=0;
	for (var i in _Uploads)
	{
		if (!_Uploads.hasOwnProperty(i)) continue;
		if (url.abs.indexOf('#')!=-1) url.abs=url.abs.substring(0,url.abs.indexOf('#'));
		++_UploadCount;
		_Uploads[i].contentWindow.document.forms[0].action=url.abs+"&FS2AJAXPartialForm";
		try{_Uploads[i].contentWindow.document.forms[0].submit();}catch(e){--_UploadCount;delete _Uploads[i];}
	}
	if (_UploadCount>0)
	{
		$log('AJAX handling upload fields');
		eventStarted('Upload');
		eventFinished('AJAXSubmit');
		_UploadTokens='';
		_UploadDoneFn=function(){$log('AJAX finished handling upload fields');SubmitPartial2(frm, ths)};
		return false;
	}
	
	$log("AJAX getting form values");
	var subs = [];
	var vals = FormValsFrom(frm, subs);
	var diff=DiffFromFormVals(vals, servervals);
	
	AddFormVals(lastvals, diff);
	AddDateTimeFields(diff, lastvals);
	
	if (ths.type=="submit"||ths.type=="image"||ths.type=="button")
		arraypush(subs, ths);
		
	for (var i=0;i<subs.length;++i)
		AddFormVal(diff, subs[i].name, subs[i].value);
		
	var append=_UploadTokens?("&FS2AJAXPartialForm="+_UploadTokens):"";
	_UploadTokens='';
	
	var postdata = ValsAsPostData(diff)+append;
	
	$log("AJAX submitting request");
	
	getText(url.abs, function(txt,status)
	{
		$log("AJAX got response");
		if (txt.indexOf("<!--AJAXForceMainPageSubmit-->")!=-1)
		{
			$log("AJAX forcing main page submission");
			var newact = _actionel.action||_actionel.value;
			var actfs2sp = parLoc(newact,"fs2s")+5;
			var respfs2sp = txt.indexOf("fs2s='")+6;
			_fs2s=txt.substr(respfs2sp,11);
			newact=newact.substr(0,actfs2sp)+_fs2s+newact.substr(actfs2sp+11);
			for (var i=0;i<document.forms.length;++i)
			{
				var f = document.forms[i];
				var oact = f.action;
				f.action = f.action.replace(_firstfs2s,_fs2s).replace(_secrettoken,_fs2s);
				if (oact!=f.action) break;
			}
			
			if (!_actionel.action) _actionel.value = newact;
			
			if (isTargetTop) {
				_actionel.target = "_top";
			}
			
			var _actionform = _actionel.action?_actionel:_actionel.form;
			if (IsInDocument(_actionform)) _actionform.submit();
			else f.submit();
			return;
		}
		$log("AJAX handling HTML");
		if (AddNextState(txt, diff, isTargetTop)==1) return;
		$log("AJAX altering page");
		var r = getScroll();
		if (GoToHistory(histpos+1)==1) return;
		scrollnewx=-1;
		scrollTo(r._x,r._y);
		scrolldo=true;
		AddToHistory(url.rel);
		$log("AJAX storing extra state");
		StoreAllState();
		$log("AJAX calling callbacks");
		for (var i in AJAXCallbacks.done) AJAXCallbacks.done[i](ths);
		$log("AJAX removing busy cursor");
		document.body.className=(document.body.className+'').replace(/\s*ajaxbusy/g,"");
		// IE doesn't update the cursor. Sticking an iframe under the cursor briefly seems to work.
		if (_isIE) ForceIERefresh();
		$log("AJAX Finishing");
		eventFinished('AJAXSubmit');
		_bwsnavig = null;
		$log("AJAX Finished");
	}, postdata);
	
	return false;
}

// Handles the default action for all links
function LinkHit(url)
{
	if (_forceloaded) return location=url;
	eventStarted('LinkHit');
	url = getAJAXURL(url);
	getText(url.abs, function(txt,status)
	{
		AddNextState(txt);
		GoToHistory(histpos+1);
		AddToHistory(url.rel);
		StoreAllState();
		eventFinished('LinkHit');
	});
}

function GetCurrentTitle()
{
	return getContent(getContent('page')+"-title");
}

function IsInDocument(el)
{
	while (el&&el!=document.body) el = el.parentNode;
	return !!el;
}

// Updates the location and the page.
function AddToHistory(url)
{
	if (_historyiframe)
	{
		_historyiframe.contentWindow.document.open();
		_historyiframe.contentWindow.document.write("<html><head><title>"+GetCurrentTitle()+"</title><script type='text/javascript'>"+(window._fsurl?"parent._fsurlhash=":"parent.location.hash=")+_serialise(histpos+url)+"</script></head><body><h1>"+histpos+url+"</h1></body></html>");
		_historyiframe.contentWindow.document.close();
	}
	else
	{
		if (window._fsurlhash)
			_fsurlhash = histpos+(url||'/FJ/');
		else if (!window._ignorelocation)
			location.hash = histpos+url;
	}
	
	urlhist[histpos-1]=url;
}

// Monitoring of the address bar - tells us when the user has modified it, so we can take over the world.
function CheckPageURL()
{
	if (eventCount>0) return;
	var url = getAJAXURL();
	if (urlhist[histpos-1]==url.rel&&histpos==url.num) return;
	if (urlhist[url.num-1]==url.rel) return GoToHistory(url.num);
	
	StoreAllState({_url:url.abs});
	document.location=url.preFJ+"/FJ/?GoBack="+2;
	eventStarted('CheckPageURL');
}

function StoreAllState(extras)
{
	if (!_historytext) return;
	var o = {_secrettoken:_secrettoken, _content:content, _hist:hist, _urlhist:urlhist, _valhist:valhist, _lastvals:lastvals, _servervals:servervals, _histpos:histpos};
	for (var i in extras)
		o[i]=extras[i];
	_historytext.value="("+_serialise(o)+")";
}

function RestoreState()
{
	var o = eval(_historytext.value);
	_secrettoken = o._secrettoken;
	content = o._content;
	hist = o._hist;
	urlhist = o._urlhist;
	valhist = o._valhist;
	lastvals = o._lastvals;
	servervals = o._servervals;
	histpos = o._histpos;
	return o;
}

var content={page:{arr:[],pos:0}};
var hist=[];
var histpos=0;
var urlhist=[];
var valhist=[];
var lastvals={};
var servervals={};

var fs2sre = /^<FS2P fs2s='(.*?)'/;
var fullpagere = /^<FS2P fs2s='(.*?)' page='(.*?)'(?: responseid='(.*?)')?>[\s\S]*?<html[\s\S]*?<head[\s\S]*?<title>([\s\S]*?)<\/title>([\s\S]*?)<script type='text\/javascript' src='[^']*\/0.js' id='bwsjs'><\/script>([\s\S]*?)<\/head>[\s\S]*?<body[\s\S]*?>([\s\S]*?)<\/body[\s\S]*?<\/FS2P>$/;
var partpagere = /^<FS2P fs2s='(.*?)' page='(.*?)'(?: responseid='(.*?)')?>([\s\S]*?)<\/FS2P>$/;
// This is used for aborting a changing in GoToHistory (when a mismatch is detected)
var lastresponseid;
function AddNextState(txt, sentvals, isTargetTop)
{
	var sects = hist[histpos] = {};
	
	var vals = {};
	
	if (!txt) {alert("Error 75.");return;}
	
	var fs2sm = fs2sre.exec(txt);
	if (!fs2sm)
	{
		var div = document.createElement("div");
		var h1 = document.createElement("h1");
		h1.innerHTML="Unrecognised response from server";
		div.appendChild(h1);
		var inner = document.createElement("div");
		inner.innerHTML=txt.replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/\n/g,"<br/>");
		div.appendChild(inner);
		div.style.position="absolute";
		div.style.width="90%";
		div.style.height="90%";
		div.style.left="1em";
		div.style.right="1em";
		div.style.top="1em";
		div.style.bottom="1em";
		div.style.backgroundColor="white";
		div.style.border="2px solid red";
		div.style.overflow='scroll';
		var odiv = document.createElement('div');
		odiv.appendChild(div);
		document.body.innerHTML=odiv.innerHTML;
		return;
	}
	txt = txt.split(fs2sm[1]).join(_secrettoken);
	AddNextPart('fs2s', fs2sm[1], sects);
	
	var page = fullpagere.exec(txt);
	if (!page) page = partpagere.exec(txt);
	if (!page) {alert("Error 77.");return;}
	lastresponseid=page[3];
	
	if (page.length<=5&&window._forceloaded)
	{
		for (var i=content['page'].arr.length;i-->0;)
			if (content['page'].arr[i]==page[2])
				break;
		if (i<0)
			page[2]='initial';
	}
	
	if (page[2]!=content.page.arr[content.page.pos-1])
	{
		AddNextPart('page', page[2], sects);
	}
	
	if (page.length>5&&window._abortonpagechange||/tinyMCE|gmaps/.exec(txt)||window.tinyMCEInited||page[2]=='initial'&&window.fs2pageid)
	{
		$log("AJAX ABORT. page[2]="+page[2]+", fs2pageid="+window.fs2pageid);
		setTimeout(function() {
		    if (isTargetTop
		        || (document.forms.length > 0 && document.forms[0].target == "_top")) {
		        window.top.location = ((_action || (frm.getAttribute ? frm.getAttribute("action") : frm.action)) + "").split(_secrettoken).join(page[3]);
		    } else {
		        document.location = ((_action || (frm.getAttribute ? frm.getAttribute("action") : frm.action)) + "").split(_secrettoken).join(page[3]);
		    }
		},1000);
		return 1;
	}
	
	if (page.length>5)
	{
		AddNextPart(page[2]+'-title', page[4], sects);
		AddNextPart(page[2]+'-head', page[5]+page[6], sects);
		AddNextPart(page[2], page[7], sects, vals);
	}
	else
	{
		var repre = /<!--FS2PS: Replacing (.*?)-->/g;
		
		var str = page[4];
		
		var replacing = null;
		var lastend = 0;
		var m=repre.exec(str);
		while (m)
		{
			var m2=repre.exec(str);
			var endpos = m2?m2.index:str.length;
			var startpos = m.index+m[0].length;
			AddNextPart(m[1], str.substring(startpos,endpos), sects, vals);
			m=m2;
		}
	}
	
	AddFormVals(lastvals, vals);
	if (sentvals) {AddFormVals(sentvals,vals);vals=sentvals;}
	// TODO: This needs to correctly handle incomplete sets of radio buttons.
	valhist[histpos] = vals;
}
function NewArrayFor(name)
{
	var arr=[];
	if (!content[name]) content[name]={arr:[], pos:0};
	content[name].arr.length = content[name].pos;
	content[name].arr[content[name].pos] = arr;
	return arr;
}
function AddNextPart(name,str,dest,vals)
{
	if (vals)
	{
		// If the caller is interested in form values, there might also be links to take care of.
		if (!_forceloaded) str = InstrumentLinks(str);
		GetValues(str, vals);
	}
	
	dest[name]=1;
	
	var splitter=/<!--FS2PS: (Begin|End) (.*?)-->/g;
	var lastpos = 0;
	var arr=NewArrayFor(name);
	var stack=[];
	var m;
	while (m = splitter.exec(str))
	{
		arraypush(arr, str.substring(lastpos, m.index));
		lastpos = m.index+m[0].length;
		
		if (m[1]=="Begin")
		{
			arraypush(arr,m[2])
			arraypush(stack,arr);
			arr=NewArrayFor(m[2]);
			if (dest[m[2]]) alert("Error 198");
			dest[m[2]] = 1;
		}
		else
			arr = stack.pop();
	}
	arraypush(arr,str.substr(lastpos));
	
	if (stack.length>0) alert("Error 682.");
	
	return dest;
}
var focusTxt = null;
function GoToHistory(n, touched, delayDraw)
{
	var origScroll = getScroll();
	
	if (!touched)
	{
		touched = {};
		var pagethen = getContent('page');
		while (histpos<n||histpos>n)
		{
			if (histpos>n) --histpos;
			valhist[histpos] = AddFormVals(servervals,valhist[histpos]);
			for (var i in hist[histpos])
			{
				touched[i]=1;
				content[i].pos+=(histpos<n?1:-1);
			}
			if (histpos<n) ++histpos;
		}
	}
	if (delayDraw) {_forcerefresh=true;return;}
	var pagenow = getContent('page');
	if (pagethen!=pagenow||_forcerefresh) touched.title=touched.head=touched.body=1;
	_forcerefresh=false;
	
	if (touched.page&&!window._forceloaded)
		document.title = getContent(pagenow+"-title");
	
	var ends = getTouchedEnds(touched);
	
	var lostFocus = false;
	
	// This element is created at the limits of the document size to make sure the scroll area doesn't ever become smaller while we're playing with the page. It should make random scrolling happen less, but some scrolling still happens, and I've had to put a workaround into SubmitPartial2
	var scrpos = getScroll();
	var noscr = document.createElement("div");
	noscr.style.position="absolute";
	noscr.style.left=scrpos._w-1+"px";
	noscr.style.top=scrpos._h-1+"px";
	noscr.style.width="1px";
	noscr.style.height="1px";
	noscr.innerHTML="#160;";
	document.body.appendChild(noscr);
	
	for (var i=0;i<ends.length;++i)
	{
		var end = ends[i];
		for (var el = end.start.nextSibling; el&&el!=end.end; el=end.start.nextSibling)
		{
			var objs = [];
			forEveryNode(el, function(el){
				if ((el.tagName=="script"||el.tagName=="SCRIPT")&&el.text)
				{
					var eltext = el.text;
					var m = /\s*<!--\s*([\s\S]*)/.exec(eltext);
					eltext = m&&m[1]||eltext;
					eltext = eltext.split("$$(");
					if (eltext.length>1)
						eval(eltext.join("$$undo("));
				}
				if (el.tagName=="object"||el.tagName=="OBJECT")
					objs.push(el);
			});
			
			// We need to hide objects directly, because otherwise they might be visibly left behind. This was certainly the case for IE6, IE7, IE8. It was sporadic, though. Also, it may have been related to the bug whereby we were accidentally using the non-recursive version of removeChild.
			for (var j=0;j<objs.length;++j)
				objs[j].style.display="none";
			// If a select-box has focus when it is destroyed, IE is likely to eventually crash.
			// Elements with focus would get loss-of-focus events after they've been removed, anyway.
			if (el._hasFocus)
			{
				lostFocus = GetFocusIDs(el);
				if (!focusTxt)
				{
					if (_isIE && _browserVersion<9)
						focusTxt = document.createElement("<input type='text'>");
					else
					{
						focusTxt = document.createElement('input');
						focusTxt.type='text';
					}
					focusTxt.style.position="absolute";
					document.body.appendChild(focusTxt);
					focusTxt.style.zIndex="-200";
					focusTxt.style.filter="alpha(opacity=1)";
					focusTxt.tabIndex=-1;
					try {
						focusTxt.style.clip="rect(0 0 1 1)";
					} catch (e) {
						focusTxt.style.clip="rect(0, 0, 1px, 1px)";
					}
				}
				moveobj(focusTxt,origScroll._x,origScroll._y);
				focusTxt.focus();
			}
			if (el.id!="fs2jsdiv")
				if (el._donoterase)
				{
					el.disabled = true;
					end.start = el;
				}
				else
					el.parentNode.removeChild(el);
			else
				end.start = el;
		}
		if (!end.end)
		{
			//alert("Unbalanced node: "+end.name+"\nI'll compensate, but please show Grant how you made this message appear.");
			// TODO: Rather than redrawing the whole page, this could try just
			//       redrawing the first containing PageSection that fully
			//       encloses el and end.
			// (Passing in touched causes a page redraw - dodgy but true)
			document.body.removeChild(noscr);
			if (window._abortonpagechange)
			{
				alert("AJAX forced refresh for "+end.start.nodeValue);
				setTimeout(function(){document.location=((_action||(frm.getAttribute?frm.getAttribute("action"):frm.action))+"").split(_secrettoken).join(lastresponseid);},1000);
				$log('AJAX ABORT for '+end.start.nodeValue);
				return 1;
			}
			return GoToHistory(n,touched);
		}
		
		var nodes = importHTML(getContent(end.name=='head'?pagenow+'-head':end.name=='body'?pagenow:end.name));
		while (nodes.length)
		{
			var nel = nodes[0];
			end.end.parentNode.insertBefore(nel, end.end);
			FixNode(nel);
		}
	}
	if (lostFocus)
	{
		var rr = getScroll();
		moveobj(focusTxt,rr._x,rr._y);
		TrySetFocus(lostFocus);
	}
	else if (focusTxt)
	{
		document.body.removeChild(focusTxt);
		focusTxt = null;
	}
	document.body.removeChild(noscr);
}

// Given a set of pagesection identifiers (for which content needs replacing), find all the start and end comment markers, but not the ones that are entirely surrounded by another of interest.
function getTouchedEnds(touched)
{
	var ret = [];
	var within = null;
	var begin = /FS2PS: Begin (.+)/;
	forEveryNodeMatching(document.documentElement, function(el)
	{
		if (el.nodeType!=8) return !within;
		if (!within)
		{
			var m = el.nodeValue.match(begin);
			if (m&&touched[m[1]]) {within = m[1];ret[ret.length]={name:within,start:el};}
		}
		else if (el.nodeValue=='FS2PS: End '+within) {within = null;ret[ret.length-1].end=el;}
		
		return !within;
	});
	return ret;
}

// Even better, IE might not bother making imported stylesheets from freshly-inserted one work. We'll manually add links to match. Adding them before the affected node means the priority order should match.
function FixIEImports(el)
{
	if (!_isIE) return;
	if (el.styleSheet&&el.styleSheet.imports.length>0)
	{
		for (var i=0;i<el.styleSheet.imports.length;++i)
		{
			var link = document.createElement("link");
			link.type=el.type;
			link.rel=el.rel;
			var imp = el.styleSheet.imports[i];
			var impurl = CombineURLs((el.href||location+''),imp.href+'');
			link.href = impurl;
			el.parentNode.insertBefore(link,el);
			FixIEImports(link);
		}
	}
}

function FixNode(el, initial)
{
	// IE and Firefox occasionally mess up stylesheets unless you do this.
	if (!initial&&(el.tagName=="link"||el.tagName=="LINK")&&!el.disabled)
	{
		el.disabled=!(el.disabled=true);
		if (_isIE) FixIEImports(el);
	}
	
	if (!initial) UseFormVals(lastvals,el);
	var scripts=[];
	var $el=$(el);
	
	$el.find('script').add($el.filter('script')).each(function(i,el){
		if (initial&&(el.bws||el.getAttribute('bws')))
			el.parentNode.removeChild(el, true);
		else
			arraypush(scripts,el);
	});
	$el.find('form').add($el.filter('form')).each(function(i,el){
		el.onsubmit=SubmitPartial2;
	});
	$el.find('button').add($el.filter('button')).each(function(i,el){
		addEvent(el,"_onmouseclick", MaybeClickButton);
		addEvent(el,"_onkeypress", KeySubmitPartial);
	});
	// TODO: Find out whether we can safely set default* rather than _default*
	$el.find('input').add($el.filter('input')).each(function(i,el){
		if (el.type=="submit"||el.type=="image"||el.type=="button")
		{
			addEvent(el,"_onmouseclick", MaybeClickButton);
			addEvent(el,"_onkeypress", KeySubmitPartial);
		}
		el._defaultChecked=el.checked;
		el._defaultValue=el.value;
	});
	if (!initial)
	{
		$el.find('option').each(function(i,el){
			el._defaultSelected=el.selected;
		});
		$el.find('textarea').add($el.filter('textarea')).each(function(i,el){
			el._defaultValue=el.value;
		});
	}
	
	// Firefox will already have run scripts - IE and Chrome need some prodding
	if (!initial&&!DoInnerHtmlScriptsRun())
		for (var i=0;i<scripts.length;++i)
		{
			var el = scripts[i];
			if (el.id=="bwsjs"||el.text=="$bwsajat()") continue;
			var par = el.parentNode;
			var next = el.nextSibling;
			par.removeChild(el);
			InsertScript(par,next,Script(el.text,el.src,el.id,el.name));
		}
	else if (!initial&&DoesLastInsertedScriptFail()&&scripts.length>0) {
		var el = scripts[scripts.length-1];
		if (!(el.id=="bwsjs"||el.text=="$bwsajat()")) {
			var par = el.parentNode;
			var next = el.nextSibling;
			par.removeChild(el);
			InsertScript(par,next,Script(el.text,el.src,el.id,el.name));
		}
	}
	
	ElementsUpdated();
}

function Script(text,src,id,name)
{
	var scr = document.createElement('script');
	if (text) scr.text = text;
	else scr.src = src;
	if (name) scr.name = name;
	if (id) scr.id = id;
	return scr;
}

function DoNothing(){}
function EventFinishedWhenLoaded()
{
	if (this.alldone) return;
	if (this.readyState=="complete"||this.readyState=="loaded"||!this.readyState)
	{
		this.alldone=true;
		this.onreadystatechange=DoNothing;
		this.onload=DoNothing;
		eventFinished('ScriptLoad '+(this.src||this.text.substr(0,30)));
	}
}

function InsertScript(par, next, el)
{
	eventStarted('ScriptLoad'+(el.src||el.text.substr(0,30)));
	// If the script has a source, that means the browser is going to delay the running. We need to register the event handler _before_ we add it to the document
	if (el.src)
	{
		el.onreadystatechange=EventFinishedWhenLoaded;
		el.onload=EventFinishedWhenLoaded;
	}
	try
	{
		if (next)
			par.insertBefore(el,next);
		else
			par.appendChild(el);
	}
	catch (e) {}
	if (!el.src) eventFinished('ScriptLoad'+(el.src||el.text.substr(0,30)));
}

// Well, do they? Caches the result, since it's a per-browser thing
var doTheyTested;
var doTheyRun;
var doesTheLastOneRun;
function DoInnerHtmlScriptsRun()
{
	if (doTheyTested) return doTheyRun;
	var div= importHTML("<div><script type='text/javascript'>doTheyRun=true;<\/script><script type='text/javascript'>doesTheLastOneRun=true;<\/script></div>")[0];
	document.body.appendChild(div);
	document.body.removeChild(div);
	doTheyTested=true;
	return doTheyRun;
}

function DoesLastInsertedScriptFail() // in FF4, they probably don't (but earlier scripts do)
{
	return DoInnerHtmlScriptsRun() && !doesTheLastOneRun;
}

function getContent(name)
{
	var ret = '';
	var arr = content[name].arr[content[name].pos-1]||[];
	for (var i=0;i<arr.length;i+=2)
	{
		ret+=arr[i];
		if (arr[i+1]) ret+="<!--FS2PS: Begin "+arr[i+1]+"-->"+getContent(arr[i+1])+"<!--FS2PS: End "+arr[i+1]+"-->";
	}
	return ret;
}

/////////////////////////////////////////////////////////////////////////////
// Supporting functions
//---------------------------------------------------------------------------
// These simply help with the dynamic goodness.
/////////////////////////////////////////////////////////////////////////////

// Gets the values from all the fields within el. el should be a node from
// either an XML Document or an HTML one.
function FormValsFrom(el, subs, doDefs, forceSelects)
{
	var vals = this.vals = {};
	if (el)
		forEveryNodeMatching(el, function(el)
		{
			if (el.nodeType!=1) return false;
			var elName = el.tagName.toLowerCase();
			var type = el.type;
			var valName = el.name;
			var val = null;
			if (elName=="input"&&(type=="text"||type=="hidden"||type=="password"))
			{
				if (el._fakeSubmit) {arraypush(subs,el);el.parentNode.removeChild(el);return false;}
				val = doDefs?el._defaultValue||el.defaultValue:el.value;
				if (val==null) val = el.getAttribute('value');
				if (val==null) val = el.textContent;
				if (val==null) val = XMLsToStr(el.childNodes);
			}
			else if (type=="checkbox"||type=="radio")
			{
				val = (doDefs?el._defaultChecked||el.defaultChecked:el.checked)?el.value:'';
			}
			else if (elName=="select")
			{
				var v=vals[valName];
				if (!v) vals[valName]=v=[];
				var firstopt;
				var sel=false;
				forEveryNodeMatching(el, function(el)
				{
					if (!el.tagName||el.tagName.toLowerCase()!="option") return true;
					if (!firstopt) firstopt=el;
					if (doDefs?el._defaultSelected||el.defaultSelected:el.selected)
					{
						sel=true;
						v[v.length]=doDefs?el.defaultValue||el.value:el.value;
					}
				});
				if (!sel&&forceSelects&&firstopt)
					v[v.length]=doDefs?firstopt.defaultValue||firstopt.value:firstopt.value;
				return true;
			}
			else if (elName=="textarea")
			{
				if (el==_historytext) return;
				val = doDefs?el._defaultValue||el.defaultValue:el.value;
				if (val==null)
				{
					val="";
					for (var chel = el.firstChild;chel;chel=chel.nextSibling)
						if (chel.nodeType=="3")
							val+=chel.nodeValue;
						else if (chel.nodeType=="1"&&chel.nodeName=="br")
							val+="\n";
				}
				val = val.replace(/\r\n|\r/g,"\n");
			}
			else return true;
			if (!vals[valName]) vals[valName] = val?[val]:[];
			else if (val) arraypush(vals[valName], val);
		});
	return vals;
}
function nullToEmpty(s)
{
	if (s==null) return '';
	else return s;
}
function UseFormVals(vals, el)
{
	if (el)
		forEveryNodeMatching(el, function(el)
		{
			if (el.nodeType!=1) return false;
			var elName = el.Name||el.tagName.toLowerCase();
			var type = el.type;
			var valName = el.name;
			var val = null;
			if (elName=="input"&&(type=="text"||type=="hidden"||type=="password")||elName=="textarea")
			{
				if (vals[valName]) el.value = vals[valName][0]||'';
			}
			else if (elName=="input"&&(type=="checkbox"||type=="radio")||elName=="option")
			{
				if (elName=="option") valName = el.parentNode.name;
				var val = el.value;
				var checked = false;
				if (vals[valName])
					for (var i=0;i<vals[valName].length;++i)
						if (vals[valName][i]==val)
							checked = true;
				el.checked=el.selected=checked;
			}
			else return true;
		});
}
function AddFormVals(all, vals)
{
	var ret = {};
	for (var i in vals)
	{
		ret[i] = all[i];
		if (vals[i])
			all[i]=vals[i];
		else
			delete all[i];
	}
	return ret;
}
function AddFormVal(all, valName, val)
{
	if (!all[valName]) all[valName] = [val];
	else arraypush(all[valName], val);
}
function IsSameValue(v1, v2)
{
	// All values are arrays or nulls
	if ((v1&&v1.length)!=(v2&&v2.length)) return false;
	
	// But order is not important. The presence of the same number of
	// each value is, so we build a count dictionary.
	var v1o = {};
	var v2o = {};
	for (var i=0;i<v1.length;++i)
	{
		v1o[v1[i]]=(v1o[v1[i]]||0)+1;
		v2o[v2[i]]=(v2o[v2[i]]||0)+1;
	}
	// We've already confirmed a length match, so this loop is sufficient.
	for (var i in v1o)
		if (v1o[i]!=v2o[i]) return false;
	return true;
}
function DiffFromFormVals(all, lastvals)
{
	var diff={};
	for (var i in all)
		if (!lastvals[i]||!IsSameValue(lastvals[i],all[i]))
			diff[i]=all[i];
	return diff;
}
// TODO: Get rid of this hack and handle date/time fields properly.
var interesting={m:1,y:1,d:1,h:1};
function AddDateTimeFields(all, other)
{
	for (var i in all)
	{
		var c = i.charAt(i.length-1);
		if (interesting[c])
		{
			c = i.substr(0,i.length-1);
			for (var j in interesting)
				if (other[c+j])
					all[c+j] = other[c+j];
		}
	}
}
function ValsAsPostData(all)
{
	var post = '';
	for (var i in all)
		post+="&"+encodeURIComponent(i)+"="+encodeURIComponent(all[i]);
	return post.substr(1);
}


// Gets all form values from the page fragment.
function GetValues(str, vals)
{
	vals=vals||{};
	var lastsel;
	var tagre = /<(\w+)((?:[^\/>'"]+"[^"]*"|[^\/>'"]+'[^']*')*)[^\/>'"]*\/?>(?:([^<]*)<\/\1>)?/g;
	var m;
	while (m = tagre.exec(str))
	{
		var attr = {};
		var attrre = /\s*(\w+)=(?:"([^"]*)"|'([^']*)')/g;
		var am;
		while (am = attrre.exec(m[2]))
			attr[am[1]] = am[2]||am[3]||'';
		
		if (m[1]=='select')
		{
			lastsel = unescapeHTML(attr.name);
			continue;
		}
		else if (m[1]=='textarea')
		{
			AddFormVal(vals, unescapeHTML(attr.name), unescapeHTML(m[3]));
			continue;
		}
		
		if (m[1]=='option')
		{
			if (m[2].indexOf("selected=")!=-1)
				AddFormVal(vals, lastsel, unescapeHTML(attr.value));
		}
		else if (attr.name&&(
		attr.type=='secret'||
				attr.type=='text'||
				attr.type=='hidden'||
				attr.checked&&
					(attr.type=='radio'||
					attr.type=='checkbox')))
		{
			AddFormVal(vals, unescapeHTML(attr.name), unescapeHTML(attr.value||''));
		}
	}
}

var knownchars = {'amp':'&', 'lt':'<', 'gt':'>', 'apos':"'", 'quot':'"'};
function unescapeHTML(html)
{
	return html.replace(/&(?:#x([0-9a-fA-F]+)|#(\d+)|(\w+));?/g,
		function(m, g1, g2, g3, pos)
		{
			if (g3) return knownchars[g3]||'';
			return String.fromCharCode(parseInt(g1||g2,g2?10:16));
		});
}

// We'll rewrite all the link destinations and hope nothing depends
// on the values being unaltered. (There's no other way to override
// only the default action for following a link.)
function InstrumentLinks(content)
{
	var hrefre = /<a ([^>]*)href=(['"])([\s\S]*?)\2([^>]*)/g;
	var m;
	while (m = hrefre.exec(content))
		if (!/\btarget=['"](?!_self|_parent).*?['"]/i.exec(' '+m[1]+' '+m[4]))
			content = content.substr(0,m.index)+"<a "+m[1]+"href="+m[2]+"javascript:LinkHit("+(m[2]=='"'?"'":'"')+m[3].replace(/([\\'"])/g,"\\$1")+(m[2]=='"'?"'":'"')+")"+m[2]+content.substr(m.index+m[0].length);
	return content;
}

// Imports an (assumedly balanced) html fragment.
var namere=/<([a-z]+)/i;
function importHTML(html)
{
	var depth = 0;
	var name = html.match(namere);
	name = name&&name[1]&&name[1].toLowerCase();
	
	// IE has real trouble with td, tr, tbody and option, so we need to
	// surround them with appropriate elements. To avoid arbitrarily
	// losing script and link elements, we surround everything else
	// with table-tbody-tr-td. There are probably simpler elements to
	// use, but div didn't work and table is the first one that did.
	// Other browsers might behave better, but they don't mind these
	// hacks.
	switch (name)
	{
	default:
		html = ++depth&&"<td>"+html+"</td>"
	case "td":
		html = ++depth&&"<tr>"+html+"</tr>";
	case "tr":
		html = ++depth&&"<tbody>"+html+"</tbody>";
	case "tbody":
	case "thead":
	case "tfoot":
		html = ++depth&&"<table>"+html+"</table>";
		break;
	case "option":
		html = ++depth&&"<select>"+html+"</select>";
		break;
	}
	var node = document.createElement("div");
	node.innerHTML = html;
	for (;depth;--depth) node = node.firstChild;
	return node.childNodes;
}

// Runs fn for el. If it returns true, recurses for every subelement.
// Typically, fn will both do something with nodes and decide whether
// or not the subelements might be interesting.
function forEveryNodeMatching(el, fn)
{
	if (fn(el))
	{
		var ch = el.firstChild;
		for (;ch;ch=next)
		{
			var next = ch.nextSibling;
			forEveryNodeMatching(ch, fn);
		}
	}
}

function forEveryNode(el, fn)
{
	fn(el);
	var ch = el.firstChild;
	for (;ch;ch=next)
	{
		var next = ch.nextSibling;
		forEveryNode(ch, fn);
	}
}

// Regex for URLs taken from RFC 2396 (some brackets removed)
var urlre = /^([^:\/?#]+:)?(\/\/[^\/?#]*)?([^?#]*)(\?[^#]*)?(#.*)?$/;
//            1           2           3       4         5
//            scheme      authority  path     query     fragment
function SplitURL(surl)
{
	surl=(surl||'')+'';
	var url = surl.match(urlre);
	return {_scheme:url[1], _authority:url[2], _path:url[3], _query:url[4], _fragment:url[5], _orig:surl};
}
function JoinURL(url)
{
	return (url._scheme||"")+(url._authority||"")+(url._path||"")+(url._query||"")+(url._fragment||"");
}

var singledots = /(?:^|\/)\.(\/|$)/g;
var doubledot = /(?:^|\/)(?:.*\/)?\.\.(\/|$)/;
function CombineURLs(base, other)
{
	base = SplitURL(base);
	other = SplitURL(other);
	if (!other._scheme&&!other._authority&&!other._path&&!other._query&&!other._fragment)
		return JoinURL(base);
	if (other._scheme)
		return JoinURL(other);
	other._scheme=base._scheme;
	if (!other._authority)
	{
		other._authority=base._authority;
		if ((other._path||"").substr(0,1)!='/')
		{
			var newpath = base._path||'';
			var slpos = newpath.lastIndexOf('/');
			if (slpos!=-1) newpath = newpath.substr(0,slpos+1);
			newpath+=(other._path||'');
			do
			{
				var pathlen = newpath.length;
				newpath = newpath.replace(singledots, '$1');
			} while (pathlen>newpath.length);
			do
			{
				var pathlen = newpath.length;
				newpath = newpath.replace(doubledot, '$1');
			} while (pathlen>newpath.length);
			other._path=newpath;
		}
	}
	return JoinURL(other);
}

// Turns any URL into either an AJAT-usable one or null.
// (If it returns no .rel, it's not an AJAT URL.)
var FJre = /^(.*?)\/FJ\/#?(\d*)(\/.*)$/;
var absurlre = /[\w]+:/;
function getAJAXURL(url)
{
	var loc = window._fsurl?_fsurl+"#"+_fsurlhash:location+"";
	var m = FJre.exec(loc);
	
	if (!m) return null;
	loc = m[1]+m[3];
	
	if (!url) return {abs:loc, rel:m[3], num:+m[2], preFJ:m[1]};
	
	url = url.replace(/(#.*)?\s*$/g,"");
	
	var abs = CombineURLs(loc, url);
	
	var ret = {num:+m[2], abs:abs, preFJ:m[1]};
	if (ret.abs.substr(0,m[1].length+1)==m[1]+'/') ret.rel = ret.abs.substr(m[1].length);
	return ret;
}

// Turns objects into expressions that generate something like them.
// Objects that expose _serialise will get that called.
_serialise=function(o)
{
	if (typeof(o)=='number'||o==null) return ""+o;
	if (o._serialise) return o._serialise();
	if (typeof(o)=='string') return "'"+o.split("\\").join("\\\\").split("'").join("\\'").split("\r").join("\\r").split("\n").join("\\n")+"'";
	if (o.constructor==Array)
	{
		var ret = "[";
		for (var i=0;i<o.length;++i)
			ret+=(i>0?', ':'')+(o[i]===void 0?'':_serialise(o[i]));
		return ret+"]";
	}
	var ret = "{";
	for (var i in o)
	{
		ret+=(ret=="{"?'':', ')+_serialise(i)+":"+_serialise(o[i]);
	}
	return ret+"}";
}
















//if (!window._ajaxmode) _ajaxmode = 'OnePageNoBack';




// Textareas get auto-sized to match their content
OnEveryLoad(function FixTextareas(force)
{
	$('textarea').each(function()
	{
		var ta = this;
		var $ta=$(ta);
		if ($ta.data('fs2fixta')) return;
		$ta.data('fs2fixta',1);
		
		ta.style.overflow='hidden';
		
		// Don't go smaller than the initial height.
		var minHeight = ta.clientHeight;
		
		// Compensate for any issues in the first getting/setting of the height
		$ta.height($ta.height());
		
		// Then, calculate the difference between our two height measures for later height changes
		var diff = $ta.height()-ta.clientHeight;
		
		var size = function()
		{
			ta.scrollHeight; // IE bug compensation
			// We're aiming to have the clientHeight always one less than the scrollHeight (for easy detection of scrollheight changes), give or take the minimum height
			if (ta.clientHeight>=minHeight&&(ta.scrollHeight==ta.clientHeight+1||ta.scrollHeight<=ta.clientHeight)) return;
			
			while (ta.scrollHeight==ta.clientHeight)
			{
				$ta.height($ta.height()-10);
				ta.scrollHeight; // IE bug compensation (though IE shouldn't really hit this line, as it doesn't force the minimum scrollHeight to be the clientHeight)
			}
			
			$ta.height(Math.max(ta.scrollHeight-1,minHeight)+diff);
		}
		$ta.keyup(size).keydown(size).change(size);
		ta.onpropertychange=size;
		ta.onpropertychange();
	});
});

OnEveryLoad(function ReworkTargetTopEls(obj) {
    var context;
    
    if (obj) {
        try {
            context = obj.ownerDocument.body;
	    } catch (err) {
	        return;
	    }

	    if (context.childNodes.length > 0) {
	        $(".idTargetTopAction * a", context).each(function() {
	            var regex = /^(.+\$c\(getel\('[^']+'\))(\))$/;

	            if (regex.test($(this).attr("href"))) {
	                $(this).attr("href", $(this).attr("href").replace(regex, "$1, true$2"));
	            }
	        });

	        $("input.idTargetTopAction", context).each(function() {
	            $(this).click(function() {
	                this.form.target = "_top";
	            });
	        });
	    }
    }
});

function OnEveryLoad(fn)
{
	OnLoad(fn);
	return OnAJAXDone(fn);
}

function OnAJAXDone(fn)
{
	return AddToObject(AJAXCallbacks.done, fn);
}

// Included for compatibility with some JS Giles wrote.
var AJAXDoneCallback=[];
AddToObject(AJAXCallbacks.done, function(){for (var i=0;i<AJAXDoneCallback.length;++i) AJAXDoneCallback[i]();});


var bigcover;
var bigcovertimer;
var BigCoverCount=0;
function OpenBigCover()
{
	if (BigCoverCount++) return;
	if (!bigcover)
	{
		bigcover = document.createElement("div");
		bigcover.style.backgroundColor='black';
		bigcover.style.opacity='0.5';
		bigcover.style.MozOpacity='0.5';
		bigcover.style.filter='alpha(opacity=50)';
		bigcover.style.position='absolute';
		bigcover.style.left='0';
		bigcover.style.top='0';
		bigcover.style.zIndex='100';
		FixBigCover();
		document.body.insertBefore(bigcover,document.body.firstChild);
		//document.body.appendChild(bigcover);
	}
	bigcover.style.display='block';
	bigcovertimer = setInterval(FixBigCover,55);
}

function FixBigCover()
{
	var r = getScroll();
	var newheight = Math.max(Math.max(r._h,document.body.offsetHeight),r._ch)+"px";
	var newwidth = Math.max(Math.max(r._w,document.body.offsetWidth),r._cw)+"px";
	if (bigcover.style.height!=newheight)
		bigcover.style.height=newheight;
	if (bigcover.style.width!=newwidth)
		bigcover.style.width=newwidth;
}

function CloseBigCover()
{
	if (--BigCoverCount) return;
	clearInterval(bigcovertimer);
	bigcovertimer=null;
	bigcover.style.display='none';
}











// Pop-up menus
//
// Parameters:
//   * Object Identifier
//   * Menu Identifier
//   * When menu should appear
//     0 = on mouseover
//     1 = on left mouse click
//     2 = on right mouse click
//   * posx (0=0,1=1,h=0.5)
//     resultant x-position=posx[0]*mousex + posx[1]*objleft + posx[2]*objwidth - posx[3]*menuwidth + posx[4]*scrollLeft + posx[5]*clientWidth
//   * posy (0=0,1=1,h=0.5)
//     resultant y-position=posy[0]*mousey + posy[1]*objtop + posy[2]*objheight - posy[3]*menuheight + posy[4]*scrollTop + posy[5]*clientHeight
//   * menuwidth
//     0 = as small as possible
//     1 = same width as object
//   * menuheight
//     0 = as small as possible
//     1 = same height as object
//
// Examples:
// "1~obj~menu~0~01~011~1" makes menu a menu for obj; the menu appears (on mouseover) below the object and matches the width of the object.
// "1~obj~menu~2~1~1" makes menu a menu for obj; the menu appears (on right-click) at the current mouse position and is as small as possible.
//
// Full example usage:
//   <script type="text/javascript"><!--
//   $$("1~obj~menu~0~01~011~1")//--></script>

var intmap={'0':0,'1':1,'2':2,'h':0.5};
function parseInts(s)
{
	var ret=[];s=s||'';
	for (var i=0;i<s.length;++i)
		arraypush(ret,intmap[s.charAt(i)]||0);
	return ret;
}

// This wants to be as fast as possible, which involves touching as little
// of the DOM as possible. We add event handlers to the event-handling
// replacement structures, which are later checked when events happen.
// TODO: Check for speed issues in bookings, now that this has been replaced.
function setaction01(s)
{
	s=s.slice(1);
	s[2]=parseInt(s[2])||0;
	s._when = s[2];
	if (s[2]==0)
		return undo2(
			AddOverHandlerByName(s[0], JS01MouseOverOrClick, s),
			AddOutHandlerByName(s[0], JS01MouseOut, s));
	else
		return AddClickHandlerByName(s[0], JS01MouseOverOrClick, s);
}

function JS01Prepare(o)
{
	o._objn = o[0];
	o._mn = o[1];
	o._obj = getel(o[0]);
	o._m = getel(o[1]);
	o._cached = [];
	o._posx = parseInts(o[3]);
	o._posy = parseInts(o[4]);
	o._menuwidth = parseInt(o[5]);
	o._menuheight = parseInt(o[6]);
	o._fixedwidth = o[10];
	addobjref(o);
	o._m._id=o._id;
	o._obj._id=o._id;
}

function domenu(obj,m,when,posx,posy,menuwidth,menuheight,fixedwidth,prep)
{
	o={_objn:obj,_mn:m,_obj:getel(obj),_m:getel(m),_cached:[],_when:when,_posx:posx,_posy:posy,_menuwidth:menuwidth,_menuheight:menuheight,_fixedwidth:fixedwidth,_prepare:prep};
	addobjref(o);
	// o._mousex, o._mousey, o._istext are null and so don't need setting
	// o._captured starts off false, and so doesn't need setting
	if (o._when==0)
		undo=undo2(
			AddOverHandlerByName(obj, JS01MouseOverOrClick, o),
			AddOutHandlerByName(obj, JS01MouseOut, o));
	else
		var undo = AddClickHandlerByName(obj, JS01MouseOverOrClick, o); //TODO: Handle undo
	return {_menu:o, _undo:undo};
}

function JS01Restore(ev,o)
{
	EndCache(o._m);
	o._obj._attachedmenu=null;
	if (o._when!=0&&o._captured) endovercapture(ev);
	o._selshated=0;
	selsunhate(o._m);
	doEvent(ev,[o],/*{*/"_onmenuclose"/*{*/);
	o._vis=false;
}

function JS01FixMenu(o)
{
	if (!o._obj._attachedmenu) return;
	if (!o._fixedwidth&&o._m.offsetWidth>getScroll()._w*0.8&&o._m.tagName.toLowerCase()!="iframe")
		o._m.style.width="20em";
	JS01FixMenu2(o)
}

function JS01FixMenu2(o)
{
	if (!o._obj._attachedmenu) return;
	var r = getScroll();
	var x=(o._posx[0]||0)*o._mousex + (o._posx[1]||0)*objleft(o._obj) + (o._posx[2]||0)*o._obj.offsetWidth  - (o._posx[3]||0)*o._m.offsetWidth + (o._posx[4]||0)*r._x + (o._posx[5]||0)*r._cw;
	var y=(o._posy[0]||0)*o._mousey + (o._posy[1]||0)*objtop(o._obj)  + (o._posy[2]||0)*o._obj.offsetHeight - (o._posy[3]||0)*o._m.offsetHeight + (o._posy[4]||0)*r._y + (o._posy[5]||0)*r._ch;
	//TODO: Use istext for something useful
	objpos(o._m,x,y);
	if (!o._selshated)
	{
		o._selshated=1;
		selshate(o._m);
	}
}

function JS01MouseClick(ev)
{
	ev._nomenuaction=true;
}

function JS01EndCapture(o)
{
	JS01MouseOut(null,null,eventobjs[o._id]);
}

function JS01MouseOut(ev,evo,o)
{
	o._captured=false;
	if (o._obj._attachedmenu)
		JS01Restore(ev,o);
}

function JS01MouseOverOrClick(ev,evo,o)
{
	if (o._vis) return false;
	if (ev._nomenuaction) return;
	for (var i=0;ev._els[i]!=o._obj;++i)
		if (ev._els[i].tagName=="TEXTAREA"||ev._els[i].tagName=="INPUT"||ev._els[i]._isRichEdit)
			o._istext=true;
	if ((o._when==2?1:0)!=((ev._button>1||ev._kctrl||ev._kalt||ev._kshift||ev._kmeta)?1:0)) return;
	if (!o._cached||o._prepare) {(o._prepare||JS01Prepare)(o);o._prepare=0;}
	if (o._when!=0&&!o._mclickdone)
	{
		//TODO: Handle undo
		AddClickHandlerByName(o._mn, JS01MouseClick, o);
		o._mclickdone = true;
	}
	//Calculate position into x and y and xs and ys (may need recalculating)
	//Compensate for istext (make sure no overlap)
	StartCache(o._m);
	setFakeParent(o._m,o._obj);
	o._obj._attachedmenu=1;
	if (o._when!=0) {overcapture(ev,o._obj,o._m,JS01EndCapture);o._captured=true;}
	doEvent(ev,[o],/*{*/"_onmenuopen"/*{*/);
	setstyle(o._m,'position','absolute');
	moveobj(o._m,-1000,-1000);
	o._m._id=o._id;
	setstyle(o._m,'visibility','visible');
	setstyle(o._m,'display','block');
	o._vis=true;
	o._mousex=ev._mx;
	o._mousey=ev._my;
	setTimeout(function(){if (o._nofix1) JS01FixMenu2(o); else JS01FixMenu(o);},1);
	return true;
}

function JS01ReplaceObject(o,newm,nofix1)
{
	if (!o._obj._attachedmenu)
	{
		o._m=newm;
		o._m._id=o._id;
		return;
	}
	EndCache(o._m);
	o._selshated=0;
	selsunhate(o._m);
	o._m=newm;
	o._m._id=o._id;
	StartCache(o._m);
	setFakeParent(o._m,o._obj);
	if (o._when!=0)
	{
		endovercapture(null,true);
		overcapture(null,o._obj,o._m,JS01EndCapture);
	}
	setstyle(o._m,'position','absolute');
	moveobj(o._m,-1000,-1000);
	setstyle(o._m,'visibility','visible');
	setstyle(o._m,'display','block');
	setTimeout(function(){JS01FixMenu2(o);},1);
}








var richeds;
var lastactive;
function JS01aAction(s)
{
	if (s[1]=="0")
	{
		// s[2] = attachedto   s[3] = mousebutton   s[4] = hiddenid   s[5] = submitid   s[6] = estimatedwidth   s[7] = estimatedheight
		return AddClickHandlerByName(s[2], JS01aMouseClick, s);
	}
	else
	{
		// s[2] = attachedto   s[3] = popupel   s[4] = x%   s[5] = y%   s[6] = pagex   s[7] = pagey   s[8] = submit
		var attachedto = getel(s[2])
		var el = getel(s[3]);
		var capt = false;
		var ie6scroll;
		setTimeout(function() {
		    var r = getScroll();
		    if (s[1] == "2") {
		        el.style.position = "fixed";
				OpenBigCover();
		        //center window
		        var heightWindow = el.clientHeight;
		        var widthWindow = el.clientWidth;
		        var x =(r._cw - widthWindow) / 2>0?(r._cw - widthWindow)/2: 1;
		        var y = (r._ch - heightWindow) / 2 > 0 ? (r._ch - heightWindow) / 2 : 1;
		        moveobj(el, x, y);
				if (_isIE6||1)
				{
					el.style.position="absolute";
					el.style.zIndex='999';
					ie6scroll = "0~~~SW~"+s[3]+"~"+x+"~"+y;
					$$(ie6scroll);
				}
		    } else {
		        if (objleft(el) + el.offsetWidth > r._x2 || objtop(el) + el.offsetHeight > r._y2)
		            objpos(el, parseFloat(s[4]) * attachedto.offsetWidth + objleft(attachedto), parseFloat(s[5]) * attachedto.offsetHeight + objtop(attachedto));
		    }
		    capt = true;
		    overcapture(null, el.parentNode || el.parentElement, el, closure(function() { capt = false; $c(getel(s[8])) }));
		    selshate(el);

		}, 200);
		return function()
		{
			if (s[1]=="2")
			{
				if (_isIE6||1) $$undo(ie6scroll);
				CloseBigCover();
			}
			if (capt) endovercapture(null,true);
		};
	}
}

function JS01aMouseClick(ev, evo, s)
{
	var x = (ev._mx-objleft(evo))/evo.offsetWidth;
	var y = (ev._my-objtop(evo))/evo.offsetHeight;
	
	if ((s[3]=='2'?1:0)!=((ev._button>1||ev._kctrl||ev._kalt||ev._kshift||ev._kmeta)?1:0)) return;
	
	var qq = document.createElement('div');
	qq.style.position='absolute';
	var pel = getel(s[5]);
	(pel.parentNode||pel.parentElement).appendChild(qq);
	pel = qq.offsetParent;
	(qq.parentNode||qq.parentElement).removeChild(qq);
	
	var w = s[6]||0;
	var h = s[7]||0;
	var r = getScroll();
	var mx = Math.min(ev._mx, r._x2-w);
	var my = Math.min(ev._my, r._y2-h);
	
	// IE likes to claim td and table are positioning/offset parents, even when they're clearly not.
	while (_isIE&&pel&&(pel.tagName=="TD"||pel.tagName=="TABLE")) pel=pel.offsetParent;
	getel(s[4]).value = x+"~"+y+"~"+(mx-objleft(pel))+"~"+(my-objtop(pel));
	$c(getel(s[5]));
	return true;
}






















// Date controls
//
// Parameters:
//   * Outer object identifier
//   * Mandatoriness
//     Mandatory if present - non-mandatory otherwise
//   * Style
//     0 - Replace controls with a date-control link
//     1 - Adds a link after the last control with a date-control link
//     2 - Adds no way to bring up a menu (only performs error checking)
//   * Minimum date
//   * Maximum date
//   * ID of button to be pressed when date is selected
//   * Static text for the link
//
// Example:
// "2~myouter" Adds a date with myyear for year, mymonth=month and myday=day.
// "2~myouter~1" does the same as the last one, but makes it a mandatory date.
//
// Full example usage:
//   <script type="text/javascript"><!--
//   $$("2~myouter")//--></script>

var monthnames;
var daynames=['M','Tu','W','T','F','Sa','Su'];

var JS02LastBlur;

function setaction02(s)
{
	var o={};
	addobjref(o);
	o._outer = getel(s[1]);
	
	// All this, just to guess which control is which.
	var a = o._outer.getElementsByTagName("SELECT");
	o._objd = o._objm = a[0];
	if (a[0]!=null && a[0].options.length<=13) o._objd = a[1];
	else o._objm = a[1];
	a = o._outer.getElementsByTagName("INPUT");
	o._outer._id = o._id;
	o._objy = a[0];
	if (o._objy.maxLength==2)
	{
		o._objy = a[1];
		o._objd = a[0];
	}
	else if (a.length==2)
		o._objd = a[1];

	o._m=s[2];
	o._datestyle=s[3]||0;
	o._mindate=s[4]?new Date(s[4]):null;
	o._maxdate=s[5]?new Date(s[5]):null;
	o._subname=s[6];
	o._linktext=s[7];
	o._lasterror=72;
	// TODO: The _getValue check means that if two bits of date JS reference the same controls, only one of them will have any effect. If that one is then removed, there's overall no effect, whereas really the other should then have the right effect.
	if (!o._objy||!o._objm||!o._objd||o._outer._getValue) return function(){};
	
	o._outer._getValue = JS02GetValue;
	o._outer._getText = JS02GetText;
	o._outer._focus = JS02FocusOuter;
	
	// o._restore starts off null and so doesn't need setting
	// o._lastdate starts off null and so doesn't need setting
	// o._laststartmonth starts off null

	// If we don't yet have the month names, and the month object is a
	// select box, we should grab the month names from that.
	if (!monthnames&&o._objm.tagName=="SELECT")
	{
		monthnames=[];
		for (var i=0;i<12;i++)
			monthnames[i]=o._objm.options[i+o._objm.options.length-12].text;
	}
	if (o._datestyle==0||o._datestyle==1)
	{
		o._txt=document.createElement("A");
		o._txt.href="date:picker";
		o._txt.id=s[1]+"Txt";
		if (o._datestyle==1) {o._txt.innerHTML="&#160;";o._txt.className="datecombo";o._txt.style.textDecoration="none";}
		if (o._objy.nextSibling)
			(o._objy.parentElement||o._objy.parentNode).insertBefore(o._txt,o._objy.nextSibling);
		else
			(o._objy.parentElement||o._objy.parentNode).appendChild(o._txt);
		if (o._datestyle==0)
		{
			setstyle(o._objy,'display','none');
			setstyle(o._objm,'display','none');
			setstyle(o._objd,'display','none');
			// TODO: Make this less destructive.
			o._objy.onfocus=o._objm.onfocus=o._objd.onfocus=JS02OnFocusField;
			o._objy.onblur=o._objm.onblur=o._objd.onblur=JS02OnBlurField;
			o._txt.onfocus=JS02OnFocus;
			o._txt.o=o._objy.o=o._objm.o=o._objd.o=o._id;
		}
		else
			o._txt.tabIndex=-1;
		var blah = domenu(o._txt.id,null,1,[1],[1],0,0,null,JS02CreateCal);
		o._menu=blah._menu;
		o._menu._cal = o;
		addEvent(o._menu,/*{*/"_onmenuopen",JS02ShowingMenu,o._id);
	}
	JS02CheckDate(o);
	o._objd.onchange=o._objd.onpropertychange=o._objm.onchange=o._objm.onpropertychange=o._objy.onchange=o._objy.onpropertychange=JS02CheckDateEvent;
	o._objd._id=o._objm._id=o._objy._id=o._id;
	
	return function()
	{
		o._outer._getValue = null;
		o._outer._getText = null;
		o._outer._focus = null;
		// TODO: Make the following match the init, when the init is fixed.
		o._objy.onfocus=o._objm.onfocus=o._objd.onfocus=null;
		o._objy.onblur=o._objm.onblur=o._objd.onblur=null;
		if (blah) blah._undo();
		if (o._txt)
		{
			o._txt.onfocus=null;
			(o._txt.parentElement||o._txt.parentNode).removeChild(o._txt, true);
		}
	}
}

function JS02CreateCal(o)
{
	o=o._cal;
	o._cal=document.createElement('DIV');
	setstyle(o._cal,'position','absolute');
	setstyle(o._cal,'visibility','hidden');
	document.body.appendChild(o._cal);
	moveobj(o._cal,-1000,-1000);
	setstyle(o._cal,'zIndex',1000000);
	o._cal._thingsavoidme='yep';
	o._cal.id="cal"+Math.floor(Math.random()*1000000000);
	o._menu._m = o._cal;
	o._menu._mn = o._cal.id;
	//o._cal.dragme=true;
	//ddscrollobs[ddscrollobs.length]=o._cal;
	addEvent(o._cal,/*{*/"_onmouseclick"/*}*/,JS02MouseClick,o._id);
}

function JS02ShowingMenu(ev,evo,o)
{
	JS02ShowDate(null,o);
}

function JS02SetError(obj,err)
{
	if (err) addclass(obj,"dateerror"); else remclass(obj,"dateerror");
	obj.title=err;
}

function JS02CheckDateEvent()
{
	if (this._id) JS02CheckDate(eventobjs[this._id]);
}

function JS02FixYear(yval)
{
	if (yval==''||yval==null) yval=new Date().getFullYear();
	if (yval<100)
	{
		var cutoffyear=new Date().getFullYear()+30;
		yval=cutoffyear-((cutoffyear-yval)%100);
	}
	return yval;
}

function JS02CheckDate(o)
{
	var lastlast = o._lastdate;
	o._lastdate=null;
	var err="";
	if (o._objd.value) 
	{
		var yval = JS02FixYear(o._objy.value);
		var d=new Date(yval,o._objm.value-1,o._objd.value);
		if (isNaN(d-0)) err="Date is invalid";
		else if (d.getDate()!=o._objd.value) err="Day is out of range for month";
		else
		{
			if (o._mindate&&d<o._mindate) err="Day is before minimum allowed date";
			else if (o._maxdate&&d>o._maxdate) err="Day is after maximum allowed date";
			o._lastdate=d;
		}
	}
	if (err!=o._lasterror)
	{
		o._lasterror=err;
		JS02SetError(o._objd,err);
		JS02SetError(o._objm,err);
		JS02SetError(o._objy,err);
	}
	
	var s=o._linktext||(o._lastdate?o._lastdate.getDate()+' '+monthnames[o._lastdate.getMonth()]+' '+o._lastdate.getFullYear():'Select a date');
	if (o._datestyle==0)
	{
		while (o._txt.firstChild) o._txt.removeChild(o._txt.firstChild);
		o._txt.appendChild(document.createTextNode(s));
	}

    if (lastlast&&+lastlast!=+o._lastdate&&o._subname && o._subname != "#form#")
		$c(o._subname);
	
	return s;
}

function JS02GetText()
{
	return JS02CheckDate(eventobjs[this._id]);
}

function JS02GetValue()
{
	var o = eventobjs[this._id];
	JS02CheckDate(o);
	return o._lastdate.getFullYear()+"-"+(o._lastdate.getMonth()<9?"0":"")+(o._lastdate.getMonth()+1)+"-"+(o._lastdate.getDate()<10?"0":"")+o._lastdate.getDate();
}

function JS02SetDate(d,ev,o)
{
	if (o._mindate&&d&&d<o._mindate) d=o._mindate;
	else if (o._maxdate&&d&&d>o._maxdate) d=o._maxdate;
	o._objy.value=d?d.getFullYear():'';
	o._objm.selectedIndex=d?d.getMonth()+o._objm.options.length-12:0;
	if (o._objd.selectedIndex!=null)
		o._objd.selectedIndex=d?d.getDate()+o._objd.options.length-32:0;
	else
		o._objd.value=d?d.getDate():'';
	JS02CheckDate(o);
    if (o._subname && o._subname == "#form#")
    {
	    var els = $(o._outer).parents("form");
	    
	    if (els.length > 0)
	    {
	        $c($(o._outer).parents("form")[0]);
	    }
	}
	JS01Restore(ev,o._menu);
}

function JS02MouseClick(ev,evo,o)
{
	var d=new Date(o._laststartmonth);
	var set=ge(ev._els[0],'set');
	var show=ge(ev._els[0],'show');
	if (set)
	{
		if (set=='t') JS02SetDate(new Date(),ev,o);
		else if (set=='n') JS02SetDate(null,ev,o);
		else {d.setDate(set);JS02SetDate(d,ev,o);}
	}
	else if (show)
	{
		d.setDate(show);
		JS02ShowDate(d,o);
	}
	return true;
}

function JS02ShowDate(d,o)
{
	var cl='cal';
	var cledge=' class="'+cl+' '+cl+'edge"';
	var clwday=' class="'+cl+' '+cl+'wday"';
	var cldate=' class="'+cl+' '+cl+'date';
	var clother=cldate+' '+cl+'dateother"';
	var clerror=cldate+' '+cl+'error"';
	var cldatesel=cldate+' '+cl+'datesel"';
	cldate+='"';
	cl=' class="'+cl+'"';
	if (document.compatMode=="BackCompat")
	{
		cledge+=" style='font-size:0.7em'";
		clwday+=" style='font-size:0.7em'";
		cldate+=" style='font-size:0.7em'";
		clother+=" style='font-size:0.7em'";
		clerror+=" style='font-size:0.7em'";
	}
	
	if (!d)
	{
		var dtdate=o._objd.value;
		var dtmonth=o._objm.value;
		var dtyear=JS02FixYear(o._objy.value);
		var newdate=new Date();
		var invaliddate=!dtyear||!dtmonth||!dtdate;
		dtdate=dtdate||newdate.getDate();
		dtmonth=dtmonth||(newdate.getMonth()+1);
		dtyear=dtyear||newdate.getFullYear();
		d=new Date(dtyear,dtmonth-1);
		if (isNaN(d-0)) {d=new Date(newdate.getFullYear(),newdate.getMonth());invaliddate=true;}
		if (o._mindate&&d<o._mindate) d=new Date(o._mindate);
		else if (o._maxdate&&d>o._maxdate) d=new Date(o._maxdate);
	}
	d.setDate(1);
	o._laststartmonth=d;
	
	var days20=1000*60*60*24*20;
	var days365=days20*365/20;
	var s="";
	// Clicking "Today" does setdate(new Date())
	// Clicking < for month does showdate(new Date(laststartmonth-days5))
	// Clicking > for month does showdate(new Date(laststartmonth+days35))
	// Clicking "None" does setdate()
	s+='<table cellpadding=0 cellspacing=0 border=0 class="cal"><tr>';
	s+='<td'+cledge+'>&#160;</td>';
	s+='<td'+cledge+'><table style="width:100%" cellpadding=0 cellspacing=0 border=0><tr><td width=1'+cledge+'><a href="" set="t">Today</a></td>';
	s+='<td'+cledge+' align="center"><A href="" show="-20">&lt;</A> <SPAN>'+monthnames[d.getMonth()]+'</SPAN> <A href="" show="40">&gt;</A></td>';
	if (!o._m)
		s+='<td width=1'+cledge+'><a href="" set="n">None</a></td>';
	else
		s+='<td width=1'+cledge+'>&#160;&#160;&#160;&#160;</td>';
	s+='</tr></table></td><td'+cledge+'>&#160;</td>';
	s+='</tr><tr>';
	s+='<td'+cledge+'>&#160;</td>';
	s+='<td'+cl+'>';

	var dmonth=d.getMonth();
	s+='<table cellpadding=2 cellspacing=0 border=0><tr>';
	for (var i=0;i<7;i++)
		s+='<td'+clwday+'>'+daynames[i]+'</td>'
	var dow=d.getDay();
	dow=(dow+6)%7;
	d=new Date(d);
	var dyear=d.getFullYear();
	d.setDate(1-dow);
	var minday=1-dow;

	for (var i=0;i<6;i++)
	{
		s+='<tr>';
		for (var j=0;j<7;j++)
		{
			if ((o._mindate&&d<o._mindate)||(o._maxdate&&d>o._maxdate))
				s+='<td'+clerror+'><a href="">'+d.getDate()+'</a></td>';
			else
				s+='<td'+(d.getMonth()!=dmonth?clother:(d-0==o._lastdate-0&&!invaliddate)?cldatesel:cldate)+'><a href="" set="'+(minday+i*7+j)+'">'+d.getDate()+'</a></td>';
				d.setDate(d.getDate()+1);
		}
		s+='</tr>';
	}
	s+='</table>';
	s+='</td>';
	s+='<td'+cledge+'>&#160;</td></tr>'
	s+='<tr><td'+cledge+'>&#160;</td>';
	s+='<td'+cledge+' align="center"><A href="" show="-3637">&lt;&lt;</A> <A href="" show="-350">&lt;</A> <SPAN>'+dyear+'</SPAN> <A href="" show="370">&gt;</A> <A href="" show="3667">&gt;&gt;</A></td>';
	s+='<td'+cledge+'>&#160;</td>';
	s+='</tr></table>';
	o._cal.innerHTML=s;
}

function JS02OnFocus(ev)
{
	// We're not interested in focus-changes caused by mouse-clicks
	if (lastmousestate._button) return;
	ev=myevent(ev);
	JS02Focus(ev._el.o,ev._kshift);
}

function JS02OnBlurField(ev)
{
	ev=myevent(ev);
	if (JS02LastBlur&&JS02LastBlur!=ev._el.o)
		JS02Hide(JS02LastBlur);
	JS02LastBlur=ev._el.o;
	setTimeout("JS02OnBlurField2("+ev._el.o+")",1);
}

function JS02OnFocusField(ev)
{
	ev=myevent(ev);
	if (JS02LastBlur&&JS02LastBlur!=ev._el.o)
		JS02Hide(JS02LastBlur);
	JS02LastBlur=null;
}

function JS02OnBlurField2(ev)
{
	if (JS02LastBlur)
		JS02Hide(JS02LastBlur);
	JS02LastBlur=null;
}

function JS02FocusOuter(backwards)
{
	JS02Focus(this._id, backwards);
}

function JS02Focus(id, backwards)
{
	var o = eventobjs[id];
	setstyle(o._objy,'display','inline');
	setstyle(o._objm,'display','inline');
	setstyle(o._objd,'display','inline');
	if (backwards)
	{
		o._objy.focus();
		o._objy.select();
	}
	else
		o._objd.focus();
	setstyle(o._txt,'display','none');
}

function JS02Hide(oid)
{
	var o=eventobjs[oid];
	setstyle(o._objy,'display','none');
	setstyle(o._objm,'display','none');
	setstyle(o._objd,'display','none');
	setstyle(o._txt,'display','inline');
}














// Showing/hiding
// Hides the object when the control does not have the given value.
//
// Parameters:
//   * Control name/id
//   * Value
//   * Object name/id
//
// Full example usage:
//   <script type="text/javascript"><!--
//   $$("3~mycontrol~myvalue~myidofthingtohide")//--></script>

(function(){

function ShowObjectOnControlVal(controlname, value, objectname)
{
	var controls = {};
	var correctcount = 0;
	var hidden = true;
	var objects = {};
	
	var HasCorrectValue = function(el)
	{
		var val=el._currentlyHidden?null:ControlValue(el);
		return val==value||(value==""&&!val);
	}
	
	var Update = function(el,hidden)
	{
		if (el.tagName=="TD")
			el.style.visibility=hidden?"hidden":"";
		else
			el.style.display=hidden?"none":"";
			
		var elsa=[el.getElementsByTagName("INPUT"),el.getElementsByTagName("SELECT")];
		for (var j=0;j<elsa.length;++j)
		{
			var els = elsa[j];
			for (var k=0;k<els.length;++k)
				if (!els[k]._currentlyHidden!=!hidden)
				{
					//TODO: This needs to somehow handle new stuff getting added inside a hidden page-part.
					els[k]._currentlyHidden=hidden;
					Changed(els[k]);
				}
		}
		maybehidesels();
	}
	
	var Hide = function(el)
	{
		if (!--el._viscount)
			Update(el,true);
	}
	
	var Show = function(el)
	{
		if (!el._viscount++)
			Update(el,false);
	}
	
	var ShowOrHide = function()
	{
		if (hidden&&correctcount>0)
		{
			hidden = false;
			for (var i in objects)
				Show(objects[i]);
		}
		else if ((!hidden)&&correctcount==0)
		{
			hidden = true;
			for (var i in objects)
				Hide(objects[i]);
		}
	}
	
	var AddControl = function(el)
	{
		var hasval = HasCorrectValue(el);
		correctcount+=hasval?1:0;
		var o = {el:el, correct:hasval};
		var id = Math.random()+3+"";
		controls[id]=o;
		ShowOrHide();
		
		o.undo = OnChange(el, function()
		{
			var newhasval = HasCorrectValue(el);
			if (newhasval==hasval) return;
			hasval = newhasval;
			correctcount+=hasval?1:-1;
			ShowOrHide();
		});
		
		return function()
		{
			delete controls[id];
			correctcount-=o.correct?1:0;
			o.undo();
			ShowOrHide();
		};
	}
	
	var AddObject = function(el)
	{
		el._3count=(el._3count||0)+1;
		el._viscount=(el._viscount||0);
		if (!hidden)
		{
			el._viscount++;
			if (el._viscount==1&&el._3count>1) Update(el,false);
		}
		else if (el._3count==1) Update(el,true);
		
		// TODO: Clean up
		$(el).removeClass('hidefordom');
		
		var id = Math.random()+3+"";
		objects[id]=el;
		
		return function()
		{
			delete objects[id];
			
			el._3count--;
			if (!hidden)
			{
				if (--el._viscount==0&&el._3count>0) Update(el,true);
			}
			else	if (el._3count==0) Update(el,false);
		}
	}
	
	// These will call RemoveObject and RemoveControl as necessary, so no other removal is necessary
	return undo2(AddNameWatch(controlname, AddControl), AddNameWatch(objectname, AddObject));
}

function Handler(s)
{
	if (s[1])
		return ShowObjectOnControlVal(s[1],s[2],s[3]);
	else if (s[2]==1)
	{
		var undos=[];
		var ctrl=s[3];
		for (var i=4;i<s.length;++i)
		{
			var obj=s[i];
			while (s[++i])
				undos[undos.length]=ShowObjectOnControlVal(ctrl, s[i], obj);
		}
		return function(){for(var i=0;i<undos.length;++i)undos[i]()};
	}
}

_basefns['3']=Handler;

})();















// Drag and drop
// Allows dragging of objects and dropping of them in specific places
//
// Parameters:
//   * draggroup
//   * control identifier for text (only required once for any draggroup)
//   * control identifier for submit (only required once for any draggroup)
//   Then, repeat the following three any number of times:
//   * Some subset of blort, for a target, or empty, for a draggable
//   * item identifier or control name
//   * identifier that's sent to the server (can be empty, if you want the same id as the control)

var dropgroups={};
var droptargetbar;
function JS04setaction(s)
{
	var dg=dropgroups[s[1]]=dropgroups[s[1]]||{};
	if (s[2]) dg._txtt=s[2];
	if (s[3]) dg._subt=s[3];
	for (var i=4;i<s.length;i+=4)
	{
		s[i+3]=s[i+3]||s[i+1];
		var el=getel(s[i+1]);
		if (!el) continue;
		if (s[i])
		{ // Is a target
			el._droptarget=el._droptarget||{_t:{},_b:{},_l:{},_r:{},_o:{}};
			if (s[i].indexOf('t')+1) el._droptarget._t[s[1]]=s[i+3];
			if (s[i].indexOf('r')+1) el._droptarget._r[s[1]]=s[i+3];
			if (s[i].indexOf('b')+1) el._droptarget._b[s[1]]=s[i+3];
			if (s[i].indexOf('l')+1) el._droptarget._l[s[1]]=s[i+3];
			if (s[i].indexOf('o')+1) el._droptarget._o[s[1]]=s[i+3];
		}
		else
		{ // Is draggable
			var de=getel(s[i+2]||s[i+1]);
			if (!el._dropgroups)
			{
				el._dropgroups={};
				var o={_det:s[i+1]};
				addobjref(o);
				addEvent(de,/*{*/"_ondragstart"/*}*/,JS04OnDragStart,o._id);
			}
			el._dropgroups[s[1]]=s[i+3];
		}
	}
}

function JS04OnDragStart(ev,evo,o)
{
	if (!droptargetbar)
	{
		droptargetbar=document.createElement('DIV');
		droptargetbar.className="droptargetbar";
		document.body.appendChild(droptargetbar);
	}
	if (_cancelotherdrag) return;
	var de = getel(o._det);
	StartCache(de);
	setstyle(de,'position','relative');
	o._hasSelect = de.getElementsByTagName("select").length>0;
	getScroll();
	_sizelocked=true;
	addEvent(ev,/*{*/"_ondragmove"/*}*/,JS04OnDragMove,o._id);
	addEvent(ev,/*{*/"_ondragdrop"/*}*/,JS04OnDragDrop,o._id);
	addEvent(ev,/*{*/"_ondragcancel"/*}*/,JS04OnDragCancel,o._id);
	document.body.style.MozUserSelect='none';
	return true;
}

function JS04OnDragMove(ev,evo,o)
{
	o._mx = ev._mx;
	o._my = ev._my;
	o._omx = evo._mx;
	o._omy = evo._my;
	if (o._to) return true;
	ev=evo=null;
	o._to = setTimeout(function(){
		JS04OnDragInner(o);
	}, o._hasSelect?100:1);

	return true;
}

function JS04OnDragInner(o)
{
	setstyle(getel(o._det),'left','-1000em');
	setstyle(getel(o._det),'top','-1000em');
	
	if (navigator.product != "Gecko") return JS04OnDragMove2(o);
	setTimeout(function(){JS04OnDragMove2(o);},1);
	return true;
}

function JS04OnDragMove2(o)
{
	o._to = null;
	var de=getel(o._det);
	var x=o._mx;
	var y=o._my;
	var ox = o._omx;
	var oy = o._omy;
	var myo=o;

	var dbs=getScroll();
	var o=document.elementFromPoint(x-dbs._x,y-dbs._y);
	if (o==droptargetbar&&droptargetbar.lastwasover) {setstyle(droptargetbar,'visibility','hidden');o=document.elementFromPoint(x-dbs._x,y-dbs._y);}
	setstyle(de,'left',x-ox+"px");
	setstyle(de,'top',y-oy+"px");
	if (o==droptargetbar) return myo._dropped&&JS04OnDragDrop(null, null, myo)||true;

	var best={_score:-1};
	while (o)
	{
		if (o._droptarget)
		{
			var $o=$(o);
			var elx=$o.offset().left;
			var ely=$o.offset().top;
			x-=elx;y-=ely;
			var elw=$o.width();
			var elh=$o.height();
			var oo=o._droptarget;
			for (var i in de._dropgroups)
			{
				if (dropgroups[i]&&dropgroups[i]._txtt&&dropgroups[i]._subt&&getel(dropgroups[i]._txtt)&&getel(dropgroups[i]._subt))
				{
					var dei=de._dropgroups[i];
					best = JS04FieldStrength(best,x-elw/2,y,elw/2,elh,dei,"t",i,oo._t[i],o,elx,ely,elw,elh,1/Math.pow(elw*elh,0.25));
					best = JS04FieldStrength(best,x-elw/2,elh-y,elw/2,elh,dei,"b",i,oo._b[i],o,elx,ely,elw,elh,1/Math.pow(elw*elh,0.25));
					best = JS04FieldStrength(best,x,y-elh/2,elw,elh/2,dei,"l",i,oo._l[i],o,elx,ely,elw,elh,1/Math.pow(elw*elh,0.25));
					best = JS04FieldStrength(best,elw-x,y-elh/2,elw,elh/2,dei,"r",i,oo._r[i],o,elx,ely,elw,elh,1/Math.pow(elw*elh,0.25));
					best = JS04FieldStrength(best,x-elw/2,y-elh/2,elw/2,elh/2,dei,"o",i,oo._o[i],o,elx,ely,elw,elh,1/Math.pow(elw*elh,0.25));
				}
			}
			x+=elx;y+=ely;
		}
		o=o.parentElement;
	}
	
	if (best._score<0) {de.targ=null;setstyle(droptargetbar,'visibility','hidden');return myo._dropped&&JS04OnDragDrop(null, null, myo)||true;}
	
	de.targ=best;
	if (best._l=="o")
	{
		droptargetbar.style.width=best._elw;
		droptargetbar.style.height=best._elh;
		droptargetbar.style.filter="Alpha(enabled=1,opacity=50)";
		droptargetbar.lastwasover=true;
	}
	else
	{
		droptargetbar.style.width=droptargetbar.style.height=
		droptargetbar.style.filter="";
		droptargetbar.lastwasover=false;
		if (best._l=="t"||best._l=="b")
		{
			droptargetbar.style.width=best._elw;
			if (best._l=="b") best._ely+=best._elh;
			else best._ely-=droptargetbar.offsetHeight;
		}
		else if (best._l=="l"||best._l=="r")
		{
			droptargetbar.style.height=best._elh;
			if (best._l=="r") best._elx+=best._elw;
			else best._elx-=droptargetbar.offsetWidth;
		}
	}
	moveobj(droptargetbar,best._elx+1,best._ely+1);
	setstyle(droptargetbar,'visibility','visible');
	
	return myo._dropped&&JS04OnDragDrop(null, null, myo)||true;
}

_abs=Math.abs;
function JS04FieldStrength(best,x1,y1,x2,y2,dei,l,i,id,o,elx,ely,elw,elh,scale)
{
	if (!id) return best;
	x1=_abs(x1);x2=_abs(x2);y1=_abs(y1);y2=_abs(y2);
	var fs = scale*(1-((y1/y2<x1/x2)?x1/x2:y1/y2));
	if (fs<=best._score) return best;
	return {_score:fs,_ret:id+"~"+dei+"~"+l,_dg:i,_l:l,_o:o,_elx:elx,_ely:ely,_elw:elw,_elh:elh,_x1:x1,_y1:y1,_x2:x2,_y2:y2};
}

function JS04OnDragDrop(ev,evo,o)
{
	if (o._to) {clearTimeout(o._to);JS04OnDragInner(o);}
	o._dropped = false;
	var a=getel(o._det).targ;
	if (a)
	{
		var b=dropgroups[a._dg];
		getel(b._txtt).value=a._ret;
		// The bar wants to vanish when the whole set of current operations is done. Typically _after_ the resulting AJAXy request.
		OnLoad(function(){setstyle(droptargetbar,'visibility','hidden');});
		return $c(b._subt);
	}
	return JS04OnDragCancel(ev,evo,o);
}

function JS04OnDragCancel(ev,evo,o)
{
	if (o._to) clearTimeout(o._to);
	setstyle(droptargetbar,'visibility','hidden');
	document.body.style.MozUserSelect='';
	EndCache(getel(o._det));
	return true;
}








// Rich Edit
// Allows rich editing of specific objects
//
// Parameters:
//   * Rich Edit ID
//   * Control ID
//
//
// Full example usage:
//   <script type="text/javascript"><!--
//   $$("5~myricheditbox~myhiddencontrol")//--></script>

var lastactive;
function JS05Action(s)
{
	if (!document.selection) return function(){}; // No richedit available
	
	var so = [{c:'Bold',s:'B'},{c:'Italic',s:'<I>I</I>'},{c:'Underline',s:'<U>U</U>'},{c:'InsertUnorderedList',s:'&#8226;'},{c:'InsertOrderedList',s:'1.'}];

	return AddNameWatch(s[1], function(o)
	{
		o.innerHTML='<div class=richeditinner contenteditable></div>';
		var inner=o.firstChild;
		addclass(o,'richeditouter');
		var dragrange;
		
		var JS05GetRich = function()
		{
			var z=document.selection.createRange();
			var o=z.parentElement();
			while (o&&o!=inner) o=o.parentElement;
			return o?z:null;
		}
		
		var srces = {};
		
		var SetValue=function(s)
		{
			for (var i in srces)
				if (srces[i].value!=s)
					srces[i].value = s;
			if (inner.innerHTML!=s) inner.innerHTML=s;
		};
		
		var blockchange = false;
		var JS05Timeout;
		var lastactive;
		var JS05DocSelChange = function()
		{
			if (JS05Timeout) JS05Timeout=clearTimeout(JS05Timeout)&&null;
			
			var z = JS05GetRich();
			if (!lastactive!=!z)
			{
				if (z)
				{
					setstyle(richeditbar,'left',objleft(o)+'px');
					if (!JS05NeedsScroll()) setstyle(richeditbar,'top',(objtop(o)-richeditbar.offsetHeight+1)+'px');
					//selshate(richeditbar);
				}
				else
				{
					setstyle(richeditbar,'left','-1000em');
					//selsunhate(richeditbar);
				}
				
				lastactive = z;
				
				if (!z) SetValue(inner.innerHTML);
			}
			if (!z) return;
			JS05Timeout=setTimeout(function(){SetValue(inner.innerHTML);JS05Timeout=null;},200);
			for (var i=0;i<so.length;i++)
			{
				so[i]._sub.className="richeditbutton"+(z.queryCommandState(so[i].c)?" richedithighlight":"");
			}
			blockchange = true;
			var l=iricheditblocks[z.queryCommandValue("FormatBlock")];
			block._el.selectedIndex=l;
			var l=iricheditfonts[z.queryCommandValue("FontName")];
			font._el.selectedIndex=l;
			var l=iricheditsizes[z.queryCommandValue("FontSize")||3];
			size._el.selectedIndex=l;
			blockchange = false;
			return true;
		}
		
		inner.onpaste=function()
		{
			var z = JS05GetRich();
			z.text = clipboardData.getData('Text');
			return event.returnValue=false;
		};
		inner.ondrop=function()
		{
			var r=document.body.createTextRange();
			r.moveToPoint(event.x,event.y);
			r.select();
			var z = JS05GetRich();
			if (z) z.text = event.dataTransfer.getData('Text');
			event.returnValue=false;
			
			if (!event.ctrlKey&&dragrange) dragrange.text='';
			dragrange=null;
		};
		inner.ondragstart=function()
		{
			dragrange=document.selection.createRange();
		};
		inner.ondragend=function()
		{
			dragrange=null;
		};
		
		
		var richeditbar = document.createElement("div");
		richeditbar.unselectable=true;
		richeditbar.className="richeditbar";
		richeditbar.style.cssText="";
		
		richeditbar.appendChild(document.createTextNode("Style: "));
		for (var i=0;i<so.length;++i)
		{
			so[i]._sub = document.createElement("button");
			so[i]._sub.unselectable=true;
			so[i]._sub.tabIndex=-1;
			so[i]._sub.title=so[i].c;
			so[i]._sub.innerHTML="<b>"+so[i].s+"</b>";
			so[i]._sub.className="richeditbutton";
			so[i]._undo=addEvent(so[i]._sub, /*{*/"_onmouseclick"/*}*/, function(ev, evo){
				var z = JS05GetRich();
				if (z) z.execCommand(evo.title);
				setTimeout(JS05DocSelChange,1);
				return false;
			});
			richeditbar.appendChild(so[i]._sub);
		}
		
		var JS05Sel = function(a, cmd, parent)
		{
			var el = document.createElement("select");
			for (var i=0;i<a.length;i++)
			{
				var op = document.createElement("option");
				op.innerText=a[i];
				op.value=a[i];
				el.appendChild(op);
			}
			el.unselectable = true;
			el.tabIndex = "-1";
			//el.selectedIndex = 0; // Otherwise defaults to -1, which is invalid...
			parent.appendChild(el);
			return {_el:el, _undo:OnChange(el, function(){
				if (blockchange) return;
				var z = JS05GetRich();
				if (!z) return;
				z.execCommand(cmd, false, ControlValue(el));
				setTimeout(JS05DocSelChange,1);
			})};
		}
		
		var block = JS05Sel(richeditblocks, "FormatBlock", richeditbar);
		richeditbar.appendChild(document.createTextNode("  Font: "));
		var font = JS05Sel(richeditfonts, "FontName", richeditbar);
		var size = JS05Sel(richeditsizes, "FontSize", richeditbar);
		
		if(JS05NeedsScroll())
		{
			var scrollClosure = function() {
				richeditbar.style.top = getScroll()._y+'px';
			}
			setInterval(scrollClosure, 55);
		}
		setstyle(richeditbar,'left','-1000em');
		
		document.body.appendChild(richeditbar);
		if (!JS05NeedsScroll()) setstyle(richeditbar,'top',(objtop(o)-richeditbar.offsetHeight+1)+'px');
		
		setFakeParent(richeditbar,inner);
		
		var scr = getScroll();
		var undo1 = addEvent(scr._o,/*{*/"_onselchange"/*}*/,function(){setTimeout(JS05DocSelChange,1);});
		var undo2 = addEvent(scr._o,/*{*/"_onkeypress"/*}*/,function(){setTimeout(JS05DocSelChange,1);});
		var undo3 = AddNameWatch(s[2], function(r)
		{
			SetValue(ControlValue(r));
			srces[r._nw] = r;
			var undo = OnChange(r, function()
			{
				SetValue(ControlValue(r));
			});
			return function()
			{
				delete srces[r._nw];
				undo();
			};
		});
		
		return function()
		{
			undo1();
			undo2();
			undo3();
			for (var i=0;i<so.length;++i)
				so[i]._undo();
			block._undo();
			font._undo();
			size._undo();
			richeditbar.removeNode(true);
		}
	});
}

//RichEdit stuff
//ForeColor, indent, outdent, createLink
//Other commands: fontsize, fontface, cut, copy, paste, justifyright, justifyleft, justifycenter, , InsertImage
function JS05Fill(a){var o={};for(var i=0;i<a.length;i++)o[a[i]]=i;return o;}
function JS05Option(a){var s="";for (var i=0;i<a.length;i++)s+='<option>'+a[i]+'</option>';return s;}
function JS05NeedsScroll(){return (window.VBArray&&!window.XMLHttpRequest)||document.compatMode=='BackCompat';}

var richeditblocks=["Normal","Formatted","Address","Heading 1","Heading 2","Heading 3","Heading 4","Heading 5","Heading 6"];
var iricheditblocks=JS05Fill(richeditblocks);
var richeditfonts=["Arial","Courier New","Times New Roman","Verdana"];
var iricheditfonts=JS05Fill(richeditfonts);
var richeditsizes=[1,2,3,4,5,6,7];
var iricheditsizes=JS05Fill(richeditsizes);


















// Column-resize
//
// Parameters:
//   * Object Identifier
//   * Textbox identifier
//   * Submit identifier
//
// Object must be a TABLE. TDs in first row must have names or ids. Must also have sizes in the same units as each other.
// On successful resize, textbox contains columnname1~columnname2~size1~size2 and submit is pressed.
//
// Examples:
// "6~obj~textbox~submit" enables column resizing for obj, using textbox and submit to give the results to the server.
//
// Full example usage:
//   <script type="text/javascript"><!--
//   $$("6~obj~textbox~submit")//--></script>

var resizesetup=false;
function setaction06(s)
{
	if (!_isIE) return;
	var obj = getel(s[1]);
	var txt = getel(s[2]);
	var sub = getel(s[3]);
	if (!obj||!txt||!sub) return function(){};
	
	var l;
	var r;
	var lw;
	var rw;
	var diff;
	var lastlw;
	var lastrw;
	var rcss;
	var lcss;
	
	var lasthit=false;
	var lastcursor=null;
	var droplastcursor=function()
	{
		_cancelotherdrag=false;
		if (lasthit) {obj.style.cursor=lastcursor;lasthit=null;}
	}
	
	var JS06GetDetails = function(ev,evo)
	{
		var tro=evo;
		while (tro&&tro.tagName!="TR"&&tro.children)
			tro=tro.children[0];
		if (!tro||tro.tagName!="TR") return droplastcursor();
		
		var i=tro.children.length-1;
		l=tro.children[i];
		while (i-->0)
		{
			r=l;
			l=tro.children[i];
			var ex=objleft(r);
			if (ev._mx>ex-6&&ev._mx<ex+10) break;
		}
		if (i<0||!r.id&&!r.name||!l.id&&!l.name) return droplastcursor();
		
		var cursorelement=tro;
		lcss=JS06GetBits(l);
		rcss=JS06GetBits(r);
		if (rcss._u!=lcss._u) return droplastcursor();
		if (!lasthit)
		{
			lastcursor=obj.style.cursor;
			lasthit=true;
			obj.style.cursor="e-resize";
		}
		_cancelotherdrag=true;
		return true;
	}
	
	// JS06GetDetails sets up the cursor correctly.
	addEventMouseMove(obj,JS06GetDetails);
	
	addEvent(obj,/*{*/"_ondragstart"/*}*/,function(ev, evo)
	{
		if (!JS06GetDetails(ev,evo)) return;
		lw=l.offsetWidth;
		rw=r.offsetWidth;
		
		addEvent(ev,/*{*/"_ondragmove"/*}*/,function(ev, evo)
		{
			diff=ev._mx-evo._mx;
			if (diff>rw||diff<-lw) return l.runtimeStyle.width=r.runtimeStyle.width="";
			r.runtimeStyle.width=lastrw=Math.round(100*rcss._s*(rw-diff)/rw)/100+rcss._u;
			l.runtimeStyle.width=lastlw=Math.round(100*lcss._s*(lw+diff)/lw)/100+lcss._u;
		});
		addEvent(ev,/*{*/"_ondragdrop"/*}*/,function(ev, evo)
		{
			if (diff<=rw&&diff>=-lw)
			{
				txt.value=(l.id||l.name)+"~"+(r.id||r.name)+"~"+lastlw+"~"+lastrw;
				return $c(sub);
			}
			return JS06OnDragCancel(ev,evo);
		});
		var JS06OnDragCancel = function(ev, evo)
		{
			droplastcursor();
			l.runtimeStyle.width="";
			r.runtimeStyle.width="";
			return true;
		};
		addEvent(ev,/*{*/"_ondragcancel"/*}*/, JS06OnDragCancel);
		
		return true;
	});
}

function JS06GetBits(o)
{
	var m = /(\d*\.?\d*)(.*)/.exec((o.currentStyle.width||'')+'');
	return {_s:m[1]-0, _u:m[2]};
}

















// Pop-up menus using iframes
//
// Parameters for return:
//   * [empty]
//   * Return value (can include '~' characters)
//
// Parameters for pop-up:
//   * Object Identifier
//   * Menu Identifier
//   * When menu should appear
//     0 = on mouseover
//     1 = on left mouse click
//     2 = on right mouse click
//   * posx (0=0,1=1,h=0.5)
//     resultant x-position=posx[0]*mousex + posx[1]*objleft + posx[2]*objwidth - posx[3]*menuwidth + posx[4]*scrollLeft + posx[5]*clientWidth
//   * posy (0=0,1=1,h=0.5)
//     resultant y-position=posy[0]*mousey + posy[1]*objtop + posy[2]*objheight - posy[3]*menuheight + posy[4]*scrollTop + posy[5]*clientHeight
//   * menuwidth
//     0 = as small as possible
//     1 = same width as object
//   * menuheight
//     0 = as small as possible
//     1 = same height as object
//   * URL for content
//   * Object Identifier for failure
//   * ID of textbox field for result
//   * ID of submit button for result

var JS07Loading={};
var JS07CheckInterval;
var JS07Press;
function setaction07(s)
{
	if (s[1]==''&&window.iframeid!=null)
		return parent[/*{*/"JS07Return"/*}*/](iframeid,s.slice(1).join("~"));
	var o={};
	addobjref(o);
	var menuish = domenu(s[1],s[2],parseInt(s[3])||0,parseInts(s[4]),parseInts(s[5]),parseInt(s[6]),parseInt(s[7]),null,JS07Prepare);
	o._menu=menuish._menu;
	o._menu._7=o;
	o._width=s[6];
	o._height=s[7];
	o._url=s[8];
	o._class=s[12]||'iframepopup';
	o._errobj=getel(s[9]);
	o._rettxt=s[10];
	o._retsubname=s[11];
	o._prehtml=s[13]||'#C#';
	if (o._prehtml=="#C#") {o._prehtml='<div class="iframecloser"><div class="iframecloseinner"><a href="#" onclick="JS07CloseByID('+o._menu._id+');return false;">Close</a></div></div>';}
	var undo1=addEvent(o._menu,/*{*/"_onmenuopen"/*}*/,JS07MenuOpen,o._id);
	var undo2=addEvent(o._menu,/*{*/"_onmenuclose"/*}*/,JS07MenuClose,o._id);
	return function(){undo1();undo2();menuish._undo();};
}

function JS07Close(el)
{
	while (el&&el._id==null) el=el.parentElement;
	return JS07CloseByID(el._id);
}

function JS07CloseByID(id)
{
	var o = eventobjs[id];
	JS01EndCapture(o);
}

function JS07Prepare(o)
{
	o=o._7;
	o._iframe = document.createElement('iframe');
	o._iframe._id=o._id;
	o._iframe.allowTransparency=true;
	o._load=1;
	JS07Loading[o._id]=1;
	setTimeout("JS07OnTimeout("+o._id+")",5000);
	o._iframe.frameBorder=0;
	o._iframe.src=o._url;
	
	o._ifrwrap = document.createElement('div');
	o._ifrwrap._id=o._id;
	//o._ifrwrap.className = o._class;
	o._ifrwrap.style.zIndex=200;
	if(o._prehtml != 0)
	{
		o._ifrwrap.innerHTML = o._prehtml;
	}
	o._ifrwrap.style.position = 'absolute';
	o._ifrwrap.style.top = '-1000em';
	document.body.appendChild(o._ifrwrap);
	o._ifrwrap.appendChild(o._iframe);
	
	if (!JS07CheckInterval)
		JS07CheckInterval=setInterval("JS07Check()",55);
}

function JS07MenuOpen(ev,evo,o)
{
	o._vis=true;
}

function JS07Refresh()
{
	for (var i in JS07Loading)
	{
		try
		{
			eventobjs[i]._iframe.contentWindow.iframeid=i;
		}catch(e){}
	}
}

function JS07LoadComplete(oid)
{
	var o=eventobjs[oid];
	if (!o) return;
	if (o._load!=2)
	{
		o._load=2;
		
		JS01ReplaceObject(o._menu,o._ifrwrap,true);
	}
}

function JS07MenuClose(ev,evo,o)
{
	o._vis=false;
	//TODO: Cancel all captures and run mouseover out of inner document.body
	CloseBigCover();
}

function JS07OnTimeout(oid)
{
	var o=JS07Loading[oid];
	if (!o) return;
	if (o._vis)
	{
		delete JS07Loading[oid];
		JS01ReplaceObject(o._menu,o._errobj,false);
	}
}

function JS07SetSize(oid,w,h)
{
	var o=eventobjs[oid];
	var r=getScroll();
	if (o._width=='2')
		w = Math.max(w, h * r._w/r._h);
	if (o._height=='2')
		h = Math.max(h, w * r._h/r._w);
	w = Math.min(w, 0.9*r._cw);
	h = Math.min(h, 0.9*r._ch);
	o._iframe.style.width=w+"px";
	o._iframe.style.height=h+"px";
	if (o._iframe._propertycache)
	{
		o._iframe._propertycache._style.width=w+"px";
		o._iframe._propertycache._style.height=h+"px";
	}
	if (o._vis&&o._load==2)
		JS01FixMenu2(o._menu);
}

function JS07GetPos(oid)
{
	var o=eventobjs[oid];
	return {_x:o._ifrwrap.offsetLeft,_y:o._ifrwrap.offsetTop};
}

function JS07Return(oid,val)
{
	var o=eventobjs[oid];
	if (o._rettxt) getel(o._rettxt).value=val;
	JS07Press=o._retsubname;
	JS01EndCapture(o._menu);
}

function JS07Check()
{
	if (JS07Press)
	{
		var e=getel(JS07Press);
		// TODO: Find out why e.click() doesn't submit the form
		// As it is, this code renames the submit and creates
		// a text field in its place, in order to make sure
		// the server thinks the button's been pressed.
		var f=e.form;
		e.name="rand"+Math.random();
		e = document.createElement("input");
		e.type = "text";
		e.name = JS07Press;
		e.value = "1";
		e.style.display = "none";
		e._fakeSubmit = true;
		f.appendChild(e);
		if (f.onsubmit) f.onsubmit();
		_bwsnavig=true;
		if (!window.SubmitPartial) f.submit();
		JS07Press=null;
	}
}






// Draggables
// $$("9~mynowdraggablething")
function JS09setaction(s)
{
	return AddNameWatch(s[1], function(de)
	{
		if (!de._dragenabled++)
		{
			de._dragenabled=1;
			de._undodragen = addEvent(de,/*{*/"_ondragstart"/*}*/, function(ev)
			{
				if (_cancelotherdrag) return;
				getScroll();
				_sizelocked=true;
				addEvent(ev,/*{*/"_ondragmove"/*}*/,function(ev, evo)
				{
					moveobj(de,x+ev._mx-evo._mx,y+ev._my-evo._my);
					return true;
				});
				addEvent(ev,/*{*/"_ondragdrop"/*}*/,hidesels);
				addEvent(ev,/*{*/"_ondragcancel"/*}*/,hidesels);
				var x=objleft(de);
				var y=objtop(de);
				return true;
			});
		}
		return function(){
		if (!--de._dragenabled)
			de._undodragen();
		};
	});
}



// Click-to-insert identifiers
// $$("a~thingwithtokensinit")
function setaction0a(s)
{
	if (!document.selection) return function(){};
	return AddNameWatch(s[1], function(el)
	{
		var txt=el.innerHTML+'';
		var newtxt=txt.split(/[,\s]+/);
		for (var i=0;i<newtxt.length;++i)
			newtxt[i]="<span unselectable=on>"+newtxt[i]+"</span>";
		el.innerHTML=newtxt.join(" ");
		var undo = addEvent(el, "_onmouseclick", function(ev)
		{
			var z=document.selection.createRange();
			var oo=z.parentElement();
			if (oo.type!='text'&&oo.tagName!='TEXTAREA') return;
			z.text=ev._el.innerHTML;
			return true;
		});
		return function(){ undo(); el.innerHTML = txt; };
	});
}



// Set values on click
// $$("b~clickable~controls~valuetoset~setalltotrue")
function setaction0b(s)
{
	var name = s[2];
	var val = s[3];
	var alltrue = s[4];
	
	return AddNameWatch(s[1], function(el)
	{
		return addEvent(el, "_onmouseclick", function()
		{
			var els = getels(name);
			for (var i in els)
			{
				var el=els[i];
				if (el.type=="checkbox"||el.type=="radio")
					el.checked=!!alltrue||val==el.value;
				else if (el.tagName.toLowercase()=="select")
					el.selectedIndex=val;
				else
					el.value=val;
			}
			return true;
		});
	});
}



// Sketch field
// $$("d~domnodeinwhichtoputit~hiddenfieldwithvalue")
// TODO: Make this and 03 use a new AddNamePairWatch.
function setaction0d(s)
{
	var src = getel("bwsjs").src.replace("0.js","WebSketch.dll");
	if (src.substring(0,5)!='http:'&&src.substring(0,6)!='https:')
	src='http:'+src;
	
	var val="";
	var controls={};
	var objects={};
	
	var SetVal = function(s)
	{
		if (val==s) return;
		val = s;
		for (var i in controls)
			if (controls[i].value!=val)
				controls[i].value=val;
		for (var i in objects)
			if (objects[i].picture!=val)
				objects[i].picture=val;
	};
	
	var undo1 = AddNameWatch(s[1], function(el)
	{
		var object = document.createElement('object');
		el.appendChild(object);
		object.classid=src+'#BWS.WebSketch';
		// Giving a dynamic size too early would make the control only appear on mouseover
		object.style.width=object.style.height="1px";
		var init = false;
		var check = function()
		{
			if (!init&&object.ready)
			{
				init=true;
				object.style.width=object.style.height="100%";
				if(val)object.picture = val;
				objects[el._nw] = object;
			}
			else if (init&&object.changed)
				SetVal(object.picture);
		};
		var interval = setInterval(check, 55);
		
		return function()
		{
			delete objects[el._nw];
			clearInterval(interval);
			object.parentNode.removeChild(object);
		}
	});
	
	var undo2 = AddNameWatch(s[2], function(el)
	{
		controls[el._nw] = el;
		SetVal(el.value);
		var undo=OnChange(el,function()
		{
			SetVal(el.value);
		});
		return function(){undo();delete controls[el._nw];};
	});

	return function(){undo1();undo2();};
}



// Press button immediately on value change
// $$("e~mycontrol~myidofthingtopress")
function setaction0e(s)
{
	return AddNameWatch(s[1], function(el)
	{
		var oldval = ControlValue(el);
		return OnChange(el, function(el, blah, balh, force)
		{
			var val = ControlValue(el);
			if (!force&&val!=oldval)
			{
				oldval=val;
				$log('E caused refresh '+val);
				$c(getel(s[2]));
			}
		});
	});
} 










// Dynamic server update
//
// Parameters:
//   * Action type:
//     1: Turn into editable collection - call given URL when data changes and focus is lost.
//       * Outer element for the set
//       * URL to call when data changes
//       Repeatedly:
//         * Value name
//         * Object identifier for value or any containing element thereof (useful for dates)

function setaction0f(s)
{
	if (s[1]=='1')
	{
		var o={_url:s[3], _outer:getel(s[2])||getel(s[5]).parentNode,_els:[]};

		var JS0FFocusOut = function()
		{
			var url = o._url;
			if (url&&url.indexOf("?")==-1) url+="?";
			for (var i=0;i<o._els.length;++i)
			{
				var s="";
				for (var j=0;j<o._els[i]._els.length;++j)
				{
					var el = o._els[i]._els[j];
					var ls = ControlText(el);
					s = combine(s, ControlValue(el));
					el._otherelement.innerHTML=ControlText(el).split("&").join("&amp;").split("<").join("&lt;");
					HideControl(el);
					ShowControl(el._otherelement);
				}
				if (url)
					url+="&"+o._els[i]._name+"="+escape(s);
			}
			
			if (url&&o._lastURL&&o._lastURL!=url)
				getXML(url);
			o._lastURL = url;
		}
		
		var JS0FFocusIn = function(ev)
		{
			var firstel = null;
			var lastel = null;
			for (var i=0;i<o._els.length;++i)
				for (var j=0;j<o._els[i]._els.length;++j)
				{
					lastel = o._els[i]._els[j];
					firstel = firstel||lastel;
					ShowControl(lastel);
				}
			
			FocusControl(ev._kshift?lastel:firstel, ev._kshift);
			
			// We can only hide the spans once we've put focus on something else
			for (var i=0;i<o._els.length;++i)
				for (var j=0;j<o._els[i]._els.length;++j)
					HideControl(o._els[i]._els[j]._otherelement);
		}
		
		addEvent(o._outer,"_onfocusout",closure(JS0FFocusOut),0);
		addEvent(o._outer,"_onfocusin",closure(JS0FFocusIn),0);
		for (var i=4;i<s.length;i+=2)
		{
			var eln = s[i];
			var el = getel(s[i+1]);
			el._fid = o._fid;
			var lo = {_name:eln, _el:el,_els:FindControls(el)};
			arraypush(o._els, lo);
			for (var j=0;j<lo._els.length;++j)
			{
				var lel = lo._els[j];
				var span = document.createElement("SPAN");
				span.tabIndex = 0;
				lel._otherelement = span;
				lel.parentElement.insertBefore(span, lel);
				span.id = ".."+lel.id+"..";
				HideControl(span);
			}
		}
		JS0FFocusOut();
	}
}

function combine(s1,s2)
{
	return !s2?s1||'':!s1?s2:s1+' '+s2;
}










// Upload controls
// 
// Paramters:
//    * id of upload box
//    * id of delete button
//    * id of filename span

function setaction0g(s)
{
	var boxid = s[1];
	var delid = s[2];
	var textid = s[3];
	
	var box = document.getElementById(boxid);
	var delbtn = document.getElementById(delid);
	var text = document.getElementById(textid);
	var par = (box||delbtn);
	par = par.parentNode||par.parentElement;
	var ifr;
	
	var somethingchanged = closure(function (ev)
	{
		var o = ifr?ifr.contentWindow.document.getElementById(boxid):box;
		var f = o?o.value.split('\\'):[''];
		f = f[f.length - 1].split('/');
		if (f[f.length-1]&&!text.innerHTML)
		{
			var o=ifr||box;
			// There's a filename, so we'll hide the upload box and show the delete button
			o.style.position = "absolute";
			o.style.left = "-1000em";
			par.insertBefore(delbtn,ifr||box);
			if (fakebutton)
				par.removeChild(fakebutton);
			
			text.appendChild(document.createTextNode(f[f.length - 1]));
			
			if (ifr) _Uploads[boxid]=ifr;
		}
	});
	
	var btnclick = closure(function (ev)
	{
		if (CreateIfr())
		{
			delete _Uploads[boxid];
		}
		else
		{
			var newbox = document.createElement("input");
			newbox.type = "file";
			newbox.id = boxid;
			newbox.name = boxid;
			newbox.onchange = somethingchanged;
			newbox.onkeydown = function() { return false; };
			newbox.oncontextmenu = function() { return false; };
			
			if (ifr||box)
				par.replaceChild(newbox, ifr||box);
			else
				par.insertBefore(newbox, delbtn.nextSibling);
			box = newbox;
		}
		
		if (fakebutton)
		{
			par.insertBefore(fakebutton, delbtn);
		}
		par.removeChild(delbtn);
		
		text.innerHTML = "";
		return false;
	});
	
	var CreateIfr=function()
	{
		if (window.SubmitPartial)
		{
			var oldifr = ifr;
			ifr = document.createElement('iframe');
			ifr.src="javascript:''";
			ifr.style.width="1px";
			ifr.style.height="1px";
			ifr.frameBorder="0";
			ifr.style.border="0";
			ifr._upid=boxid;
			if (oldifr)
				par.replaceChild(ifr, oldifr);
			else
				par.insertBefore(ifr, box||delbtn.nextSibling);
			ifr.contentWindow.document.open();
			ifr.contentWindow.document.write("<html><head><title>Upload</title><style>body {margin:0;padding:0} table {border-collapse:collapse;border:0;} td {border:0}</style><script>function go(){var tab=document.getElementById('thetable');if (tab.offsetWidth==0) return setTimeout(go,1);frameElement.style.width=tab.offsetWidth+'px';frameElement.style.height=tab.offsetHeight+'px';document.body.style.overflow='hidden';}</script></head><body onload='setTimeout(go,1)'><form class='uploadform' method='post' enctype='multipart/form-data' action=''><table id='thetable'><tr><td id='thetd'><input type='file' id='"+boxid+"' name='"+boxid+"' onchange='frameElement.onchange()'/></td></tr></table></form></body></html>");
			ifr.contentWindow.document.close();
			ifr.onchange = somethingchanged;
			return true;
		}
	};
	if (box&&CreateIfr())
	{
		par.removeChild(box);
		box = null;
	}
	
	if (!delbtn)
	{
		delbtn = document.createElement("input");
		delbtn.type = "submit";
		delbtn.id = delid;
		delbtn.className = "genericsubmit";
		delbtn.name = delid;
		delbtn.onclick = btnclick;
		delbtn.value = "X";
		
		// No delbtn means no text.
		text = document.createElement("span");
		text.id = textid;
		par.insertBefore(text,box||ifr);
	}
	else
	{
		// We only need a fakebutton if there is initially a delbtn.
		var fakebutton = document.createElement("input");
		fakebutton.type = "hidden";
		fakebutton.name = delid;
		fakebutton.value = "yes";
		fakebutton._fakeSubmit = true;
	}

	if (box) box.onchange = somethingchanged;
	if (delbtn) delbtn.onclick = btnclick;
}





// Forwardslash-based hierarchy
// Splits a single select control into a dynamic series of controls, based on forward slashes in the original set of options.
//
// Parameters:
//   * Select control id
//
// Full example usage:
//   <script type="text/javascript"><!--
//   $$("h~myselectid")//--></script>

function clearChildSelect(select) {
	if (select.childSelect) {
		clearChildSelect(select.childSelect);
		select.childSelect.parentNode.removeChild(select.childSelect);
		select.childSelect = null;
	}
}
function cascadePopulate(select, level, finalList, isRoot) {
	if (!isRoot) {
		var rootOption = document.createElement("OPTION");
		rootOption.text = "";
		rootOption.value = level.id;
		try {
			select.add(rootOption, null);
		}
		catch(ex) {
			select.add(rootOption);
		}
	}
	for (var kid in level.children) {
		var kidOption = document.createElement("OPTION");
		kidOption.parentSelect = select;
		kidOption.text = "/" + kid;
		kidOption.value = level.children[kid].id;
		kidOption.level = level.children[kid];
		try {
			select.add(kidOption, null);
		}
		catch(ex) {
			select.add(kidOption);
		}
	}
	select.onchange = function() {
		var newSelect = document.createElement("SELECT");
		newSelect.className = "genericselect";
		var selOption = this.options[this.selectedIndex];
		var anyKids = false;
		for (var _ in selOption.level.children) {
			anyKids = true;
			break;
		}
		if (anyKids) {
			cascadePopulate(newSelect, selOption.level, finalList, false);
			clearChildSelect(this);
			this.parentNode.appendChild(newSelect);
			this.childSelect = newSelect;
		}
		else {
			clearChildSelect(this);
		}
		finalList.value = selOption.level.id;
	}
	select.onblur = select.onchange
}
function setaction0h(s) {
	var fieldID = s[1];
	var fullList = document.getElementById(fieldID);
	var hierarchy = {};
	
	for (var i=0, imax=fullList.options.length; i<imax; i++) {
		var option = fullList.options[i];
		var segments = (" " + option.text).split(/\//g);
		var level = hierarchy;
		if (segments.length == 1) {
			if (!level.children)
				level.children = {};
			level.children[''] = {id: option.value, path: option.text};
		}
		for (var j=1, jmax=segments.length-1; j<=jmax; j++) {
			if (!level.children)
				level.children = {};
			var newLevel = level.children[segments[j]];
			if (!newLevel) {
				newLevel = {};
				level.children[segments[j]] = newLevel;
			}
			level = newLevel;
			if (j == jmax) {
				level.id = option.value;
				level.path = option.text;
			}
		}
	}
	
	var topLevelSelect = document.createElement("SELECT");
	topLevelSelect.className = "genericselect";
	cascadePopulate(topLevelSelect, hierarchy, fullList, true);
	
	fullList.parentNode.appendChild(topLevelSelect);
	fullList.style.display = 'none';
}







var activesig;
var activestart;
var sigswaiting = self.sigswaiting||[];

function setaction0s(s)
{
	var divid = s[1];
	// If fieldid is present, we're editable. Otherwise, val is present.
	var fieldid = s[2];
	var val = s[3];
	
	return AddNameWatch(divid, function(div)
	{
		var sigid='sig'+Math.floor(Math.random()*1000000000);
		var startid='sigstart'+Math.floor(Math.random()*1000000000);
		var finishid='sigfinish'+Math.floor(Math.random()*1000000000);
		
		$(div).html('<object classid="clsid:69A40DA3-4D42-11D0-86B0-0000C025864A" height="75" id="'+sigid+'" name="SigPlus1" style="height: 120px; width: 360px" viewastext="yes"><param name="_Version" value="131095" /><param name="_ExtentX" value="4842" /><param name="_ExtentY" value="1323" /><param name="_StockProps" value="0" />You need to enable the SigPlus ActiveX control in order to use this field.</object>'+(fieldid?'<br /><input type="button" value="Start Sign" class="genericsubmit" id="'+startid+'"/><input type="button" value="Clear" class="genericsubmit" /><input type="button" value="Finish Sign" class="genericsubmit" id="'+finishid+'"/>':''));
		
		if (fieldid)
		{
			$(div).find('input')[0].onclick=function(){StartSig(sigid, startid);return false;}
			$(div).find('input')[1].onclick=function(){ClearSig(sigid, startid, finishid, fieldid);return false;}
			$(div).find('input')[2].onclick=function(){FinishSig(sigid, fieldid, finishid);return false;}
		}
		
		var sig = getel(sigid);
		
		sig.SigCompressionMode=1;
		sig.DisplayPenWidth=10;
		sig.JustifyMode=0;
		sig.EncryptionMode=0;
		if (val) sig.SigString=val;
		
		var undo1 = function()
		{
			RemoveSigFromWaiting(sigid,fieldid);
		}
		
		var undo2 = fieldid?AddNameWatch(fieldid, function(field)
		{
			sig.SigCompressionMode=1;
			sig.DisplayPenWidth=10;
			sig.JustifyMode=0;
			sig.EncryptionMode=0;
			
			if (field.value)
			{
				sig.SigString=field.value;
				getel(startid).disabled=true;
				getel(finishid).disabled=true;
			}
			else
			{
				AddSigToWaiting(sigid,startid);
			}
			return function(){};
		}):function(){};
		return function(){undo1();undo2();};
	});
}

function RemoveSigFromWaiting(sigid, startid)
{
	while (activesig==sigid) ActivateNextSig();
	var oldsw = sigswaiting;
	sigswaiting=[];
	for (var i=0;i<sigswaiting.length;++i)
		if (oldsw[i][0]!=sigid)
			sigswaiting[i]=oldsw[i];
}

function AddSigToWaiting(sigid, startid)
{
	if (!activesig) StartSig(sigid, startid);
	else arraypush(sigswaiting, [sigid, startid]);
}

function StartSig(sigid, startid)
{
	RemoveSigFromWaiting(sigid, startid);
	
	if (activesig)
	{
		getel(activestart).disabled=false;
		getel(activesig).TabletState=0;
		activesig=null;
		activestart=null;
	}
	if (getel(sigid)==null) return ActivateNextSig();
	getel(sigid).TabletState=1;
	getel(startid).disabled=true;
	activesig = sigid;
	activestart = startid;
}

function ActivateNextSig()
{
	//TODO: Find out why sometimes !getel(activesig)
	if (activesig&&getel(activesig)) getel(activesig).TabletState=0;
	activesig = null;
	activestart = null;
	if (sigswaiting.length>0)
	{
		var sigid = sigswaiting[0][0];
		var startid = sigswaiting[0][1];
		sigswaiting=sigswaiting.slice(1);
		StartSig(sigid, startid);
	}
}

function FinishSig(sigid, fieldid, finishid) {
	var sigplus = getel(sigid);
	if (activesig==sigid)
	{
		sigplus.TabletState=0;
		ActivateNextSig();
	}
	sigplus.EncryptionMode=0;
	sigplus.SigCompressionMode=1;
	getel(fieldid).value=sigplus.NumberOfTabletPoints==0?'':sigplus.SigString;
	getel(finishid).disabled=true;
}

function ClearSig(sigid, startid, finishid, fieldid)
{
	var sigplus = getel(sigid);
	sigplus.ClearTablet();
	getel(fieldid).value="";
	getel(finishid).disabled=false;
	if (activesig==sigid)
		sigplus.TabletState = 1;
	else
		getel(startid).disabled=false;
	if (!activesig)
		StartSig(sigid, startid);
	else
		arraypush(sigswaiting, [sigid, startid]);
}








// Ruler
// It allows the dragging of marks in a horizontal ruler
//
// Parameters for the ruler
//   * 0
//   * id for the whole ruler
//   * id of ruler element
//   * id of form control that receives the values for a drag operation  "ID~poschange" (poschange is a possibly negative non-integer number of ruler-widths by which the element has moved)
//   * id of form control that gets clicked for a drag operation

// Parameters for a ruler mark
//   * 1
//   * id for the whole ruler
//   * Opaque ID of an alineation position
//   * id of a corresponding page element (assumed to be positioned)

_rulers = {};
_rulermarks = {};
_aligned = {};
function setaction0v(s)
{
	if (s[1]=="0")
	{
		return AddNameWatch(s[3], function(ruler)
		{
			return AddNameWatch(s[4],function(txt)
			{
				return AddNameWatch(s[5],function(sub)
				{
					return AddObject(/*{*/"_rulers"/*}*/, s[2], function(o)
					{
						o._ruler = ruler;
						o._txt = txt;
						o._sub = sub;
						o._right = s[6];
					});
				});
			});
		});
	}
	else if (s[1]=="1")
	{
		return AddNameWatch(s[4], function(el)
		{
			return AddObject(/*{*/"_rulermarks"/*}*/, s[2], function(o)
			{
				o._el = el;
				o._name = s[3];
				return addEvent(el, /*{*/"_ondragstart"/*}*/, function(ev)
				{
					_sizelocked=true;
					var line = document.createElement("div");
					var scr = getScroll();
					line.className="rulerguideline";
					line.style.height=scr._h+"px";
					var undobodyadd = BodyAdd(line);
					var undocache = StartCache(el);
					var offset = 0;
					var basepos = objleft(el)+el.offsetWidth/2;
					var undom = addEvent(ev, /*{*/"_ondragmove"/*}*/, function(evm)
					{
						offset = evm._mx-ev._mx;
						
						setstyle(el,"left",offset-el.offsetWidth/2+"px");
						line.style.left=objleft(el)+el.offsetWidth/2+1+"px";
						return true;
					});
					var undod = addEvent(ev, /*{*/"_ondragdrop"/*}*/, function(evd)
					{
						if (!_rulers[s[2]]) return lundo();
						for (var i in _rulers[s[2]])
						{
							var r = _rulers[s[2]][i];
							r._txt.value = s[3]+"~"+(basepos+offset-objleft(r._ruler))/r._ruler.offsetWidth;
							$c(r._sub);
						}
						lundo();
						return true;
					});
					
					var lundo = function()
					{
						undobodyadd(line);
						undocache();
						undom();
						undod();
						_sizelocked=false;
					};
					return true;
				});
			});
		});
	}
	else if (s[1]=="2")
	{
		var currentlyover = false;
		var undo = AddOverHandlerByName(s[3], function(el){
			undo();
			currentlyover = true;
			undo = AddNameWatch(s[3], gogogo);
			currentlyover = false;
		});
		var gogogo = function(el)
		{
			return AddNameWatch(s[4], function(txt)
			{
				return AddNameWatch(s[5], function(sub)
				{
					var side = s[6]; // l or r
					
					var handleouter = document.createElement("div");
					var handleinner = document.createElement("div");
					var handleimg = document.createElement("img");
					handleimg.src=s[7]+(side=='l'?"rulerleft.gif":"rulerright.gif");
					handleouter.className=(side=='l'?"edgehandleleft":"edgehandleright");
					handleouter.appendChild(handleinner);
					handleouter.appendChild(handleimg);
					el.style.position="relative";
					el.appendChild(handleouter);
					if (currentlyover) handleouter.style.visibility="visible";
					
					var undo1 = addEvent(el, /*{*/"_onmouseover"/*}*/, function()
					{
						handleouter.style.visibility="visible";
					});
					var undo2 = addEvent(el, /*{*/"_onmouseout"/*}*/, function()
					{
						handleouter.style.visibility="hidden";
					});
					
					var interest = {};
					var lmark = s[8];
					var lmarkinc = s[9];
					var rmark = s[10];
					var rmarkinc = s[11];
					var undo3 = addEvent(handleouter, /*{*/"_ondragstart"/*}*/, function(ev)
					{
						if (!_rulermarks[s[2]]) return false;
						_sizelocked=true;
						
						var undo = [];
						var sizer = document.createElement("div");
						sizer.className="sizer";
						arraypush(undo, BodyAdd(sizer));
						setstyle(sizer, "width", el.offsetWidth+"px");
						setstyle(sizer, "height", el.offsetHeight+"px");
						moveobj(sizer, objleft(el), objtop(el));
						var sizerright = objleft(sizer)+sizer.offsetWidth;
						
						var marks = [];
						var interesting = false;
						var leftlimit;
						var rightlimit;
						// NOTE: This is dependent on objects iterating in the same order that entries were inserted. This is guaranteed, according to recent JS standards and every browser that isn't Safari/KHTML.
						var scr = getScroll();
						for (var i in _rulermarks[s[2]])
						{
							var o = _rulermarks[s[2]][i];
							var n = o._name;
							var p =objleft(o._el)+o._el.offsetWidth/2+1;
							if (n==lmark)
							{
								leftlimit = p;
								interesting = true;
								if (!lmarkinc) continue;
							}
							if (n==rmark)
							{
								rightlimit = p;
								if (!rmarkinc) interesting = false;
							}
							if (!interesting) continue;
							var line = document.createElement("div");
							line.className="rulerguideline";
							line.style.height=scr._h+"px";
							arraypush(undo, BodyAdd(line));
							arraypush(marks, {pos:p, name: n});
							line.style.left=p;
							if (n==rmark) interesting=false;
						}
						
						var closest = null;
						arraypush(undo,addEvent(ev, /*{*/"_ondragmove"/*}*/, function(evm)
						{
							var thisclosest = null;
							var dist = 1000000;
							for (var i=0;i<marks.length;++i)
							{
								var o = marks[i];
								var p = marks[i].pos;
								if (s[6]=="l"&&p+10>=objleft(sizer)+sizer.offsetWidth) continue;
								if (s[6]=="r"&&p-10<=objleft(sizer)) continue;
								var el2dist = Math.abs(p-evm._mx);
								if (el2dist<dist&&el2dist<20) {dist = el2dist; thisclosest = marks[i];}
							}
							if (!thisclosest)
								thisclosest = {pos:Math.max(leftlimit+20, Math.min(rightlimit-20, evm._mx))};
							closest = thisclosest;
							if (s[6]=="l")
							{
								setstyle(sizer, "width", sizerright-closest.pos);
								moveobj(sizer, closest.pos, objtop(el));
							}
							else
								setstyle(sizer, "width", closest.pos-objleft(sizer));
							return true;
						}));
						arraypush(undo,addEvent(ev, /*{*/"_ondragdrop"/*}*/, function(evd)
						{
							if (closest)
							{
								if (!closest.name)
								{
									for (var i in _rulers[s[2]])
										var r = _rulers[s[2]][i];
									txt.value = (closest.pos - objleft(r._ruler))/r._ruler.offsetWidth;
								}
								else
									txt.value=closest.name;
								$c(sub);
							}
							lundo();
							return true;
						}));
						
						var lundo = function()
						{
							for (var i=0;i<undo.length;++i)
								undo[i]();
						}
						return true;
					});
					
					return function(){undo1();undo2();undo3();_sizelocked=false;};
				});
			});
		};
		// undo can be reassigned, so don't mess with this.
		return function(){undo();};
	}
}

function AddObject(name, sub, fn)
{
	if (!window[name]) window[name]={};
	var o = window[name];
	if (!o[sub]) o[sub]={};
	o=o[sub];
	var key = Math.random()+"";
	o[key] = {};
	var undo = fn(o[key]);
	return function()
	{
		if (undo) undo();
		delete o[key];
	}
}

function BodyAdd(el)
{
	return AppendChild(document.body, el);
}

function AppendChild(p, ch)
{
	p.appendChild(ch);
	return function()
	{
		p.removeChild(ch);
	}
}




var jsshandlers = {};
jsshandlers.jssfsiframe = function(el)
{
	if (!el.id) el.id="fsiframe"+Math.floor(Math.random()*100000);
	var emptydiv = document.createElement("div");
	emptydiv.id="fsemptydiv"+Math.floor(Math.random()*100000);
	emptydiv.display="none";
	document.body.appendChild(emptydiv);
	var action = "7~"+el.id+"~"+emptydiv.id+"~1~000h1h~000h1h~2~0~"+el.href+"~"+emptydiv.id
	$$(action);
	return function(){$$undo(action)}
};

function addjssmouseclick(ev)
{
	for (var i=0;i<ev._els.length;++i)
	{
		var el = ev._els[i];
		if ((el.className+'').indexOf("jssmouseclick")==-1) continue;
		var classes = (el.className+'').split(/\s+/g);
		for (var j=0;j<classes.length;++j)
		{
			var fn = jsshandlers[classes[j]];
			if (!fn) continue;
			el.className+=" jssundo";
			el._jssundo=el._jssundo||[];
			el._jssundo.push(fn(el));
		}
	}
}






$log("0.js loaded");

