
//------------------------------------------------------------------------
// Package: Structures
// The various structures used by the system.
// 
// Topic: Dependencies
// - <DOM Library>
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Class: Legato_Structure_Color
// Handles a single color.
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Constructor: Legato_Structure_Color()
// Class constructor.
//
// Parameters:
//     RGB - A six character string of Red, Green and Blue values, eg: "FF2345".
//     
//     OR
//
//     R - Red value, 0 - 255.
//     G - Green value, 0 - 255.
//     B - Blue value, 0 - 255.
//------------------------------------------------------------------------
function Legato_Structure_Color()
{

	// What type of argument was passed in?
	if ( arguments.length == 1 && (typeof arguments[0] == "string" || arguments[0] instanceof String) )
	{

		this.R = parseInt( arguments[0].substring( 0, 2 ), 16 );
		this.G = parseInt( arguments[0].substring( 2, 4 ), 16 );
		this.B = parseInt( arguments[0].substring( 4, 6 ), 16 );

	}  // End if hex string.
	else if ( arguments.length == 3 )
	{

		this.R = arguments[0];
		this.G = arguments[1];
		this.B = arguments[2];

	}  // End if numbers passed in.
	else
	{

		this.R = null;
		this.G = null;
		this.B = null;

	}  // End if null passed in.

}


//------------------------------------------------------------------------
// Public Member Functions
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Function: toHexString()
// Returns the color as a string of hexidecimal values.
//------------------------------------------------------------------------
Legato_Structure_Color.prototype.toHexString = function()
{

	var R = (this.R < 16) ? "0" + this.R.toString( 16 ) : this.R.toString( 16 );
	var G = (this.G < 16) ? "0" + this.G.toString( 16 ) : this.G.toString( 16 );
	var B = (this.B < 16) ? "0" + this.B.toString( 16 ) : this.B.toString( 16 );

	return R + G + B;

}


//------------------------------------------------------------------------
// Class: Legato_Structure_Dimensions
// Handles a single set of dimensions.
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Constructor: Legato_Structure_Dimensions()
// Class constructor.
//
// Parameters:
//     width - The width to store.
//     height - The height to store.
//
//     OR
//
//     element - A DOM element, in which case it will store the dimensions
//               of that element at the time this structure was created.
//------------------------------------------------------------------------
function Legato_Structure_Dimensions()
{
	
	// What type of arguments passed in?
	if ( arguments.length == 2 )
	{

		// Store the passed in parameters.
		this.width   = arguments[0];
		this.height  = arguments[1];
		
	}  // End if a width and height.
	else if ( arguments.length == 1 )
	{
		
		var element = $( arguments[0] );

		// Get the element's dimensions.
		var dimensions = element.dimensions();
		this.width = dimensions[0];
		this.height = dimensions[1];
		
	}  // End if element passed in.

}


//------------------------------------------------------------------------
// Class: Legato_Structure_Position
// Handles a single position. For positioning objects relative to its
// containing element.
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Constructor: Legato_Structure_Position()
// Class constructor.
//
// Parameters:
//     top - The value from the top of the containing element.
//     right - The value from the right of the containing element.
//     bottom - The value from the bottom of the containing element.
//     left - The value from the left of the containing element.
//------------------------------------------------------------------------
function Legato_Structure_Position( top, right, bottom, left )
{

	// Store the passed in parameters.
	this.top     = top;
	this.right   = right;
	this.bottom  = bottom;
	this.left    = left;

}


//------------------------------------------------------------------------
// Class: Legato_Structure_Point
// Handles a single point.
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Constructor: Legato_Structure_Point()
// Class constructor.
//
// Parameters:
//     X - The X value of the point.
//     Y - The Y value of the point.
//------------------------------------------------------------------------
function Legato_Structure_Point( X, Y )
{

	// Store the passed in parameters.
	this.X = X;
	this.Y = Y;

}


//------------------------------------------------------------------------
// Class: Legato_Structure_Region
// Handles a single region.
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Constructor: Legato_Structure_Region()
// Class constructor.
//
// Parameters:
//     min_point - The <Legato_Structure_Point> of the top left corner.
//     max_point - The <Legato_Structure_Point> of the bottom right corner.
//
//     OR
//
//     element - A DOM element, in which case the region will be the
//               containing region of the element.
//------------------------------------------------------------------------
function Legato_Structure_Region()
{

	// What was passed in?
	if ( arguments.length == 2 )
	{

		// Store the passed in parameters.
	  this.min_point  = arguments[0];
	  this.max_point  = arguments[1];

	}  // End if points passed in.
	else if ( arguments.length == 1 )
	{

		var element = $( arguments[0] );

		// Get the element's position for the region's min point.
		this.min_point = this.element.position();
		this.min_point = new Legato_Structure_Point( this.min_point[0], this.min_point[1] );

		// Get the max point for the region.
		var dimensions = element.dimensions();
		this.max_point = new Legato_Structure_Point( (this.min_point.X + dimensions[0]), (this.min_point.Y + dimensions[1]) );

	}  // End if HTML element passed in.

}


//------------------------------------------------------------------------
// Public Member Functions
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Function: intersectRegion()
// Used to test if two <Legato_Structure_Region> objects are intersecting
// each other.
//
// Parameters:
//     region - The <Legato_Structure_Region> that you'd like to test for
//              intersection against this object.
//     in_test - (Optional) If this is set to true, the region passed in
//               must be fully contained by this object.
//     epsilon - (Optional) This is the allowable mistake that can happen
//               between the tests, eg: if this is 1, then if the region
//               is 1 pixel away from intersecting, this function will
//               still return true.
//------------------------------------------------------------------------
Legato_Structure_Region.prototype.intersectRegion = function( region, in_test, epsilon )
{

	// Get the epsilon.
	epsilon = (epsilon) ? epsilon : 0;

	// Only do this if we are testing if it's completely contained.
	if ( in_test )
	{

	  var contained = true;

		// Is the region completely contained in this region?
		if ( region.min_point.X < (this.min_point.X + epsilon) || region.min_point.X > (this.max_point.X + epsilon) ) contained = false;
		else if ( region.min_point.Y < (this.min_point.Y + epsilon) || region.min_point.Y > (this.max_point.Y + epsilon) ) contained = false;
		else if ( region.max_point.X < (this.min_point.X + epsilon) || region.max_point.X > (this.max_point.X + epsilon) ) contained = false;
		else if ( region.max_point.Y < (this.min_point.Y + epsilon) || region.max_point.Y > (this.max_point.Y + epsilon) ) contained = false;

		// Return.
		return contained;

	}  // End if completely contained test.
	else
	{

		// Perform the intersection test.
		return ((this.min_point.X + epsilon) <= region.max_point.X) &&
						((this.min_point.Y + epsilon) <= region.max_point.Y) &&
						((this.max_point.X + epsilon) >= region.min_point.X) &&
						((this.max_point.Y + epsilon) >= region.min_point.Y);

	}  // End normal touching test.

}


//------------------------------------------------------------------------
// Function: containsPoint()
// Used to test if there is an <Legato_Structure_Point> object within the
// bounds of this region.
//
// Parameters:
//     point - The <Legato_Structure_Point> object.
//------------------------------------------------------------------------
Legato_Structure_Region.prototype.containsPoint = function( point )
{

	var contained = true;

	// Is the point completely contained in this region?
	if ( this.min_point.X > point.X || this.max_point.X < point.X ) contained = false;
	if ( this.min_point.Y > point.Y || this.max_point.Y < point.Y ) contained = false;

	// Return.
	return contained;

}


// Developed by Robert Nyman/DOMAssistant team
// Code/licensing: http://domassistant.googlecode.com/
// Documentation: http://www.domassistant.com/documentation
// Version 2.8
var DOMAssistant = function () {
	var HTMLArray = function () {
		// Constructor
	},
	w = window, _$ = w.$, _$$ = w.$$,
	isIE = /*@cc_on!@*/false,
	isIE5 = isIE && parseFloat(navigator.appVersion) < 6,
	sort, tagCache = {}, lastCache = {}, useCache = true,
	slice = Array.prototype.slice,
	camel = {
		"accesskey": "accessKey",
		"class": "className",
		"colspan": "colSpan",
		"for": "htmlFor",
		"maxlength": "maxLength",
		"readonly": "readOnly",
		"rowspan": "rowSpan",
		"tabindex": "tabIndex",
		"valign": "vAlign",
		"cellspacing": "cellSpacing",
		"cellpadding": "cellPadding"
	},
	regex = {
		rules: /\s*,\s*/g,
		selector: /^(\w+|\*)?(#[\w\u00C0-\uFFFF\-=$]+)?((\.[\w\u00C0-\uFFFF\-]+)*)?((\[\w+\s*([~^$*|])?(=\s*([-\w\u00C0-\uFFFF\s.]+|"[^"]*"|'[^']*'))?\]+)*)?((:\w[-\w]*(\((odd|even|\-?\d*n?([-+]\d+)?|[:#]?[-\w\u00C0-\uFFFF.]+|"[^"]*"|'[^']*'|((\w*\.[-\w\u00C0-\uFFFF]+)*)?|(\[#?\w+([~^$*|])?=?[-\w\u00C0-\uFFFF\s.'"]+\]+)|(:\w[-\w]*\(.+\)))\))?)*)?([+>~])?/,
		selectorSplit: /(?:\[.*\]|\(.*\)|[^\s+>~[(])+|[+>~]/g,
		id: /^#([-\w\u00C0-\uFFFF=$]+)$/,
		tag: /^\w+/,
		relation: /^[+>~]$/,
		pseudo: /^:(\w[-\w]*)(\((.+)\))?$/,
		pseudos: /:(\w[-\w]*)(\((([^(]+)|([^(]+\([^(]+)\))\))?/g,
		attribs: /\[(\w+)\s*([~^$*|])?(=)?\s*([^\[\]]*|"[^"]*"|'[^']*')?\](?=$|\[|:|\s)/g,
		classes: /\.([-\w\u00C0-\uFFFF]+)/g,
		quoted: /^["'](.*)["']$/,
		nth: /^((odd|even)|([1-9]\d*)|((([1-9]\d*)?)n([-+]\d+)?)|(-(([1-9]\d*)?)n\+(\d+)))$/,
		special: /(:check|:enabl|\bselect)ed\b/
	},
	navigate = function (node, direction, checkTagName) {
		var oldName = node.tagName;
		while ((node = node[direction + "Sibling"]) && (node.nodeType !== 1 || (checkTagName? node.tagName !== oldName : node.tagName === "!"))) {}
		return node;
	},
	def = function (obj) {
		return typeof obj !== "undefined";
	},
	sortDocumentOrder = function (elmArray) {
		return (sortDocumentOrder = elmArray[0].compareDocumentPosition? function (elmArray) { return elmArray.sort( function (a, b) { return 3 - (a.compareDocumentPosition(b) & 6); } ); } :
			isIE? function (elmArray) { return elmArray.sort( function (a, b) { return a.sourceIndex - b.sourceIndex; } ); } :
			function (elmArray) { return elmArray.sort( function (a, b) {
				var range1 = document.createRange(), range2 = document.createRange();
				range1.setStart(a, 0);
				range1.setEnd(a, 0);
				range2.setStart(b, 0);
				range2.setEnd(b, 0);
				return range1.compareBoundaryPoints(Range.START_TO_END, range2);
			} ); })(elmArray);
	};
	var pushAll = function (set1, set2) {
		set1.push.apply(set1, slice.apply(set2));
		return set1;
	};
	if (isIE) {
		pushAll = function (set1, set2) {
			if (set2.slice) {
				return set1.concat(set2);
			}
			var i=0, item;
			while ((item = set2[i++])) {
				set1[set1.length] = item;
			}
			return set1;
		};
	}
	return {
		isIE : isIE,
		camel : camel,
		def : def,
		allMethods : [],
		publicMethods : [
			"prev",
			"next",
			"hasChild",
			"cssSelect",
			"elmsByClass",
			"elmsByAttribute",
			"elmsByTag"
		],

		harmonize : function () {
			w.$ = _$;
			w.$$ = _$$;
			return this;
		},

		initCore : function () {
			this.applyMethod.call(w, "$", this.$);
			this.applyMethod.call(w, "$$", this.$$);
			w.DOMAssistant = this;
			if (isIE) {
				HTMLArray = Array;
			}
			HTMLArray.prototype = [];
			(function (H) {
				H.each = function (fn, context) {
					for (var i=0, il=this.length; i<il; i++) {
						if (fn.call(context || this[i], this[i], i, this) === false) {
							break;
						}
					}
					return this;
				};
				H.first = function () {
					return def(this[0])? DOMAssistant.addMethodsToElm(this[0]) : null;
				};
				H.end = function () {
					return this.previousSet;
				};
				H.indexOf = H.indexOf || function (elm) {
					for (var i=0, il=this.length; i<il; i++) {
						if (i in this && this[i] === elm) {
							return i;
						}
					}
					return -1;
				};
				H.map = function (fn, context) {
					var res = [];
					for (var i=0, il=this.length; i<il; i++) {
						if (i in this) {
							res[i] = fn.call(context || this[i], this[i], i, this);
						}
					}
					return res;
				};
				H.filter = function (fn, context) {
					var res = new HTMLArray();
					res.previousSet = this;
					for (var i=0, il=this.length; i<il; i++) {
						if (i in this && fn.call(context || this[i], this[i], i, this)) {
							res.push(this[i]);
						}
					}
					return res;
				};
				H.every = function (fn, context) {
					for (var i=0, il=this.length; i<il; i++) {
						if (i in this && !fn.call(context || this[i], this[i], i, this)) {
							return false;
						}
					}
					return true;
				};
				H.some = function (fn, context) {
					for (var i=0, il=this.length; i<il; i++) {
						if (i in this && fn.call(context || this[i], this[i], i, this)) {
							return true;
						}
					}
					return false;
				};
			})(HTMLArray.prototype);
			this.attach(this);
		},

		addMethods : function (name, method) {
			if (!def(this.allMethods[name])) {
				this.allMethods[name] = method;
				this.addHTMLArrayPrototype(name, method);
			}
		},

		addMethodsToElm : function (elm) {
			for (var method in this.allMethods) {
				if (def(this.allMethods[method])) {
					this.applyMethod.call(elm, method, this.allMethods[method]);
				}
			}
			return elm;
		},

		applyMethod : function (method, func) {
			if (typeof this[method] !== "function") {
				this[method] = func;
			}
		},

		attach : function (plugin) {
			var publicMethods = plugin.publicMethods;
			if (!def(publicMethods)) {
				for (var method in plugin) {
					if (method !== "init" && def(plugin[method])) {
						this.addMethods(method, plugin[method]);
					}
				}
			}
			else if (publicMethods.constructor === Array) {
				for (var i=0, current; (current=publicMethods[i]); i++) {
					this.addMethods(current, plugin[current]);
				}
			}
			if (typeof plugin.init === "function") {
				plugin.init();
			}
		},

		addHTMLArrayPrototype : function (name, method) {
			HTMLArray.prototype[name] = function () {
				var elmsToReturn = new HTMLArray();
				elmsToReturn.previousSet = this;
				for (var i=0, il=this.length; i<il; i++) {
					elmsToReturn.push(method.apply(DOMAssistant.$$(this[i]), arguments));
				}
				return elmsToReturn;
			};
		},

		cleanUp : function (elm) {
			var children = elm.all || elm.getElementsByTagName("*");
			for (var i=0, child; (child=children[i++]);) {
				if (child.hasData && child.hasData()) {
					if (child.removeEvent) { child.removeEvent(); }
					child.unstore();
				}
			}
			elm.innerHTML = "";
		},

		setCache : function (cache) {
			useCache = cache;
		},

		$ : function () {
			var obj = arguments[0];
			if (arguments.length === 1 && (typeof obj === "object" || (typeof obj === "function" && !!obj.nodeName))) {
				return DOMAssistant.$$(obj);
			}
			var elm = !!obj? new HTMLArray() : null;
			for (var i=0, arg, idMatch; (arg=arguments[i]); i++) {
				if (typeof arg === "string") {
					arg = arg.replace(/^[^#\(]*(#)/, "$1");
					if (regex.id.test(arg)) {
						if ((idMatch = DOMAssistant.$$(arg.substr(1), false))) {
							elm.push(idMatch);
						}
					}
					else {
						var doc = (document.all || document.getElementsByTagName("*")).length;
						elm = (!document.querySelectorAll && useCache && lastCache.rule && lastCache.rule === arg && lastCache.doc === doc)? lastCache.elms : pushAll(elm, DOMAssistant.cssSelection.call(document, arg));
						lastCache = { rule: arg, elms: elm, doc: doc };
					}
				}
			}
			return elm;
		},

		$$ : function (id, addMethods) {
			var elm = (typeof id === "object" || typeof id === "function" && !!id.nodeName)? id : document.getElementById(id),
				applyMethods = def(addMethods)? addMethods : true,
				getId = function(el) { var eid = el.id; return typeof eid !== "object"? eid : el.attributes.id.nodeValue; };
			if (typeof id === "string" && elm && getId(elm) !== id) {
				elm = null;
				for (var i=0, item; (item=document.all[i]); i++) {
					if (getId(item) === id) {
						elm = item;
						break;
					}
				}
			}
			if (elm && applyMethods && !elm.next) {
				DOMAssistant.addMethodsToElm(elm);
			}
			return elm;
		},

		prev : function () {
			return DOMAssistant.$$(navigate(this, "previous"));
		},

		next : function () {
			return DOMAssistant.$$(navigate(this, "next"));
		},

		hasChild: function (elm) {
			return this === document || this !== elm && (this.contains? this.contains(elm) : !!(this.compareDocumentPosition(elm) & 16));
		},

		getSequence : function (expression) {
			var start, add = 2, max = -1, modVal = -1,
				pseudoVal = regex.nth.exec(expression.replace(/^0n\+/, "").replace(/^2n$/, "even").replace(/^2n+1$/, "odd"));
			if (!pseudoVal) {
				return null;
			}
			if (pseudoVal[2]) {	// odd or even
				start = (pseudoVal[2] === "odd")? 1 : 2;
				modVal = (start === 1)? 1 : 0;
			}
			else if (pseudoVal[3]) {	// single digit
				start = max = parseInt(pseudoVal[3], 10);
				add = 0;
			}
			else if (pseudoVal[4]) {	// an+b
				add = pseudoVal[6]? parseInt(pseudoVal[6], 10) : 1;
				start = pseudoVal[7]? parseInt(pseudoVal[7], 10) : 0;
				while (start < 1) {
					start += add;
				}
				modVal = (start >= add)? (start - add) % add : start;
			}
			else if (pseudoVal[8]) {	// -an+b
				add = pseudoVal[10]? parseInt(pseudoVal[10], 10) : 1;
				start = max = parseInt(pseudoVal[11], 10);
				while (start > add) {
					start -= add;
				}
				modVal = (max >= add)? (max - add) % add : max;
			}
			return { start: start, add: add, max: max, modVal: modVal };
		},

		cssByDOM : function (cssRule) {
			var prevParents, currentRule, cssSelectors, childOrSiblingRef, nextTag, nextRegExp, current, previous, prevParent, notElm, addElm, iteratorNext, childElm, sequence, anyTag,
				elm = new HTMLArray(), index = elm.indexOf, prevElm = [], matchingElms = [], cssRules = cssRule.replace(regex.rules, ",").split(","), splitRule = {};
			function clearAdded (elm) {
				elm = elm || prevElm;
				for (var n=elm.length; n--;) {
					elm[n].added = null;
					elm[n].removeAttribute("added");
				}
			}
			function clearChildElms () {
				for (var n=prevParents.length; n--;) {
					prevParents[n].childElms = null;
				}
			}
			function subtractArray (arr1, arr2) {
				for (var i=0, src1; (src1=arr1[i]); i++) {
					var found = false;
					for (var j=0, src2; (src2=arr2[j]); j++) {
						if (src2 === src1) {
							found = true;
							arr2.splice(j, 1);
							break;
						}
					}
					if (found) {
						arr1.splice(i--, 1);
					}
				}
				return arr1;
			}
			function getAttr (elm, attr) {
				return (isIE || regex.special.test(attr))? elm[camel[attr.toLowerCase()] || attr] : elm.getAttribute(attr, 2);
			}
			function attrToRegExp (attrVal, substrOperator) {
				attrVal = attrVal? attrVal.replace(regex.quoted, "$1").replace(/(\.|\[|\])/g, "\\$1") : null;
				return {
					"^": "^" + attrVal,
					"$": attrVal + "$",
					"*": attrVal,
					"|": "^" + attrVal + "(\\-\\w+)*$",
					"~": "\\b" + attrVal + "\\b"
				}[substrOperator] || (attrVal !== null? "^" + attrVal + "$" : attrVal);
			}
			function notComment(el) {
				return (el || this).tagName !== "!";
			}
			function getTags (tag, context) {
				return isIE5? (tag === "*"? context.all : context.all.tags(tag)) : context.getElementsByTagName(tag);
			}
			function getElementsByTagName (tag, parent) {
				tag = tag || "*";
				parent = parent || document;
				return (parent === document || parent.lastModified)? tagCache[tag] || (tagCache[tag] = getTags(tag, document)) : getTags(tag, parent);
			}
			function getElementsByPseudo (previousMatch, pseudoClass, pseudoValue) {
				prevParents = [];
				var pseudo = pseudoClass.split("-"), matchingElms = [], idx = 0, checkNodeName = /\-of\-type$/.test(pseudoClass), recur,
				match = {
					first: function(el) { return !navigate(el, "previous", checkNodeName); },
					last: function(el) { return !navigate(el, "next", checkNodeName); },
					empty: function(el) { return !el.firstChild; },
					enabled: function(el) { return !el.disabled && el.type !== "hidden"; },
					disabled: function(el) { return el.disabled; },
					checked: function(el) { return el.checked; },
					contains: function(el) { return (el.innerText || el.textContent || "").indexOf(pseudoValue.replace(regex.quoted, "$1")) > -1; },
					other: function(el) { return getAttr(el, pseudoClass) === pseudoValue; }
				};
				function basicMatch(key) {
					while ((previous=previousMatch[idx++])) {
						if (notComment(previous) && match[key](previous)) {
							matchingElms[matchingElms.length] = previous;
						}
					}
					return matchingElms;
				}
				var word = pseudo[0] || null;
				if (word && match[word]) {
					return basicMatch(word);
				}
				switch (word) {
					case "only":
						var kParent, kTag;
						while ((previous=previousMatch[idx++])) {
							prevParent = previous.parentNode;
							var q = previous.nodeName;
							if (prevParent !== kParent || q !== kTag) {
								if (match.first(previous) && match.last(previous)) {
									matchingElms[matchingElms.length] = previous;
								}
								kParent = prevParent;
								kTag = q;
							}
						}
						break;
					case "nth":
						if (pseudoValue === "n") {
							matchingElms = previousMatch;
						}
						else {
							var direction = (pseudo[1] === "last")? ["lastChild", "previousSibling"] : ["firstChild", "nextSibling"];
							sequence = DOMAssistant.getSequence(pseudoValue);
							if (sequence) {
								while ((previous=previousMatch[idx++])) {
									prevParent = previous.parentNode;
									prevParent.childElms = prevParent.childElms || {};
									var p = previous.nodeName;
									if (!prevParent.childElms[p]) {
										var childCount = 0;
										iteratorNext = sequence.start;
										childElm = prevParent[direction[0]];
										while (childElm && (sequence.max < 0 || iteratorNext <= sequence.max)) {
											var c = childElm.nodeName;
											if ((checkNodeName && c === p) || (!checkNodeName && childElm.nodeType === 1 && c !== "!")) {
												if (++childCount === iteratorNext) {
													if (c === p) { matchingElms[matchingElms.length] = childElm; }
													iteratorNext += sequence.add;
												}
											}
											childElm = childElm[direction[1]];
										}
										if (anyTag) { sort++; }
										prevParent.childElms[p] = true;
										prevParents[prevParents.length] = prevParent;
									}
								}
								clearChildElms();
							}
						}
						break;
					case "target":
						var hash = document.location.hash.slice(1);
						if (hash) {
							while ((previous=previousMatch[idx++])) {
								if (getAttr(previous, "name") === hash || getAttr(previous, "id") === hash) {
									matchingElms[matchingElms.length] = previous;
									break;
								}
							}
						}
						break;
					case "not":
						if ((recur = regex.pseudo.exec(pseudoValue))) {
							matchingElms = subtractArray(previousMatch, getElementsByPseudo(previousMatch, recur[1]? recur[1].toLowerCase() : null, recur[3] || null));
						}
						else {
							for (var re in regex) {
								if (regex[re].lastIndex) {
									regex[re].lastIndex = 0;
								}
							}
							pseudoValue = pseudoValue.replace(regex.id, "[id=$1]");
							var notTag = regex.tag.exec(pseudoValue);
							var notClass = regex.classes.exec(pseudoValue);
							var notAttr = regex.attribs.exec(pseudoValue);
							var notRegExp = new RegExp(notAttr? attrToRegExp(notAttr[4], notAttr[2]) : "(^|\\s)" + (notTag? notTag[0] : notClass? notClass[1] : "") + "(\\s|$)", "i");
							while ((notElm=previousMatch[idx++])) {
								addElm = null;
								if (notTag && !notRegExp.test(notElm.nodeName) || notClass && !notRegExp.test(notElm.className)) {
									addElm = notElm;
								}
								else if (notAttr) {
									var att = getAttr(notElm, notAttr[1]);
									if (!def(att) || att === false || typeof att === "string" && !notRegExp.test(att)) {
										addElm = notElm;
									}
								}
								if (addElm && !addElm.added) {
									addElm.added = true;
									matchingElms[matchingElms.length] = addElm;
								}
							}
						}
						break;
					default: return basicMatch("other");
				}
				return matchingElms;
			}
			function pushUnique(set1, set2) {
				var i=0, s=set1, item;
				while ((item = set2[i++])) {
					if (!s.length || s.indexOf(item) < 0) {
						set1.push(item);
					}
				}
				return set1;
			}
			sort = -1;
			for (var a=0, tagBin=[]; (currentRule=cssRules[a]); a++) {
				if (!(cssSelectors = currentRule.match(regex.selectorSplit)) || a && index.call(cssRules.slice(0, a), currentRule) > -1) { continue; }
				prevElm = [this];
				for (var i=0, rule; (rule=cssSelectors[i]); i++) {
					matchingElms = [];
					if ((childOrSiblingRef = regex.relation.exec(rule))) {
						var idElm = null, nextWord = cssSelectors[i+1];
						if ((nextTag = regex.tag.exec(nextWord))) {
							nextTag = nextTag[0];
							nextRegExp = new RegExp("(^|\\s)" + nextTag + "(\\s|$)", "i");
						}
						else if (regex.id.test(nextWord)) {
							idElm = DOMAssistant.$(nextWord) || null;
						}
						for (var j=0, prevRef; (prevRef=prevElm[j]); j++) {
							switch (childOrSiblingRef[0]) {
								case ">":
									var children = idElm || getElementsByTagName(nextTag, prevRef);
									for (var k=0, child; (child=children[k]); k++) {
										if (child.parentNode === prevRef) {
											matchingElms[matchingElms.length] = child;
										}
									}
									break;
								case "+":
									if ((prevRef = navigate(prevRef, "next"))) {
										if ((idElm && idElm[0] === prevRef) || (!idElm && (!nextTag || nextRegExp.test(prevRef.nodeName)))) {
											matchingElms[matchingElms.length] = prevRef;
										}
									}
									break;
								case "~":
									while ((prevRef = prevRef.nextSibling) && !prevRef.added) {
										if ((idElm && idElm[0] === prevRef) || (!idElm && (!nextTag || nextRegExp.test(prevRef.nodeName)))) {
											prevRef.added = true;
											matchingElms[matchingElms.length] = prevRef;
										}
									}
									break;
							}
						}
						prevElm = matchingElms;
						clearAdded();
						rule = cssSelectors[++i];
						if (/^\w+$/.test(rule) || regex.id.test(rule)) {
							continue;
						}
						prevElm.skipTag = true;
					}
					var cssSelector = regex.selector.exec(rule);
					splitRule = {
						tag : cssSelector[1]? cssSelector[1] : "*",
						id : cssSelector[2],
						allClasses : cssSelector[3],
						allAttr : cssSelector[5],
						allPseudos : cssSelector[10]
					};
					anyTag = (splitRule.tag === "*");
					if (splitRule.id) {
						var u = 0, DOMElm = document.getElementById(splitRule.id.slice(1));
						if (DOMElm) {
							while (prevElm[u] && !DOMAssistant.hasChild.call(prevElm[u], DOMElm)) { u++; }
							matchingElms = (u < prevElm.length && (anyTag || splitRule.tag === DOMElm.tagName.toLowerCase()))? [DOMElm] : [];
						}
						prevElm = matchingElms;
					}
					else if (splitRule.tag && !prevElm.skipTag) {
						if (i===0 && !matchingElms.length && prevElm.length === 1) {
							prevElm = matchingElms = pushAll([], getElementsByTagName(splitRule.tag, prevElm[0]));
						}
						else {
							for (var l=0, ll=prevElm.length, tagCollectionMatches, tagMatch; l<ll; l++) {
								tagCollectionMatches = getElementsByTagName(splitRule.tag, prevElm[l]);
								for (var m=0; (tagMatch=tagCollectionMatches[m]); m++) {
									if (!tagMatch.added) {
										tagMatch.added = true;
										matchingElms[matchingElms.length] = tagMatch;
									}
								}
							}
							prevElm = matchingElms;
							clearAdded();
						}
					}
					if (!matchingElms.length) {
						break;
					}
					prevElm.skipTag = false;
					if (splitRule.allClasses) {
						var n = 0, matchingClassElms = [], allClasses = splitRule.allClasses.split(".").slice(1);
						while ((current = prevElm[n++])) {
							var matchCls = true, elmClass = current.className;
							if (elmClass && elmClass.length) {
								elmClass = elmClass.split(" ");
								for (var o=allClasses.length; o--;) {
									if (elmClass.indexOf(allClasses[o]) < 0) {
										matchCls = false;
										break;
									}
								}
								if (matchCls) {
									matchingClassElms[matchingClassElms.length] = current;
								}
							}
						}
						prevElm = matchingElms = matchingClassElms;
					}
					if (splitRule.allAttr) {
						var matchAttr, r = 0, regExpAttributes = [], matchingAttributeElms = [], allAttr = splitRule.allAttr.match(regex.attribs);
						for (var specialStrip = /^\[(selected|readonly)(\s*=.+)?\]$/, q=0, ql=allAttr.length, attributeMatch, attrVal; q<ql; q++) {
							regex.attribs.lastIndex = 0;
							attributeMatch = regex.attribs.exec(allAttr[q].replace(specialStrip, "[$1]"));
							attrVal = attrToRegExp(attributeMatch[4], attributeMatch[2] || null);
							regExpAttributes[q] = [(attrVal? new RegExp(attrVal) : null), attributeMatch[1]];
						}
						while ((current = matchingElms[r++])) {
							for (var s=0, sl=regExpAttributes.length; s<sl; s++) {
								var attributeRegExp = regExpAttributes[s][0], currentAttr = getAttr(current, regExpAttributes[s][1]);
								matchAttr = true;
								if (!attributeRegExp && currentAttr === true) { continue; }
								if ((!attributeRegExp && (!currentAttr || typeof currentAttr !== "string" || !currentAttr.length)) || (!!attributeRegExp && !attributeRegExp.test(currentAttr))) {
									matchAttr = false;
									break;
								}
							}
							if (matchAttr) {
								matchingAttributeElms[matchingAttributeElms.length] = current;
							}
						}
						prevElm = matchingElms = matchingAttributeElms;
					}
					if (splitRule.allPseudos) {
						var allPseudos = splitRule.allPseudos.match(regex.pseudos);
						for (var t=0, tl=allPseudos.length; t<tl; t++) {
							regex.pseudos.lastIndex = 0;
							var pseudo = regex.pseudos.exec(allPseudos[t]);
							var pseudoClass = pseudo[1]? pseudo[1].toLowerCase() : null;
							var pseudoValue = pseudo[3] || null;
							matchingElms = getElementsByPseudo(matchingElms, pseudoClass, pseudoValue);
							clearAdded(matchingElms);
						}
						prevElm = matchingElms;
					}
				}
				elm = ((tagBin.length && (anyTag || index.call(tagBin, splitRule.tag) >= 0 || index.call(tagBin, "*") >= 0))? pushUnique : pushAll)(elm, prevElm);
				tagBin.push(splitRule.tag);
				if (isIE && anyTag) { elm = elm.filter(notComment); }
			}
			return ((elm.length > 1 && cssRules.length > 1) || sort > 0)? sortDocumentOrder(elm) : elm;
		},

		cssByXpath : function (cssRule) {
			var ns = { xhtml: "http://www.w3.org/1999/xhtml" },
				prefix = (document.documentElement.namespaceURI === ns.xhtml)? "xhtml:" : "",
				nsResolver = function lookupNamespaceURI (prefix) {
					return ns[prefix] || null;
				};
			DOMAssistant.cssByXpath = function (cssRule) {
				var currentRule, cssSelectors, xPathExpression, cssSelector, splitRule, sequence,
					elm = new HTMLArray(), cssRules = cssRule.replace(regex.rules, ",").split(",");
				function attrToXPath (wrap) {
					var pre = wrap? "[" : "", post = wrap? "]" : "";
					return function (match, p1, p2, p3, p4) {
						p4 = (p4 || "").replace(regex.quoted, "$1");
						if (p1 === p4 && p1 === "readonly") { p3 = null; }
						return pre + ({
							"^": "starts-with(@" + p1 + ", \"" + p4 + "\")",
							"$": "substring(@" + p1 + ", (string-length(@" + p1 + ") - " + (p4.length - 1) + "), " + p4.length + ") = \"" + p4 + "\"",
							"*": "contains(concat(\" \", @" + p1 + ", \" \"), \"" + p4 + "\")",
							"|": "@" + p1 + "=\"" + p4 + "\" or starts-with(@" + p1 + ", \"" + p4 + "-\")",
							"~": "contains(concat(\" \", @" + p1 + ", \" \"), \" " + p4 + " \")"
						}[p2] || ("@" + p1 + (p3? "=\"" + p4 + "\"" : ""))) + post;
					};
				}
				function pseudoToXPath (tag, pseudoClass, pseudoValue) {
					tag = /\-child$/.test(pseudoClass)? "*" : tag;
					var pseudo = pseudoClass.split("-"), position = ((pseudo[1] === "last")? "(count(following-sibling::" : "(count(preceding-sibling::") + tag + ") + 1)", recur, hash;
					switch (pseudo[0]) {
						case "nth": return (pseudoValue !== "n" && (sequence = DOMAssistant.getSequence(pseudoValue)))? ((sequence.start === sequence.max)? position + " = " + sequence.start : position + " mod " + sequence.add + " = " + sequence.modVal + ((sequence.start > 1)? " and " + position + " >= " + sequence.start : "") + ((sequence.max > 0)? " and " + position + " <= " + sequence.max: "")) : "";
						case "not": return "not(" + ((recur = regex.pseudo.exec(pseudoValue))? pseudoToXPath(tag, recur[1]? recur[1].toLowerCase() : null, recur[3] || null) : pseudoValue.replace(regex.id, "[id=$1]").replace(regex.tag, "self::$0").replace(regex.classes, "contains(concat(\" \", @class, \" \"), \" $1 \")").replace(regex.attribs, attrToXPath())) + ")";
						case "first": return "not(preceding-sibling::" + tag + ")";
						case "last": return "not(following-sibling::" + tag + ")";
						case "only": return "not(preceding-sibling::" + tag + " or following-sibling::" + tag + ")";
						case "empty": return "not(child::*) and not(text())";
						case "contains": return "contains(., \"" + pseudoValue.replace(regex.quoted, "$1") + "\")";
						case "enabled": return "not(@disabled) and not(@type=\"hidden\")";
						case "disabled": return "@disabled";
						case "target": return "@name=\"" + (hash = document.location.hash.slice(1)) + "\" or @id=\"" + hash + "\"";
						default: return "@" + pseudoClass + "=\"" + pseudoValue + "\"";
					}
				}
				for (var i=0; (currentRule=cssRules[i]); i++) {
					if (!(cssSelectors = currentRule.match(regex.selectorSplit)) || i && elm.indexOf.call(cssRules.slice(0, i), currentRule) > -1) { continue; }
					xPathExpression = xPathExpression? xPathExpression + " | ." : ".";
					for (var j=0, jl=cssSelectors.length; j<jl; j++) {
						cssSelector = regex.selector.exec(cssSelectors[j]);
						splitRule = {
							tag: prefix + (cssSelector[1]? cssSelector[1] : "*"),
							id: cssSelector[2],
							allClasses: cssSelector[3],
							allAttr: cssSelector[5],
							allPseudos: cssSelector[10],
							tagRelation: cssSelector[20]
						};
						xPathExpression +=
							(splitRule.tagRelation? ({ ">": "/", "+": "/following-sibling::*[1]/self::", "~": "/following-sibling::" }[splitRule.tagRelation] || "") : ((j > 0 && regex.relation.test(cssSelectors[j-1]))? splitRule.tag : ("//" + splitRule.tag))) +
							(splitRule.id || "").replace(regex.id, "[@id = \"$1\"]") +
							(splitRule.allClasses || "").replace(regex.classes, "[contains(concat(\" \", @class, \" \"), \" $1 \")]") +
							(splitRule.allAttr || "").replace(regex.attribs, attrToXPath(true));
						if (splitRule.allPseudos) {
							var allPseudos = splitRule.allPseudos.match(regex.pseudos);
							for (var k=0, kl=allPseudos.length; k<kl; k++) {
								regex.pseudos.lastIndex = 0;
								var pseudo = regex.pseudos.exec(allPseudos[k]),
									pseudoClass = pseudo[1]? pseudo[1].toLowerCase() : null,
									pseudoValue = pseudo[3] || null,
									xpath = pseudoToXPath(splitRule.tag, pseudoClass, pseudoValue);
								if (xpath.length) {
									xPathExpression += "[" + xpath + "]";
								}
							}
						}
					}
				}
				try {
					var xPathNodes = document.evaluate(xPathExpression, this, nsResolver, 7, null), node, p=0;
					while ((node = xPathNodes.snapshotItem(p++))) { elm.push(node); }
				} catch (e) {}
				return elm;
			};
			return DOMAssistant.cssByXpath.call(this, cssRule);
		},

		cssSelection : function (cssRule) {
			if (!cssRule) { return null; }
			var special = regex.special.test(cssRule);
			try {
				if (document.querySelectorAll && !special) {
					return pushAll(new HTMLArray(), this.querySelectorAll(cssRule));
				}
			} catch (e) {}
			return ((document.evaluate && !special && !/-of-type/.test(cssRule))? DOMAssistant.cssByXpath : DOMAssistant.cssByDOM).call(this, cssRule);
		},

		cssSelect : function (cssRule) {
			return DOMAssistant.cssSelection.call(this, cssRule);
		},

		elmsByClass : function (className, tag) {
			var cssRule = (tag || "") + "." + className;
			return DOMAssistant.cssSelection.call(this, cssRule);
		},

		elmsByAttribute : function (attr, attrVal, tag, substrMatchSelector) {
			var cssRule = (tag || "") + "[" + attr + ((attrVal && attrVal !== "*")? ((substrMatchSelector || "") + "=" + attrVal + "]") : "]");
			return DOMAssistant.cssSelection.call(this, cssRule);
		},

		elmsByTag : function (tag) {
			return DOMAssistant.cssSelection.call(this, tag);
		}
	};
}();
DOMAssistant.initCore();
DOMAssistant.Storage = function () {
	var uniqueId = 1, data = [], expando = "_da" + +new Date();
	return {
		hasData : function () {
			var uid = this[expando];
			return !!uid && !!data[uid];
		},
		retrieve : function (key) {
			if (!DOMAssistant.def(key)) {
				return this[expando] || (this[expando] = uniqueId++);
			}
			if (!this[expando] || !data[this[expando]]) { return; }
			return data[this[expando]][key];
		},

		store : function (key, val) {
			var uid = this[expando] || (this[expando] = uniqueId++);
			data[uid] = data[uid] || {};
			if (typeof key === "object") {
				for (var i in key) {
					if (typeof i === "string") {
						data[uid][i] = key[i];
					}
				}
			}
			else {
				data[uid][key] = val;
			}
			return this;
		},

		unstore : function (key) {
			var uid = this[expando] || (this[expando] = uniqueId++);
			if (data[uid]) {
				if (DOMAssistant.def(key)) {
					delete data[uid][key];
				}
				else {
					data[uid] = null;
				}
			}
			return this;
		}
	};
}();
DOMAssistant.attach(DOMAssistant.Storage);
DOMAssistant.CSS = function () {
	var def = DOMAssistant.def,
		direct = { display: true };
	return {
		addClass : function (className) {
			if (!this.hasClass(className)) {
				var currentClass = this.className;
				this.className = currentClass + (currentClass.length? " " : "") + className;
			}
			return this;
		},

		removeClass : function (className) {
			return this.replaceClass(className);
		},

		replaceClass : function (className, newClass) {
			var classToRemove = new RegExp(("(^|\\s)" + className + "(\\s|$)"), "i");
			this.className = this.className.replace(classToRemove, function (match, p1, p2) {
				return newClass? (p1 + newClass + p2) : " ";
			}).replace(/^\s+|\s+$/g, "");
			return this;
		},

		hasClass : function (className) {
			return (" " + this.className + " ").indexOf(" " + className + " ") > -1;
		},

		setStyle : function (style, value) {
			var css = this.style;
			if ("filters" in this && (typeof style === "string"? /opacity/i.test(style) : def(style.opacity))) {
				css.zoom = 1;
				css.filter = (css.filter || "").replace(/alpha\([^)]*\)/, "") + "alpha(opacity=" + (def(style.opacity)? style.opacity : value) * 100 + ")";
			}
			if (def(css.cssText)) {
				var styleToSet = css.cssText;
				if (typeof style === "object") {
					for (var i in style) {
						if (typeof i === "string") {
							if (direct[i]) { css[i] = style[i]; }
							styleToSet += ";" + i + ":" + style[i];
						}
					}
				}
				else {
					if (direct[style]) { css[style] = value; }
					styleToSet += ";" + style + ":" + value;
				}
				css.cssText = styleToSet;
			}
			return this;
		},

		getStyle : function (cssRule) {
			var val = "", f;
			cssRule = cssRule.toLowerCase();
			if (document.defaultView && document.defaultView.getComputedStyle) {
				val = document.defaultView.getComputedStyle(this, "").getPropertyValue(cssRule);
			}
			else if (this.currentStyle) {
				if ("filters" in this && cssRule === "opacity") {
					val = (f = this.style.filter || this.currentStyle.filter) && f.indexOf("opacity=") >= 0? parseFloat(f.match(/opacity=([^)]*)/)[1]) / 100 : 1;
				}
				else {
					cssRule = cssRule.replace(/^float$/, "styleFloat").replace(/\-(\w)/g, function (match, p1) {
						return p1.toUpperCase();
					});
					val = this.currentStyle[cssRule];
				}
				if (val === "auto" && /^(width|height)$/.test(cssRule) && this.currentStyle.display !== "none") {
					val = this["offset" + cssRule.charAt(0).toUpperCase() + cssRule.substr(1)] + "px";
				}
			}
			return val;
		}
	};
}();
DOMAssistant.attach(DOMAssistant.CSS);
DOMAssistant.Content = function () {
	var D$ = DOMAssistant.$$;
	return {
		init : function () {
			DOMAssistant.setCache(false);
		},

		create : function (name, attr, append, content) {
			var elm = D$(document.createElement(name));
			if (attr) {
				elm = elm.setAttributes(attr);
			}
			if (DOMAssistant.def(content)) {
				elm.addContent(content);
			}
			if (append) {
				this.appendChild(elm);
			}
			return elm;
		},

		setAttributes : function (attr) {
			if (DOMAssistant.isIE) {
				var setAttr = function (elm, att, val) {
					var attLower = att.toLowerCase();
					switch (attLower) {
						case "name":
						case "type":
						case "multiple":
							return D$(document.createElement(elm.outerHTML.replace(new RegExp(attLower + "(=[a-zA-Z]+)?"), " ").replace(">", " " + attLower + "=" + val + ">")));
						case "style":
							elm.style.cssText = val;
							return elm;
						default:
							elm[DOMAssistant.camel[attLower] || att] = val;
							return elm;
					}
				};
				DOMAssistant.Content.setAttributes = function (attr) {
					var elem = this;
					var parent = this.parentNode;
					for (var i in attr) {
						if (typeof attr[i] === "string" || typeof attr[i] === "number") {
							var newElem = setAttr(elem, i, attr[i]);
							if (parent && /(name|type)/i.test(i)) {
								if (elem.innerHTML) {
									newElem.innerHTML = elem.innerHTML;
								}
								parent.replaceChild(newElem, elem);
							}
							elem = newElem;
						}
					}
					return elem;
				};
			}
			else {
				DOMAssistant.Content.setAttributes = function (attr) {
					for (var i in attr) {
						if (/class/i.test(i)) {
							this.className = attr[i];
						}
						else {
							this.setAttribute(i, attr[i]);
						}
					}
					return this;
				};
			}
			return DOMAssistant.Content.setAttributes.call(this, attr);
		},

		addContent : function (content) {
			var type = typeof content;
			if (type === "string" || type === "number") {
				if (!this.firstChild) {
					this.innerHTML = content;
				}
				else {
					var tmp = document.createElement("div");
					tmp.innerHTML = content;
					for (var i=tmp.childNodes.length-1, last=null; i>=0; i--) {
						last = this.insertBefore(tmp.childNodes[i], last);
					}
				}
			}
			else if (type === "object" || (type === "function" && !!content.nodeName)) {
				this.appendChild(content);
			}
			return this;
		},

		replaceContent : function (content) {
			DOMAssistant.cleanUp(this);
			return this.addContent(content);
		},

		replace : function (content, returnNew) {
			var type = typeof content;
			if (type === "string" || type === "number") {
				var parent = this.parentNode;
				var tmp = DOMAssistant.Content.create.call(parent, "div", null, false, content);
				for (var i=tmp.childNodes.length; i--;) {
					parent.insertBefore(tmp.childNodes[i], this.nextSibling);
				}
				content = this.nextSibling;
				parent.removeChild(this);
			}
			else if (type === "object" || (type === "function" && !!content.nodeName)) {
				this.parentNode.replaceChild(content, this);
			}
			return returnNew? content : this;
		},

		remove : function () {
			DOMAssistant.cleanUp(this);
			if (this.hasData()) {
				if (this.removeEvent) { this.removeEvent(); }
				this.unstore();
			}
			this.parentNode.removeChild(this);
			return null;
		}
	};
}();
DOMAssistant.attach(DOMAssistant.Content);


/*
	Class: Legato_DOM_Library
	Provides a plugin to DOMAssistant to allow extra features for working with the DOM.
*/
Legato_DOM_Library = {};

Legato_DOM_Library.DOMAssistantPlugIn = function ()
{

	return {

		/*
			Function: dimensions()
			Sets/gets the dimension's of the element.
			If no dimensions passed in, will return the element's dimensions.

			Syntax:
				*Getting Dimensions*

				array dimensions()

				*Setting Dimensions*

				object dimensions( int width, int height )

			Parameters:
				*Setting Dimensions*

				int width - The new width you'd like the element to have. Pass in null if you would like the width to stay the same.
				int height - The new height you'd like the element to have. Pass in null if you would like the height to stay the same.

			Returns:
				*Getting Dimensions*

				Returns an array of the dimensions, with the first item being the width and the second item being the height.

				*Setting Dimensions*

				Returns the element the dimensions were set on.

			Examples:
			(begin code)
				var dimensions = $$( 'container' ).dimensions();
				alert( dimensions[0] )  // Show the width of the container.
			(end)

			(begin code)
				// Set the height of the container to 300 pixels.
				$$( 'container' ).dimensions( null, 300 );
			(end)
		*/
		dimensions: function()
		{

			if ( this.window == window )
			{

				var width = window.innerWidth || (window.document.documentElement.clientWidth || window.document.body.clientWidth);
		        var height = window.innerHeight || (window.document.documentElement.clientHeight || window.document.body.clientHeight);

		        return [ width, height ];

			}
			else if ( arguments.length == 0 )
			{

				return [ this.offsetWidth, this.offsetHeight ];

			}
			else
			{

				if ( arguments[0] !== null ) this.setStyle( 'width', arguments[0] + 'px' );
				if ( arguments[1] !== null ) this.setStyle( 'height', arguments[1] + 'px' );
				return this;

			}

		},


		/*
			Function: position()
			Sets/gets the position of an element.
			If no position passed in, will return the current position of the element.

			Syntax:
				*Getting Position*

				array position()

				*Setting Position*

				object position( int X, int Y )

			Parameters:
				*Setting Position*

				int X - The new X value that you'd like the element to have. Pass in null if you would like the X position to stay the same.
				int Y - The new Y value that you'd like the element to have. Pass in null if you would like the Y position to stay the same.

			Returns:
				*Getting Position*

				Returns an array of the position, with the first item being the X value and the second item being the Y value.

				*Setting Position*

				Returns the element the position was set on.

			Notes:
				This function works off of the page grid and not the containing element. So, setting an X value of 50 would put the element
				50 pixels from the top of the page.

			Examples:
			(begin code)
				// Show the position of the container element.
				var pos = $$( 'container' ).position();
				alert( pos[0] + ' | ' + pos[1] );

				// Set the Y position to 50 pixels.
				$$( 'container' ).position( null, 50 );
			(end)
		*/
		position: function()
		{

			if ( arguments.length == 0 )
			{

				var offsetLeft = offsetTop = 0;
				var elem = this;

				while ( elem !== null )
				{
					offsetLeft += elem.offsetLeft;
					offsetTop += elem.offsetTop;
					elem = elem.offsetParent;
				}

				return [ offsetLeft, offsetTop ];

			}
			else
			{

				// Get the positioning of this element.
				var positioning = this.getStyle( 'position' );

				// If it's statically positioned, we change it to relative positioning.
				// If it's absolute, we leave it.
                if ( positioning == 'static' )
				{
					positioning = 'relative';
                    this.setStyle( 'position', 'relative' );
                }

                // Try to get the offset value.
                var offset =
				[
                    parseInt( this.getStyle( 'left' ), 10 ),
                    parseInt( this.getStyle( 'top' ), 10 )
                ];

            	// If auto was returned, retrieve the correct offset.
                if ( isNaN( offset[0] ) )
                    offset[0] = (positioning == 'relative') ? 0 : this.offsetLeft;

                // If auto was returned, retrieve the correct offset.`
                if ( isNaN( offset[1] ) )
                    offset[1] = (positioning == 'relative') ? 0 : this.offsetTop;

                // Get the page XY position of the element.
                var posXY = this.position();

                // If a new X or Y value was passed in, set it.
                if ( arguments[0] !== null ) this.setStyle( 'left', arguments[0] - posXY[0] + offset[0] + 'px' );
                if ( arguments[1] !== null ) this.setStyle( 'top', arguments[1] - posXY[1] + offset[1] + 'px' );

				// One some browsers (Opera) top/left offsets get calculated relative to
				// the offset parent and not the actual relative positioning. We do this
				// so that if there is any error in the positioning, it will correct it.
				// This only needs to be done once.
				if ( arguments[0] && !this.retrieve( 'legato_delta_x' ) || arguments[1] && !this.retrieve( 'legato_delta_y' ) )
				{

					if ( arguments[0] ) this.store( 'legato_delta_x', true );
					if ( arguments[1] ) this.store( 'legato_delta_y', true );

	                var curPos = this.position();

	                // If this element didn't get set to the position we'd like it to be, set it again.
	                if ( (arguments[0] && curPos[0] != arguments[0]) || (arguments[1] && curPos[1] != arguments[1]) )
	                {

						offset[0] -= curPos[0] - arguments[0];
						offset[1] -= curPos[1] - arguments[1];

						// Set the position again. This time with updated deltas.
						if ( arguments[0] !== null ) this.setStyle( 'left', arguments[0] - posXY[0] + offset[0] + 'px' );
	                	if ( arguments[1] !== null ) this.setStyle( 'top', arguments[1] - posXY[1] + offset[1] + 'px' );

	                }

 				}

                return this;

			}

		},


		/*
			Function: scrollOffset()
			Sets/gets the scroll offset of an element.
			If no offset passed in, will return the current offset of the element.

			Syntax:
				*Getting Offset*

				array scrollOffset()

				*Setting Offset*

				object scrollOffset( int X, int Y )

			Parameters:
				*Setting Offset*

				int X - The new X value that you'd like the element's scroll offset to be. Pass in null if you would like the X offset to stay the same.
				int Y - The new Y value that you'd like the element's scroll offset to be. Pass in null if you would like the Y offset to stay the same.

			Returns:
				*Getting Offset*

				Returns an array of the scroll offset, with the first item being the X offset and the second item being the Y offset.

				*Setting Offset*

				Returns the element the scroll offset was set on.

			Examples:
			(begin code)
				// Show the Y offset of the container element.
				alert( $$( 'container' ).position() );

				// Set the X offset to 75 pixels.
				$$( 'container' ).scrollOffset( 75, null );
			(end)
		*/
		scrollOffset: function()
		{

			if ( this.window == window || this == document.body )
			{

				var X = Y = 0;

				if( typeof( window.pageXOffset ) == 'number' )
				{
					X = window.pageXOffset;
					Y = window.pageYOffset;
				}  // Netscape.
				else if( document.body && ( document.body.scrollLeft || document.body.scrollTop ) )
				{
					X = document.body.scrollLeft;
					Y = document.body.scrollTop;
				}  // Standards compliant.
				else if( document.documentElement && ( document.documentElement.scrollLeft || document.documentElement.scrollTop ) )
				{
					X = document.documentElement.scrollLeft;
					Y = document.documentElement.scrollTop;
				}  // IE 6 standards mode.

				return [ X, Y ];

			}
			else if ( arguments.length == 0 )
			{

				return [ this.scrollLeft, this.scrollTop ];

			}
			else
			{

				if ( arguments[0] !== null ) this.scrollLeft = arguments[0];
				if ( arguments[1] !== null ) this.scrollTop = arguments[1];
				return this;

			}

		}

	};

}();

DOMAssistant.attach( Legato_DOM_Library.DOMAssistantPlugIn );
// Developed by Robert Nyman/DOMAssistant team
// Code/licensing: http://domassistant.googlecode.com/
// Documentation: http://www.domassistant.com/documentation
// Version 2.8
DOMAssistant.Events = function () {
	var handler,
		key = "_events",
		w3cMode = !!document.addEventListener,
		useCapture = { focus: true, blur: true },
		translate = DOMAssistant.isIE? { focus: "activate", blur: "deactivate", mouseenter: "mouseover", mouseleave: "mouseout" } : { mouseenter: "mouseover", mouseleave: "mouseout" },
		regex = {
			special: /^submit|reset|change|select$/i,
			mouseenterleave: /^mouse(enter|leave)$/i,
			dom: /^DOM/,
			on: /^on/i
		},
		special = function (e) {
			return DOMAssistant.isIE && regex.special.test(e);
		},
		fix = function (e) {
			return translate[e] || e;
		},
		createEvent = function (e, type, target) {
			e = e || window.event || {};
			if (e.event) { return e; }
			var event = {
				event: e,
				type: type || e.type,
				bubbles: e.bubbles || true,
				cancelable: e.cancelable || false,
				target: target || e.target || e.srcElement,
				clientX: e.clientX || 0,
				clientY: e.clientY || 0,
				altKey: e.altKey || false,
				ctrlKey: e.ctrlKey || false,
				shiftKey: e.shiftKey || false,
				button: e.button || null,
				timeStamp: +new Date(),
				preventDefault: function() {
					if (e.preventDefault) { e.preventDefault(); }
					this.returnValue = e.returnValue = false;
				},
				stopPropagation: function() {
					if (e.stopPropagation) { e.stopPropagation(); }
					this.cancelBubble = e.cancelBubble = true;
				}
			};
			if (event.target && 3 === event.target.nodeType) { // Safari textnode bug
				event.target = event.target.parentNode;
			}
			event.currentTarget = event.target;
			event.relatedTarget = e.relatedTarget || (e.fromElement === event.target? e.toElement : e.fromElement) || null;
			var de = document.documentElement, b = document.body;
			event.pageX = DOMAssistant.def(e.pageX)? e.pageX : (event.clientX + (de.scrollLeft || b.scrollLeft) - (de.clientLeft || 0));
			event.pageY = DOMAssistant.def(e.pageY)? e.pageY : (event.clientY + (de.scrollTop || b.scrollTop) - (de.clientTop || 0));
			if ("number" === typeof e.which) {
				event.keyCode = e.keyCode;
				event.charCode = event.which = e.which;
			}
			else if (e.keyCode) {
				event.keyCode = event.charCode = e.keyCode;
			}
			return event;
		};

	return {
		publicMethods : [
			"triggerEvent",
			"addEvent",
			"removeEvent",
			"relayEvent",
			"unrelayEvent",
			"preventDefault",
			"cancelBubble"
		],

		init : function () {
			DOMAssistant.preventDefault = this.preventDefault;
			DOMAssistant.cancelBubble = this.cancelBubble;
			handler = this.handleEvent;
		},

		triggerEvent : function (evt, target, e) {
			var fevt = fix(evt),
				events = this.retrieve(key),
				event = e || createEvent(e, fevt, target || this);
			event.currentTarget = this;
			if (events && events[fevt]) {
				for (var i=0, iL=events[fevt].length; i<iL; i++) {
					if (events[fevt][i].call(this, event) === false) { event.stopPropagation(); }
				}
			}
			else if (typeof this["on" + fevt] === "function") {
				this["on" + fevt].call(this, event);
			}
			var p = DOMAssistant.$$(this.parentNode);
			if (!event.cancelBubble && p && p.nodeType === 1) {
				p.triggerEvent(fevt, target, event);
			}
			return this;
		},

		addEvent : function (evt, func, relay, proxy, selector) {
			var existingEvent,
				fevt = fix(evt),
				uid = fevt + this.retrieve(),
				onevt = "on" + fevt;
			if (!(func.attachedElements && func.attachedElements[uid])) {
				var events = this.retrieve(key) || {};
				if (!events[fevt]) {
					events[fevt] = [];
					existingEvent = this[onevt];
					this[onevt] = null;
				}
				if (typeof this.window === "object") { this.window[onevt] = handler; }
				else if (!events[fevt].length) {
					if (w3cMode) { this.addEventListener(fevt, handler, useCapture[fevt]); }
					else { this[onevt] = handler; }
				}
				if (existingEvent) {
					events[fevt].push(existingEvent);
				}
				if (fevt !== evt) { func.evt = evt; }
				func.relay = relay;
				func.proxy = proxy;
				func.selector = selector;
				func.attachedElements = func.attachedElements || {};
				func.attachedElements[uid] = true;
				events[fevt].push(func);
				this.store(key, events);
			}
			return this;
		},

		handleEvent : function (evt) {
			var currentEvt = (evt && regex.dom.test(evt.type) && w3cMode)? evt : createEvent(evt),
				type = fix(currentEvt.type),
				targ = currentEvt.target,
				relatedTarg = currentEvt.relatedTarget,
				eventColl = this.retrieve(key)[type].slice(0), eventCollLength, eventReturn, oevt;
			if ((eventCollLength = eventColl.length)) {
				for (var i=0; i<eventCollLength; i++) {
					if (typeof eventColl[i] === "function") {
						if ((oevt = eventColl[i].evt) && oevt !== type) {
							currentEvt.type = oevt;
							if (relatedTarg && regex.mouseenterleave.test(oevt)) {
								if (eventColl[i].relay) {
									var elms = eventColl[i].elms || (eventColl[i].elms = this.cssSelect(eventColl[i].selector));
									if (elms.indexOf(targ) < 0 || !DOMAssistant.hasChild.call(relatedTarg, targ)) { continue; }
								}
								else if (this === relatedTarg || this.hasChild(relatedTarg)) { continue; }
							}
						}
						eventReturn = eventColl[i].call(this, currentEvt);
					}
				}
				if (eventReturn === false) { currentEvt.stopPropagation(); }
				return eventReturn;
			}
		},

		removeEvent : function (evt, func, relay, proxy) {
			var uid = (evt = fix(evt)) + this.retrieve(),
				events = this.retrieve(key),
				onevt = "on" + evt;
			if (events && !evt) {
				for (var ev in events) {
					if (events[ev].length) { this.removeEvent(ev); }
				}
				var attr = this.attributes;
				for (var att, j=attr.length; j--;) {
					att = attr[j].nodeName.toLowerCase();
					if (regex.on.test(att) && typeof this[att] === "function") {
						this[att] = null;
					}
				}
			}
			else if (events && events[evt]) {
				var eventColl = events[evt];
				for (var fn, i=eventColl.length; i--;) {
					fn = func || eventColl[i];
					if (eventColl[i] === fn && relay === fn.relay && proxy === fn.proxy) {
						eventColl.splice(i, 1);
						if (!!proxy && fn.selector) {
							this.cssSelect(fn.selector).removeEvent(proxy);
						}
						if (fn.attachedElements) {
							fn.attachedElements[uid] = null;
						}
					}
				}
				if (!events[evt].length) {
					if (w3cMode) { this.removeEventListener(evt, handler, useCapture[evt]); }
					else { this[onevt] = null; }
				}
			}
			else if (this[onevt] && !func && !relay) {
				this[onevt] = null;
			}
			return this;
		},

		relayEvent: function (evt, selector, fn, proxy) {
			if (special(evt)) {
				this.relayEvent("focus", selector, function() {
					DOMAssistant.$$(this).removeEvent(evt).addEvent(evt, function(e) {
						return fn.call(this, createEvent(e));
					});
				}, evt).relayEvent("blur", selector, function() {
					DOMAssistant.$$(this).removeEvent(evt);
				}, evt);
				return this;
			}
			return this.addEvent(evt, function(e) {
				e = createEvent(e);
				var target = e.target, args = arguments, i = 0, elm, elms = this.cssSelect(selector);
				while ((elm = elms[i++])) {
					if ((elm === target || DOMAssistant.hasChild.call(elm, target)) && !elm.disabled) {
						e.currentTarget = elm;
						var retVal = fn.apply(elm, args);
						if (!retVal) { e.preventDefault(); }
						return retVal;
					}
				}
			}, true, proxy, selector);
		},

		unrelayEvent: function (evt) {
			if (special(evt)) {
				return this.removeEvent("focus", null, true, evt).removeEvent("blur", null, true, evt);
			}
			return this.removeEvent(evt, null, true);
		},

		preventDefault : function (evt) {
			if (evt.preventDefault) { evt.preventDefault(); }
			evt.returnValue = false;
		},

		cancelBubble : function (evt) {
			if (evt.stopPropagation) { evt.stopPropagation(); }
			evt.cancelBubble = true;
		}
	};
}();
DOMAssistant.attach(DOMAssistant.Events);
//------------------------------------------------------------------------
// Name: Legato_Form
// Desc: A helper class to help with the PHP Form class.
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Public Member Functions
//------------------------------------------------------------------------
//------------------------------------------------------------------------
// Name: Legato_Form
// Desc: Class constructor.
//------------------------------------------------------------------------
function Legato_Form( form, options_object )
{

	this.form                  = $$( form );
	this.input_elements        = [];
	this.groups                = [];

	// Store the parameters.
	this.submit_buttons         = options_object.submit_buttons;
	this.submit_form           = (options_object.submit_form == null) ? false : true;
	this.redirect_url          = options_object.redirect_url;
	this.request_url           = options_object.request_url;
	this.modified_request_url  = '';
	this.errors                = [];

	this.processing            = false;

	// Add on onsubmit event for the form.
	var form = this;
	$( this.form ).addEvent( 'submit', function(){ return form.validateForm(); } );

	// Add onclick events for all the submit buttons.
	for ( var i = 0; i < this.submit_buttons.length; ++i )
		if ( this.submit_buttons[i] != null )
			$( this.submit_buttons[i] ).addEvent( 'click', function(){ form.submitOnClick( this ); } );

}


//----------------------------------------------------------------------
// Name: validateForm()
// Desc: Start the validation.
//----------------------------------------------------------------------
Legato_Form.prototype.validateForm = function()
{

	var errors = false;

	// Are we processing?
	if ( this.processing == true )
		return false;
	else
		this.processing = true;

	// Disable the submit button.
	for ( var i = 0; i < this.submit_buttons.length; ++i )
		if ( this.submit_buttons[i] != null )
			this.submit_buttons[i].disabled = true;

	// First make sure the form is clean.
	this.cleanupForm();

	// If there is a request URL, send the request.
	if ( this.request_url != null )
	{

		var query_string = this.getQueryString();

		// Send the request.
		var form = this;
		Legato_RequestManager.makeRequest( this.modified_request_url, function( response ){ form.processResponse( response ); }, query_string );

		// Return. We will continue processing in the processResponse() function.
		return false;

	}

	// Finish the validation.
	this.finishValidation();

};


//----------------------------------------------------------------------
// Name: processResponse()
// Desc: Processes the response from the XHR request.
//----------------------------------------------------------------------
Legato_Form.prototype.processResponse = function( response )
{

	var any_errors = false;

	// JSON or XML?
	if ( !response.responseXML || response.responseXML.getElementsByTagName( 'errors' ).length == 0 )
	{

		var errors = eval( '(' + response.responseText + ')' );

		// Any errors?
		if ( errors.length != 0 )
		{

			// Set errors on.
			any_errors = true;

			// Loop through each error.
			for ( var id in errors )
			{

				// Get the message.
				var message = errors[id];

				// Post the error.
				this.postError( id, message );

			}  // Next error.

		}

	}
	else
	{

		response = response.responseXML;

		// Get the errors.
		var errors = response.getElementsByTagName( 'error' );

		// Is there any errors?
		if ( errors.length != 0 )
		{

			// Set errors on.
			any_errors = true;

			// Loop through each error.
			for ( var i = 0; i < errors.length; i++ )
			{

				// Get the error details.
				var id = errors[i].getElementsByTagName( "id" );

				if ( id.length == 0 )
					id = null;
				else
					id = id[0].firstChild.data;

				// Get the message.
				var message = errors[i].getElementsByTagName( "message" )[0].firstChild.data;

				// Post the error.
				this.postError( id, message );

			}  // Next error.

		}

	}

	// Any errors?
	if ( !any_errors )
	{

		// Finish validation.
		this.finishValidation();

		// Return.
		return;

	}

	// Show the generic error message.
	this.postError( null, 'There were errors while processing the form. Please fix them and try submitting the form again.' );

	// Set processing to false.
	this.processing = false;

	// Enable the submit button.
	for ( var i = 0; i < this.submit_buttons.length; ++i )
		if ( this.submit_buttons[i] != null )
			this.submit_buttons[i].disabled = false;

};


//----------------------------------------------------------------------
// Name: finishValidation()
// Desc: Finishes the validation.
//----------------------------------------------------------------------
Legato_Form.prototype.finishValidation = function()
{

	// Check for a redirect URL and if there is one redirect them.
	if ( this.redirect_url != null )
	{
		window.location = this.redirect_url;
		return true;
	}

	// If the submit form flag is set, submit the form.
	if ( this.submit_form )
		return true;

	// Set processing to false.
	this.processing = false;

	// Enable the submit button.
	for ( var i = 0; i < this.submit_buttons.length; ++i )
		if ( this.submit_buttons[i] != null )
			this.submit_buttons[i].disabled = false;

	// Don't submit form.
	return false;

};


//----------------------------------------------------------------------
// Name: submitOnClick()
// Desc: Gets called when a submit button is clicked for this form.
//----------------------------------------------------------------------
Legato_Form.prototype.submitOnClick = function( elem )
{

	// Since we disable the submit buttons during submission, we have to
	// add a hidden form element with the same name/value as the element
	// being clicked, so that we know which submit button was clicked
	// during processing.
	this.form.addContent( '<input type="hidden" name="' + elem.name + '" value="' + elem.value + '">' );

};


//----------------------------------------------------------------------
// Name: postError()
// Desc: Posts an error to the form with information about what went
//       wrong and why it went wrong.
// Note: If null is passed in for element_id, the error will be placed
//       at the end of the form.
//----------------------------------------------------------------------
Legato_Form.prototype.postError = function( element_id, error )
{

	// Create the error node.
	var error_node = $( document.body ).create( 'p', { className: 'error' }, false, error );

	// What type of placement?
	if ( element_id == null )
	{

		// Do we have a submit button?
		if ( this.submit_buttons[0] != null )
		{

			// A group or not?
			if ( $( this.submit_buttons[0].parentNode.parentNode ).hasClass( 'group_elements' ) )
				this.submit_buttons[0].parentNode.parentNode.parentNode.insertBefore( error_node, this.submit_buttons[0].parentNode.parentNode.parentNode.firstChild );
			else
				this.submit_buttons[0].parentNode.insertBefore( error_node, this.submit_buttons[0].parentNode.firstChild );
		}
		else
			this.form.addContent( error_node );

	}  // End if general error message.
	else
	{

		var html_element = this.form.cssSelect( '#' + element_id )[0];

		// Group or normal?
		if ( $( html_element.parentNode.parentNode ).hasClass( 'group' ) )
			html_element.parentNode.parentNode.insertBefore( error_node, html_element.parentNode.parentNode.firstChild );
		else
			html_element.parentNode.insertBefore( error_node, html_element.parentNode.firstChild );

	}  // End if normal/group error message.

};


//----------------------------------------------------------------------
// Name: cleanupForm()
// Desc: Cleans up the form to make it ready for form validation.
//----------------------------------------------------------------------
Legato_Form.prototype.cleanupForm = function()
{

	// Get all the forms error's.
	var errors = this.form.cssSelect( 'p.error' );

	// Loop through each error.
	// We get the length before hand, because we take elements away from the
	// array in the loop.
	for ( var i = 0; i < errors.length; i++ )
	{

		// Get the element. We retrieve the 0th element because we remove
		// the child below, and the next one will fall in this place.
		var error_element = errors[i];

		// Is this an error element?
		error_element.remove();  // Remove the element.


	}  // Next error node.

};


//----------------------------------------------------------------------
// Name: getQueryString()
// Desc: Concatenates all the managed input elements in to a query
//       string suitable for appending to a URL.
//----------------------------------------------------------------------
Legato_Form.prototype.getQueryString = function()
{

	var values = Array();

	// Loop through each input element in the form.
	for ( var i = 0; i < this.form.elements.length; i++ )
	{

		var element = this.form.elements[i];

		// What type of element?
		switch ( element.type )
		{

		// Simple elements.
		case 'text':
		case 'password':
		case 'file':
		case 'textarea':
		case 'hidden':
		case 'select-one':

			values.push( (element.name + "=" + encodeURIComponent( element.value )) );
			break;

		// Checkboxes and radio buttons.
		case 'checkbox':
		case 'radio':

			// Only add if checked.
			if ( element.checked )
				values.push( (element.name + "=" + encodeURIComponent( element.value )) );

			break;

		// Select multiples.
		case 'select-multiple':

			// Loop through each option.
			for ( var n = 0; n < element.options.length; n++ )
			{

				// Only add if option is selected.
				if ( element.options[n].selected )
					values.push( (element.name + "=" + encodeURIComponent( element.options[n].value )) );

			}  // Next option.

			break;

		}  // End what type of elements.

	}  // Next input element.

	// Put the values together into a string.
	var query_string = values.join( "&" );

	// Get the pieces of the request URL.
	var pieces = this.request_url.match( /(\S*)\?(\S*)/ );

	// If the request_url does not have a ? in it, get rid of the query values from
	// the request URL, and add it to the query string.
	if ( pieces != null )
	{

		this.modified_request_url = pieces[1];
		query_string              = pieces[2] + "&" + query_string;

	}
	else
	{

		this.modified_request_url = this.request_url;

	}

	// Return the query string.
	return query_string;

};


//------------------------------------------------------------------------
// Package: Animation
// For animating elements in the DOM in various ways.
//
// Topic: Dependencies
// - <Events Handler>
// - <Structures>
// - <DOM Library>
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Class: Legato_Animation_Controller
// Stores the animations parameters.
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Constructor: Legato_Animation_Controller()
// Class constructor. You don't need to instantiate this as it is used by
// the system.
//------------------------------------------------------------------------
function Legato_Animation_Controller()
{

	// Store the default values.
	this.move              = { to:    new Legato_Structure_Point(),
	                           by:    new Legato_Structure_Point(),
							   ease:  Legato_Animation.EASE_NONE };

	this.width             = { to:    null,
	                           by:    null,
							   ease:  Legato_Animation.EASE_NONE };

	this.height            = { to:    null,
	                           by:    null,
							   ease:  Legato_Animation.EASE_NONE };

	this.opacity           = { to:    null,
	                           by:    null,
							   ease:  Legato_Animation.EASE_NONE };

	this.background_color  = { to:    new Legato_Structure_Color(),
	                           by:    new Legato_Structure_Color() };

	this.border_color      = { to:    new Legato_Structure_Color(),
	                           by:    new Legato_Structure_Color() };

	this.text_color        = { to:    new Legato_Structure_Color(),
	                           by:    new Legato_Structure_Color() };

	this.delay             = 0;

}


//------------------------------------------------------------------------
// Class: Legato_Animation
// Holds a single animation for an element and the necessary methods to
// handle it.
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Class Constants
//------------------------------------------------------------------------
Legato_Animation.EASE_NONE          = 0;
Legato_Animation.EASE_IN            = 1;
Legato_Animation.EASE_OUT           = 2;
Legato_Animation.EASE_BOTH          = 3;
Legato_Animation.STRONG_EASE_IN     = 4;
Legato_Animation.STRONG_EASE_OUT    = 5;
Legato_Animation.STRONG_EASE_BOTH   = 6;
Legato_Animation.BACK_EASE_IN       = 7;
Legato_Animation.BACK_EASE_OUT      = 8;
Legato_Animation.BACK_EASE_BOTH     = 9;
Legato_Animation.BOUNCE_EASE_IN     = 10;
Legato_Animation.BOUNCE_EASE_OUT    = 11;
Legato_Animation.BOUNCE_EASE_BOTH   = 12;
Legato_Animation.ELASTIC_EASE_IN    = 13;
Legato_Animation.ELASTIC_EASE_OUT   = 14;
Legato_Animation.ELASTIC_EASE_BOTH  = 15;


//------------------------------------------------------------------------
// Public Member Functions
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Constructor: Legato_Animation()
// Class constructor.
//
// Parameters:
//     element - The DOM element that you'd like to animate.
//     run_time - The length that you'd like the animation to run.
//------------------------------------------------------------------------
function Legato_Animation( element, run_time )
{

	// Store the values.
	this.element               = $( element );
	this.element_properties    = Object();
	this.run_time              = run_time;
	this.controller            = new Legato_Animation_Controller();

	this.onStart               = null;
	this.onInterval            = null;
	this.onAdvance             = null;
	this.onEventFrame          = null;
	this.onStop                = null;
	this.onFinish              = null;

	// These values are used by the Legato_Animation system internally.
	this.status                = false;
	this.event_frames          = new Array();

	this.start_time            = null;
	this.current_time          = null;

	this.begin_width           = null;
	this.begin_height          = null;
	this.begin_pos             = null;
	this.begin_back_color      = null;
	this.begin_border_color    = null;
	this.begin_text_color      = null;
	this.begin_opacity         = null;

	this.offset_width          = null;
	this.offset_height         = null;
	this.offset_pos            = new Legato_Structure_Point();
	this.offset_back_color     = new Legato_Structure_Color();
	this.offset_border_color   = new Legato_Structure_Color();
	this.offset_text_color     = new Legato_Structure_Color();
	this.offset_opacity        = null;

	this.desired_width         = null;
	this.desired_height        = null;
	this.desired_pos           = new Legato_Structure_Point();
	this.desired_back_color    = null;
	this.desired_border_color  = null;
	this.desired_text_color    = null;
	this.desired_opacity       = null;

}


//------------------------------------------------------------------------
// Function: addEventFrame()
// Adds an event frame to the animation.
//
// Parameters:
//      time_offset - The offset from the beginning of the animation that
//                    you'd like this event to be fired.
//      event_func - The function that you'd like to execute.
//------------------------------------------------------------------------
Legato_Animation.prototype.addEventFrame = function( time_offset, event_func )
{

  // Add the new event frame to the animation.
	this.event_frames.push( { time_offset: time_offset, event_func: event_func, triggered: false } );

}


//------------------------------------------------------------------------
// Function: start()
// Sets the animation to start playing.
//------------------------------------------------------------------------
Legato_Animation.prototype.start = function()
{

	// Don't start if in the middle of playing.
	if ( this.status ) return;

	// Set the necessary values.
	this.status      = true;
	this.start_time  = new Date();

	// Width.
	if ( this.controller.width.to != null || this.controller.width.by != null )
	{

		// Get the element's width.
		this.begin_width = this.element.dimensions()[0];

		// Get the desired width and offset width.
		this.desired_width  = (this.controller.width.to != null) ? (this.controller.width.to) : (this.begin_width + this.controller.width.by);
		this.offset_width   = this.desired_width - this.begin_width;

	}

	// Height.
	if ( this.controller.height.to != null || this.controller.height.by != null )
	{

		// Get the element's height.
		this.begin_height = this.element.dimensions()[1];

		// Get the desired height and offset height.
		this.desired_height  = (this.controller.height.to != null) ? (this.controller.height.to) : (this.begin_height + this.controller.height.by);
		this.offset_height   = this.desired_height - this.begin_height;

	}

	// Position.
	if ( this.controller.move.to.X != null || this.controller.move.to.Y != null || this.controller.move.by.X != null || this.controller.move.by.Y != null )
	{

		// Get the element's position.
		this.begin_pos = this.element.position();
		this.begin_pos = new Legato_Structure_Point( this.begin_pos[0], this.begin_pos[1] );

		// Get the desired X position and X offset position.
		if ( this.controller.move.to.X != null || this.controller.move.by.X != null )
		{

			this.desired_pos.X  = (this.controller.move.to.X != null) ? (this.controller.move.to.X) : (this.begin_pos.X + this.controller.move.by.X);
			this.offset_pos.X   = this.desired_pos.X - this.begin_pos.X;

		}

		// Get the desired Y position and Y offset position.
		if ( this.controller.move.to.Y != null || this.controller.move.by.Y != null )
		{

			this.desired_pos.Y  = (this.controller.move.to.Y != null) ? (this.controller.move.to.Y) : (this.begin_pos.Y + this.controller.move.by.Y);
			this.offset_pos.Y   = this.desired_pos.Y - this.begin_pos.Y;

		}

	}

	// Opacity.
	if ( this.controller.opacity.to != null || this.controller.opacity.by != null )
	{
		
		// Get the element's opacity.
		this.begin_opacity = this.element.getStyle( 'opacity' );
		
		// Get the desired opacity and offset opacity.
		this.desired_opacity  = (this.controller.opacity.to != null) ? (this.controller.opacity.to) : (this.begin_opacity + this.controller.opacity.by);
		this.offset_opacity   = this.desired_opacity - this.begin_opacity;
		
	}

	// Background Color.
	if ( this.controller.background_color.to.R != null || 
	     this.controller.background_color.to.G != null || 
		 this.controller.background_color.to.B != null || 
	     this.controller.background_color.by.R != null ||
		 this.controller.background_color.by.G != null ||
		 this.controller.background_color.by.B != null )
	{
		
		// Get the element's background color.
		this.begin_back_color   = new Legato_Structure_Color( this.element.getStyle( 'background-color' ).substring( 1 ) );
		this.desired_back_color = new Legato_Structure_Color( this.begin_back_color.toHexString() );

		// Get the desired red value and offset value.
		if ( this.controller.background_color.to.R != null || this.controller.background_color.by.R != null )
		{

			this.desired_back_color.R  = (this.controller.background_color.to.R != null) ? (this.controller.background_color.to.R) : (this.begin_back_color.R + this.controller.background_color.by.R);
			this.offset_back_color.R   = this.desired_back_color.R - this.begin_back_color.R;

		}

		// Get the desired green value and offset value.
		if ( this.controller.background_color.to.G != null || this.controller.background_color.by.G != null )
		{

			this.desired_back_color.G  = (this.controller.background_color.to.G != null) ? (this.controller.background_color.to.G) : (this.begin_back_color.G + this.controller.background_color.by.G);
			this.offset_back_color.G   = this.desired_back_color.G - this.begin_back_color.G;

		}

		// Get the desired blue value and offset value.
		if ( this.controller.background_color.to.B != null || this.controller.background_color.by.B != null )
		{

			this.desired_back_color.B  = (this.controller.background_color.to.B != null) ? (this.controller.background_color.to.B) : (this.begin_back_color.B + this.controller.background_color.by.B);
			this.offset_back_color.B   = this.desired_back_color.B - this.begin_back_color.B;

		}

	}

	// Border Color.
	if ( this.controller.border_color.to.R != null || 
	     this.controller.border_color.to.G != null || 
		 this.controller.border_color.to.B != null || 
	     this.controller.border_color.by.R != null ||
		 this.controller.border_color.by.G != null ||
		 this.controller.border_color.by.B != null )
	{
		
		// Get the element's border color.
		this.begin_border_color   = new Legato_Structure_Color( this.element.getStyle( 'border-color' ).substring( 1 ) );
		this.desired_border_color = new Legato_Structure_Color( this.begin_border_color.toHexString() );

		// Get the desired red value and offset value.
		if ( this.controller.border_color.to.R != null || this.controller.border_color.by.R != null )
		{

			this.desired_border_color.R  = (this.controller.border_color.to.R != null) ? (this.controller.border_color.to.R) : (this.begin_border_color.R + this.controller.border_color.by.R);
			this.offset_border_color.R   = this.desired_border_color.R - this.begin_border_color.R;

		}

		// Get the desired green value and offset value.
		if ( this.controller.border_color.to.G != null || this.controller.border_color.by.G != null )
		{

			this.desired_border_color.G  = (this.controller.border_color.to.G != null) ? (this.controller.border_color.to.G) : (this.begin_border_color.G + this.controller.border_color.by.G);
			this.offset_border_color.G   = this.desired_border_color.G - this.begin_border_color.G;

		}

		// Get the desired blue value and offset value.
		if ( this.controller.border_color.to.B != null || this.controller.border_color.by.B != null )
		{

			this.desired_border_color.B  = (this.controller.border_color.to.B != null) ? (this.controller.border_color.to.B) : (this.begin_border_color.B + this.controller.border_color.by.B);
			this.offset_border_color.B   = this.desired_border_color.B - this.begin_border_color.B;

		}

	}

	// Text Color.
	if ( this.controller.text_color.to.R != null || 
	     this.controller.text_color.to.G != null || 
		 this.controller.text_color.to.B != null || 
	     this.controller.text_color.by.R != null ||
		 this.controller.text_color.by.G != null ||
		 this.controller.text_color.by.B != null )
	{

		// Get the element's text color.
		this.begin_text_color   = new Legato_Structure_Color( this.element.getStyle( 'color' ).substring( 1 ) );
		this.desired_text_color = new Legato_Structure_Color( this.begin_text_color.toHexString() );
		
		// Get the desired red value and offset value.
		if ( this.controller.text_color.to.R != null || this.controller.text_color.by.R != null )
		{

			this.desired_text_color.R  = (this.controller.text_color.to.R != null) ? (this.controller.text_color.to.R) : (this.begin_text_color.R + this.controller.text_color.by.R);
			this.offset_text_color.R   = this.desired_text_color.R - this.begin_text_color.R;

		}

		// Get the desired green value and offset value.
		if ( this.controller.text_color.to.G != null || this.controller.text_color.by.G != null )
		{

			this.desired_text_color.G  = (this.controller.text_color.to.G != null) ? (this.controller.text_color.to.G) : (this.begin_text_color.G + this.controller.text_color.by.G);
			this.offset_text_color.G   = this.desired_text_color.G - this.begin_text_color.G;

		}

		// Get the desired blue value and offset value.
		if ( this.controller.text_color.to.B != null || this.controller.text_color.by.B != null )
		{

			this.desired_text_color.B  = (this.controller.text_color.to.B != null) ? (this.controller.text_color.to.B) : (this.begin_text_color.B + this.controller.text_color.by.B);
			this.offset_text_color.B   = this.desired_text_color.B - this.begin_text_color.B;

		}

	}

	// Call the onStart function if there is any.
	if ( this.onStart != null )
	  this.onStart( this );

	// Call the incrementAnimation function. It will start the animation.
	Legato_Animation_Manager.addAnimation( this );

}


//------------------------------------------------------------------------
// (Exclude)
// Function: advanceWidth()
// Advances the width.
//------------------------------------------------------------------------
Legato_Animation.prototype.advanceWidth = function()
{

	// Get the new width.
	var new_width = Legato_Animation.tweenValue( this.controller.width.ease, (this.current_time - this.controller.delay), this.run_time, this.begin_width, this.offset_width );

	// Bounds.
	new_width = Math.max( new_width, 0 );

	// Set the new width on the element.
	this.element.dimensions( Math.ceil( new_width ), null );

}


//------------------------------------------------------------------------
// (Exclude)
// Function: advanceHeight()
// Advances the height.
//------------------------------------------------------------------------
Legato_Animation.prototype.advanceHeight = function()
{

	// Get the new height.
	var new_height = Legato_Animation.tweenValue( this.controller.height.ease, (this.current_time - this.controller.delay), this.run_time, this.begin_height, this.offset_height );
	
	// Bounds.
	new_height = Math.max( new_height, 0 );
	
	// Set the new height on the element.
	this.element.dimensions( null, Math.ceil( new_height ) );

}


//------------------------------------------------------------------------
// (Exclude)
// Function: advancePosition()
// Advances the position.
//------------------------------------------------------------------------
Legato_Animation.prototype.advancePosition = function()
{

	// Updating X position?
	if ( this.offset_pos.X != null )
	{

		// Get the new X position.
		var new_X_pos = Legato_Animation.tweenValue( this.controller.move.ease, (this.current_time - this.controller.delay), this.run_time, this.begin_pos.X, this.offset_pos.X );
		
		// Bounds.
		new_X_pos = Math.max( new_X_pos, 0 );

		// Set the new X position on the element.
		this.element.position( Math.ceil( new_X_pos ), null );

	}  // End if updating X position.

	// Updating Y position?
	if ( this.offset_pos.Y != null )
	{

		// Get the new Y position.
		var new_Y_pos = Legato_Animation.tweenValue( this.controller.move.ease, (this.current_time - this.controller.delay), this.run_time, this.begin_pos.Y, this.offset_pos.Y );
		
		// Bounds.
		new_Y_pos = Math.max( new_Y_pos, 0 );

		// Set the new Y position on the element.
		this.element.position( null, Math.ceil( new_Y_pos ) );

	}  // End if updating Y position.

}


//------------------------------------------------------------------------
// (Exclude)
// Function: advanceOpacity()
// Advances the opacity.
//------------------------------------------------------------------------
Legato_Animation.prototype.advanceOpacity = function()
{

	// Get the new opacity.
	var new_opacity = (Legato_Animation.tweenValue( this.controller.opacity.ease, (this.current_time - this.controller.delay), this.run_time, (this.begin_opacity * 100), (this.offset_opacity * 100) ) / 100);
	
	// Bounds.
	new_opacity = Math.min( Math.max( new_opacity, 0 ), 1 );
	
	// Set the new opacity on the element.
	this.element.setStyle( 'opacity', new_opacity );

}


//------------------------------------------------------------------------
// (Exclude)
// Function: advanceBackgroundColor()
// Advances the background color.
//------------------------------------------------------------------------
Legato_Animation.prototype.advanceBackgroundColor = function()
{

	// Set the new back color as the beginning color.
	var new_back_color = new Legato_Structure_Color( this.begin_back_color.toHexString() );

	// Updating red value?
	if ( this.offset_back_color.R != null )
	{

		// Get the new background color.
		new_back_color.R = Math.ceil( Legato_Animation.tweenValue( Legato_Animation.EASE_NONE, (this.current_time - this.controller.delay), this.run_time, this.begin_back_color.R, this.offset_back_color.R ) );
	
	}  // End if updating red value.

	// Updating green value?
	if ( this.offset_back_color.G != null )
	{

		// Get the new background color.
		new_back_color.G = Math.ceil( Legato_Animation.tweenValue( Legato_Animation.EASE_NONE, (this.current_time - this.controller.delay), this.run_time, this.begin_back_color.G, this.offset_back_color.G ) );

	}  // End if updating red value.

	// Updating blue value?
	if ( this.offset_back_color.B != null )
	{

		// Get the new background color.
		new_back_color.B = Math.ceil( Legato_Animation.tweenValue( Legato_Animation.EASE_NONE, (this.current_time - this.controller.delay), this.run_time, this.begin_back_color.B, this.offset_back_color.B ) );

	}  // End if updating red value.
	
	// Bounds.
	new_back_color.R = Math.min( Math.max( new_back_color.R, 0 ), 255 );
	new_back_color.G = Math.min( Math.max( new_back_color.G, 0 ), 255 );
	new_back_color.B = Math.min( Math.max( new_back_color.B, 0 ), 255 );
	
	// Set the new background color on the element.
	this.element.setStyle( 'background-color', '#' + new_back_color.toHexString() );

}


//------------------------------------------------------------------------
// (Exclude)
// Function: advanceBorderColor()
// Advances the border color.
//------------------------------------------------------------------------
Legato_Animation.prototype.advanceBorderColor = function()
{

	// Set the new border color as the beginning color.
	var new_border_color = new Legato_Structure_Color( this.begin_border_color.toHexString() );

	// Updating red value?
	if ( this.offset_border_color.R != null )
	{

		// Get the new border color.
		new_border_color.R = Math.ceil( Legato_Animation.tweenValue( Legato_Animation.EASE_NONE, (this.current_time - this.controller.delay), this.run_time, this.begin_border_color.R, this.offset_border_color.R ) );

	}  // End if updating red value.

	// Updating green value?
	if ( this.offset_back_color.G != null )
	{

		// Get the new border color.
		new_border_color.G = Math.ceil( Legato_Animation.tweenValue( Legato_Animation.EASE_NONE, (this.current_time - this.controller.delay), this.run_time, this.begin_border_color.G, this.offset_border_color.G ) );

	}  // End if updating red value.

	// Updating blue value?
	if ( this.offset_border_color.B != null )
	{

		// Get the new border color.
		new_border_color.B = Math.ceil( Legato_Animation.tweenValue( Legato_Animation.EASE_NONE, (this.current_time - this.controller.delay), this.run_time, this.begin_border_color.B, this.offset_border_color.B ) );

	}  // End if updating red value.
	
	// Bounds.
	new_border_color.R = Math.min( Math.max( new_border_color.R, 0 ), 255 );
	new_border_color.G = Math.min( Math.max( new_border_color.G, 0 ), 255 );
	new_border_color.B = Math.min( Math.max( new_border_color.B, 0 ), 255 );

	// Set the new border color on the element.
	this.element.setStyle( 'border-color', '#' + new_border_color.toHexString() );
	
}


//------------------------------------------------------------------------
// (Exclude)
// Function: advanceTextColor()
// Advances the text color.
//------------------------------------------------------------------------
Legato_Animation.prototype.advanceTextColor = function()
{
	
	// Set the new text color as the beginning color.
	var new_text_color = new Legato_Structure_Color( this.begin_text_color.toHexString() );

	// Updating red value?
	if ( this.offset_text_color.R != null )
	{
		
		// Get the new text color.
		new_text_color.R = Math.ceil( Legato_Animation.tweenValue( Legato_Animation.EASE_NONE, (this.current_time - this.controller.delay), this.run_time, this.begin_text_color.R, this.offset_text_color.R ) );

	}  // End if updating red value.

	// Updating green value?
	if ( this.offset_text_color.G != null )
	{

		// Get the new text color.
		new_text_color.G = Math.ceil( Legato_Animation.tweenValue( Legato_Animation.EASE_NONE, (this.current_time - this.controller.delay), this.run_time, this.begin_text_color.G, this.offset_text_color.G ) );

	}  // End if updating red value.

	// Updating blue value?
	if ( this.offset_text_color.B != null )
	{

		// Get the new text color.
		new_text_color.B = Math.ceil( Legato_Animation.tweenValue( Legato_Animation.EASE_NONE, (this.current_time - this.controller.delay), this.run_time, this.begin_text_color.B, this.offset_text_color.B ) );

	}  // End if updating red value.
	
	// Bounds.
	new_text_color.R = Math.min( Math.max( new_text_color.R, 0 ), 255 );
	new_text_color.G = Math.min( Math.max( new_text_color.G, 0 ), 255 );
	new_text_color.B = Math.min( Math.max( new_text_color.B, 0 ), 255 );

	// Set the new text color on the element.
	this.element.setStyle( 'color', '#' + new_text_color.toHexString() );

}


//------------------------------------------------------------------------
// (Exclude)
// Function: advanceFrame()
// Carries out the next frame of the animation.
//------------------------------------------------------------------------
Legato_Animation.prototype.advanceFrame = function()
{

	// If the animation is stopped, return false.
	if ( !this.status )
	  return false;

	// Update the current time.
	this.current_time = new Date() - this.start_time;

	// Only start incrementing if we have passed the delay time.
	if ( this.current_time > this.controller.delay )
	{
		
		// Animating width?
		if ( this.desired_width != null )
			this.advanceWidth();

		// Animating height?
		if ( this.desired_height != null )
			this.advanceHeight();

		// Animating position?
		if ( this.desired_pos.X != null || this.desired_pos.Y != null )
			this.advancePosition();

		// Animating opacity?
		if ( this.desired_opacity != null )
			this.advanceOpacity();

		// Animating background color?
		if ( this.desired_back_color != null )
			this.advanceBackgroundColor();

		// Animating border color?
		if ( this.desired_border_color != null )
			this.advanceBorderColor();

		// Animating text color?
		if ( this.desired_text_color != null )
			this.advanceTextColor();
			
		// Loop through each event frame.
		var event_triggered = false;
		for ( var i = 0; i < this.event_frames.length; i++ )
		{

			// If it is time (or passed time) to trigger the event, do so.
			if ( !this.event_frames[i].triggered && this.current_time >= this.controller.delay + this.event_frames[i].time_offset )
			{

			  this.event_frames[i].event_func( this );
				this.event_frames[i].triggered = true;
				event_triggered = true;

			}

		}  // Next event frame.

		// If an event was triggered and there's an onEventFrame function, call it.
		if ( event_triggered && this.onEventFrame )
		  this.onEventFrame( this );

		// Call the onAdvance function if there is any.
		if ( this.onAdvance )
			this.onAdvance( this );

	}  // End if delay is over.

	// Call the onAdvance function if there is any.
	if ( this.onInterval )
		this.onInterval( this );

	// Should we continue processing?
	if ( this.current_time < this.run_time + this.controller.delay )
		return true;
	else
		return false;

}


//------------------------------------------------------------------------
// Function: stop()
// Stops the animation where it currently is. Does not finish it.
//------------------------------------------------------------------------
Legato_Animation.prototype.stop = function()
{

	// Set the animation's status to not playing.
	this.status = false;

	// Call the onStop function if there is any.
	if ( this.onStop )
	  this.onStop( this );

}


//------------------------------------------------------------------------
// (Exclude)
// Function: finish()
// Does the required clean up of the animation.
//------------------------------------------------------------------------
Legato_Animation.prototype.finish = function()
{

	// Set the animation's status to not playing.
	this.status = false;
	
	// Get rid of any animation errors. Set the desired values on the elements.
	if ( this.desired_width        ) this.element.dimensions( this.desired_width, null );
	if ( this.desired_height       ) this.element.dimensions( null, this.desired_height );

	if ( this.desired_pos.X        ) this.element.position( this.desired_pos.X, null );
	if ( this.desired_pos.Y        ) this.element.position( null, this.desired_pos.Y );

	if ( this.desired_opacity      ) this.element.setStyle( 'opacity', this.desired_opacity );

	if ( this.desired_back_color   ) this.element.setStyle( 'background-color', '#' + this.desired_back_color.toHexString() );

	if ( this.desired_border_color ) this.element.setStyle( 'border-color', '#' + this.desired_border_color.toHexString() );

	if ( this.desired_text_color   ) this.element.setStyle( 'color', '#' + this.desired_text_color.toHexString() );

	// Call the onFinish function if there is any.
	if ( this.onFinish )
	  this.onFinish( this );

}


//------------------------------------------------------------------------
// Public Static Member Functions
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// (Exclude)
// Function: tweenValue()
// Tweens the value.
//------------------------------------------------------------------------
Legato_Animation.tweenValue = function( ease_type, current_time, duration, begin_val, change_val )
{
	
	// What easing equation?
	switch( ease_type )
	{
		
	// EASE NONE
	case Legato_Animation.EASE_NONE:	
		return change_val * (current_time / duration) + begin_val;
		
	// EASE IN
	case Legato_Animation.EASE_IN:
		return change_val * (current_time /= duration) * current_time + begin_val;
		
	// EASE OUT
	case Legato_Animation.EASE_OUT:
		return -change_val * (current_time /= duration) * (current_time - 2) + begin_val;
		
	// EASE BOTH
	case Legato_Animation.EASE_BOTH:
		
		if ( (current_time /= duration / 2) < 1 ) 
			return change_val / 2 * current_time * current_time + begin_val;

		return -change_val / 2 * ((--current_time) * (current_time - 2) - 1) + begin_val;
		
	// STRONG EASE IN
	case Legato_Animation.STRONG_EASE_IN:
		return change_val * (current_time /= duration) * current_time * current_time * current_time + begin_val;
	
	// STRONG EASE OUT	
	case Legato_Animation.STRONG_EASE_OUT:
		return -change_val * ((current_time = current_time / duration - 1) * current_time * current_time * current_time - 1) + begin_val;
		
	// STRONG EASE BOTH
	case Legato_Animation.STRONG_EASE_BOTH:
	
		if ( (current_time /= duration / 2) < 1 ) 
			return change_val / 2 * current_time * current_time * current_time * current_time + begin_val;

		return -change_val / 2 * ((current_time -= 2) * current_time * current_time * current_time - 2) + begin_val;
		
	// BACK EASE IN
	case Legato_Animation.BACK_EASE_IN:
		return change_val * (current_time /= duration) * current_time * (2.70158 * current_time - 1.70158) + begin_val;
		
	// BACK EASE OUT
	case Legato_Animation.BACK_EASE_OUT:
		return change_val * ((current_time = current_time / duration - 1) * current_time * (2.70158 * current_time + 1.70158) + 1) + begin_val;
		
	// BACK EASE BOTH
	case Legato_Animation.BACK_EASE_BOTH:
		
		if ( (current_time /= duration / 2) < 1 ) 
			return change_val / 2 * (current_time * current_time * (3.5949095 * current_time - 2.5949095)) + begin_val;

		return change_val / 2 * ((current_time -= 2) * current_time * (3.5949095 * current_time + 2.5949095) + 2) + begin_val;
		
	// BOUNCE EASE IN
	case Legato_Animation.BOUNCE_EASE_IN:
		
		current_time = duration - current_time;

		if ( (current_time /= duration) < (1 / 2.75) )
			return change_val - (change_val * (7.5625 * current_time * current_time)) + begin_val;
		else if ( current_time < (2 / 2.75 ) )
			return change_val - (change_val * (7.5625 * (current_time -= (1.5 / 2.75)) * current_time + 0.75)) + begin_val;
		else if ( current_time < (2.5 / 2.75) )
			return change_val - (change_val * (7.5625 * (current_time -= (2.25 / 2.75)) * current_time + 0.9375)) + begin_val;
		else
			return change_val - (change_val * (7.5625 * (current_time -= (2.625 / 2.75)) * current_time + 0.984375)) + begin_val;
		
	// BOUNCE EASE OUT	
	case Legato_Animation.BOUNCE_EASE_OUT:
	
		if ( (current_time /= duration) < (1 / 2.75) )
		  return change_val * (7.5625 * current_time * current_time) + begin_val;
		else if ( current_time < (2 / 2.75 ) )
		  return change_val * (7.5625 * (current_time -= (1.5 / 2.75)) * current_time + 0.75) + begin_val;
		else if ( current_time < (2.5 / 2.75) )
		  return change_val * (7.5625 * (current_time -= (2.25 / 2.75)) * current_time + 0.9375) + begin_val;
		else
		  return change_val * (7.5625 * (current_time -= (2.625 / 2.75)) * current_time + 0.984375) + begin_val;
		  
	// BOUNCE EASE BOTH
	case Legato_Animation.BOUNCE_EASE_BOTH:
	
		if ( current_time < duration / 2 )
		{

			current_time = duration - (current_time * 2);

			if ( (current_time /= duration) < (1 / 2.75) )
				return (change_val - (change_val * (7.5625 * current_time * current_time))) * 0.5 + begin_val;
			else if ( current_time < (2 / 2.75 ) )
				return (change_val - (change_val * (7.5625 * (current_time -= (1.5 / 2.75)) * current_time + 0.75))) * 0.5 + begin_val;
			else if ( current_time < (2.5 / 2.75) )
				return (change_val - (change_val * (7.5625 * (current_time -= (2.25 / 2.75)) * current_time + 0.9375))) * 0.5 + begin_val;
			else
				return (change_val - (change_val * (7.5625 * (current_time -= (2.625 / 2.75)) * current_time + 0.984375))) * 0.5 + begin_val;

		}

		current_time = current_time * 2 - duration;

		if ( (current_time /= duration) < (1 / 2.75) )
		  return change_val * (7.5625 * current_time * current_time) * 0.5 + change_val * 0.5 + begin_val;
		else if ( current_time < (2 / 2.75 ) )
		  return change_val * (7.5625 * (current_time -= (1.5 / 2.75)) * current_time + 0.75) * 0.5 + change_val * 0.5 + begin_val;
		else if ( current_time < (2.5 / 2.75) )
		  return change_val * (7.5625 * (current_time -= (2.25 / 2.75)) * current_time + 0.9375) * 0.5 + change_val * 0.5 + begin_val;
		else
		  return change_val * (7.5625 * (current_time -= (2.625 / 2.75)) * current_time + 0.984375) * 0.5 + change_val * 0.5 + begin_val;
		  
	// ELASTIC EASE IN
	case Legato_Animation.ELASTIC_EASE_IN:
	
		if ( current_time == 0 ) 
			return begin_val;
			
		if ( (current_time /= duration) == 1 ) 
			return begin_val + change_val;

		var p = duration * 0.3;
		var a = change_val;
		var s = p / 4;

		return -(a * Math.pow( 2, 10 * (current_time -= 1) ) * Math.sin( (current_time * duration - s) * (2 * Math.PI) / p )) + begin_val;
		
	// ELASTIC EASE OUT
	case Legato_Animation.ELASTIC_EASE_OUT:
		
		if ( current_time == 0 ) 
			return begin_val;
			
		if ( (current_time /= duration) == 1 ) 
			return begin_val + change_val;

		var p = duration * 0.3;
		var a = change_val;
		var s = p / 4;

		return a * Math.pow( 2, -10 * current_time ) * Math.sin( (current_time * duration - s) * (2 * Math.PI) / p ) + change_val + begin_val;
		
	// ELASTIC EASE BOTH
	case Legato_Animation.ELASTIC_EASE_BOTH:
	
		if ( current_time == 0 ) 
			return begin_val;
			
		if ( (current_time /= duration / 2) == 2 ) 
			return begin_val + change_val;

		var p = duration * (0.3 * 1.5);
		var a = change_val;
		var s = p / 4;

		if ( current_time < 1 ) return -0.5 * (a * Math.pow( 2, 10 * (current_time -= 1) ) * Math.sin( (current_time * duration - s) * (2 * Math.PI) / p )) + begin_val;

		return a * Math.pow( 2, -10 * (current_time -= 1) ) * Math.sin( (current_time * duration - s) * (2 * Math.PI) / p ) * 0.5 + change_val + begin_val;
		
	}

}


//------------------------------------------------------------------------
// Class: Legato_Animation_Sequence
// Stores a sequence of <Legato_Animation> objects.
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Static Variables
//------------------------------------------------------------------------
Legato_Animation_Sequence.sequences = new Array();


//------------------------------------------------------------------------
// Public Member Functions
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// Constructor: Legato_Animation_Sequence()
// Class constructor.
//
// Parameters:
//     options - An optional object of options for the Animation Sequence.
//------------------------------------------------------------------------
function Legato_Animation_Sequence( options )
{

	// Store the default values.
	this.animations               = new Array();
	this.current_animation_index  = 0;
	this.sequence_index           = Legato_Animation_Sequence.sequences.length;
	this.status                   = false;
	this.options                  = options;
	
	// Callbacks.
	this.onStart               = null;
	this.onAdvance             = null;
	this.onLoop                = null;
	this.onFinish              = null;

	// Store this animation sequence in the global sequences array.
	Legato_Animation_Sequence.sequences[this.sequence_index] = this;

}


//------------------------------------------------------------------------
// Function: addAnimation()
// Adds an <Legato_Animation> object to the animation sequence.
//
// Parameters:
//     animation - An <Legato_Animation> object that you would like to set up
//                 to play in the animation. Will add it at the end of the
//                 sequence.
//------------------------------------------------------------------------
Legato_Animation_Sequence.prototype.addAnimation = function( animation )
{

	// Store the animation in the sequence.
	this.animations.push( animation );

	// Add the onFinish and onStop functions.
	Legato_Events_Handler.addEvent( animation, "onFinish", Legato_Animation_Sequence.nextAnimation );
	Legato_Events_Handler.addEvent( animation, "onStop", Legato_Animation_Sequence.nextAnimation );

	// Store the sequence index in the animation.
	animation.sequence_index = this.sequence_index;

}


//------------------------------------------------------------------------
// Function: start()
// Sets the Animation Sequence to start playing.
//------------------------------------------------------------------------
Legato_Animation_Sequence.prototype.start = function()
{

	// Only start if there is at least one animation in the sequence
	// and we are not already playing.
	if ( this.animations.length == 0 || this.status ) return;

	// Set as playing.
	this.status = true;
	
	// On start callback.
	if ( this.onStart != null && this.onStart( this ) == false )
		return;

	// Start the first animation in the sequence.
	this.animations[0].start();

}


//------------------------------------------------------------------------
// (Exclude)
// Function: reset()
// Cleans up the animation sequence.
//------------------------------------------------------------------------
Legato_Animation_Sequence.prototype.reset = function()
{

	// Reset the values.
	this.status                   = false;
	this.current_animation_index  = 0;

}


//------------------------------------------------------------------------
// Public Static Member Functions
//------------------------------------------------------------------------

//------------------------------------------------------------------------
// (Exclude)
// Function: nextAnimation()
// Plays the next animation in the sequence. This is set as the previous
// animation's onFinish function so that a chain forms.
//------------------------------------------------------------------------
Legato_Animation_Sequence.nextAnimation = function( animation )
{

	// Get the animation sequence.
	var animation_sequence = Legato_Animation_Sequence.sequences[animation.sequence_index];

	// If the animation sequence is stopped, return false.
	if ( !animation_sequence.status )
	  return false;

	// Increment the current animation index.
	animation_sequence.current_animation_index++;

	// Play the next animation if there is one.
	if ( animation_sequence.animations[animation_sequence.current_animation_index] != null )
	{
		
		// On advance callback.
		if ( animation_sequence.onAdvance != null && animation_sequence.onAdvance( animation_sequence ) == false )
			return;

		// Start the next animation.
		animation_sequence.animations[animation_sequence.current_animation_index].start();

	}  // End if next animation.
	else
	{
		
		// Should we loop?
		if ( animation_sequence.options && animation_sequence.options.loop == true )
		{
			
			// On loop callback.
			if ( animation_sequence.onLoop != null && animation_sequence.onLoop( animation_sequence ) == false )
				return;
				
			// Loop.
			animation_sequence.reset();
			animation_sequence.start();
			
		}  // End if looping.
		else
		{

			// Finish the animation sequence.
			animation_sequence.reset();
			
			// On finish callback.
			if ( animation_sequence.onFinish != null )
				animation_sequence.onFinish( animation_sequence );
			
		}

	}  // End if no more animations.

}


//------------------------------------------------------------------------
// (Exclude)
// Class: Legato_Animation_Manager
// Manages each animation. All the animations are incremented through the
// manager.
//------------------------------------------------------------------------
Legato_Animation_Manager =
{

	//----------------------------------------------------------------------
	// Public Variables
	//----------------------------------------------------------------------
	increment_speed:     20,           // The speed at which the animation manager will increment each animation.
	playing_animations:  new Array(),  // An array of all the currently playing animations.
	interval_handle:     null,         // The handle that the setInterval() function returns.


	//----------------------------------------------------------------------
	// Public Member Functions
	//----------------------------------------------------------------------
	//----------------------------------------------------------------------
	// (Exclude)
	// Function: addAnimation()
	// This function is used to add an animation to the animation manager
	// for playing.
	//----------------------------------------------------------------------
	addAnimation: function( animation )
	{

		// Loop through each animation being played.
		for ( var i = 0; i < this.playing_animations.length; i++ )
		{

			// Get the animation.
			var playing_animation = this.playing_animations[i];

			// Is the animation we're adding animate the
			// same element than this animation's element?
			if ( animation.element == playing_animation.element )
			{

				// Remove the animation from the playing animations array.
				this.playing_animations.splice( i, 1 );

				// Stop the currently playing animation so that we can play this one.
				playing_animation.stop();

			}  // End if managing the same element.

		}  // Next playing animation.

		// Add the animation to the playing animations array.
		this.playing_animations.push( animation );

		// If we don't have any animations playing,
		// we have to set up the timeout.
		if ( this.interval_handle == null )
		{

			// Set to advanced all animations.
			this.interval_handle = setInterval( Legato_Animation_Manager.advanceAnimations, this.increment_speed, null );

		}  // End if no animations currently playing.

	},


	//------------------------------------------------------------------------
	// (Exclude)
	// Function: advanceAnimations()
	// Advances each animation being played.
	//------------------------------------------------------------------------
	advanceAnimations: function()
	{

		// Loop through each animation being played.
		for ( var i = 0; i < Legato_Animation_Manager.playing_animations.length; i++ )
		{

			// Get the animation from the array.
			var animation = Legato_Animation_Manager.playing_animations[i];

			// Advance the animation.
			var continue_playing = animation.advanceFrame();

			// Is the animation done playing?
			if ( !continue_playing )
			{
				
				// Remove the animation from the playing animations array.
				Legato_Animation_Manager.playing_animations.splice( i, 1 );
				
				// Finish up the animation.
				animation.finish();
				
				// If we don't have any more animations to play, stop
				// JavaScript from calling this function again.
				if ( Legato_Animation_Manager.playing_animations.length == 0 )
				{
					clearInterval( Legato_Animation_Manager.interval_handle );
					Legato_Animation_Manager.interval_handle = null;
				}

			}  // End if stop playing this animation.

		}  // Next playing animation.

	}

}



// Developed by Robert Nyman/DOMAssistant team
// Code/licensing: http://domassistant.googlecode.com/
// Documentation: http://www.domassistant.com/documentation
// Version 2.8
DOMAssistant.AJAX = function () {
	var globalXMLHttp = null,
	readyState = 0,
	status = -1,
	statusText = "",
	requestPool = [],
	createAjaxObj = function (url, method, callback, addToContent) {
		var params = null;
		if (/POST/i.test(method)) {
			url = url.split("?");
			params = url[1];
			url = url[0];
		}
		return {
			url : url,
			method : method,
			callback : callback,
			params : params,
			headers : {},
			responseType : "text",
			addToContent : addToContent || false
		};
	};
	return {
		publicMethods : [
			"ajax",
			"get",
			"post",
			"load"
		],

		initRequest : function () {
			var XMLHttp = null;
			if (!!window.XMLHttpRequest && !DOMAssistant.isIE) {
				XMLHttp = new XMLHttpRequest();
				DOMAssistant.AJAX.initRequest = function () {
					return requestPool.length? requestPool.pop() : new XMLHttpRequest();
				};
			}
			else if (!!window.ActiveXObject) {
				var XMLHttpMS = ["Msxml2.XMLHTTP.6.0", "Msxml2.XMLHTTP.3.0", "Msxml2.XMLHTTP", "Microsoft.XMLHTTP"];
				for (var i=0; i<XMLHttpMS.length; i++) {
					try {
						XMLHttp = new window.ActiveXObject(XMLHttpMS[i]);
						DOMAssistant.AJAX.initRequest = function () {
							return requestPool.length? requestPool.pop() : new window.ActiveXObject(XMLHttpMS[i]);
						};
						break;
					}
					catch (e) {
						XMLHttp = null;
					}
				}
			}
			return XMLHttp;
		},

		ajax : function (ajaxObj) {
			if (!ajaxObj.noParse && ajaxObj.url && /\?/.test(ajaxObj.url) && ajaxObj.method && /POST/i.test(ajaxObj.method)) {
				var url = ajaxObj.url.split("?");
				ajaxObj.url = url[0];
				ajaxObj.params = url[1] + ((url[1].length > 0 && ajaxObj.params)? ("&" + ajaxObj.params) : "");
			}
			return DOMAssistant.AJAX.makeCall.call(this, ajaxObj);
		},

		get : function (url, callback, addToContent) {
			return DOMAssistant.AJAX.makeCall.call(this, createAjaxObj(url, "GET", callback, addToContent));
		},

		post : function (url, callback) {
			return DOMAssistant.AJAX.makeCall.call(this, createAjaxObj(url, "POST", callback));
		},

		load : function (url, addToContent) {
			this.get(url, DOMAssistant.AJAX.replaceWithAJAXContent, addToContent);
		},

		makeCall : function (ajaxObj) {
			var XMLHttp = DOMAssistant.AJAX.initRequest();
			if (XMLHttp) {
				globalXMLHttp = XMLHttp;
				(function (elm) {
					var url = ajaxObj.url,
						method = ajaxObj.method || "GET",
						callback = ajaxObj.callback,
						params = ajaxObj.params,
						headers = ajaxObj.headers,
						responseType = ajaxObj.responseType || "text",
						addToContent = ajaxObj.addToContent,
						timeout = ajaxObj.timeout || null,
						ex = ajaxObj.exception,
						timeoutId = null,
						done = false;
					XMLHttp.open(method, url, true);
					XMLHttp.setRequestHeader("AJAX", "true");
					XMLHttp.setRequestHeader("X-Requested-With", "XMLHttpRequest");
					if (method === "POST") {
						XMLHttp.setRequestHeader("Content-type", "application/x-www-form-urlencoded");
						XMLHttp.setRequestHeader("Content-length", params? params.length : 0);
						if (XMLHttp.overrideMimeType) {
							XMLHttp.setRequestHeader("Connection", "close");
						}
					}
					if (responseType === "json") {
						XMLHttp.setRequestHeader("Accept", "application/json, text/javascript, */*");
					}
					for (var i in headers) {
						if (typeof i === "string") {
							XMLHttp.setRequestHeader(i, headers[i]);
						}
					}
					if (typeof callback === "function") {
						XMLHttp.onreadystatechange = function () {
							try {
								if (XMLHttp.readyState === 4 && !done) {
									window.clearTimeout(timeoutId);
									done = true;
									status = XMLHttp.status;
									statusText = XMLHttp.statusText;
									readyState = 4;
									if ((status || location.protocol !== "file:") && (status < 200 || status >= 300)) {
										throw new Error(statusText);
									}
									var response = /xml/i.test(responseType)? XMLHttp.responseXML : XMLHttp.responseText;
									if (/json/i.test(responseType) && !!response) {
										response = (typeof JSON === "object" && typeof JSON.parse === "function")? JSON.parse(response) : eval("(" + response + ")");
									}
									globalXMLHttp = null;
									XMLHttp.onreadystatechange = function () {};
									requestPool.push(XMLHttp);
									callback.call(elm, response, addToContent);
								}
							}
							catch (e) {
								globalXMLHttp = XMLHttp = null;
								if (typeof ex === "function") {
									ex.call(elm, e);
									ex = null;
								}
							}
						};
					}
					XMLHttp.send(params);
					if (timeout) {
						timeoutId = window.setTimeout( function () {
							if (!done) {
								XMLHttp.abort();
								done = true;
								if (typeof ex === "function") {
									readyState = 0;
									status = 408;
									statusText = "Request timeout";
									globalXMLHttp = XMLHttp = null;
									ex.call(elm, new Error(statusText));
									ex = null;
								}
							}
						}, timeout);
					}
				})(this);
			}
			return this;
		},

		replaceWithAJAXContent : function (content, add) {
			if (add) {
				this.innerHTML += content;
			}
			else {
				DOMAssistant.cleanUp(this);
				this.innerHTML = content;
			}
		},

		getReadyState : function () {
			return (globalXMLHttp && DOMAssistant.def(globalXMLHttp.readyState))? globalXMLHttp.readyState : readyState;
		},

		getStatus : function () {
			return status;
		},

		getStatusText : function () {
			return statusText;
		}
	};
}();
DOMAssistant.attach(DOMAssistant.AJAX);
if ( Legato === undefined )
	Legato = {};

Legato.Drag = {};
Legato.Drop = {};

Legato.DragDrop = function()
{

	// Private variables.
	var _started_dragging = false;
	var _active_handle = null;
	var _last_replaced_pos = 0;
	var _new_position = new Legato_Structure_Point();
	var _old_mouse_pos = new Legato_Structure_Point();

	function _getCursorPos( event )
	{

		var pos = new Legato_Structure_Point();

		if ( !event )
			event = window.event;

		if ( event.pageX || event.pageY )
		{
			pos.X = event.pageX;
			pos.Y = event.pageY;
		}
		else if ( event.clientX || event.clientY )
		{
			pos.X = event.clientX + document.body.scrollLeft;
			pos.Y = event.clientY + document.body.scrollTop;
		}

		// Finally, return the cursor position.
		return pos;

	}

	function _intersectTest( handle, target )
	{

		var intersected = false;

		// Get the target's min/max points.
		var target_min = target.elem.position();
		var target_max = target.elem.dimensions();
		target_max[0] += target_min[0];
		target_max[1] += target_min[1];

		switch ( handle.intersect_test )
		{

		// Elements overlapping.
		case Legato.DragDrop.INTERSECT_OVERLAP:

			var handle_min = handle.elem.position();
			var handle_max = handle.elem.dimensions();
			handle_max[0] += handle_min[0];
			handle_max[1] += handle_min[1];

			if
			(
				(handle_min[0] <= target_max[0]) &&
				(handle_min[1] <= target_max[1]) &&
				(handle_max[0] >= target_min[0]) &&
				(handle_max[1] >= target_min[1])
			)
			{

				intersected = true;

			}

			break;

		// Cursor position.
		case Legato.DragDrop.INTERSECT_CURSOR_POS:

			var contained = true;

			// Is the point completely contained in this region?
			if ( target_min[0] > handle.mouse_pos.X || target_max[0] < handle.mouse_pos.X ) contained = false;
			if ( target_min[1] > handle.mouse_pos.Y || target_max[1] < handle.mouse_pos.Y ) contained = false;

			if ( contained )
				intersected = true;

			break;

		}

		// If we have successfully intersected...
		if ( intersected )
		{
			handle.on_target = true;
			handle.last_target = target;
			return true;
		}

		// Set the handle's on target attribute to false.
		handle.on_target = false;

		// Return false if no intersection occured.
		return false;

	}

	return {

		// Constants.
		INTERSECT_OVERLAP: 0,
		INTERSECT_CURSOR_POS: 1,
		INTERSECT_HALF_DIMENSIONS: 2,

		ON_INTERSECTED_NOTHING: 0,
		ON_INTERSECTED_SWITCH_POSITIONS: 1,

		ON_DRAGGED_NOTHING: 0,
		ON_DRAGGED_MOVE: 1,

		handles: [],
		targets: [],

		preStart: function( handle, e )
		{

			// Store the mouse position and the element's initial position.
			handle.mouse_pos = _getCursorPos( e );
			handle.initial_pos = handle.elem.position();

			// Set the required parameters.
			_active_handle = handle;
			_last_replaced_pos = handle.initial_pos;

			// Add the onmousemove event to the document.
			$( document ).addEvent( 'mousemove', function( e ){ Legato.DragDrop.drag( e ); } );


		},

		startDrag: function()
		{

			// Retrieve the drag handle.
			var handle = _active_handle;

			// Set the element's z index to a very high number.
			handle.elem.setStyle( 'z-index', 10000 );

			// Call the onStartDrag function. Will stop dragging if it returns false.
			if ( handle.onStartDrag && handle.onStartDrag() === false )
				Legato.DragDrop.stopDragging();

			// Set the started dragging flag to true.
			_started_dragging = true;

		},

		drag: function( e )
		{

			// Retrieve the values we need.
			var handle = _active_handle;
			var current_mouse_pos = _getCursorPos( e );
			var current_pos = handle.elem.position();

			// Start dragging, if we haven't already.
			if ( !_started_dragging )
				this.startDrag();

			// Store the old mouse position.
			_old_mouse_pos = handle.mouse_pos;

			// Get the difference in the mouse position since the last update.
			var mouse_delta = {};
			mouse_delta.X = current_mouse_pos.X - _old_mouse_pos.X;
			mouse_delta.Y = current_mouse_pos.Y - _old_mouse_pos.Y;

			// Reset the drag handle's mouse position.
			handle.mouse_pos = current_mouse_pos;

			// Get the new X and Y positions.
			_new_position.X = current_pos[0] + mouse_delta.X;
			_new_position.Y = current_pos[1] + mouse_delta.Y;

			// Store the previous on target value and the previous target.
			var old_on_target = handle.on_target;
			var old_target = handle.last_target;

			// Loop through each target.
			for ( var i = 0; i < handle.targets.length; i++ )
			{

				// Get the target.
				var target = handle.targets[i];

				// Perform the intersection test, and if we intersected, break.
				if ( _intersectTest( handle, target ) )
					break;

			}  // Next target.

			// Call the onDrag function. Won't drag the handle if the function returns false.
			if ( handle.onDrag && handle.onDrag() == false )
			{
				// Return the mouse position back to it's old position.
				handle.mouse_pos = Legato.DragDrop.old_mouse_pos;
				return;

			}  // End if onDrag() returned false.

			// Compare the new on target value to the old one.
			if ( !old_on_target && handle.on_target )
			{

				// If we were previously set as off target, then we have dragged on to the target.
				// In this case, call the target's onDragOver function.
				if ( handle.last_target.onDragOver )
					handle.last_target.onDragOver( handle );

			}  // End if dragged over.
			else if ( old_on_target && !handle.on_target )
			{

				// If we were previously set as on target, then we have dragged out of the target.
				// In this case, call the target's onDragOut function.
				if ( handle.last_target.onDragOut )
					handle.last_target.onDragOut( handle );

			}  // End if dragged out.
			else if ( old_target && old_on_target && handle.on_target && old_target != handle.last_target )
			{

				// Call the old target's onDragOut function and call the new target's onDragOver function.
				if ( old_target.onDragOut )
					old_target.onDragOut( handle );

				if ( handle.last_target.onDragOver )
					handle.last_target.onDragOver( handle );

			}  // End if dragged on to a different target.

			// Only move if we set it to move on drag.
			if ( handle.on_dragged == Legato.DragDrop.ON_DRAGGED_MOVE )
			{

				// Set the current element's new position.
				if ( !handle.constrain_y ) handle.elem.position( _new_position.X, null );
				if ( !handle.constrain_x ) handle.elem.position( null, _new_position.Y );

			}

		},

		stopDragging: function()
		{

			var on_drop_return = true;
			var on_stop_return = true;
			var return_val = true;
			var handle = _active_handle;

			if ( handle != null )
			{

				// If we are on a target, and there is an onDrop function for the target, call it.
				if ( handle.on_target && handle.last_target.onDrop )
					on_drop_return = handle.last_target.onDrop( handle );

				// Call the onStopDrag function.
				if ( handle.onStopDrag )
					on_stop_return = handle.onStopDrag();

				// If onDrop or onStopDrag returned false, we must set return val to false.
				if ( on_drop_return == false || on_stop_return == false )
					return_val = false;

				// If the onStopDrag function returned false, put the drag handle to its initial position.
				if ( !return_val && handle.on_dragged == Legato.DragDrop.ON_DRAGGED_MOVE )
					handle.elem.position( handle.initial_pos[0], handle.initial_pos[1] );

				// Return the element's z index to normal.
				handle.elem.setStyle( 'z-index', handle.zindex );

				// Null out values.
				handle.on_target = false;
				handle.last_target = null;

				_started_dragging = false;
				_active_handle = null;
				_last_replaced_pos = null;

				// Remove the mousemove event from the document.
				$( document ).removeEvent( 'mousemove' );

			}

			return true;

		}

	};

}();

// Add an onmouseup event to the document.
$( document ).addEvent( 'mouseup', function(){ return Legato.DragDrop.stopDragging(); } );

Legato.Drag.Handle = function( elem )
{

	this.elem = $( elem );
	this.index = Legato.DragDrop.handles.length;

	this.targets = [];

	this.constrain_x = false;
	this.constrain_y = false;

	this.intersect_test = Legato.DragDrop.INTERSECTED_OVERLAP;
	this.on_intersected = Legato.DragDrop.ON_INTERSECTED_NOTHING;
	this.on_dragged = Legato.DragDrop.ON_DRAGGED_MOVE;
	this.intersect_threshold = 0;

	this.mouse_pos = null;
	this.initial_pos = null;
	this.on_taget = false;
	this.last_target = null;
	this.zindex = elem.getStyle( 'z-index' );

	this.onStartDrag = null;	// Called when the handle starts being dragged.
	this.onDrag = null;			// Called when the handle is being dragged.
	this.onStopDrag = null;		// Called when the handle stops being dragged.

	// Store this drag handle.
	Legato.DragDrop.handles.push( this );

	// Get rid of text selection events.
	this.elem.onselectstart = function() { return false; };
	this.elem.unselectable = "on";
	this.elem.setStyle( 'user-select', 'none' );
	this.elem.setStyle( '-o-user-select', 'none' );
	this.elem.setStyle( '-khtml-user-select', 'none' );
	this.elem.setStyle( '-moz-user-select', 'none' );

	// Add the mousedown event for the element.
	var self = this;
	$( this.elem ).addEvent( 'mousedown', function( e ){ Legato.DragDrop.preStart( self, e ); return false; } );

};

Legato.Drop.Target = function( elem )
{

	this.elem = $( elem );
	this.index = null;

	this.onDragOver = null;
	this.onDragOut = null;
	this.onDrop = null;

}
/*
	Class: Legato_Widgets_DynamicTable
	Make a table dynamic, with the ability to sort, search, pull data in
	from a data source, etc.
*/


/*
	Group: Functions
*/

/*
	Function: Legato_Widgets_DynamicTable()
	The class constructor.

	Syntax:
		object Legato_Widgets_DynamicTable( mixed table_id )

	Parameters:
		mixed table_id - Can be either the ID of your table element, or an element reference.

	Notes:
		You must call the <Legato_Widgets_DynamicTable::initialize()> function on this
		object to actually initialize it.

	Examples:
		> var table = new Legato_Widgets_DynamicTable( 'cool_table' );

	See Also:
		- <Legato_Widgets_DynamicTable::initialize()>
*/
function Legato_Widgets_DynamicTable( table_id )
{

	this.table_element = $$( table_id );
	this.head_element = null;
	this.body_element = null;
	this.table_data = [];
	this.last_clicked = null;
	this.direction = 'asc';
	this.unsortable = {};
	this.initialized = false;
	this.data_source = '';

	this.search_field = null;
	this.search_timeout = null;

	// Events.
	this.onPreSort = null;
	this.onPostSort = null;
	this.onPreUpdateData = null;
	this.onPostUpdateData = null;
	this.onInitSearchTimeout = null;
	this.onSendDataSearch = null;

}


/*
	Function: initialize()
	Called to actually set up and initialize the table to be dynamic.

	Syntax:
		void initialize( object options = {} )

	Parameters:
		object options - *optional* - This is the options object.
		Defaults to the default options, or a blank options object.

	Options:
		These are the options that you are able to pass in to the initialize() function.

		- search_field - The ID/element reference of a text input element that you'd like to use
		as a search field.

		- sorted_col - If you've pre-sorted the table data, you can use this to pass in the sorted
		column index.

		- sorted_dir - If you've pre-sorted the table data, you can use this to pass in the sorted
		column's direction. Can be either 'asc' or 'desc'.

		- unsortable - An array of indices for any column's that you wouldn't like to be sortable.

		- data_source - A URL that you would like to query when updating the table's data. Note that
		you can define this option if you wouldn't like to sort with JavaScript, but would rather sort/search
		by querying a data source.

	Examples:
	(begin code)
		<input type="submit" id="results_search">

		<table id="results">
			<thead>
				<tr>
					<th>Name</th>
					<th>Position</th>
				</tr>
			</thead>
			<tbody>
				<tr>
					<td>David DeCarmine</td>
					<td>Lead Developer</td>
				</tr>
				<tr>
					<td>Trevor Gerhardt</td>
					<td>Programmer</td>
				</tr>
			</tbody>
		</table>
	(end)

	(begin code)
		var table = new Legato_Widgets_DynamicTable( 'results' );
		table.initialize
		( {
			search_field: 'results_search',
			sorted_col: 0
		} );
	(end)
*/
Legato_Widgets_DynamicTable.prototype.initialize = function( options )
{

	// Clear out variables.
	this.table_data = [];
	this.last_clicked = null;
	this.direction = 'asc';

	// Any search field?
	if ( options && options.search_field != undefined )
		this.search_field = $$( options.search_field );

	// Only do this stuff the first time initialization is called.
	if ( !this.initialized )
	{

		// Make sure the table is correctly structured.
		this.restructureTable();

		// Store the head and body elements for easy access.
		this.head_element = this.table_element.elmsByTag( 'thead' ).first();
		this.body_element = this.table_element.elmsByTag( 'tbody' ).first();

		// Add the table's events.
		this.addEvents();

	}
	else  // Only do on reinitialization.
	{

		// Get the table's headings.
		this.head_element.elmsByTag( 'th' ).each( function()
		{
			$( this ).removeClass( 'sorted_asc' );
			$( this ).removeClass( 'sorted_desc' );
		} );

	}

	// Initialize the rows.
	this.initRows();

	// Any options?
	if ( options )
	{

		// Get the table's headings.
		var table_headings = this.table_element.elmsByTag( 'th' );

		// Are we already sorted?
		if ( options.sorted_col != undefined )
		{
			if ( options.sorted_dir == undefined )
				options.sorted_dir = 'asc';

			this.direction = options.sorted_dir;
			this.last_clicked = options.sorted_col;
			$( table_headings[options.sorted_col] ).addClass( 'sorted_' + this.direction );
		}

		// If there's any unsortable columns passed in, make an object out of the
		// unsortable columns so we can test for it quickly.
		if ( options.unsortable != undefined )
			for( var i = 0; i < options.unsortable.length; i++ )
			{
				this.unsortable[options.unsortable[i]] = true;
				$( table_headings[options.unsortable[i]] ).addClass( 'unsortable' );
			}

		// Any datasource?
		if ( options.data_source != undefined )
			this.data_source = options.data_source;

	}  // End if options passed in.

	// Set as initialized.
	this.initialized = true;

	// Send the data search.
	if ( this.data_source )
		this.sendDataSearch();

}


/*
	(Exclude)
	Function: initRows()
	Stores all the rows' data so that we can sort through it later.
	This should be called any time the table's data is changed manually.
*/
Legato_Widgets_DynamicTable.prototype.initRows = function()
{

	this.table_data = [];

	// Loop through the table's rows.
	var table_rows = this.body_element.elmsByTag( 'tr' );

	for ( var i = 0; i < table_rows.length; i++ )
	{

		// Get the columns.
		var table_cols = $( table_rows[i] ).elmsByTag( 'td' );

		// Skip empty rows.
		if ( table_cols.length == 0 )
			continue;

		// Loop through the cols and store them.
		for ( n = 0; n < table_cols.length; n++ )
		{

			// Make sure we have an array for this table data index.
			if ( typeof( this.table_data[n] ) != "object" )
				this.table_data[n] = [];

			// Add the column.
			this.table_data[n].push( table_cols[n] );


		}  // Next col.

	}  // Next row.

}


/*
	(Exclude)
	Function: initSort()
	Called to sort the table data.
	This will set up the system to sort, either with JavaScript or through
	a data source.
*/
Legato_Widgets_DynamicTable.prototype.initSort = function( col_index )
{

	// If this is an unsortable column, don't process.
	if ( this.unsortable[col_index] )
		return;

	// Call the onPreSort event.
	if ( this.onPreSort != null && this.onPreSort( col_index ) == false )
		return;

	// Get the correct direction that the clicked column is facing.
	if ( (col_index != this.last_clicked) || (col_index == this.last_clicked && this.direction == 'desc') )
		this.direction = 'asc';
	else
		this.direction = 'desc';

	// Are we using a data source?
	if ( this.data_source )
	{

		// Set last clicked.
		this.last_clicked = col_index;

		// Send off the search request now.
		this.sendDataSearch();

	}
	else
	{

		// Is this the last clicked column, or a new column to sort?
		if ( col_index != this.last_clicked )
		{

			// Set last clicked and sort.
			this.last_clicked = col_index;
			this.sort( col_index );

		}
		else
			this.reverse( col_index );

		// Update the table with the new information.
		this.updateTable( col_index );

	}

	// Call the onPostSort event.
	if ( this.onPostSort != null )
		this.onPostSort( col_index );

}


/*
	(Exclude)
	Function: sort()
	Called to sort the table data.
	This will call updateTable to update the actual table's rows once the data
	is sorted.
*/
Legato_Widgets_DynamicTable.prototype.sort = function( col_index )
{

	// Sort the table data.
	if ( (this.table_data[col_index][0].innerText || this.table_data[col_index][0].textContent || '' ).charAt( 0 ) == "$" )
	{

		// The function to use in comparing.
		var sort_func = function ( a, b )
		{

			a = (a.innerText || a.textContent || '' ).replace( /[,\\s]/g, "" );
			b = (b.innerText || b.textContent || '' ).replace( /[,\\s]/g, "" );

			a = new Number( a.substring( 1 ) );
			b = new Number( b.substring( 1 ) );

			return (b < a) - (a < b);

		}

	}  // Money sort.
	else
	{

		// The function to use in comparing.
		var sort_func = function ( a, b )
		{

			a = (a.innerText || a.textContent || '' ).toLowerCase();
			b = (b.innerText || b.textContent || '' ).toLowerCase();

			if ( b < a )
				return 1;
			else if ( a < b )
				return -1;

			return 0;

		}

	}  // Normal sort.

	// Sort the data.
	this.table_data[col_index].sort( sort_func );

}


/*
	(Exclude)
	Function: reverse()
	Called to simply reverse all the rows in the table.
	This is here so that we can quickly reverse if the user clicks on the same
	column twice. So that when the direction changes, all we do is reverse.
*/
Legato_Widgets_DynamicTable.prototype.reverse = function( col_index )
{

	// Reverse the active column.
	this.table_data[col_index].reverse();

	// Update the table with the new information.
	this.updateTable( col_index );

}


/*
	(Exclude)
	Function: updateTable()
	This is called to reorder the table's rows to match the data modified
	by the sort function.
*/
Legato_Widgets_DynamicTable.prototype.updateTable = function( col_index )
{

	var th = this.table_element.elmsByTag( 'th' );

	th.each( function()
	{
		$( this ).removeClass( 'sorted_asc' );
		$( this ).removeClass( 'sorted_desc' );
	} );

	// Set the class for the column heading.
	th[col_index].addClass( 'sorted_' + this.direction );

	// If there is no data for this column, just return.
	if ( !this.table_data[col_index] )
		return;

	// Loop through all the rows in the table data and update the table.
	for ( var i = 0; i < this.table_data[col_index].length; i++ )
		this.body_element.appendChild( this.table_data[col_index][i].parentNode );

}


/*
	(Exclude)
	Function: sendDataSearch()
	Sends a request out to get data to populate the table.
	Queries the data source passed in when the table was constructed.
	It should receive a JSON request.
*/
Legato_Widgets_DynamicTable.prototype.sendDataSearch = function()
{

	// Make sure a data source was passed in.
	if ( !this.data_source )
		return false;

	var th = this.head_element.elmsByTag( 'th' );

	var params = {};
	params['query'] = '';
	params['page'] = 1;
	params['column'] = th[this.last_clicked].id;
	params['direction'] = this.direction;

	// If there is a search field, try to get the value of it.
	if ( this.search_field )
	{
		this.search_timeout = null;
		params['query'] = this.search_field.value;
	}

	// Call the onSendDataSearch event.
	if ( this.onSendDataSearch != null )
		params = this.onSendDataSearch( params );

	// Create the query string from the parameters.
	var query_string = '';
	for ( var key in params )
		query_string = query_string + key + '=' + params[key] + '&';

	// Send off the request.
	var table = this;
	DOMAssistant.AJAX.post( this.data_source + '?' + query_string, function( response ){ table.updateTableData( response ); } );

}


/*
	(Exclude)
	Function: updateTableData()
	Updates the table with data received from the data source.
*/
Legato_Widgets_DynamicTable.prototype.updateTableData = function( response )
{

	var response = eval( '(' + response.responseText + ')' );

	// Call the onPreUpdateData event.
	if ( this.onPreUpdateData != null && this.onPreUpdateData( response ) == false )
		return false;

	// Remove all the rows.
	this.body_element.elmsByTag( 'tr' ).each( function(){ this.remove(); } );

	// Loop through all the rows returned.
	if ( response.rows && response.total_count > 0 )
	{
		for ( var row_id in response.rows )
		{

			var row_data = response.rows[row_id];
			var row = this.body_element.create( 'tr', {}, true );
			var new_cells = [];

			// Add all the new cells before we populate them with data.
			for ( var i in row_data )
			{
				if ( i == 'class_name' )
					continue;

				new_cells.push( row.create( 'td', {}, true ) );
			}

			// Now run through the data and populate the correct cells with the data.
			var headings = this.head_element.elmsByTag( 'th' );
			for ( var i in row_data )
			{
				if ( i == 'class_name' )
					continue;

				var th = $$( i );
				new_cells[th.cellIndex].innerHTML = row_data[i];
			}

			// Check for a class name.
			if ( row_data['class_name'] )
				$( row ).addClass( row_data['class_name'] );

		}
	}
	else
	{

		var td_elem = document.createElement( 'td' );
		td_elem.colSpan = this.head_element.elmsByTag( 'th' ).length;
		td_elem.className = 'no_results';
		td_elem.innerHTML = 'No results to show.';

		var row_elem = this.body_element.create( 'tr', {}, true ).addContent( td_elem );

	}

	// Reinitialize the dynamic table.
	this.initRows();

	// Call the onPostUpdateData event.
	if ( this.onPostUpdateData != null )
		this.onPostUpdateData( response );

	this.updateTable( this.last_clicked );

}


/*
	(Exclude)
	Function: initSearchTimeout()
	Initializes the search timeout.
*/
Legato_Widgets_DynamicTable.prototype.initSearchTimeout = function()
{

	// Send off the request.
	var table = this;

	// If the timeout already started, clear it out so we can start it again.
	if ( this.search_timeout != null )
		window.clearTimeout( this.search_timeout );

	// Set the timeout.
	// Delay one second, then send the search.
	this.search_timeout = window.setTimeout( function(){ table.sendDataSearch(); }, 1000 );

	// Call the onInitSearchTimeout event.
	if ( this.onInitSearchTimeout != null )
		this.onInitSearchTimeout();

}


/*
	(Exclude)
	Function: addEvents()
	Adds the correct events to the table.
*/
Legato_Widgets_DynamicTable.prototype.addEvents = function()
{

	var table = this;

	// Set an onclick event for the heading.
	$( this.head_element ).relayEvent( 'click', 'th', function()
	{

		table.initSort( this.cellIndex );

	} );

	// Any search field to worry about?
	// Set up the timeout event on key up.
	if ( this.search_field )
		$( this.search_field ).addEvent( 'keyup', function(){ table.initSearchTimeout(); } );

}


/*
	(Exclude)
	Function: restructureTable()
	Restructures the table to make sure it's in a good format to work with.
*/
Legato_Widgets_DynamicTable.prototype.restructureTable = function()
{

	// Try to get thead and th elements.
	var thead = this.table_element.elmsByTag( 'thead');
	var th = this.table_element.elmsByTag( 'th' )

	// If there are THs, make sure there is a THEAD to enclose them.
	if ( thead.length == 0 && th.length != 0 )
	{

		var head_tr = th.first().parentNode;

		// Add the TR element to a new THEAD element.
		var thead_elem = $( this.table_element ).create( 'thead' ).addContent( head_tr );

		// Insert the THEAD element as the first element in the table.
		this.table_element.insertBefore( thead_elem, this.table_element.firstChild );

	}

}


/*
	Group: Events

	Event: onPreSort( int col_index )
	Called prior to sorting a column. Is passed in the index of the column that's being sorted.
	If the event handler returns false, the column will not be sorted.

	Event: onPostSort( int col_index )
	Called after sorting a column. Is passed in the index of the column that's being sorted.

	Event: onSendDataSearch( object params )
	Called when sending a data search to a data source. Is passed in the array of parameters
	that will be sent to the data source. This allows you to modify the parameters before they're
	actually sent. Note that you MUST return the parameter object or no parameters will be sent.
	The parameters set in the params object are as follows:

	- query - The search string to be sent.
	- page - The page number. Currently is always equal to 1. This allows you to modify it correctly.
	- column - The currently active column's ID.
	- direction - The direction the currently active column is being sorted.

	Event: onPreUpdateData( object response )
	Called prior to updating the data from a retrieval from a data source.
	Is passed the response object returned from the data source.
	If you return false, the table will not be updated with the newly retreived data.

	Event: onPostUpdateData( object response )
	Called after updating the data from a retrieval from a data source.
	Is passed the response object returned from the data source.

	Event: onInitSearchTimeout()
	Called when someone starts typing into the search box specified. If you specified one.
*/

function calendarUtils()
{
	// Do nothing.
}

calendarUtils.escapeKeyListener = function( e )
{

	e = (e) ? e : window.event;
	var charCode = (e.which) ? e.which : e.keyCode

	// Esc
	if ( charCode == 27 )
		calendarUtils.hideModalWindow();

}

calendarUtils.populateEvent = function( event_id, date, time, lesson_id, notice_text )
{

	$$( 'event_id' ).value = event_id;
	$$( 'date' ).value = date;

	if ( null != time )
	{

		var colonPos = time.indexOf(':');
		var spacePos = time.indexOf(' ');

		selectOptionWithValue( 'time_hour', time.substr( 0, colonPos ) );
		selectOptionWithValue( 'time_minute', time.substr( colonPos + 1, spacePos - colonPos - 1 ) );
		selectOptionWithValue( 'time_meridian', time.substr( spacePos + 1, 2 ) );

	}

	if ( notice_text )
		$$( 'data' ).value = notice_text;
	else
		$$( 'data' ).value = '';
	
	if ( lesson_id )
		selectOptionWithValue( 'lesson_id', lesson_id );
	else
		selectOptionFirst( 'lesson_id' );

	calendarUtils.showModalWindow();

};

calendarUtils.showModalWindow = function()
{

	var modalWindow = $$( 'calendar-add-event' );
	modalWindow.setStyle( 'display', 'block' );

	var window_dimensions = $( window ).dimensions();
	var document_dimensions = $( document.body ).dimensions();
	var window_width  = Math.max( window_dimensions[0], document_dimensions[0] );
	var window_height = Math.max( window_dimensions[1], document_dimensions[1] );

	var window_offset = $( window ).scrollOffset();
	var window_x_offset = window_offset[0];
	var window_y_offset = window_offset[1];

	var elem_dimensions = modalWindow.dimensions();
	var elem_width = elem_dimensions[0];
	var elem_height = elem_dimensions[1];

	var left = window_x_offset + ( (window_width * 0.5) - (elem_width * 0.5) );
	var top  = window_y_offset + ( (window_height * 0.5) - (elem_height * 0.8) );

	modalWindow.position( left, top );

	calendarUtils.showModalShade();
	
	
	// Put the focus in the dropdown box
	$$( "lesson_id" ).focus();


	// Allow the modal window to be closed with the ESC key.
	// The document will be waiting for this key press.
	$( document ).addEvent( 'keyup', calendarUtils.escapeKeyListener );

};

calendarUtils.hideModalWindow = function()
{

	// The document is still waiting for the ESC key to be pressed.
	// Remove this event, so it doesn't keep waiting.
	$( document ).removeEvent( 'keyup', calendarUtils.escapeKeyListener );

	calendarUtils.hideModalShade();

	$$( 'calendar-add-event' ).setStyle( 'display', 'none' );

};

calendarUtils.showModalShade = function()
{

	$$( 'calendar-add-event-shade' ).setStyle( 'display', 'block' );

	var window_dimensions = $( window ).dimensions();
	var document_dimensions = $( document.body ).dimensions();
	var window_width  = Math.max( window_dimensions[0], document_dimensions[0] );
	var window_height = Math.max( window_dimensions[1], document_dimensions[1] );

	var window_offset = $( window ).scrollOffset();
	var window_x_offset = window_offset[0];
	var window_y_offset = window_offset[1];

	$$( 'calendar-add-event-shade' ).position( 0, 0 );
	$$( 'calendar-add-event-shade' ).dimensions( window_x_offset + window_width, window_y_offset + window_height );

	setOpacity( $$( 'calendar-add-event-shade' ), 55 );

};

calendarUtils.hideModalShade = function()
{

	$$( 'calendar-add-event-shade' ).setStyle( 'display', 'none' );

};

var Calendar = function()
{

	var _elem;

	var _month = 0;
	var _year = 0;
	var _time;

	return {

		initialize: function( month, year, user_entered_time )
		{

			_elem = $$( 'calendar' );

			_month = month;
			_year = year;
			_time = user_entered_time;

			_elem.relayEvent( 'mouseover', 'td', this.mouseOverDay );
			_elem.relayEvent( 'mouseout', 'td', this.mouseOutDay );

			_elem.relayEvent( 'click', '.schedule-event-link', this.showAddEventPopup );
			_elem.relayEvent( 'click', '.event-edit', this.showEditEventPopup );

		},

		mouseOverDay: function( e )
		{

			var elem = $( this ).cssSelect( '.schedule-event-link' )[0];
			$( elem ).setStyle( 'visibility', 'visible' );

			// This will show the Edit link for every Event that is scheduled for this day
			// This is done because some browsers only show the Edit link when your mouse is hovering
			// over the actual text (and this makes it very difficult to actually click the Edit link)
			$( this ).cssSelect( '.event-edit' ).each( function()
			{
				$( this ).setStyle( 'visibility', 'visible' );
			});

		},

		mouseOutDay: function( e )
		{

			var elem = $( this ).cssSelect( '.schedule-event-link' )[0];
			$( elem ).setStyle( 'visibility', 'hidden' );

			// Hide all the Edit links
			$( this ).cssSelect( '.event-edit' ).each( function()
			{
				$( this ).setStyle( 'visibility', 'hidden' );
			});

		},

		showAddEventPopup: function( e )
		{

			var elem = $$( this ).parentNode;

			// Parent element will have an ID similar to "day-MM-DD-YYYY"
			var date = elem.id.substr( 4 ).replace( /-/g, '/' );

			// New event should have a time starting at the current time.
			var time = Calendar.currentTimeString();

			// Event Add
			$$( 'calendar_add_event_submit' ).value = 'Create Event';

			calendarUtils.populateEvent( '', date, time );

		},

		showEditEventPopup: function( e )
		{

			// The event Edit link is down several levels from the main event divisioni
			var elem = $$( this ).parentNode.parentNode;

			var event_id = elem.id.substr( elem.id.lastIndexOf('-') + 1 );

			// Parent element will have an ID similar to "events-day-MM-DD-YYYY"
			var date = elem.parentNode.id.substr( 11 ).replace( /-/g, '/' );

			// Retrieve event information from the document.
			var time = elem.elmsByClass( 'event-time', 'span' )[0].innerHTML;
			var lesson_id = elem.elmsByClass( 'event-lesson-id', 'div' )[0].innerHTML;
			var notice_text = elem.elmsByClass( 'event-content', 'div' )[0].innerHTML;

			// Event Update
			$$( 'calendar_add_event_submit' ).value = 'Update Event';

			calendarUtils.populateEvent( event_id, date, time, lesson_id, notice_text );

		},

		highlightDropTarget: function( elem )
		{
   			elem.addClass( 'highlight' );
		},

		resetDropTarget: function( elem )
		{
			elem.removeClass( 'highlight' );
		},

		hideAllLinks: function()
		{

			$( 'td' ).each( function()
			{

				var elem = $( this ).cssSelect( '.schedule-event-link' )[0];
				$( elem ).setStyle( 'visibility', 'hidden' );

			} );

			$( '.event' ).each( function()
			{

				var elem = $( this ).cssSelect( '.event-edit' )[0];
				$( elem ).setStyle( 'visibility', 'hidden' );

			} );

		},

		moveEvent: function( elem_event_id, elem_date_id )
		{

			// Update the data in the database
			Calendar.updateEventOnServer( elem_event_id, elem_date_id );

			var event = $$( elem_event_id );
			var events = $$( 'events-' + elem_date_id );

			events.addContent( event );
			event.setStyle( 'position', 'static' );

			// Sometime, there will be multiple events in a single day,
			// and we want to maintain their ordering, by the time of day.
			Calendar.rearrangeEventsByDate( elem_date_id );

		},

		updateEventOnServer: function( elem_event_id, elem_date_id )
		{

			var event_id = elem_event_id.substr( elem_event_id.lastIndexOf('-') + 1 );
			var date_id = elem_date_id.substr( elem_date_id.indexOf('-') + 1 );

			// Check to make sure this event was moved to a new day
			var events = $$( 'events-' + elem_date_id );

			var current_events = events.elmsByClass( "event", "div" );

			var current_events_length = current_events.length;
			var i;
			for( i = 0; i < current_events_length; i++ )
			{
				// This event has not actually moved.
				// It was picked up, dragged, and dropped right back where it started from.
				// Do not trouble the server with this supposed "update".
				if ( elem_event_id == current_events[i].id )
				{
					return;
				}
			}

			DOMAssistant.AJAX.post( SITE_URL + '/admin/ajax/calendar/move/' + event_id + '/' + date_id + '/' );

		},

		rearrangeEventsByDate: function( elem_date_id )
		{

			var events = $$( 'events-' + elem_date_id );

			// Make sure the new event is inserted into the right position
			var events_sortable = new Array();

			var current_events = events.elmsByClass( "event", "div" );

			var current_events_length = current_events.length;
			var i;
			for( i = 0; i < current_events_length; i++ )
			{
				var arr = new Array();
				arr[0] = current_events[i].elmsByClass( 'event-sort', 'div' )[0].innerHTML;
				arr[1] = current_events[i];
				events_sortable.push( arr );
			}

			events_sortable.sort( function( a, b ) { return a[0] - b[0]; } );

			for ( i = 0; i < current_events_length; i++ )
			{
				events.addContent( events_sortable[i][1] );
			}

		},

		currentTimeString: function()
		{

			// Remember what time was entered for this event
			if ( '' != _time )
				return _time;

			var d = new Date();

			var hour = d.getHours();
			if ( 0 == hour )
				hour = 12;
			else if ( 12 < hour )
				hour -= 12;

			var minute = ( d.getMinutes() < 10 ? '0' + d.getMinutes() : d.getMinutes() );
			var meridian = ( d.getHours() < 12 ? 'AM' : 'PM' );

			// Minutes on the form are show in increments of 5 minutes;
			var minute_increment = 5;
			minute = Math.floor( minute / minute_increment ) * minute_increment;

			return hour + ':' + minute + ' ' + meridian;

		}

	};

}();
var CalendarList = function()
{

	return {

		initialize: function()
		{

			$$( 'calendar-index' ).relayEvent( 'click', '.event-edit', this.showEditEventPopup );

		},

		showEditEventPopup: function( e )
		{

			var elem = $$( this ).parentNode.parentNode;

			var event_id = elem.id.substr( elem.id.lastIndexOf('-') + 1 );

			// Retrieve event information from the document.
			elem = $$( elem );
			var date = elem.elmsByClass( 'event-date', 'div' )[0].innerHTML;
			var time = elem.elmsByClass( 'event-time', 'div' )[0].innerHTML;
			var lesson_id = elem.elmsByClass( 'event-lesson-id', 'div' )[0].innerHTML;
			var notice_text = elem.elmsByClass( 'event-content', 'div' )[0].innerHTML;

			// Event Update
			$$( 'calendar_add_event_submit' ).value = 'Update Event';

			calendarUtils.populateEvent( event_id, date, time, lesson_id, notice_text );

		}

	};

}();
var Campfire = function()
{

	var _ajax_base_url;
	var _users;

	var _selected_campfire_id = 0;
	var _editing_campfire = false;

	return {

		initialize: function( ajax_base_url, users, campfires )
		{

			_ajax_base_url = ajax_base_url;
			_users = users;

			var campfires_elem = $$( 'campfires' );

			var campfires_length = campfires.length;
			for ( var i = 0; i < campfires_length; i++ )
				campfires_elem.addContent( Campfire.createCampfireContent( campfires[i][0], campfires[i][1] ) );

			// Set the first campfire as the selected one.
			Campfire.selectFirst();

			// Un-hide the form on body load so it is available for submissions
			$$( 'add_new_campfire_form' ).removeClass( 'hidden' );
			$$( 'user_search' ).removeClass( 'hidden' );

			// Add events for all the Campfires
			campfires_elem.relayEvent( 'mouseover', '.campfire', this.mouseOverCampfire );
			campfires_elem.relayEvent( 'mouseout', '.campfire', this.mouseOutCampfire );
			campfires_elem.relayEvent( 'click', '.campfire', this.clickOnCampfire );

			// Add click events for all the links
			campfires_elem.relayEvent( 'click', '.link-edit', this.clickOnEdit );
			campfires_elem.relayEvent( 'click', '.link-delete', this.clickOnDelete );

			// Add click events for all the users
			$$( 'users' ).relayEvent( 'click', '.user-toggle', this.clickToToggleUser );
			$$( 'users' ).relayEvent( 'click', '.user-leader', this.clickToToggleCampfireLeader );

			// Put the keyboard cursor in the "Add a New Campfire Group" box
			$$( 'campfire_name' ).focus();

		},

		createCampfireContent: function( id, name )
		{

			return ''
				+ '<div class="campfire" id="campfire-' + id + '">'
					+ '<div class="campfire-inner">'
						+ '<div class="campfire-name" id="campfire-name-' + id + '">' + name  + '</div>'
						+ '<div class="edit nodisplay" id="campfire-edit-container-' + id + '">'
							+ '<form onsubmit="javascript: Campfire.submitEdit(' + id + '); return false;">'
								+ '<input type="text" name="campfire-edit-' + id + '" id="campfire-edit-' + id + '" style="width: 40%;"> '
								+ '<input type="submit" value="Save" onclick="javascript: Campfire.submitEdit(' + id + ')"> '
								+ '<input type="button" value="Cancel" onclick="javascript: Campfire.stopAllEdits();">'
							+ '</form>'
						+ '</div>'
						+ '<div class="links hidden">'
							+ '<a href="javascript: void(0);" class="link-edit"><img src="' + SITE_URL + '/img/edit-1.png"></a> '
							+ '<a href="javascript: void(0);" class="link-delete"><img src="' + SITE_URL + '/img/delete-1.png"></a>'
						+ '</div>'
						+ '<div class="clear"></div>'
					+ '</div>'
				+ '</div>';

		},

		getFirstCampfire: function()
		{

			return $$( 'campfires' ).cssSelect( '.campfire' )[0];

		},

		selectFirst: function()
		{

			// Select the very first Campfire in the list, if there is one.
			var firstCampfire = Campfire.getFirstCampfire();
			if ( firstCampfire )
			{
				$$( 'add_new_campfire_notice' ).addClass( 'nodisplay' );

				// Remove the "campfire-" prefix
				var campfire_id = firstCampfire.id.substr( 9 );

				Campfire.selectCampfire( campfire_id );
			}
			else
			{
				$$( 'add_new_campfire_notice' ).removeClass( 'nodisplay' );
				_selected_campfire_id = 0;
			}

		},

		clickToToggleUser: function( e )
		{

			// Remove the "user-toggle-" prefix from the ID.
			var user_id = this.id.substr( 12 );

			if ( this.checked )
				Campfire.addUser( user_id );
			else
				Campfire.removeUser( user_id );

			return true;

		},

		addUser: function( user_id )
		{

			DOMAssistant.AJAX.post( _ajax_base_url + 'assign/?user_id=' + user_id + '&campfire_id=' + _selected_campfire_id );

			// Find this particular user's info.
			var user = _users[user_id];
			var user_elem = $$( 'user-' + user_id );

			// Remove them from their old campfire and add them to the new one.
			user_elem.removeClass( 'user-campfire-' + user.campfire_id ).addClass( 'user-campfire-' + _selected_campfire_id );
			user.campfire_id = _selected_campfire_id;

			// Update the user list.
			Campfire.updateUserList( _selected_campfire_id );

		},

		removeUser: function( user_id )
		{

			DOMAssistant.AJAX.post( _ajax_base_url + 'assign/?user_id=' + user_id + '&campfire_id=0' );

			// Find this particular user's info.
			var user = _users[user_id];
			var user_elem = $$( 'user-' + user_id );

			// Remove them from their old campfire.
			user_elem.removeClass( 'user-campfire-' + user.campfire_id ).addClass( 'user-campfire-0' );
			user.campfire_id = 0;
			user.campfire_leader = 0;

			// Update the user list.
			Campfire.updateUserList( _selected_campfire_id );

		},

		clickToToggleCampfireLeader: function( e )
		{

			// Remove the "user-leader-" prefix from the ID.
			var user_id = this.id.substr( 12 );

			if ( _users[user_id].campfire_leader == 0 )
			{
				_users[user_id].campfire_leader = 1;
				DOMAssistant.AJAX.post( _ajax_base_url + 'campfire_leader/?user_id=' + user_id + '&campfire_leader=1' );
				$( this ).addClass( 'user-leader-active' );
			}
			else
			{
				_users[user_id].campfire_leader = 0;
				DOMAssistant.AJAX.post( _ajax_base_url + 'campfire_leader/?user_id=' + user_id + '&campfire_leader=0' );
				$( this ).removeClass( 'user-leader-active' );
			}

		},

		mouseOverCampfire: function( e )
		{

			// Remove the "campfire-" prefix from the ID.
			var id = this.id.substring( 9 );

			// If we're editing this campfire, don't show the links.
			if ( _editing_campfire && id == _selected_campfire_id )
				return;

			// Show the Edit/Delete links
			var elem = $( this ).cssSelect( '.links' )[0];
			$( elem ).removeClass( 'hidden' );

		},

		mouseOutCampfire: function( e )
		{

			// Hide the Edit/Delete links
			var elem = $( this ).cssSelect( '.links' )[0];
			$( elem ).addClass( 'hidden' );

		},

		clickOnCampfire: function( e )
		{

			// Remove the "campfire-" prefix from the ID.
			var id = $( this ).id.substr( 9 );

			// Don't reselect a currently selected item
			// Redundant AJAX calls will be constantly sent to the server, and it's just a mess
			if ( _selected_campfire_id != id )
				Campfire.selectCampfire( id );

		},

		selectCampfire: function( campfire_id )
		{

			_selected_campfire_id = campfire_id;

			//Campfire.stopAllEdits();
			Campfire.unselectAllCampfires();

			$$( 'campfire-' + campfire_id ).addClass( 'highlight' );

			// Update the user list for this campfire.
			Campfire.updateUserList( campfire_id );

			// Scroll to the top of the user list.
			$$( 'scrollable-content' ).scrollTop = 0;

		},

		unselectAllCampfires: function()
		{

			var campfires = $$( 'campfires' );

			// Un-highlight the box for the Campfire name
			campfires.cssSelect( '.campfire' ).each( function()
			{

				$( this ).removeClass( 'highlight' );

			} );

		},

		updateUserList: function( campfire_id )
		{

			var users_elem = $$( 'users' );
			var campfire_users_elem = $$( 'campfire-users' );

			// Loop through all the users in the campfire users list and take them out.
			campfire_users_elem.cssSelect( '.user' ).each( function()
			{

				users_elem.addContent( this );

			} );

			// Loop through all the users.
			users_elem.cssSelect( '.user' ).each( function()
			{

				var toggle_elem = $( this ).cssSelect( '.user-toggle' )[0];
				var group_elem = $( this ).cssSelect( '.user-group' )[0];

				// Remove the "user-" prefix from the ID.
				var user_id = this.id.substr( 5 );
				var user = _users[user_id];

				// If no campfire ID is passed in, disable the toggle.
				if ( campfire_id )
					toggle_elem.disabled = false;
				else
					toggle_elem.disabled = true;

				// Uncheck the toggle.
				toggle_elem.checked = false;

				// Update the campfire for this user.
				if ( user.campfire_id != 0 )
				{
					var group_name = $$( 'campfire-name-' + user.campfire_id ).innerText || $$( 'campfire-name-' + user.campfire_id ).textContent;
					group_elem.innerHTML = group_name;
				}
				else
					group_elem.innerHTML = 'Not Assigned Yet';

				// Whether they're set as grouped or not.
				if ( user.campfire_id != 0 && user.campfire_id != _selected_campfire_id )
					$( this ).addClass( 'user-grouped' );
				else
					$( this ).removeClass( 'user-grouped' );

				$( this ).removeClass( 'active-grouped' );

				// Hide the leader tag.
				$( this ).cssSelect( '.user-leader' ).each( function(){ $( this ).addClass( 'hidden' ); } );

			} );

			// Sort the user list.
			Campfire.sortUserList();

			// Early out if no campfire ID passed in.
			if ( !campfire_id )
				return;

			// Push all the users in this campfire at the top of the list.
			users_elem.cssSelect( '.user-campfire-' + campfire_id ).each( function()
			{

				campfire_users_elem.addContent( this );
				$( this ).cssSelect( '.user-toggle' )[0].checked = true;
				$( this ).cssSelect( '.user-leader' )[0].removeClass( 'hidden' );
				$( this ).addClass( 'active-grouped' );

			} );

		},

		sortUserList: function()
		{

			var users_elem = $$( 'users' );
			var users_sortable = [];

			var users = users_elem.elmsByClass( "user", "div" );

			for ( var i = 0; i < users.length; i++ )
			{
				var arr = [];
				var user_name = $( users[i] ).cssSelect( '.user-name' )[0];
				arr[0] = user_name.innerText || user_name.textContent;
				arr[1] = users[i];
				users_sortable.push( arr );
			}

			users_sortable.sort( Campfire.compareText );

			for ( i = 0; i < users.length; i++ )
				users_elem.addContent( users_sortable[i][1] );

		},

		addCampfire: function()
		{

			// Input is taken from a form field
			var name = encodeURIComponent( $$( 'campfire_name').value );

			DOMAssistant.AJAX.post( _ajax_base_url + 'add/?name=' + name, Campfire.updateCampfireList );

		},

		updateCampfireList: function( responseText )
		{

			var response = eval( '(' + responseText + ')' );
			var campfire_id = response.Result;

			// Add the new Campfire to the running list
			if ( campfire_id && '0' != campfire_id )
			{

				var campfire_name = $$( 'campfire_name');
				var name = campfire_name.value;
				campfire_name.value = '';
				campfire_name.focus();

				$$( 'campfires' ).addContent( Campfire.createCampfireContent( campfire_id, name ) );

				// Sort the list of Campfires
				Campfire.sortCampfireList();

				// Select the recently created Campfire
				Campfire.selectCampfire( campfire_id );

				// Scroll to the new Campfire
				$$( 'campfire-' + campfire_id ).scrollIntoView(true);

				// Remove the message telling us there are no Campfire groups for this Organization
				$$( 'add_new_campfire_notice' ).addClass( 'nodisplay' );

			}

		},

		sortCampfireList: function()
		{

			var campfires_elem = $$( 'campfires' );
			var campfires_sortable = [];

			var campfires = campfires_elem.elmsByClass( "campfire", "div" );

			for ( var i = 0; i < campfires.length; i++ )
			{
				var arr = [];
				var campfire_name = $( campfires[i] ).cssSelect( '.campfire-name' )[0];
				arr[0] = campfire_name.innerText || campfire_name.textContent;
				arr[1] = campfires[i];
				campfires_sortable.push( arr );
			}

			campfires_sortable.sort( Campfire.compareText );

			for ( i = 0; i < campfires.length; i++ )
				campfires_elem.addContent( campfires_sortable[i][1] );

		},

		compareText: function( a, b )
		{

			var aLower = a[0].toLowerCase();
			var bLower = b[0].toLowerCase();

			if ( bLower < aLower )
				return 1;
			else if ( aLower < bLower )
				return -1;

			return 0;

		},

		searchUsers: function()
		{

			var users_elem = $$( 'users' );
			var query = $$( 'user_search' ).value.toLowerCase();

			// Loop through each user and check their name against the query.
			users_elem.cssSelect( '.user' ).each( function()
			{

				// Get the user's name.
				var name_elem = $( this ).cssSelect( '.user-name' )[0];
				var name = name_elem.innerText || name_elem.textContent;

				// Hide if not matching. Show if matching.
				if ( name.toLowerCase().indexOf( query ) > -1 )
					$( this ).removeClass( 'nodisplay' );
				else
					$( this ).addClass( 'nodisplay' );

			} );

		},

		clickOnEdit: function( e )
		{

			var elem = $( this ).parentNode.parentNode.parentNode;

			// Remove the "campfire-" prefix from the ID.
			var campfire_id = elem.id.substr( 9 );

			// Start editing the respective campfire.
			Campfire.startEdit( campfire_id );

		},

		submitEdit: function( id )
		{

			// Input is taken from a form field
			var name = encodeURIComponent( $$( 'campfire-edit-' + id ).value );

			// Update the campfire's name on the page.
			$$( 'campfire-name-' + id ).innerHTML = $$( 'campfire-edit-' + id ).value;

			// Sort the campfire list and update the user list.
			Campfire.sortCampfireList();
			Campfire.updateUserList( id );

			// Scroll to the current Campfire
			$$( 'campfire-' + id ).scrollIntoView( true );

			// Update the campfire on the server.
			DOMAssistant.AJAX.post( _ajax_base_url + 'edit/?campfire_id=' + id + '&name=' + name, Campfire.stopAllEdits );

		},

		clickOnDelete: function( e )
		{

			var elem = $( this ).parentNode.parentNode.parentNode;

			if ( confirm( 'Do you want to delete this Campfire?' ) )
			{

				// Remove the "campfire-" prefix from the ID.
				var campfire_id = elem.id.substr( 9 );

				DOMAssistant.AJAX.post( _ajax_base_url + 'delete/?campfire_id=' + campfire_id );

				// Remove this element from the DOM
				elem.remove();

				// Find all Users that belonged to this Campfire, and mark them as not belonging to any Campfire
				$$( 'users' ).cssSelect( '.user-campfire-' + campfire_id ).each( function()
				{

					// Remove the "user-" prefix from the ID.
					var user_id = this.id.substr( 5 );
					_users[user_id].campfire_id = 0;
					_users[user_id].campfire_leader = 0;
					$( this ).cssSelect( '.user-leader' )[0].removeClass( 'user-leader-active' );

				} );

				// Select the first campfire.
				Campfire.selectFirst();

			}

		},

		startEdit: function( campfire_id )
		{

			// Set that we're editing a campfire.
			_editing_campfire = true;

			var name = $$( 'campfire-name-' + campfire_id );
			nameValue = name.innerText || name.textContent;
			name.addClass( 'nodisplay' );

			var edit_container = $$( 'campfire-edit-container-' + campfire_id );
			edit_container.removeClass( 'nodisplay' );

			var edit = $$( 'campfire-edit-' + campfire_id );
			edit.value = nameValue;
			edit.focus();

			// Hide the Edit/Delete links
			var elem = $$( 'campfire-' + campfire_id ).cssSelect( '.links' )[0];
			$( elem ).addClass( 'hidden' );

			// Allow the edit controls to be closed with the ESC key.
			// The document will be waiting for this key press.
			$( document ).addEvent( 'keyup', escapeKeyListener );

		},

		stopAllEdits: function()
		{

			// Set that we're no longer editing a campfire.
			_editing_campfire = false;

			// The document is still waiting for the ESC key to be pressed.
			// Remove this event, so it doesn't keep waiting.
			$( document ).removeEvent( 'keyup', escapeKeyListener );

			var campfires = $$( 'campfires' );

			campfires.elmsByClass( "campfire", "div" ).each( function()
			{

				// Remove the "campfire-" prefix from the ID.
				var campfire_id = this.id.substr( 9 );

				var name = $$( 'campfire-name-' + campfire_id );
				name.removeClass( 'nodisplay' );

				var edit_container = $$( 'campfire-edit-container-' + campfire_id );
				edit_container.addClass( 'nodisplay' );

				var edit = $$( 'campfire-edit-' + campfire_id );
				edit.removeEvent( 'blur', Campfire.stopAllEdits );

			});

		}

	};

}();

/*	SWFObject v2.2 <http://code.google.com/p/swfobject/> 
	is released under the MIT License <http://www.opensource.org/licenses/mit-license.php> 
*/
var audioplayer_swfobject=function(){var d="undefined",R="object",s="Shockwave Flash",w="ShockwaveFlash.ShockwaveFlash",Q="application/x-shockwave-flash",r="SWFObjectExprInst",X="onreadystatechange",o=window,J=document,T=navigator,t=false,u=[H],O=[],n=[],i=[],L,q,e,b,j=false,A=false,N,g,M=true,m=function(){var AA=typeof J.getElementById!=d&&typeof J.getElementsByTagName!=d&&typeof J.createElement!=d,AH=T.userAgent.toLowerCase(),y=T.platform.toLowerCase(),AE=y?/win/.test(y):/win/.test(AH),AC=y?/mac/.test(y):/mac/.test(AH),AF=/webkit/.test(AH)?parseFloat(AH.replace(/^.*webkit\/(\d+(\.\d+)?).*$/,"$1")):false,x=!+"\v1",AG=[0,0,0],AB=null;if(typeof T.plugins!=d&&typeof T.plugins[s]==R){AB=T.plugins[s].description;if(AB&&!(typeof T.mimeTypes!=d&&T.mimeTypes[Q]&&!T.mimeTypes[Q].enabledPlugin)){t=true;x=false;AB=AB.replace(/^.*\s+(\S+\s+\S+$)/,"$1");AG[0]=parseInt(AB.replace(/^(.*)\..*$/,"$1"),10);AG[1]=parseInt(AB.replace(/^.*\.(.*)\s.*$/,"$1"),10);AG[2]=/[a-zA-Z]/.test(AB)?parseInt(AB.replace(/^.*[a-zA-Z]+(.*)$/,"$1"),10):0}}else{if(typeof o.ActiveXObject!=d){try{var AD=new ActiveXObject(w);if(AD){AB=AD.GetVariable("$version");if(AB){x=true;AB=AB.split(" ")[1].split(",");AG=[parseInt(AB[0],10),parseInt(AB[1],10),parseInt(AB[2],10)]}}}catch(z){}}}return{w3:AA,pv:AG,wk:AF,ie:x,win:AE,mac:AC}}(),K=function(){if(!m.w3){return }if((typeof J.readyState!=d&&J.readyState=="complete")||(typeof J.readyState==d&&(J.getElementsByTagName("body")[0]||J.body))){F()}if(!j){if(typeof J.addEventListener!=d){J.addEventListener("DOMContentLoaded",F,false)}if(m.ie&&m.win){J.attachEvent(X,function(){if(J.readyState=="complete"){J.detachEvent(X,arguments.callee);F()}});if(o==top){(function(){if(j){return }try{J.documentElement.doScroll("left")}catch(x){setTimeout(arguments.callee,0);return }F()})()}}if(m.wk){(function(){if(j){return }if(!/loaded|complete/.test(J.readyState)){setTimeout(arguments.callee,0);return }F()})()}S(F)}}();function F(){if(j){return }try{var z=J.getElementsByTagName("body")[0].appendChild(c("span"));z.parentNode.removeChild(z)}catch(AA){return }j=true;var x=u.length;for(var y=0;y<x;y++){u[y]()}}function k(x){if(j){x()}else{u[u.length]=x}}function S(y){if(typeof o.addEventListener!=d){o.addEventListener("load",y,false)}else{if(typeof J.addEventListener!=d){J.addEventListener("load",y,false)}else{if(typeof o.attachEvent!=d){I(o,"onload",y)}else{if(typeof o.onload=="function"){var x=o.onload;o.onload=function(){x();y()}}else{o.onload=y}}}}}function H(){if(t){v()}else{h()}}function v(){var x=J.getElementsByTagName("body")[0];var AA=c(R);AA.setAttribute("type",Q);var z=x.appendChild(AA);if(z){var y=0;(function(){if(typeof z.GetVariable!=d){var AB=z.GetVariable("$version");if(AB){AB=AB.split(" ")[1].split(",");m.pv=[parseInt(AB[0],10),parseInt(AB[1],10),parseInt(AB[2],10)]}}else{if(y<10){y++;setTimeout(arguments.callee,10);return }}x.removeChild(AA);z=null;h()})()}else{h()}}function h(){var AG=O.length;if(AG>0){for(var AF=0;AF<AG;AF++){var y=O[AF].id;var AB=O[AF].callbackFn;var AA={success:false,id:y};if(m.pv[0]>0){var AE=C(y);if(AE){if(f(O[AF].swfVersion)&&!(m.wk&&m.wk<312)){W(y,true);if(AB){AA.success=true;AA.ref=Z(y);AB(AA)}}else{if(O[AF].expressInstall&&a()){var AI={};AI.data=O[AF].expressInstall;AI.width=AE.getAttribute("width")||"0";AI.height=AE.getAttribute("height")||"0";if(AE.getAttribute("class")){AI.styleclass=AE.getAttribute("class")}if(AE.getAttribute("align")){AI.align=AE.getAttribute("align")}var AH={};var x=AE.getElementsByTagName("param");var AC=x.length;for(var AD=0;AD<AC;AD++){if(x[AD].getAttribute("name").toLowerCase()!="movie"){AH[x[AD].getAttribute("name")]=x[AD].getAttribute("value")}}p(AI,AH,y,AB)}else{P(AE);if(AB){AB(AA)}}}}}else{W(y,true);if(AB){var z=Z(y);if(z&&typeof z.SetVariable!=d){AA.success=true;AA.ref=z}AB(AA)}}}}}function Z(AA){var x=null;var y=C(AA);if(y&&y.nodeName=="OBJECT"){if(typeof y.SetVariable!=d){x=y}else{var z=y.getElementsByTagName(R)[0];if(z){x=z}}}return x}function a(){return !A&&f("6.0.65")&&(m.win||m.mac)&&!(m.wk&&m.wk<312)}function p(AA,AB,x,z){A=true;e=z||null;b={success:false,id:x};var AE=C(x);if(AE){if(AE.nodeName=="OBJECT"){L=G(AE);q=null}else{L=AE;q=x}AA.id=r;if(typeof AA.width==d||(!/%$/.test(AA.width)&&parseInt(AA.width,10)<310)){AA.width="310"}if(typeof AA.height==d||(!/%$/.test(AA.height)&&parseInt(AA.height,10)<137)){AA.height="137"}J.title=J.title.slice(0,47)+" - Flash Player Installation";var AD=m.ie&&m.win?"ActiveX":"PlugIn",AC="MMredirectURL="+o.location.toString().replace(/&/g,"%26")+"&MMplayerType="+AD+"&MMdoctitle="+J.title;if(typeof AB.flashvars!=d){AB.flashvars+="&"+AC}else{AB.flashvars=AC}if(m.ie&&m.win&&AE.readyState!=4){var y=c("div");x+="SWFObjectNew";y.setAttribute("id",x);AE.parentNode.insertBefore(y,AE);AE.style.display="none";(function(){if(AE.readyState==4){AE.parentNode.removeChild(AE)}else{setTimeout(arguments.callee,10)}})()}U(AA,AB,x)}}function P(y){if(m.ie&&m.win&&y.readyState!=4){var x=c("div");y.parentNode.insertBefore(x,y);x.parentNode.replaceChild(G(y),x);y.style.display="none";(function(){if(y.readyState==4){y.parentNode.removeChild(y)}else{setTimeout(arguments.callee,10)}})()}else{y.parentNode.replaceChild(G(y),y)}}function G(AB){var AA=c("div");if(m.win&&m.ie){AA.innerHTML=AB.innerHTML}else{var y=AB.getElementsByTagName(R)[0];if(y){var AC=y.childNodes;if(AC){var x=AC.length;for(var z=0;z<x;z++){if(!(AC[z].nodeType==1&&AC[z].nodeName=="PARAM")&&!(AC[z].nodeType==8)){AA.appendChild(AC[z].cloneNode(true))}}}}}return AA}function U(AI,AG,y){var x,AA=C(y);if(m.wk&&m.wk<312){return x}if(AA){if(typeof AI.id==d){AI.id=y}if(m.ie&&m.win){var AH="";for(var AE in AI){if(AI[AE]!=Object.prototype[AE]){if(AE.toLowerCase()=="data"){AG.movie=AI[AE]}else{if(AE.toLowerCase()=="styleclass"){AH+=' class="'+AI[AE]+'"'}else{if(AE.toLowerCase()!="classid"){AH+=" "+AE+'="'+AI[AE]+'"'}}}}}var AF="";for(var AD in AG){if(AG[AD]!=Object.prototype[AD]){AF+='<param name="'+AD+'" value="'+AG[AD]+'" />'}}AA.outerHTML='<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000"'+AH+">"+AF+"</object>";n[n.length]=AI.id;x=C(AI.id)}else{var z=c(R);z.setAttribute("type",Q);for(var AC in AI){if(AI[AC]!=Object.prototype[AC]){if(AC.toLowerCase()=="styleclass"){z.setAttribute("class",AI[AC])}else{if(AC.toLowerCase()!="classid"){z.setAttribute(AC,AI[AC])}}}}for(var AB in AG){if(AG[AB]!=Object.prototype[AB]&&AB.toLowerCase()!="movie"){E(z,AB,AG[AB])}}AA.parentNode.replaceChild(z,AA);x=z}}return x}function E(z,x,y){var AA=c("param");AA.setAttribute("name",x);AA.setAttribute("value",y);z.appendChild(AA)}function Y(y){var x=C(y);if(x&&x.nodeName=="OBJECT"){if(m.ie&&m.win){x.style.display="none";(function(){if(x.readyState==4){B(y)}else{setTimeout(arguments.callee,10)}})()}else{x.parentNode.removeChild(x)}}}function B(z){var y=C(z);if(y){for(var x in y){if(typeof y[x]=="function"){y[x]=null}}y.parentNode.removeChild(y)}}function C(z){var x=null;try{x=J.getElementById(z)}catch(y){}return x}function c(x){return J.createElement(x)}function I(z,x,y){z.attachEvent(x,y);i[i.length]=[z,x,y]}function f(z){var y=m.pv,x=z.split(".");x[0]=parseInt(x[0],10);x[1]=parseInt(x[1],10)||0;x[2]=parseInt(x[2],10)||0;return(y[0]>x[0]||(y[0]==x[0]&&y[1]>x[1])||(y[0]==x[0]&&y[1]==x[1]&&y[2]>=x[2]))?true:false}function V(AC,y,AD,AB){if(m.ie&&m.mac){return }var AA=J.getElementsByTagName("head")[0];if(!AA){return }var x=(AD&&typeof AD=="string")?AD:"screen";if(AB){N=null;g=null}if(!N||g!=x){var z=c("style");z.setAttribute("type","text/css");z.setAttribute("media",x);N=AA.appendChild(z);if(m.ie&&m.win&&typeof J.styleSheets!=d&&J.styleSheets.length>0){N=J.styleSheets[J.styleSheets.length-1]}g=x}if(m.ie&&m.win){if(N&&typeof N.addRule==R){N.addRule(AC,y)}}else{if(N&&typeof J.createTextNode!=d){N.appendChild(J.createTextNode(AC+" {"+y+"}"))}}}function W(z,x){if(!M){return }var y=x?"visible":"hidden";if(j&&C(z)){C(z).style.visibility=y}else{V("#"+z,"visibility:"+y)}}function l(y){var z=/[\\\"<>\.;]/;var x=z.exec(y)!=null;return x&&typeof encodeURIComponent!=d?encodeURIComponent(y):y}var D=function(){if(m.ie&&m.win){window.attachEvent("onunload",function(){var AC=i.length;for(var AB=0;AB<AC;AB++){i[AB][0].detachEvent(i[AB][1],i[AB][2])}var z=n.length;for(var AA=0;AA<z;AA++){Y(n[AA])}for(var y in m){m[y]=null}m=null;for(var x in audioplayer_swfobject){audioplayer_swfobject[x]=null}audioplayer_swfobject=null})}}();return{registerObject:function(AB,x,AA,z){if(m.w3&&AB&&x){var y={};y.id=AB;y.swfVersion=x;y.expressInstall=AA;y.callbackFn=z;O[O.length]=y;W(AB,false)}else{if(z){z({success:false,id:AB})}}},getObjectById:function(x){if(m.w3){return Z(x)}},embedSWF:function(AB,AH,AE,AG,y,AA,z,AD,AF,AC){var x={success:false,id:AH};if(m.w3&&!(m.wk&&m.wk<312)&&AB&&AH&&AE&&AG&&y){W(AH,false);k(function(){AE+="";AG+="";var AJ={};if(AF&&typeof AF===R){for(var AL in AF){AJ[AL]=AF[AL]}}AJ.data=AB;AJ.width=AE;AJ.height=AG;var AM={};if(AD&&typeof AD===R){for(var AK in AD){AM[AK]=AD[AK]}}if(z&&typeof z===R){for(var AI in z){if(typeof AM.flashvars!=d){AM.flashvars+="&"+AI+"="+z[AI]}else{AM.flashvars=AI+"="+z[AI]}}}if(f(y)){var AN=U(AJ,AM,AH);if(AJ.id==AH){W(AH,true)}x.success=true;x.ref=AN}else{if(AA&&a()){AJ.data=AA;p(AJ,AM,AH,AC);return }else{W(AH,true)}}if(AC){AC(x)}})}else{if(AC){AC(x)}}},switchOffAutoHideShow:function(){M=false},ua:m,getFlashPlayerVersion:function(){return{major:m.pv[0],minor:m.pv[1],release:m.pv[2]}},hasFlashPlayerVersion:f,createSWF:function(z,y,x){if(m.w3){return U(z,y,x)}else{return undefined}},showExpressInstall:function(z,AA,x,y){if(m.w3&&a()){p(z,AA,x,y)}},removeSWF:function(x){if(m.w3){Y(x)}},createCSS:function(AA,z,y,x){if(m.w3){V(AA,z,y,x)}},addDomLoadEvent:k,addLoadEvent:S,getQueryParamValue:function(AA){var z=J.location.search||J.location.hash;if(z){if(/\?/.test(z)){z=z.split("?")[1]}if(AA==null){return l(z)}var y=z.split("&");for(var x=0;x<y.length;x++){if(y[x].substring(0,y[x].indexOf("="))==AA){return l(y[x].substring((y[x].indexOf("=")+1)))}}}return""},expressInstallCallback:function(){if(A){var x=C(r);if(x&&L){x.parentNode.replaceChild(L,x);if(q){W(q,true);if(m.ie&&m.win){L.style.display="block"}}if(e){e(b)}}A=false}}}}();var AudioPlayer=function(){var H=[];var D;var F="";var A={};var E=-1;var G="9";function B(I){if(document.all&&!window[I]){for(var J=0;J<document.forms.length;J++){if(document.forms[J][I]){return document.forms[J][I];break}}}return document.all?window[I]:document[I]}function C(I,J,K){B(I).addListener(J,K)}return{setup:function(J,I){F=J;A=I;if(audioplayer_swfobject.hasFlashPlayerVersion(G)){audioplayer_swfobject.switchOffAutoHideShow();audioplayer_swfobject.createCSS("p.audioplayer_container span","visibility:hidden;height:24px;overflow:hidden;padding:0;border:none;")}},getPlayer:function(I){return B(I)},addListener:function(I,J,K){C(I,J,K)},embed:function(I,K){var N={};var L;var J={};var O={};var M={};for(L in A){N[L]=A[L]}for(L in K){N[L]=K[L]}if(N.transparentpagebg=="yes"){J.bgcolor="#FFFFFF";J.wmode="transparent"}else{if(N.pagebg){J.bgcolor="#"+N.pagebg}J.wmode="opaque"}J.menu="false";for(L in N){if(L=="pagebg"||L=="width"||L=="transparentpagebg"){continue}O[L]=N[L]}M.name=I;M.style="outline: none";O.playerID=I;audioplayer_swfobject.embedSWF(F,I,N.width.toString(),"24",G,false,O,J,M);H.push(I)},syncVolumes:function(I,K){E=K;for(var J=0;J<H.length;J++){if(H[J]!=I){B(H[J]).setVolume(E)}}},activate:function(I,J){if(D&&D!=I){B(D).close()}D=I},load:function(K,I,L,J){B(K).load(I,L,J)},close:function(I){B(I).close();if(I==D){D=null}},open:function(I,J){if(J==undefined){J=1}B(I).open(J==undefined?0:J-1)},getVolume:function(I){return E}}}();

	function addProduct( id )
	{

		var add_count = $$( 'product-count-add-' + id ) ? $$( 'product-count-add-' + id ).value : 1;

		// No quantity has been selected
		if ( parseInt( add_count ) < 1 )
			return;

		DOMAssistant.AJAX.post( SITE_URL + '/products/add/' + id + '/' + add_count + '/', updateCart );
		
	}

	function adjustProduct( id, total_count )
	{

		DOMAssistant.AJAX.post( SITE_URL + '/products/adjust/' + id + '/' + total_count + '/', updateCart );
		
	}
	
	function updateCart( responseText )
	{
		
		var response = eval( '(' + responseText + ')' );
		
		var id = response.ProductId;
		var num = response.ProductCountInCart;
		var total = response.TotalProductsInCart;
		var subtotal = response.Subtotal;
		
		$$( 'product-count-' + id ).innerHTML = num;
		$$( 'total_items' ).innerHTML         = total + ( 1 == total ? ' item' : ' items' );
		$$( 'total_price' ).innerHTML         = subtotal;
		
	}

function makeTableDraggable( table_elem, ajax_url )
{

	var targets = [];
	$( table_elem ).cssSelect( '.movable' ).each( function()
	{

		var target = new Legato.Drop.Target( this );
		target.onDragOver = makeTableDraggable.onTableDragOver;
		target.onDragOut = makeTableDraggable.onTableDragOut;
		target.onDrop = function( handle ) { makeTableDraggable.onTableDrop( this.elem, handle, ajax_url ) };
		targets.push( target );

	} );

	$( table_elem ).cssSelect( '.movable' ).each( function()
	{

		var handle = new Legato.Drag.Handle( this );
		handle.constrain_y = true;
		handle.intersect_test = Legato.DragDrop.INTERSECT_CURSOR_POS;
		handle.on_dragged = Legato.DragDrop.ON_DRAGGED_NOTHING;
		handle.targets = targets;

	} );

}

makeTableDraggable.onTableDragOver = function( handle )
{

	makeTableDraggable.highlightDropTarget( this.elem );

	if ( this.elem == handle.elem )
		return;

	var next_sibling = this.elem.next();

	if ( next_sibling == null )
		next_sibling = this.elem.nextSibling;

	else if ( next_sibling == handle.elem )
		next_sibling = this.elem;

	handle.elem.parentNode.insertBefore( handle.elem, next_sibling );

};

makeTableDraggable.onTableDragOut = function( handle )
{

	makeTableDraggable.resetDropTarget( this.elem );

};

makeTableDraggable.onTableDrop = function( elem, handle, ajax_url )
{

	var ids = '';

	var table_elem = elem.parentNode.parentNode;
	$( table_elem ).cssSelect( '.movable' ).each( function()
	{
		ids += this.id + ',';
	} );

	makeTableDraggable.resetDropTarget( elem );

	DOMAssistant.AJAX.post( ajax_url + ids + '/' );

};

makeTableDraggable.highlightDropTarget = function( elem )
{

	// We cannot apply styles to this element directly, because it is a table row
	// (and there are already other conflicting styles on it)
	// We need to find all the cells in this row, and apply highlighting to them
	$$( elem ).elmsByTag( 'td' ).each( function()
	{
		$$( this ).addClass( 'highlight' );
	});

};

makeTableDraggable.resetDropTarget = function( elem )
{

	var table_elem = $( elem ).parentNode.parentNode;
	$( table_elem ).cssSelect( '.movable' ).each( function()
	{
		// Remove the highlights from all the cells in this row.
		$$( this ).elmsByTag( 'td' ).each( function()
		{
			$$( this ).removeClass( 'highlight' );
		});

	} );

};



function nav( page )
{
	
	window.location = page;
	
}


	var download_button_clicked = false;

	function downloadFormSubmit()
	{
		
		var addr = $$( 'download_email' );
		
		if ( addr && '' != addr.value && 'Enter your e-mail here...' != addr.value )
		{
			
			if ( !download_button_clicked )
			{
				
				download_button_clicked = true;
				
				$$( 'download_button_inner_main' ).addClass( 'nodisplay' );
				$$( 'download_button_inner_waiting' ).removeClass( 'nodisplay' );
				
				$$( 'download_button_message' ).innerHTML = 'Please wait...';
				
				DOMAssistant.AJAX.post( SITE_URL + '/download/?download_email=' + $$( 'download_email' ).value + '&type=' + $$( 'download_type' ).value, downloadComplete );
				
			}
			
		}
		else
		{
			
			$$( 'download_button_message' ).innerHTML = 'Please enter your e-mail address.';
			
		}
		
	}
	
	function downloadComplete( responseText )
	{
		
		download_button_clicked = false;

		$$( 'download_button_inner_main' ).removeClass( 'nodisplay' );
		$$( 'download_button_inner_waiting' ).addClass( 'nodisplay' );
		
		var success = responseText == 'sent';
		var elm = $$( 'download_button_message' );
		
		if ( success )
		{
			elm.innerHTML = 'An e-mail has been sent to the address you entered.<br><br>The e-mail will have a PDF document that contains the Knights Program Summary.';
		}
		else
		{
			elm.innerHTML = 'E-mail could not be sent to that address.<br><br>Please enter your e-mail address.';
		}
		
	}
	
	function downloadClick()
	{
		
		if ( 'Enter your e-mail here...' == $$( 'download_email' ).value )
		{
			
			$$( 'download_email' ).value = '';
			
		}
		
	}

	function downloadUnclick()
	{

		if ( '' == $$( 'download_email' ).value )
		{
			
			$$( 'download_email' ).value = 'Enter your e-mail here...';
			
		}
		
	}
	
HeaderSlides =
{

	current_index: 1,

	initialize: function()
	{

		setInterval( function(){ HeaderSlides.switchFrames(); }, 4000 );

	},

	switchFrames: function()
	{

		var next_index = (HeaderSlides.current_index != 4) ? (HeaderSlides.current_index + 1) : 1;

		var elem1 = $$( 'slide-' + HeaderSlides.current_index );
		var elem2 = $$( 'slide-' + next_index );

		elem1.setStyle( 'z-index', 2 );
		elem2.setStyle( 'opacity', 0.01 );
		elem2.setStyle( 'z-index', 3 );

		anim2 = new Legato_Animation( elem2, 1500 );
		anim2.controller.opacity.to = 1;
		anim2.onFinish = function(){ elem1.setStyle( 'z-index', 1 ); };

		anim2.start();

		HeaderSlides.current_index = next_index;

	}

}

if ( $$( "header-slides" ) )
{
	HeaderSlides.initialize();
}

//------------------------------------------------------------------------
// Name: Quotes
// Desc: Handles the retrieving and showing of quotes.
//------------------------------------------------------------------------
Quotes =
{

	//----------------------------------------------------------------------
	// Public Variables
	//----------------------------------------------------------------------
	text_array:            null, // Populated with the quotes that will be displayed.
	current_index:         0,    // The current index into the text array.
	update_speed:          80,   // The speed at which the text will update (smaller means less time between updates).
	update_step:           5,    // Amount of transparency change between updates (larger means choppier updates).
	pause_between_updates: 7000, // Pause between text updates. 
 


	//----------------------------------------------------------------------
	// Public Member Functions
	//----------------------------------------------------------------------
	//------------------------------------------------------------------------
	// Name: initialize()
	// Desc: Used to initialize the system. Must be called before anything
	//       else.
	//------------------------------------------------------------------------
	initialize: function( text_array )
	{

		Quotes.text_array = text_array;
		Quotes.current_index = Quotes.getRandomIndex();
		Quotes.showCurrentAndContinue();
		
	},
	
	showCurrentAndContinue: function()
	{
		
		Quotes.setText( Quotes.current_index );
		
		startFadeIn( $$( 'testimonial_text' ), Quotes.update_step, Quotes.update_speed, function() {
			
			setTimeout( Quotes.update, Quotes.pause_between_updates );
			
		} );
		
	},

	//------------------------------------------------------------------------
	// Name: update()
	// Desc: Update the text.
	//------------------------------------------------------------------------
	update: function()
	{

		// Don't update anything unless there is at least a second quote to update to. 
		if ( Quotes.text_array.length < 2 )
			return;
		
		
		// Choose a random quote from the list
		// And don't accidentally choose the same one twice in a row
		var next_index;
		do
		{
			next_index = Quotes.getRandomIndex();
			
		} while( next_index == Quotes.current_index );
		
		Quotes.current_index = next_index;
		
			
		startFadeOut( $$( 'testimonial_text' ), Quotes.update_step, Quotes.update_speed, function() {
		
			Quotes.showCurrentAndContinue();
			
		} );
		
	},
	
	getRandomIndex: function()
	{
		var max_index = Quotes.text_array.length - 1;
		return Math.floor( Math.random() * ( max_index + 1 ) );
	},
	
	setText: function( i )
	{
		var elem = $$( 'testimonial_text' );
		elem.innerHTML = Quotes.text_array[i];
	}

}

Slides =
{

	current_index: 1,
	
	total: 0,
	
	initialize: function()
	{
	
		// Count the total number of slides on this page
		$$( "slides" ).elmsByClass("slide", "div").each( function() {
			
			Slides.total++;
			
		} );
		
		$$( 'slide_numbers' ).innerHTML = ( '1 / ' + Slides.total );
		
	},

	next: function()
	{
		
		var next_index = (Slides.current_index < Slides.total) ? (Slides.current_index + 1) : 1;

		var elem1 = $$( 'slide-' + Slides.current_index );
		var elem2 = $$( 'slide-' + next_index );

		$$( 'slide_numbers' ).innerHTML = ( next_index + ' / ' + Slides.total );
		Slides.swap_and_fade( elem1, elem2 );
		
		Slides.current_index = next_index;

	},

	
	prev: function()
	{
		
		var next_index = (Slides.current_index > 1) ? (Slides.current_index - 1) : Slides.total;

		var elem1 = $$( 'slide-' + Slides.current_index );
		var elem2 = $$( 'slide-' + next_index );

		$$( 'slide_numbers' ).innerHTML = ( next_index + ' / ' + Slides.total );
		Slides.swap_and_fade( elem1, elem2 );

		Slides.current_index = next_index;

	},
	
	swap_and_fade: function( elem1, elem2 )
	{
		
		elem1.addClass( 'nodisplay' );
		elem2.setStyle( 'opacity', 0.01 );
		elem2.removeClass( 'nodisplay' );

		anim2 = new Legato_Animation( elem2, 1500 );
		anim2.controller.opacity.to = 1;
		anim2.onFinish = function() {};

		anim2.start();
		
	}
	
}

if ( $$( "slides" ) )
{
	Slides.initialize();
}


function setOpacity(obj, opacity)
{
	opacity = (100 == opacity) ? 99.999 : opacity;

	// IE/Win
	obj.style.filter = 'alpha(opacity:' + opacity + ')';

	// Safari<1.2, Konqueror
	obj.style.KHTMLOpacity = opacity/100;

	// Older Mozilla and Firefox
	obj.style.MozOpacity = opacity/100;

	// Safari 1.2, newer Firefox and Mozilla, CSS3
	obj.style.opacity = opacity/100;
}

// Convenience method for fading
function startFadeIn(elem, opacityStep, msWaitBetweenSteps, fnCallbackWhenComplete)
{
   var startingOpacity = 0;
   continuousFadeIn(elem, startingOpacity, opacityStep, msWaitBetweenSteps, fnCallbackWhenComplete);
}

function continuousFadeIn(elem, startingOpacity, opacityStep, msWaitBetweenSteps, fnCallbackWhenComplete)
{
   var opacity = startingOpacity + opacityStep;
    if (opacity < 100)
    {
      var fnContinue = function() {
         setOpacity(elem, opacity); continuousFadeIn(elem, opacity, opacityStep, msWaitBetweenSteps, fnCallbackWhenComplete);
      };
      setTimeout(fnContinue, msWaitBetweenSteps);
    }
    else
    {
      setOpacity(elem, 100);
      if (fnCallbackWhenComplete)
      {
         fnCallbackWhenComplete();
      }
    }
}

// Convenience method for fading
function startFadeOut(elem, opacityStep, msWaitBetweenSteps, fnCallbackWhenComplete)
{
   var startingOpacity = 100;
   continuousFadeOut(elem, startingOpacity, opacityStep, msWaitBetweenSteps, fnCallbackWhenComplete);
}

function continuousFadeOut(elem, startingOpacity, opacityStep, msWaitBetweenSteps, fnCallbackWhenComplete)
{
   var opacity = startingOpacity - opacityStep;
    if (0 < opacity)
    {
      var fnContinue = function() {
         setOpacity(elem, opacity); continuousFadeOut(elem, opacity, opacityStep, msWaitBetweenSteps, fnCallbackWhenComplete);
      };
      setTimeout(fnContinue, msWaitBetweenSteps);
    }
    else
    {
      setOpacity(elem, 0);
      if (fnCallbackWhenComplete)
      {
         fnCallbackWhenComplete();
      }
    }
}

/*
Facelift Image Replacement v1.2
Facelift was written and is maintained by Cory Mawhorter.  
It is available from http://facelift.mawhorter.net/

===

This file is part of Facelife Image Replacement ("FLIR").

FLIR is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.

FLIR is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
GNU General Public License for more details.

You should have received a copy of the GNU General Public License
along with Facelift Image Replacement.  If not, see <http://www.gnu.org/licenses/>.
*/

var FLIR = {
     version: '1.2.2'
    
    ,options: {
         path: ''
        ,classnameIgnore: false
        ,findEmbededFonts: false
        ,ignoredElements: 'BR,HR,IMG,INPUT,SELECT'
    }
    
    ,onreplacing: null
    ,onreplaced: null
    ,onreplacingchild: null
    ,onreplacedchild: null
    
    ,flirElements: {}
    ,flirPlugins: []
    
    ,isCraptastic: true
    ,isIE: true

    ,defaultStyle: null
    ,classStyles: {}
        
    ,embededFonts: {}

    ,dpi: 96
    
    // either (options Object, fstyle FLIRStyle Object) or (fstyle FLIRStyle Object)
    ,init: function(options, fstyle) { // or options for flir style
        if(this.isFStyle(options)) { // (fstyle FLIRStyle Object)
            this.defaultStyle = options;
        }else { // [options Object, fstyle FLIRStyle Object]
            if(typeof options != 'undefined')
                this.loadOptions(options);
        
            if(typeof fstyle == 'undefined') {
                this.defaultStyle = new FLIRStyle();
            }else {
                if(this.isFStyle(fstyle))
                    this.defaultStyle = fstyle;
                else
                    this.defaultStyle = new FLIRStyle(fstyle);
            }
        }

        this.calcDPI();
                        
        if(this.options.findEmbededFonts)
            this.discoverEmbededFonts();

        this.isIE = (navigator.userAgent.toLowerCase().indexOf('msie')>-1 && navigator.userAgent.toLowerCase().indexOf('opera')<0);
        this.isCraptastic = (typeof document.body.style.maxHeight=='undefined');

        if(this.isIE) {
            this.flirIERepObj = [];
            this.flirIEHovEls = [];
            this.flirIEHovStyles = [];    
        }

        FLIR._call_plugin('init', arguments);
    }
    
    ,loadOptions: function(options) {
        for(var i in options)
            this.options[i] = options[i];
    }    
    
    ,installPlugin: function(plugin) {
        this.flirPlugins.push(plugin);
    }
    
    ,_call_plugin: function(func, call) {
        var ret = call;
        for(var i=0; i<this.flirPlugins.length; i++) {
            if(typeof this.flirPlugins[i][func] == 'function') {
                var pluginret = this.flirPlugins[i][func](ret);

                if(typeof pluginret == 'undefined') {
                    continue;
                }
                if(typeof pluginret == 'boolean' && pluginret == false) {
                    return false;
                }
                if(typeof pluginret != 'boolean') // passes changes on
                    ret = call;

            }
        }
        
        var ret = typeof ret != 'object' ? [ret] : ret;
        if(ret.length && ret[0] && ret[0].callee)
            return ret[0];
        else
            return ret;
    }
    
    ,auto: function(els) {
        if(!(args = FLIR._call_plugin('auto', arguments))) return;
        els = args[0];
        
        var tags = typeof els=='undefined'?['h1','h2','h3','h4','h5']:(els.indexOf && els.indexOf(',')>-1?els.split(','):els);
        var elements;
        for(var i=0; i<tags.length; i++) {
            elements = this.getElements(tags[i]);            

            if(elements.length>0)
                this.replace(elements);
        }
    }
    
    
    ,hover: function(e) {
        var o=FLIR.evsrc(e);
        var targ=o;
        var targDescHover = o.flirHasHover;
        var hoverTree = o;
        
        var on = (e.type == 'mouseover');
        
        while(o != document.body && !o.flirMainObj) {
            o = FLIR.getParentNode(o);
            
            if(!targDescHover) {
                    targDescHover = o.flirHasHover;
                    hoverTree = o;
            }
        }
        
        if(o==document.body) return;
        
        var FStyle = FLIR.getFStyle(o);
        if(on && FStyle != FStyle.hoverStyle)
            FStyle = FStyle.hoverStyle;
            
        if(!(args = FLIR._call_plugin('hover', [ on, targ, o, hoverTree ]))) return;
        on                = args[0];
        targ             = args[1];
        o                 = args[2];
        hoverTree     = args[3];
        
        var objs = FLIR.getChildren(hoverTree);
        if(objs.length == 0 || (objs.length == 1 && (objs[0].flirImage || objs[0].flirHasHover))) {
            objs = [hoverTree];
        }else if(objs.length == 1 && !FLIR.isIgnoredElement(objs[0])) {
            var subobjs = FLIR.getChildren(objs[0]);
            if(subobjs.length > 0)
                if((subobjs.length==1 && !subobjs[0].flirImage) || subobjs.length > 1)
                    objs = subobjs;
        }

        var rep_obj;
        for(var i=0; i < objs.length; i++) {
            rep_obj = objs[i];
            if(rep_obj.nodeName == 'IMG') continue;
            if(!rep_obj.innerHTML) continue; // IE 

            if(FLIR.isIE) {
                var idx = FLIR.flirIEHovEls.length;
                FLIR.flirIERepObj[idx] = rep_obj;
                FLIR.flirIEHovStyles[idx] = FStyle;
                
                if(!FLIR.isCraptastic) {
                    if(FStyle.useBackgroundMethod && FLIR.getStyle(rep_obj, 'display') == 'block') {
                        FLIR.flirIEHovEls[idx] = rep_obj;
                        setTimeout('FLIR.flirIERepObj['+idx+'].style.background = "url("+('+on+' ? FLIR.flirIEHovStyles['+idx+'].generateURL(FLIR.flirIERepObj['+idx+']) : FLIR.flirIERepObj['+idx+'].flirOrig)+") no-repeat";', 0);
                    }else {
                        FLIR.flirIEHovEls[idx] = rep_obj.flirImage ? rep_obj : FLIR.getChildren(rep_obj)[0];
                        if(!FLIR.flirIEHovEls[idx].flirOrigWidth) {
                            FLIR.flirIEHovEls[idx].flirOrigWidth = FLIR.flirIEHovEls[idx].width;
                            FLIR.flirIEHovEls[idx].flirOrigHeight = FLIR.flirIEHovEls[idx].height;
                        }
                        var ie_js = 'FLIR.flirIEHovEls['+idx+'].src = '+on+' ? FLIR.flirIEHovStyles['+idx+'].generateURL(FLIR.flirIERepObj['+idx+'], FLIR.flirIEHovEls['+idx+'].alt) : FLIR.flirIERepObj['+idx+'].flirOrig;'
                        ie_js += 'FLIR.flirIEHovEls['+idx+'].onload = function() { ';
                        if(on && !FLIR.flirIEHovEls[idx].flirHoverWidth) {
                            ie_js += '        FLIR.flirIEHovEls['+idx+'].flirHoverWidth = this.width; ';
                            ie_js += '        FLIR.flirIEHovEls['+idx+'].flirHoverHeight = this.height; ';
                        }
                        ie_js += '    this.style.width = FLIR.flirIEHovEls['+idx+'].'+(on?'flirHoverWidth':'flirOrigWidth')+'+"px"; ';
                        ie_js += '    this.style.height = FLIR.flirIEHovEls['+idx+'].'+(on?'flirHoverHeight':'flirOrigHeight')+'+"px"; ';
                        ie_js += '}; ';
                        setTimeout(ie_js, 0);
                    }
                }else {
                    FLIR.flirIEHovEls[idx] = rep_obj.flirImage ? rep_obj : FLIR.getChildren(rep_obj)[0];
                    setTimeout('  FLIR.flirIEHovEls['+idx+'].style.filter = \'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="\'+FLIR.flirIEHovStyles['+idx+'].generateURL(FLIR.flirIERepObj['+idx+'], FLIR.flirIEHovEls['+idx+'].alt)+\'", sizingMethod="image")\';  ', 0);
                }
            }else {
                if(FStyle.useBackgroundMethod && FLIR.getStyle(rep_obj, 'display') == 'block') {
                    var hovURL = rep_obj.flirHoverURL ? rep_obj.flirHoverURL : FStyle.generateURL(rep_obj);
                    rep_obj.style.background='url('+(on?hovURL:rep_obj.flirOrig)+') no-repeat';
                }else {
                    var img = rep_obj.flirImage ? rep_obj : FLIR.getChildren(rep_obj)[0];
                    var hovURL = rep_obj.flirHoverURL ? rep_obj.flirHoverURL : FStyle.generateURL(rep_obj, img.alt);
                    img.src = on?hovURL:rep_obj.flirOrig;
                }
            }
        }
    }

    ,addHover: function(obj) {
        if(!(args = FLIR._call_plugin('addHover', arguments))) return;
        obj    = args[0];
        
        obj.flirHasHover = true;
        
        if(obj.addEventListener) {
            obj.addEventListener( 'mouseover', FLIR.hover, false );
            obj.addEventListener( 'mouseout', FLIR.hover, false );
        }else if (obj.attachEvent) {
            obj.attachEvent( 'onmouseover', function() { FLIR.hover( window.event ); } );
            obj.attachEvent( 'onmouseout', function() { FLIR.hover( window.event ); } );
        }
    }
    
    ,prepare: function(n) {
        if(!(args = FLIR._call_plugin('prepare', arguments))) return;
        n = args[0];
        
        if(n && n.hasChildNodes() && n.childNodes.length > 1) {
            for(var i in n.childNodes) {
                var node = n.childNodes[i];
                if(node && node.nodeType == 3) {
                    var span = document.createElement('SPAN');
                    span.style.margin = span.style.padding = span.style.border = '0px';
                    span.className = 'flir-span';
						  span.flirSpan = true;
                    var txt = node.nodeValue.replace(/[\t\n\r]/g, ' ').replace(/\s\s+/g, ' ');
                    span.innerHTML = !FLIR.isIE ? txt : node.nodeValue.replace(/^\s+|\s+$/g,'&nbsp;');
                    n.replaceChild(span, node);
                }
            }
        }
    }
    
    ,replace: function(o, FStyle) {
        if(!(args = FLIR._call_plugin('replace', arguments))) return;
        o         = args[0];
        FStyle     = args[1];

        if (!o || o.flirReplaced)
            return;
        
        if(!this.isFStyle(FStyle))
            FStyle = this.getFStyle(o);

        if(typeof o == 'string')
            o = this.getElements(o);
        
        if(typeof o.length != 'undefined') {
            if(o.length == 0) return;

            for(var i=0; i< o.length; i++)
                this.replace(o[i], FStyle);
            
            return;
        }

        if(typeof FLIR.onreplacing == 'function') o = FLIR.onreplacing(o, FStyle);
        
        o.flirMainObj = true;
        this.setFStyle(o, FStyle);
        this.saveObject(o);
        
        if(this.options.findEmbededFonts && typeof this.embededFonts[FStyle.getFont(o)] != 'undefined')
            return;
        
        FLIR.prepare(o);        
        this._replace_tree(o, FStyle);

        if(typeof FLIR.onreplaced == 'function') FLIR.onreplaced(o, FStyle);
    }
    
    ,_replace_tree: function(o, FStyle) {
        if(typeof __flir_replacetree_recurse == 'undefined') __flir_replacetree_recurse = 1;
        else __flir_replacetree_recurse++;
        
        if(__flir_replacetree_recurse>1000) {
            console.error('Facelift: Too much recursion.');
            return;
        }
        
        var objs = !o.hasChildNodes() || (o.hasChildNodes() && o.childNodes.length==1 && o.childNodes[0].nodeType==3) ? [o] : o.childNodes;

        var rep_obj;
        for(var i=0; i < objs.length; i++) {
            rep_obj = objs[i];
            if(typeof FLIR.onreplacingchild == 'function') rep_obj = FLIR.onreplacingchild(rep_obj, FStyle);

            if(!rep_obj.innerHTML || rep_obj.nodeType != 1) continue;
            if(FLIR.isIgnoredElement(rep_obj)) continue;
            if(rep_obj.flirReplaced) continue;

            if(rep_obj.nodeName == 'A' && !rep_obj.flirHasHover)
                FLIR.addHover(rep_obj);

            if(rep_obj.hasChildNodes() && (rep_obj.childNodes.length > 1 || rep_obj.childNodes[0].nodeType != 3)) {
                FLIR.prepare(rep_obj);
                FLIR._replace_tree(rep_obj, FStyle);
                continue;
            }

            if(rep_obj.innerHTML == '') continue; // skip empty tags, if they exist
            
            if(!FLIR.isCraptastic)
                if(FStyle.useBackgroundMethod)
                    FLIR.replaceMethodBackground(rep_obj, FStyle);
                else
                    FLIR.replaceMethodOverlay(rep_obj, FStyle);
            else
                FLIR.replaceMethodCraptastic(rep_obj, FStyle);

            rep_obj.className += ' flir-replaced';
            rep_obj.flirReplaced = true;
            
            if(typeof FLIR.onreplacedchild == 'function') FLIR.onreplacedchild(o, FStyle);
        }
    }
    
    ,replaceMethodBackground: function(o, FStyle) {
        if(!(args = FLIR._call_plugin('replaceMethodBackground', arguments))) return;
        o         = args[0];
        FStyle     = args[1];

        var oid = this.saveObject(o);
        var url = FStyle.generateURL(o);
        
        if(FLIR.getStyle(o, 'display') != 'block')
            o.style.display='block';
        
        var tmp = new Image();
        tmp.onload = function() {
            FLIR.flirElements[oid].style.width=this.width+'px';
            FLIR.flirElements[oid].style.height=this.height+'px';
            
            if(FStyle != FStyle.hoverStyle) {
                var h_img = new Image();
                o.flirHoverURL = h_img.src = FStyle.hoverStyle.generateURL(o);
            }
        };
        tmp.src = url;
        
        o.style.background = 'url("'+url.replace(/ /g, '%20')+'") no-repeat';
        o.flirOrig = url;
        
        o.oldTextIndent = o.style.textIndent;
        o.style.textIndent='-9999px';
    }

    ,replaceMethodOverlay: function(o, FStyle) {
        if(!(args = FLIR._call_plugin('replaceMethodOverlay', arguments))) return;
        o         = args[0];
        FStyle     = args[1];

        var oid = this.saveObject(o);
        var img = document.createElement('IMG');
		
        img.alt = this.sanitizeHTML(o.innerHTML);

        if(FStyle != FStyle.hoverStyle) {
            img.onload = function() {
                    var h_img = new Image();
                    o.flirHoverURL = h_img.src = FStyle.hoverStyle.generateURL(o, img.alt);
            };
        }
        
        if(img.onerror) {
            img.onerror = function() {
                var span = document.createElement('SPAN');
                span.innerHTML = img.alt;
                try {
                    o.replaceChild(span,img)
                }catch(err) { }
            };
        }

        img.flirImage = true;
        img.className = 'flir-image';
        img.src = FStyle.generateURL(o);
        img.style.border='none';
		img.style.width = 'inherit';
		img.style.height = 'inherit';
		
        o.flirOrig = img.src;
        o.innerHTML='';
        o.appendChild(img);
    }

    ,replaceMethodCraptastic: function(o, FStyle) {
        if(!(args = FLIR._call_plugin('replaceMethodCraptastic', arguments))) return;
        o         = args[0];
        FStyle     = args[1];

        var oid = this.saveObject(o);
        var url = FStyle.generateURL(o);
        
        var img = document.createElement('IMG');
        img.alt = this.sanitizeHTML(o.innerHTML);        
        if(FStyle != FStyle.hoverStyle) {
            img.onload = function() {
                    var h_img = new Image();
                    o.flirHoverURL = h_img.src = FStyle.hoverStyle.generateURL(o, img.alt);
            };
        }

        img.flirImage = true;
        img.className = 'flir-image';
        img.src = this.options.path+'spacer.png';
        img.style.width=o.offsetWidth+'px';
        img.style.height=o.offsetHeight+'px';
        img.style.filter = 'progid:DXImageTransform.Microsoft.AlphaImageLoader(src="'+url+'", sizingMethod="image")';

        o.flirOrig = url;
        o.innerHTML='';
        o.appendChild(img);
    }

    ,saveObject: function(o) {
        if(typeof o.flirId == 'undefined') {
            o.flirId = this.getUID();
            this.flirElements[o.flirId] = o;
        }
        
        return o.flirId;
    }
    
    ,getUID: function() {
        var prefix='flir-';
        var id=prefix+Math.random().toString().split('.')[1];
        var i=0;
        while(typeof this.flirElements[id] != 'undefined') {
            if(i>100000) {
                console.error('Facelift: Unable to generate unique id.');    
            }
            id=prefix+Math.random().toString().split('.')[1];
            i++;
        }
        
        return id;
    }
    
    ,getElements: function(tag) {
        if(!(args = FLIR._call_plugin('getElements', arguments))) return;
        switch(args.length) {
            case 1:
                tag = args[0];
                break;
            case 2: // plugin returned list of elements
                return args[0];
                break;
        }
        
        var found = [];

        if(document.querySelectorAll) {
            var qsa = false;
            try{
                found = document.querySelectorAll(tag);
                qsa = true;
            }catch(err){ qsa=false; }

            if(qsa)
                return found;
        }

        var objs,subels,cn,childs,tag,el,matches,subel,rep_el;
    
        el = tag;
        
        subel=false;
		if(el.indexOf(' ')>-1) {
			var parts = el.split(' ');
			el = parts[0];
			subel = parts[1];
		}else if(el[0] == '#') {
			return document.getElementById(el.substr(1));
		}
        
        var grain_id=false;
        if(el.indexOf('#') > -1) {
            grain_id = el.split('#')[1];
            tag = el.split('#')[0];
        }

        var grain_cn=false;
        if(el.indexOf('.') > -1) {
            grain_cn = el.split('.')[1];
            tag = el.split('.')[0];
        }

        objs = document.getElementsByTagName(tag);
        for(var p=0; p<objs.length; p++) {
            if(objs[p].nodeType != 1) continue;
            matches = false;
            cn = objs[p].className?objs[p].className:'';
            
            if(grain_id && objs[p].id && objs[p].id == grain_id)
                matches=true;
            if(grain_cn && FLIR.hasClass(objs[p], grain_cn))
                matches=true;
            if(!grain_id && !grain_cn)
                matches=true;
            
            if(!matches) continue;
            if(this.options.classnameIgnore && cn.indexOf(this.options.classnameIgnore)>-1) continue;
            
            subels = false != subel ? objs[p].getElementsByTagName(subel) : [objs[p]];
            for(var pp=0; pp<subels.length; pp++) {
                rep_el = subels[pp];
                if(this.options.classnameIgnore && rep_el.className && rep_el.className.indexOf(this.options.classnameIgnore)>-1) continue;

                found.push(rep_el);
            }
        }
        
        return found;
    }
    
    ,discoverEmbededFonts: function() {
        this.embededFonts = {};
        for(var i in document.styleSheets) {
            if(!document.styleSheets[i].cssRules) continue;
            for(var ii in document.styleSheets[i].cssRules) {
                if(!document.styleSheets[0].cssRules[ii]) continue;
                var node = document.styleSheets[0].cssRules[ii];
                
                if(node.type && node.type == node.FONT_FACE_RULE) {
                    var nodesrc = node.style.getPropertyValue('src').match(/url\("?([^"\)]+\.[ot]tf)"?\)/i)[1];
                    var font = node.style.getPropertyValue('font-family');
                    if(font.indexOf(',')) {
                        font = font.split(',')[0];
                    }
                
                    font = font.replace(/['"]/g, '').toLowerCase();
                    
                    if(font!='' && nodesrc != '')
                        this.embededFonts[font] = nodesrc;
                }
            }
        }    
    }

    ,getStyle: function(el,prop) {
        if(el.currentStyle) {
            if(prop.indexOf('-') > -1)
                prop = prop.split('-')[0]+prop.split('-')[1].substr(0, 1).toUpperCase()+prop.split('-')[1].substr(1);
            var y = el.currentStyle[prop];
        }else if(window.getComputedStyle) {
            var y = document.defaultView.getComputedStyle(el,'').getPropertyValue(prop);
        }
        return y;
    }
        
    ,getChildren: function(n) {
        var children=[];
        if(n && n.hasChildNodes())
            for(var i in n.childNodes)
                if(n.childNodes[i] && n.childNodes[i].nodeType == 1)
                    children[children.length]=n.childNodes[i];
    
        return children;
    }
    
    ,getParentNode: function(n) {
        var o=n.parentNode;
        while(o != document && o.nodeType != 1)
            o=o.parentNode;
    
        return o;
    }
    
    ,hasClass: function(o, cn) {
        return (o && o.className && o.className.indexOf(cn)>-1);
    }
    
    ,evsrc: function(e) {
        var o;
        if (e.target) o = e.target;
        else if (e.srcElement) o = e.srcElement;
        if (o.nodeType == 3) // defeat Safari bug
            o = o.parentNode;    
            
        return o;
    }
    
    ,calcDPI: function() {
        if(screen.logicalXDPI) {
            var dpi = screen.logicalXDPI;
        }else {
            var id = 'flir-dpi-div-test';
            if(document.getElementById(id)) {
                var test = document.getElementById(id);
            }else {
                var test = document.createElement('DIV');
                test.id = id;
                test.style.position='absolute';
                test.style.visibility='hidden';
                test.style.border=test.style.padding=test.style.margin='0';
                test.style.left=test.style.top='-1000px';
                test.style.height=test.style.width='1in';
                document.body.appendChild(test);
            }
            
            var dpi = test.offsetHeight;
        }
        
        this.dpi = parseInt(dpi);
    }
    
    ,isIgnoredElement: function(el, breakIgnored) { return ((','+this.options.ignoredElements).indexOf(','+el.nodeName)>-1); }
    ,sanitizeHTML: function(html) { return html.replace(/<[^>]+>/g, ''); }
    
    ,getFStyle: function(o, fstyle) { 
        var cStyle = this.getClassStyle(o);
        if(this.isFStyle(cStyle))
            fstyle = cStyle;

        if(this.isFStyle(fstyle)) {
            return fstyle;
        }else if(this.isFStyle(o.flirStyle)) {
            return o.flirStyle;
        }else {
            return this.defaultStyle;
        }
    }
    ,setFStyle: function(o, FStyle) { o.flirStyle = FStyle; }
    ,isFStyle: function(o) { if(!o) return false; return (o.toString() == '[FLIRStyle Object]'); }

    ,addClassStyle: function(classname, FStyle) {
        if(this.isFStyle(FStyle))
            this.classStyles[classname] = FStyle;
    }
    ,getClassStyle: function(o) {
        if(!(args = FLIR._call_plugin('getClassStyle', arguments))) return;
        switch(args.length) {
            case 1:
                o = args[0];
                break;
            case 2: // plugin returned a style
                return args[0];
                break;
        }

        var cn = o.className;
        if(this.classStyles.length == 0 || typeof cn == 'undefined' || cn=='') return false;
        
        var classes = cn.split(' ');
        for(var i in this.classStyles) {
            for(var ii=0; ii<classes.length; ii++) {
                if(classes[ii]==i) {
                    return this.classStyles[i];
                }
            }
        }
        
        return false;
    }
};












function FLIRStyle(options) {
    this.useBackgroundMethod     = false;
    this.inheritStyle             = true;
    this.useExtendedStyles        = false;
    this.hoverStyle             = (arguments[1] && FLIR.isFStyle(arguments[1])) ? arguments[1] : this;
    
    // options are sent along with the query string
    this.options = {
         mode: '' // none (''), wrap,progressive or name of a plugin
        ,output:'auto' // auto, png, gif, jpg
        
        ,cSize: null
        ,cColor: null
        ,cFont: null // font-family
        
        ,realFontHeight: false
        ,dpi: 96
    };
    
    // supported css properties to internal name
    this.cssStyles = {
         'background-color'    : 'Background'
        ,'color'             : 'Color'
        ,'font-family'        : 'Font'
        ,'font-size'        : 'Size'
        ,'letter-spacing'    : 'Spacing'
        ,'line-height'        : 'Line'
        ,'text-align'        : 'Align'
        ,'text-transform'    : 'Transform'
    };
    
    this.extendedStyles = {
         'font-stretch'        : 'Stretch'
        ,'font-style'        : 'FontStyle'
        ,'font-variant'        : 'Variant'
        ,'font-weight'        : 'Weight'
        ,'opacity'            : 'Opacity'
        ,'text-decoration'    : 'Decoration'
    }
    
    // legacy option support
    for(var i in options) {
        if(i.indexOf('css')==0)
            i = 'c'+i.substr(3);

        if(typeof this[i] != 'undefined') {
            this[i] = options[i];
        }else {
            this.options[i] = options[i];
        }
    }
    this.options.dpi = FLIR.dpi;
    
    
    if(this.useExtendedStyles)
        for(var i in this.extendedStyles)
            this.cssStyles[i] = this.extendedStyles[i];
    
    for(var i=0; i<FLIR.flirPlugins.length; i++)
        if(FLIR.flirPlugins[i].FLIRStyleExtend && typeof FLIR.flirPlugins[i].FLIRStyleExtend.init)
            FLIR.flirPlugins[i].FLIRStyleExtend.init.call(this);
}

// generate a url based on an object
FLIRStyle.prototype.generateURL = function(o) { // [, text]
    var enc_text = (arguments[1]?arguments[1]:o.innerHTML);
    var transform = this.options.cTransform;
    if(transform==null)
        transform = FLIR.getStyle(o, 'text-transform');

    switch(transform) {
        case 'capitalize':
            enc_text = enc_text.replace(/\w+/g, function(w){
                              return w.charAt(0).toUpperCase() + w.substr(1).toLowerCase();
                         });
            break;
        case 'lowercase':
            enc_text = enc_text.toLowerCase();
            break;
        case 'uppercase':
            enc_text = enc_text.toUpperCase().replace(/&[a-z0-9]+;/gi, function(m) { return m.toLowerCase(); }); // keep entities lowercase, numeric don't matter
            break;
    }

    enc_text = encodeURIComponent(enc_text.replace(/&/g, '{amp}').replace(/\+/g, '{plus}'));

    return FLIR.options.path+'generate.php?text='+enc_text+'&h='+o.offsetHeight+'&w='+o.offsetWidth+'&fstyle='+this.serialize(o);
};

// create a custom image on the fly
FLIRStyle.prototype.buildURL = function(text, o, maxwidth, maxheight) {
    var enc_text = encodeURIComponent(text.replace(/&/g, '{amp}').replace(/\+/g, '{plus}'));
    return FLIR.options.path+'generate.php?text='+enc_text+'&h='+(maxheight?maxheight:'200')+'&w='+(maxwidth?maxwidth:'800')+'&fstyle='+(o?this.serialize(o):this.serialize());
};

FLIRStyle.prototype.serialize = function(o) {
    var sdata='';
    var options = this.copyObject(this.options);    
    
    if(o && this.inheritStyle) {
        for(var i in this.cssStyles) {
            var name = this.cssStyles[i];

            if(this.options['c'+name] == null || name=='Size')
                this.options['c'+name] = this.get(o, i, name);    
        }
    }
    
    for(var i in this.options) {
        if(this.options[i] == null || typeof this.options[i] == 'undefined' || this.options[i] == 'NaN')
            continue;
        sdata += ',"'+i+'":"'+this.options[i].toString().replace(/"/g, "'")+'"';
    }

    sdata = '{'+sdata.substr(1)+'}';
    this.options = options;

    return escape(sdata);
};

FLIRStyle.prototype.get = function(o, css_property, flirstyle_name) {
    var func = 'get'+flirstyle_name;
    
	 while(o.flirSpan && o != document.body)
	 	o = FLIR.getParentNode(o);
    
    return typeof this[func] == 'function' ? this[func](o) : FLIR.getStyle(o, css_property);
};

FLIRStyle.prototype.getFontStyle = function(o) { 
    return o.nodeName=='EM' || FLIR.getParentNode(o).nodeName=='EM' ? 'italic' : FLIR.getStyle(o, 'font-style');
};

FLIRStyle.prototype.getWeight = function(o) { 
    var fontweight = o.nodeName=='STRONG' || FLIR.getParentNode(o).nodeName=='STRONG' ? 'bold' : FLIR.getStyle(o, 'font-weight');
    
    switch(fontweight.toString()) {
        case '100': case '200': case '300': case 'lighter':
            return 'lighter';
        case '400': case 'normal':
            return '';
        case '500': case '600': case '700': case 'bold':
            return 'bold';
        case '800': case '900': case 'bolder':
            return 'bolder';
    }
};

FLIRStyle.prototype.getFont = function(o) { 
    var font = FLIR.getStyle(o, 'font-family');
    if(font.indexOf(',')) {
        font = font.split(',')[0];
    }

    return font.replace(/['"]/g, '').toLowerCase();
};

FLIRStyle.prototype.getColor = function(o) { 
    var color = FLIR.getStyle(o, 'color');
    if(color.substr(0, 1)=='#')
        color = color.substr(1);
    
    return color.replace(/['"]/g, '').toLowerCase();
};

FLIRStyle.prototype.getSize = function(o) {
    if(this.options.cSize!=null && '*/+-'.indexOf(this.options.cSize[0])<0)
        return this.options.cSize;
    
    var raw = FLIR.getStyle(o, 'font-size');

    var pix;
    if(raw.indexOf('px') > -1) {
        pix = Math.round(parseFloat(raw));
    }else {
        if(raw.indexOf('pt') > -1) {
            var pts = parseFloat(raw);
            pix = pts/(72/this.options.dpi);
        }else if(raw.indexOf('em') > -1 || raw.indexOf('%') > -1) {
            pix = this.calcFontSize(o);
        }
    }

    if(this.options.cSize && '*/+-'.indexOf(this.options.cSize[0])>-1) {
        try {
            pix = this.roundFloat(parseFloat(eval(pix.toString().concat(this.options.cSize))));
        }catch(err) { }
    }
    
    o.flirFontSize = pix;
    
    return pix;
};

FLIRStyle.prototype.getSpacing = function(o) {
    var spacing = FLIR.getStyle(o, 'letter-spacing');
    var ret;
    if(spacing != 'normal') {
        if(spacing.indexOf('em') > -1) {
            var fontsize = o.flirFontSize ? o.flirFontSize : this.getSize(o);
            ret = (parseFloat(spacing)*fontsize);
        }else if(spacing.indexOf('px') > -1) {
            ret = parseFloat(spacing);
        }else if(spacing.indexOf('pt') > -1) {
            var pts = parseFloat(spacing);
            ret = pts/(72/this.options.dpi);            
        }
        
        return this.roundFloat(ret);
    }

    return '';    
};

FLIRStyle.prototype.getLine = function(o) {
    var spacing = FLIR.getStyle(o, 'line-height');
    var val = parseFloat(spacing);
    var fontsize = o.flirFontSize ? o.flirFontSize : this.getSize(o);
    if(spacing.indexOf('em') > -1) {
        ret = (val*fontsize)/fontsize;
    }else if(spacing.indexOf('px') > -1) {
        ret = val/fontsize;
    }else if(spacing.indexOf('pt') > -1) {
        var pts = val;
        ret = (pts/(72/this.options.dpi))/fontsize;
    }else if(spacing.indexOf('%') > -1) {
        return 1.0;    
    }else {
        ret = val;    
    }
    
    return this.roundFloat(ret);
};

FLIRStyle.prototype.roundFloat = function(val) {
    return Math.round(val*10000)/10000;
};

FLIRStyle.prototype.calcFontSize = function(o) {
    var test = document.createElement('DIV');
    test.style.border = '0';
    test.style.padding = '0';
    test.style.position='absolute';
    test.style.visibility='hidden';
    test.style.left=test.style.top='-1000px';
    test.style.left=test.style.top='10px';
    test.style.lineHeight = '100%';
    test.innerHTML = 'Flir_Test';        
    o.appendChild(test);
    
    var size = test.offsetHeight;
    o.removeChild(test);

    return size;
};

FLIRStyle.prototype.copyObject = function(obj) { 
    var copy = {};
    for(var i in obj) {
        copy[i] = obj[i];    
    }
    
    return copy;
};

FLIRStyle.prototype.toString = function() { return '[FLIRStyle Object]'; };

